diff --git a/.deadcode-out b/.deadcode-out index f6a194b17f..dd81cc6c3f 100644 --- a/.deadcode-out +++ b/.deadcode-out @@ -1,7 +1,7 @@ -code.gitea.io/gitea/cmd +forgejo.org/cmd NoMainListener -code.gitea.io/gitea/cmd/forgejo +forgejo.org/cmd/forgejo ContextSetNoInit ContextSetNoExit ContextSetStderr @@ -9,204 +9,152 @@ code.gitea.io/gitea/cmd/forgejo ContextSetStdout ContextSetStdin -code.gitea.io/gitea/models - IsErrUpdateTaskNotExist - ErrUpdateTaskNotExist.Error - ErrUpdateTaskNotExist.Unwrap +forgejo.org/models IsErrSHANotFound IsErrMergeDivergingFastForwardOnly - GetYamlFixturesAccess -code.gitea.io/gitea/models/actions - ScheduleList.GetUserIDs - ScheduleList.GetRepoIDs - ScheduleList.LoadTriggerUser - ScheduleList.LoadRepos - -code.gitea.io/gitea/models/asymkey - ErrGPGKeyAccessDenied.Error - ErrGPGKeyAccessDenied.Unwrap - HasDeployKey - -code.gitea.io/gitea/models/auth - GetSourceByName +forgejo.org/models/auth WebAuthnCredentials -code.gitea.io/gitea/models/db +forgejo.org/models/db TruncateBeans InTransaction DumpTables -code.gitea.io/gitea/models/dbfs +forgejo.org/models/dbfs file.renameTo Create Rename -code.gitea.io/gitea/models/forgefed +forgejo.org/models/forgefed GetFederationHost -code.gitea.io/gitea/models/forgejo/semver +forgejo.org/models/forgejo/semver GetVersion SetVersionString SetVersion -code.gitea.io/gitea/models/git +forgejo.org/models/git RemoveDeletedBranchByID -code.gitea.io/gitea/models/issues +forgejo.org/models/issues IsErrUnknownDependencyType - ErrNewIssueInsert.Error IsErrIssueWasClosed - ChangeMilestoneStatus -code.gitea.io/gitea/models/organization - GetTeamNamesByID - UpdateTeamUnits +forgejo.org/models/organization SearchMembersOptions.ToConds - UsersInTeamsCount -code.gitea.io/gitea/models/perm/access +forgejo.org/models/perm/access GetRepoWriters -code.gitea.io/gitea/models/project - UpdateColumnSorting - ChangeProjectStatus - -code.gitea.io/gitea/models/repo - DeleteAttachmentsByIssue - FindReposMapByIDs - IsErrTopicNotExist - ErrTopicNotExist.Error - ErrTopicNotExist.Unwrap - GetTopicByName +forgejo.org/models/repo WatchRepoMode -code.gitea.io/gitea/models/user - ErrUserInactive.Error - ErrUserInactive.Unwrap +forgejo.org/models/user IsErrExternalLoginUserAlreadyExist IsErrExternalLoginUserNotExist NewFederatedUser IsErrUserSettingIsNotExist GetUserAllSettings DeleteUserSetting - GetUserEmailsByNames - GetUserNamesByIDs -code.gitea.io/gitea/modules/activitypub +forgejo.org/modules/activitypub NewContext Context.APClientFactory -code.gitea.io/gitea/modules/assetfs +forgejo.org/modules/assetfs Bindata -code.gitea.io/gitea/modules/auth/password/hash +forgejo.org/modules/auth/password/hash DummyHasher.HashWithSaltBytes NewDummyHasher -code.gitea.io/gitea/modules/auth/password/pwn +forgejo.org/modules/auth/password/pwn WithHTTP -code.gitea.io/gitea/modules/base +forgejo.org/modules/base SetupGiteaRoot -code.gitea.io/gitea/modules/cache +forgejo.org/modules/cache GetInt WithNoCacheContext RemoveContextData -code.gitea.io/gitea/modules/charset - BreakWriter.Write - -code.gitea.io/gitea/modules/emoji +forgejo.org/modules/emoji ReplaceCodes -code.gitea.io/gitea/modules/eventsource +forgejo.org/modules/eventsource Event.String -code.gitea.io/gitea/modules/forgefed +forgejo.org/modules/forgefed + NewForgeUndoLike + ForgeUndoLike.UnmarshalJSON + ForgeUndoLike.Validate GetItemByType JSONUnmarshalerFn NotEmpty ToRepository OnRepository -code.gitea.io/gitea/modules/git +forgejo.org/modules/git AllowLFSFiltersArgs AddChanges AddChangesWithArgs CommitChanges CommitChangesWithArgs - IsErrExecTimeout - ErrExecTimeout.Error - ErrUnsupportedVersion.Error SetUpdateHook openRepositoryWithDefaultContext - IsTagExist ToEntryMode - LimitedReaderCloser.Read - LimitedReaderCloser.Close -code.gitea.io/gitea/modules/gitgraph - Parser.Reset - -code.gitea.io/gitea/modules/gitrepo +forgejo.org/modules/gitrepo GetBranchCommitID GetWikiDefaultBranch -code.gitea.io/gitea/modules/graceful +forgejo.org/modules/graceful Manager.TerminateContext Manager.Err Manager.Value Manager.Deadline -code.gitea.io/gitea/modules/hcaptcha +forgejo.org/modules/hcaptcha WithHTTP -code.gitea.io/gitea/modules/hostmatcher +forgejo.org/modules/hostmatcher HostMatchList.AppendPattern -code.gitea.io/gitea/modules/json +forgejo.org/modules/json StdJSON.Marshal StdJSON.Unmarshal StdJSON.NewEncoder StdJSON.NewDecoder StdJSON.Indent -code.gitea.io/gitea/modules/markup +forgejo.org/modules/log + NewEventWriterBuffer + +forgejo.org/modules/markup GetRendererByType RenderString IsMarkupFile -code.gitea.io/gitea/modules/markup/console +forgejo.org/modules/markup/console Render RenderString -code.gitea.io/gitea/modules/markup/markdown - IsDetails - IsSummary - IsTaskCheckBoxListItem - IsIcon +forgejo.org/modules/markup/markdown RenderRawString -code.gitea.io/gitea/modules/markup/markdown/math - WithInlineDollarParser - WithBlockDollarParser - -code.gitea.io/gitea/modules/markup/mdstripper +forgejo.org/modules/markup/mdstripper stripRenderer.AddOptions StripMarkdown -code.gitea.io/gitea/modules/markup/orgmode +forgejo.org/modules/markup/orgmode RenderString -code.gitea.io/gitea/modules/private - ActionsRunnerRegister - -code.gitea.io/gitea/modules/process +forgejo.org/modules/process Manager.ExecTimeout -code.gitea.io/gitea/modules/queue +forgejo.org/modules/queue newBaseChannelSimple newBaseChannelUnique newBaseRedisSimple @@ -215,89 +163,73 @@ code.gitea.io/gitea/modules/queue testStateRecorder.Reset newWorkerPoolQueueForTest -code.gitea.io/gitea/modules/queue/lqinternal +forgejo.org/modules/queue/lqinternal QueueItemIDBytes QueueItemKeyBytes ListLevelQueueKeys -code.gitea.io/gitea/modules/setting +forgejo.org/modules/setting NewConfigProviderFromData GitConfigType.GetOption InitLoggersForTest -code.gitea.io/gitea/modules/storage - ErrInvalidConfiguration.Error - IsErrInvalidConfiguration - -code.gitea.io/gitea/modules/structs - ParseCreateHook - ParsePushHook - -code.gitea.io/gitea/modules/sync +forgejo.org/modules/sync StatusTable.Start StatusTable.IsRunning -code.gitea.io/gitea/modules/timeutil +forgejo.org/modules/timeutil GetExecutableModTime MockSet MockUnset -code.gitea.io/gitea/modules/translation +forgejo.org/modules/translation MockLocale.Language MockLocale.TrString MockLocale.Tr MockLocale.TrN + MockLocale.TrPluralString + MockLocale.TrPluralStringAllForms MockLocale.TrSize + MockLocale.HasKey MockLocale.PrettyNumber -code.gitea.io/gitea/modules/util/filebuffer +forgejo.org/modules/translation/localeiter + IterateMessagesContent + +forgejo.org/modules/util + OptionalArg + +forgejo.org/modules/util/filebuffer CreateFromReader -code.gitea.io/gitea/modules/validation +forgejo.org/modules/validation IsErrNotValid + ValidateIDExists -code.gitea.io/gitea/modules/web +forgejo.org/modules/web RouteMock RouteMockReset -code.gitea.io/gitea/modules/web/middleware - DeleteLocaleCookie - -code.gitea.io/gitea/modules/zstd +forgejo.org/modules/zstd NewWriter Writer.Write Writer.Close -code.gitea.io/gitea/routers/web - NotFound - -code.gitea.io/gitea/routers/web/org +forgejo.org/routers/web/org MustEnableProjects -code.gitea.io/gitea/services/context +forgejo.org/services/context GetPrivateContext -code.gitea.io/gitea/services/convert - ToSecret - -code.gitea.io/gitea/services/forms - DeadlineForm.Validate - -code.gitea.io/gitea/services/pull - IsCommitStatusContextSuccess - -code.gitea.io/gitea/services/repository +forgejo.org/services/repository IsErrForkAlreadyExist -code.gitea.io/gitea/services/repository/archiver - ArchiveRepository - -code.gitea.io/gitea/services/repository/files +forgejo.org/services/repository/files ContentType.String - GetFileResponseFromCommit - TemporaryUploadRepository.GetLastCommit - TemporaryUploadRepository.GetLastCommitByRef -code.gitea.io/gitea/services/webhook +forgejo.org/services/repository/gitgraph + Parser.Reset + +forgejo.org/services/webhook NewNotifier diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index da649017ae..9d8f54ee13 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,16 +1,12 @@ { "name": "Gitea DevContainer", - "image": "mcr.microsoft.com/devcontainers/go:1.23-bullseye", + "image": "mcr.microsoft.com/devcontainers/go:1.24-bullseye", "features": { // installs nodejs into container "ghcr.io/devcontainers/features/node:1": { - "version": "20" - }, - "ghcr.io/devcontainers/features/git-lfs:1.2.1": {}, - "ghcr.io/devcontainers-contrib/features/poetry:2": {}, - "ghcr.io/devcontainers/features/python:1": { - "version": "3.12" + "version": "22" }, + "ghcr.io/devcontainers/features/git-lfs:1.2.3": {}, "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {} }, "customizations": { diff --git a/.dockerignore b/.dockerignore index a1611a1ca5..5e7a893014 100644 --- a/.dockerignore +++ b/.dockerignore @@ -34,6 +34,7 @@ _testmain.go *coverage.out coverage.all +coverage/ cpu.out /modules/migration/bindata.go diff --git a/.editorconfig b/.editorconfig index 8e2234e64b..5476eb02fb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,6 +12,9 @@ insert_final_newline = true [{*.{go,tmpl,html},Makefile,go.mod}] indent_style = tab +[go.*] +indent_style = tab + [templates/custom/*.tmpl] insert_final_newline = false @@ -26,3 +29,8 @@ insert_final_newline = false [options/locale/locale_*.ini] insert_final_newline = false + +# Weblate JSON output defaults to four spaces +[options/locale_next/locale_*.json] +indent_style = space +indent_size = 4 diff --git a/.envrc b/.envrc.example similarity index 100% rename from .envrc rename to .envrc.example diff --git a/.eslintrc.yaml b/.eslintrc.yaml deleted file mode 100644 index db85b143dd..0000000000 --- a/.eslintrc.yaml +++ /dev/null @@ -1,803 +0,0 @@ -root: true -reportUnusedDisableDirectives: true - -ignorePatterns: - - /web_src/js/vendor - - /web_src/fomantic - - /public/assets/js - -parserOptions: - sourceType: module - ecmaVersion: latest - -plugins: - - "@eslint-community/eslint-plugin-eslint-comments" - - "@stylistic/eslint-plugin-js" - - "@vitest" - - eslint-plugin-array-func - - eslint-plugin-github - - eslint-plugin-i - - eslint-plugin-no-jquery - - eslint-plugin-no-use-extend-native - - eslint-plugin-regexp - - eslint-plugin-sonarjs - - eslint-plugin-unicorn - - eslint-plugin-vitest-globals - - eslint-plugin-wc - -env: - es2024: true - node: true - -overrides: - - files: ["web_src/**/*"] - globals: - __webpack_public_path__: true - process: false # https://github.com/webpack/webpack/issues/15833 - - files: ["web_src/**/*", "docs/**/*"] - env: - browser: true - node: false - - files: ["web_src/**/*worker.*"] - env: - worker: true - rules: - no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, status, statusbar, stop, toolbar, top] - - files: ["*.config.*"] - rules: - i/no-unused-modules: [0] - - files: ["**/*.test.*", "web_src/js/test/setup.js"] - env: - vitest-globals/env: true - rules: - "@vitest/consistent-test-filename": [0] - "@vitest/consistent-test-it": [0] - "@vitest/expect-expect": [0] - "@vitest/max-expects": [0] - "@vitest/max-nested-describe": [0] - "@vitest/no-alias-methods": [0] - "@vitest/no-commented-out-tests": [0] - "@vitest/no-conditional-expect": [0] - "@vitest/no-conditional-in-test": [0] - "@vitest/no-conditional-tests": [0] - "@vitest/no-disabled-tests": [0] - "@vitest/no-done-callback": [0] - "@vitest/no-duplicate-hooks": [0] - "@vitest/no-focused-tests": [0] - "@vitest/no-hooks": [0] - "@vitest/no-identical-title": [2] - "@vitest/no-interpolation-in-snapshots": [0] - "@vitest/no-large-snapshots": [0] - "@vitest/no-mocks-import": [0] - "@vitest/no-restricted-matchers": [0] - "@vitest/no-restricted-vi-methods": [0] - "@vitest/no-standalone-expect": [0] - "@vitest/no-test-prefixes": [0] - "@vitest/no-test-return-statement": [0] - "@vitest/prefer-called-with": [0] - "@vitest/prefer-comparison-matcher": [0] - "@vitest/prefer-each": [0] - "@vitest/prefer-equality-matcher": [0] - "@vitest/prefer-expect-resolves": [0] - "@vitest/prefer-hooks-in-order": [0] - "@vitest/prefer-hooks-on-top": [2] - "@vitest/prefer-lowercase-title": [0] - "@vitest/prefer-mock-promise-shorthand": [0] - "@vitest/prefer-snapshot-hint": [0] - "@vitest/prefer-spy-on": [0] - "@vitest/prefer-strict-equal": [0] - "@vitest/prefer-to-be": [0] - "@vitest/prefer-to-be-falsy": [0] - "@vitest/prefer-to-be-object": [0] - "@vitest/prefer-to-be-truthy": [0] - "@vitest/prefer-to-contain": [0] - "@vitest/prefer-to-have-length": [0] - "@vitest/prefer-todo": [0] - "@vitest/require-hook": [0] - "@vitest/require-to-throw-message": [0] - "@vitest/require-top-level-describe": [0] - "@vitest/valid-describe-callback": [2] - "@vitest/valid-expect": [2] - "@vitest/valid-title": [2] - - files: ["web_src/js/modules/fetch.js", "web_src/js/standalone/**/*"] - rules: - no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression] - -rules: - "@eslint-community/eslint-comments/disable-enable-pair": [2] - "@eslint-community/eslint-comments/no-aggregating-enable": [2] - "@eslint-community/eslint-comments/no-duplicate-disable": [2] - "@eslint-community/eslint-comments/no-restricted-disable": [0] - "@eslint-community/eslint-comments/no-unlimited-disable": [2] - "@eslint-community/eslint-comments/no-unused-disable": [2] - "@eslint-community/eslint-comments/no-unused-enable": [2] - "@eslint-community/eslint-comments/no-use": [0] - "@eslint-community/eslint-comments/require-description": [0] - "@stylistic/js/array-bracket-newline": [0] - "@stylistic/js/array-bracket-spacing": [2, never] - "@stylistic/js/array-element-newline": [0] - "@stylistic/js/arrow-parens": [2, always] - "@stylistic/js/arrow-spacing": [2, {before: true, after: true}] - "@stylistic/js/block-spacing": [0] - "@stylistic/js/brace-style": [2, 1tbs, {allowSingleLine: true}] - "@stylistic/js/comma-dangle": [2, always-multiline] - "@stylistic/js/comma-spacing": [2, {before: false, after: true}] - "@stylistic/js/comma-style": [2, last] - "@stylistic/js/computed-property-spacing": [2, never] - "@stylistic/js/dot-location": [2, property] - "@stylistic/js/eol-last": [2] - "@stylistic/js/function-call-spacing": [2, never] - "@stylistic/js/function-call-argument-newline": [0] - "@stylistic/js/function-paren-newline": [0] - "@stylistic/js/generator-star-spacing": [0] - "@stylistic/js/implicit-arrow-linebreak": [0] - "@stylistic/js/indent": [2, 2, {ignoreComments: true, SwitchCase: 1}] - "@stylistic/js/key-spacing": [2] - "@stylistic/js/keyword-spacing": [2] - "@stylistic/js/linebreak-style": [2, unix] - "@stylistic/js/lines-around-comment": [0] - "@stylistic/js/lines-between-class-members": [0] - "@stylistic/js/max-len": [0] - "@stylistic/js/max-statements-per-line": [0] - "@stylistic/js/multiline-ternary": [0] - "@stylistic/js/new-parens": [2] - "@stylistic/js/newline-per-chained-call": [0] - "@stylistic/js/no-confusing-arrow": [0] - "@stylistic/js/no-extra-parens": [0] - "@stylistic/js/no-extra-semi": [2] - "@stylistic/js/no-floating-decimal": [0] - "@stylistic/js/no-mixed-operators": [0] - "@stylistic/js/no-mixed-spaces-and-tabs": [2] - "@stylistic/js/no-multi-spaces": [2, {ignoreEOLComments: true, exceptions: {Property: true}}] - "@stylistic/js/no-multiple-empty-lines": [2, {max: 1, maxEOF: 0, maxBOF: 0}] - "@stylistic/js/no-tabs": [2] - "@stylistic/js/no-trailing-spaces": [2] - "@stylistic/js/no-whitespace-before-property": [2] - "@stylistic/js/nonblock-statement-body-position": [2] - "@stylistic/js/object-curly-newline": [0] - "@stylistic/js/object-curly-spacing": [2, never] - "@stylistic/js/object-property-newline": [0] - "@stylistic/js/one-var-declaration-per-line": [0] - "@stylistic/js/operator-linebreak": [2, after] - "@stylistic/js/padded-blocks": [2, never] - "@stylistic/js/padding-line-between-statements": [0] - "@stylistic/js/quote-props": [0] - "@stylistic/js/quotes": [2, single, {avoidEscape: true, allowTemplateLiterals: true}] - "@stylistic/js/rest-spread-spacing": [2, never] - "@stylistic/js/semi": [2, always, {omitLastInOneLineBlock: true}] - "@stylistic/js/semi-spacing": [2, {before: false, after: true}] - "@stylistic/js/semi-style": [2, last] - "@stylistic/js/space-before-blocks": [2, always] - "@stylistic/js/space-before-function-paren": [2, {anonymous: ignore, named: never, asyncArrow: always}] - "@stylistic/js/space-in-parens": [2, never] - "@stylistic/js/space-infix-ops": [2] - "@stylistic/js/space-unary-ops": [2] - "@stylistic/js/spaced-comment": [2, always] - "@stylistic/js/switch-colon-spacing": [2] - "@stylistic/js/template-curly-spacing": [2, never] - "@stylistic/js/template-tag-spacing": [2, never] - "@stylistic/js/wrap-iife": [2, inside] - "@stylistic/js/wrap-regex": [0] - "@stylistic/js/yield-star-spacing": [2, after] - accessor-pairs: [2] - array-callback-return: [2, {checkForEach: true}] - array-func/avoid-reverse: [2] - array-func/from-map: [2] - array-func/no-unnecessary-this-arg: [2] - array-func/prefer-array-from: [2] - array-func/prefer-flat-map: [0] # handled by unicorn/prefer-array-flat-map - array-func/prefer-flat: [0] # handled by unicorn/prefer-array-flat - arrow-body-style: [0] - block-scoped-var: [2] - camelcase: [0] - capitalized-comments: [0] - class-methods-use-this: [0] - complexity: [0] - consistent-return: [0] - consistent-this: [0] - constructor-super: [2] - curly: [0] - default-case-last: [2] - default-case: [0] - default-param-last: [0] - dot-notation: [0] - eqeqeq: [2] - for-direction: [2] - func-name-matching: [2] - func-names: [0] - func-style: [0] - getter-return: [2] - github/a11y-aria-label-is-well-formatted: [0] - github/a11y-no-title-attribute: [0] - github/a11y-no-visually-hidden-interactive-element: [0] - github/a11y-role-supports-aria-props: [0] - github/a11y-svg-has-accessible-name: [0] - github/array-foreach: [0] - github/async-currenttarget: [2] - github/async-preventdefault: [2] - github/authenticity-token: [0] - github/get-attribute: [0] - github/js-class-name: [0] - github/no-blur: [0] - github/no-d-none: [0] - github/no-dataset: [2] - github/no-dynamic-script-tag: [2] - github/no-implicit-buggy-globals: [2] - github/no-inner-html: [0] - github/no-innerText: [2] - github/no-then: [2] - github/no-useless-passive: [2] - github/prefer-observers: [2] - github/require-passive-events: [2] - github/unescaped-html-literal: [0] - grouped-accessor-pairs: [2] - guard-for-in: [0] - id-blacklist: [0] - id-length: [0] - id-match: [0] - i/consistent-type-specifier-style: [0] - i/default: [0] - i/dynamic-import-chunkname: [0] - i/export: [2] - i/exports-last: [0] - i/extensions: [2, always, {ignorePackages: true}] - i/first: [2] - i/group-exports: [0] - i/max-dependencies: [0] - i/named: [2] - i/namespace: [0] - i/newline-after-import: [0] - i/no-absolute-path: [0] - i/no-amd: [2] - i/no-anonymous-default-export: [0] - i/no-commonjs: [2] - i/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}] - i/no-default-export: [0] - i/no-deprecated: [0] - i/no-dynamic-require: [0] - i/no-empty-named-blocks: [2] - i/no-extraneous-dependencies: [2] - i/no-import-module-exports: [0] - i/no-internal-modules: [0] - i/no-mutable-exports: [0] - i/no-named-as-default-member: [0] - i/no-named-as-default: [2] - i/no-named-default: [0] - i/no-named-export: [0] - i/no-namespace: [0] - i/no-nodejs-modules: [0] - i/no-relative-packages: [0] - i/no-relative-parent-imports: [0] - i/no-restricted-paths: [0] - i/no-self-import: [2] - i/no-unassigned-import: [0] - i/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$", ^vitest/]}] - i/no-unused-modules: [2, {unusedExports: true}] - i/no-useless-path-segments: [2, {commonjs: true}] - i/no-webpack-loader-syntax: [2] - i/order: [0] - i/prefer-default-export: [0] - i/unambiguous: [0] - init-declarations: [0] - line-comment-position: [0] - logical-assignment-operators: [0] - max-classes-per-file: [0] - max-depth: [0] - max-lines-per-function: [0] - max-lines: [0] - max-nested-callbacks: [0] - max-params: [0] - max-statements: [0] - multiline-comment-style: [2, separate-lines] - new-cap: [0] - no-alert: [0] - no-array-constructor: [2] - no-async-promise-executor: [0] - no-await-in-loop: [0] - no-bitwise: [0] - no-buffer-constructor: [0] - no-caller: [2] - no-case-declarations: [2] - no-class-assign: [2] - no-compare-neg-zero: [2] - no-cond-assign: [2, except-parens] - no-console: [1, {allow: [debug, info, warn, error]}] - no-const-assign: [2] - no-constant-binary-expression: [2] - no-constant-condition: [0] - no-constructor-return: [2] - no-continue: [0] - no-control-regex: [0] - no-debugger: [1] - no-delete-var: [2] - no-div-regex: [0] - no-dupe-args: [2] - no-dupe-class-members: [2] - no-dupe-else-if: [2] - no-dupe-keys: [2] - no-duplicate-case: [2] - no-duplicate-imports: [2] - no-else-return: [2] - no-empty-character-class: [2] - no-empty-function: [0] - no-empty-pattern: [2] - no-empty-static-block: [2] - no-empty: [2, {allowEmptyCatch: true}] - no-eq-null: [2] - no-eval: [2] - no-ex-assign: [2] - no-extend-native: [2] - no-extra-bind: [2] - no-extra-boolean-cast: [2] - no-extra-label: [0] - no-fallthrough: [2] - no-func-assign: [2] - no-global-assign: [2] - no-implicit-coercion: [2] - no-implicit-globals: [0] - no-implied-eval: [2] - no-import-assign: [2] - no-inline-comments: [0] - no-inner-declarations: [2] - no-invalid-regexp: [2] - no-invalid-this: [0] - no-irregular-whitespace: [2] - no-iterator: [2] - no-jquery/no-ajax-events: [2] - no-jquery/no-ajax: [2] - no-jquery/no-and-self: [2] - no-jquery/no-animate-toggle: [2] - no-jquery/no-animate: [2] - no-jquery/no-append-html: [2] - no-jquery/no-attr: [2] - no-jquery/no-bind: [2] - no-jquery/no-box-model: [2] - no-jquery/no-browser: [2] - no-jquery/no-camel-case: [2] - no-jquery/no-class-state: [2] - no-jquery/no-class: [0] - no-jquery/no-clone: [2] - no-jquery/no-closest: [0] - no-jquery/no-constructor-attributes: [2] - no-jquery/no-contains: [2] - no-jquery/no-context-prop: [2] - no-jquery/no-css: [2] - no-jquery/no-data: [0] - no-jquery/no-deferred: [2] - no-jquery/no-delegate: [2] - no-jquery/no-each-collection: [0] - no-jquery/no-each-util: [0] - no-jquery/no-each: [0] - no-jquery/no-error-shorthand: [2] - no-jquery/no-error: [2] - no-jquery/no-escape-selector: [2] - no-jquery/no-event-shorthand: [2] - no-jquery/no-extend: [2] - no-jquery/no-fade: [2] - no-jquery/no-filter: [0] - no-jquery/no-find-collection: [0] - no-jquery/no-find-util: [2] - no-jquery/no-find: [0] - no-jquery/no-fx-interval: [2] - no-jquery/no-global-eval: [2] - no-jquery/no-global-selector: [0] - no-jquery/no-grep: [2] - no-jquery/no-has: [2] - no-jquery/no-hold-ready: [2] - no-jquery/no-html: [0] - no-jquery/no-in-array: [2] - no-jquery/no-is-array: [2] - no-jquery/no-is-empty-object: [2] - no-jquery/no-is-function: [2] - no-jquery/no-is-numeric: [2] - no-jquery/no-is-plain-object: [2] - no-jquery/no-is-window: [2] - no-jquery/no-is: [2] - no-jquery/no-jquery-constructor: [0] - no-jquery/no-live: [2] - no-jquery/no-load-shorthand: [2] - no-jquery/no-load: [2] - no-jquery/no-map-collection: [0] - no-jquery/no-map-util: [2] - no-jquery/no-map: [2] - no-jquery/no-merge: [2] - no-jquery/no-node-name: [2] - no-jquery/no-noop: [2] - no-jquery/no-now: [2] - no-jquery/no-on-ready: [2] - no-jquery/no-other-methods: [0] - no-jquery/no-other-utils: [2] - no-jquery/no-param: [2] - no-jquery/no-parent: [0] - no-jquery/no-parents: [2] - no-jquery/no-parse-html-literal: [2] - no-jquery/no-parse-html: [2] - no-jquery/no-parse-json: [2] - no-jquery/no-parse-xml: [2] - no-jquery/no-prop: [2] - no-jquery/no-proxy: [2] - no-jquery/no-ready-shorthand: [2] - no-jquery/no-ready: [2] - no-jquery/no-selector-prop: [2] - no-jquery/no-serialize: [2] - no-jquery/no-size: [2] - no-jquery/no-sizzle: [0] - no-jquery/no-slide: [2] - no-jquery/no-sub: [2] - no-jquery/no-support: [2] - no-jquery/no-text: [0] - no-jquery/no-trigger: [0] - no-jquery/no-trim: [2] - no-jquery/no-type: [2] - no-jquery/no-unique: [2] - no-jquery/no-unload-shorthand: [2] - no-jquery/no-val: [0] - no-jquery/no-visibility: [2] - no-jquery/no-when: [2] - no-jquery/no-wrap: [2] - no-jquery/variable-pattern: [2] - no-label-var: [2] - no-labels: [0] # handled by no-restricted-syntax - no-lone-blocks: [2] - no-lonely-if: [0] - no-loop-func: [0] - no-loss-of-precision: [2] - no-magic-numbers: [0] - no-misleading-character-class: [2] - no-multi-assign: [0] - no-multi-str: [2] - no-negated-condition: [0] - no-nested-ternary: [0] - no-new-func: [2] - no-new-native-nonconstructor: [2] - no-new-object: [2] - no-new-symbol: [2] - no-new-wrappers: [2] - no-new: [0] - no-nonoctal-decimal-escape: [2] - no-obj-calls: [2] - no-octal-escape: [2] - no-octal: [2] - no-param-reassign: [0] - no-plusplus: [0] - no-promise-executor-return: [0] - no-proto: [2] - no-prototype-builtins: [2] - no-redeclare: [2] - no-regex-spaces: [2] - no-restricted-exports: [0] - no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, location, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, self, status, statusbar, stop, toolbar, top, __dirname, __filename] - no-restricted-imports: [0] - no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression, {selector: "CallExpression[callee.name='fetch']", message: "use modules/fetch.js instead"}] - no-return-assign: [0] - no-script-url: [2] - no-self-assign: [2, {props: true}] - no-self-compare: [2] - no-sequences: [2] - no-setter-return: [2] - no-shadow-restricted-names: [2] - no-shadow: [0] - no-sparse-arrays: [2] - no-template-curly-in-string: [2] - no-ternary: [0] - no-this-before-super: [2] - no-throw-literal: [2] - no-undef-init: [2] - no-undef: [2, {typeof: true}] - no-undefined: [0] - no-underscore-dangle: [0] - no-unexpected-multiline: [2] - no-unmodified-loop-condition: [2] - no-unneeded-ternary: [2] - no-unreachable-loop: [2] - no-unreachable: [2] - no-unsafe-finally: [2] - no-unsafe-negation: [2] - no-unused-expressions: [2] - no-unused-labels: [2] - no-unused-private-class-members: [2] - no-unused-vars: [2, {args: all, argsIgnorePattern: ^_, varsIgnorePattern: ^_, caughtErrorsIgnorePattern: ^_, destructuredArrayIgnorePattern: ^_, ignoreRestSiblings: false}] - no-use-before-define: [2, {functions: false, classes: true, variables: true, allowNamedExports: true}] - no-use-extend-native/no-use-extend-native: [2] - no-useless-backreference: [2] - no-useless-call: [2] - no-useless-catch: [2] - no-useless-computed-key: [2] - no-useless-concat: [2] - no-useless-constructor: [2] - no-useless-escape: [2] - no-useless-rename: [2] - no-useless-return: [2] - no-var: [2] - no-void: [2] - no-warning-comments: [0] - no-with: [0] # handled by no-restricted-syntax - object-shorthand: [2, always] - one-var-declaration-per-line: [0] - one-var: [0] - operator-assignment: [2, always] - operator-linebreak: [2, after] - prefer-arrow-callback: [2, {allowNamedFunctions: true, allowUnboundThis: true}] - prefer-const: [2, {destructuring: all, ignoreReadBeforeAssign: true}] - prefer-destructuring: [0] - prefer-exponentiation-operator: [2] - prefer-named-capture-group: [0] - prefer-numeric-literals: [2] - prefer-object-has-own: [2] - prefer-object-spread: [2] - prefer-promise-reject-errors: [2, {allowEmptyReject: false}] - prefer-regex-literals: [2] - prefer-rest-params: [2] - prefer-spread: [2] - prefer-template: [2] - radix: [2, as-needed] - regexp/confusing-quantifier: [2] - regexp/control-character-escape: [2] - regexp/hexadecimal-escape: [0] - regexp/letter-case: [0] - regexp/match-any: [2] - regexp/negation: [2] - regexp/no-contradiction-with-assertion: [0] - regexp/no-control-character: [0] - regexp/no-dupe-characters-character-class: [2] - regexp/no-dupe-disjunctions: [2] - regexp/no-empty-alternative: [2] - regexp/no-empty-capturing-group: [2] - regexp/no-empty-character-class: [0] - regexp/no-empty-group: [2] - regexp/no-empty-lookarounds-assertion: [2] - regexp/no-empty-string-literal: [2] - regexp/no-escape-backspace: [2] - regexp/no-extra-lookaround-assertions: [0] - regexp/no-invalid-regexp: [2] - regexp/no-invisible-character: [2] - regexp/no-lazy-ends: [2] - regexp/no-legacy-features: [2] - regexp/no-misleading-capturing-group: [0] - regexp/no-misleading-unicode-character: [0] - regexp/no-missing-g-flag: [2] - regexp/no-non-standard-flag: [2] - regexp/no-obscure-range: [2] - regexp/no-octal: [2] - regexp/no-optional-assertion: [2] - regexp/no-potentially-useless-backreference: [2] - regexp/no-standalone-backslash: [2] - regexp/no-super-linear-backtracking: [0] - regexp/no-super-linear-move: [0] - regexp/no-trivially-nested-assertion: [2] - regexp/no-trivially-nested-quantifier: [2] - regexp/no-unused-capturing-group: [0] - regexp/no-useless-assertions: [2] - regexp/no-useless-backreference: [2] - regexp/no-useless-character-class: [2] - regexp/no-useless-dollar-replacements: [2] - regexp/no-useless-escape: [2] - regexp/no-useless-flag: [2] - regexp/no-useless-lazy: [2] - regexp/no-useless-non-capturing-group: [2] - regexp/no-useless-quantifier: [2] - regexp/no-useless-range: [2] - regexp/no-useless-set-operand: [2] - regexp/no-useless-string-literal: [2] - regexp/no-useless-two-nums-quantifier: [2] - regexp/no-zero-quantifier: [2] - regexp/optimal-lookaround-quantifier: [2] - regexp/optimal-quantifier-concatenation: [0] - regexp/prefer-character-class: [0] - regexp/prefer-d: [0] - regexp/prefer-escape-replacement-dollar-char: [0] - regexp/prefer-lookaround: [0] - regexp/prefer-named-backreference: [0] - regexp/prefer-named-capture-group: [0] - regexp/prefer-named-replacement: [0] - regexp/prefer-plus-quantifier: [2] - regexp/prefer-predefined-assertion: [2] - regexp/prefer-quantifier: [0] - regexp/prefer-question-quantifier: [2] - regexp/prefer-range: [2] - regexp/prefer-regexp-exec: [2] - regexp/prefer-regexp-test: [2] - regexp/prefer-result-array-groups: [0] - regexp/prefer-set-operation: [2] - regexp/prefer-star-quantifier: [2] - regexp/prefer-unicode-codepoint-escapes: [2] - regexp/prefer-w: [0] - regexp/require-unicode-regexp: [0] - regexp/simplify-set-operations: [2] - regexp/sort-alternatives: [0] - regexp/sort-character-class-elements: [0] - regexp/sort-flags: [0] - regexp/strict: [2] - regexp/unicode-escape: [0] - regexp/use-ignore-case: [0] - require-atomic-updates: [0] - require-await: [0] - require-unicode-regexp: [0] - require-yield: [2] - sonarjs/cognitive-complexity: [0] - sonarjs/elseif-without-else: [0] - sonarjs/max-switch-cases: [0] - sonarjs/no-all-duplicated-branches: [2] - sonarjs/no-collapsible-if: [0] - sonarjs/no-collection-size-mischeck: [2] - sonarjs/no-duplicate-string: [0] - sonarjs/no-duplicated-branches: [0] - sonarjs/no-element-overwrite: [2] - sonarjs/no-empty-collection: [2] - sonarjs/no-extra-arguments: [2] - sonarjs/no-gratuitous-expressions: [2] - sonarjs/no-identical-conditions: [2] - sonarjs/no-identical-expressions: [2] - sonarjs/no-identical-functions: [2, 5] - sonarjs/no-ignored-return: [2] - sonarjs/no-inverted-boolean-check: [2] - sonarjs/no-nested-switch: [0] - sonarjs/no-nested-template-literals: [0] - sonarjs/no-one-iteration-loop: [2] - sonarjs/no-redundant-boolean: [2] - sonarjs/no-redundant-jump: [2] - sonarjs/no-same-line-conditional: [2] - sonarjs/no-small-switch: [0] - sonarjs/no-unused-collection: [2] - sonarjs/no-use-of-empty-return-value: [2] - sonarjs/no-useless-catch: [2] - sonarjs/non-existent-operator: [2] - sonarjs/prefer-immediate-return: [0] - sonarjs/prefer-object-literal: [0] - sonarjs/prefer-single-boolean-return: [0] - sonarjs/prefer-while: [2] - sort-imports: [0] - sort-keys: [0] - sort-vars: [0] - strict: [0] - symbol-description: [2] - unicode-bom: [2, never] - unicorn/better-regex: [0] - unicorn/catch-error-name: [0] - unicorn/consistent-destructuring: [2] - unicorn/consistent-empty-array-spread: [2] - unicorn/consistent-function-scoping: [2] - unicorn/custom-error-definition: [0] - unicorn/empty-brace-spaces: [2] - unicorn/error-message: [0] - unicorn/escape-case: [0] - unicorn/expiring-todo-comments: [0] - 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-anonymous-default-export: [0] - unicorn/no-array-callback-reference: [0] - unicorn/no-array-for-each: [2] - unicorn/no-array-method-this-argument: [2] - unicorn/no-array-push-push: [2] - unicorn/no-array-reduce: [2] - unicorn/no-await-expression-member: [0] - unicorn/no-await-in-promise-methods: [2] - unicorn/no-console-spaces: [0] - unicorn/no-document-cookie: [2] - unicorn/no-empty-file: [2] - unicorn/no-for-loop: [0] - unicorn/no-hex-escape: [0] - unicorn/no-instanceof-array: [0] - unicorn/no-invalid-fetch-options: [2] - unicorn/no-invalid-remove-event-listener: [2] - unicorn/no-keyword-prefix: [0] - unicorn/no-length-as-slice-end: [2] - unicorn/no-lonely-if: [2] - unicorn/no-magic-array-flat-depth: [0] - unicorn/no-negated-condition: [0] - unicorn/no-negation-in-equality-check: [2] - unicorn/no-nested-ternary: [0] - unicorn/no-new-array: [0] - unicorn/no-new-buffer: [0] - unicorn/no-null: [0] - unicorn/no-object-as-default-parameter: [0] - unicorn/no-process-exit: [0] - unicorn/no-single-promise-in-promise-methods: [2] - unicorn/no-static-only-class: [2] - unicorn/no-thenable: [2] - unicorn/no-this-assignment: [2] - unicorn/no-typeof-undefined: [2] - unicorn/no-unnecessary-await: [2] - unicorn/no-unnecessary-polyfills: [2] - unicorn/no-unreadable-array-destructuring: [0] - unicorn/no-unreadable-iife: [2] - unicorn/no-unused-properties: [2] - unicorn/no-useless-fallback-in-spread: [2] - unicorn/no-useless-length-check: [2] - unicorn/no-useless-promise-resolve-reject: [2] - unicorn/no-useless-spread: [2] - unicorn/no-useless-switch-case: [2] - 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-array-flat-map: [2] - unicorn/prefer-array-flat: [2] - unicorn/prefer-array-index-of: [2] - unicorn/prefer-array-some: [2] - unicorn/prefer-at: [0] - unicorn/prefer-blob-reading-methods: [2] - unicorn/prefer-code-point: [0] - unicorn/prefer-date-now: [2] - unicorn/prefer-default-parameters: [0] - unicorn/prefer-dom-node-append: [2] - unicorn/prefer-dom-node-dataset: [0] - unicorn/prefer-dom-node-remove: [2] - unicorn/prefer-dom-node-text-content: [2] - unicorn/prefer-event-target: [2] - unicorn/prefer-export-from: [0] - unicorn/prefer-includes: [2] - unicorn/prefer-json-parse-buffer: [0] - unicorn/prefer-keyboard-event-key: [2] - unicorn/prefer-logical-operator-over-ternary: [2] - unicorn/prefer-math-trunc: [2] - unicorn/prefer-modern-dom-apis: [0] - unicorn/prefer-modern-math-apis: [2] - unicorn/prefer-module: [2] - unicorn/prefer-native-coercion-functions: [2] - unicorn/prefer-negative-index: [2] - unicorn/prefer-node-protocol: [2] - unicorn/prefer-number-properties: [0] - unicorn/prefer-object-from-entries: [2] - unicorn/prefer-object-has-own: [0] - unicorn/prefer-optional-catch-binding: [2] - unicorn/prefer-prototype-methods: [0] - unicorn/prefer-query-selector: [0] - unicorn/prefer-reflect-apply: [0] - unicorn/prefer-regexp-test: [2] - unicorn/prefer-set-has: [0] - unicorn/prefer-set-size: [2] - unicorn/prefer-spread: [0] - unicorn/prefer-string-raw: [0] - unicorn/prefer-string-replace-all: [0] - unicorn/prefer-string-slice: [0] - unicorn/prefer-string-starts-ends-with: [2] - unicorn/prefer-string-trim-start-end: [2] - unicorn/prefer-structured-clone: [2] - unicorn/prefer-switch: [0] - unicorn/prefer-ternary: [0] - unicorn/prefer-text-content: [2] - unicorn/prefer-top-level-await: [0] - unicorn/prefer-type-error: [0] - unicorn/prevent-abbreviations: [0] - unicorn/relative-url-style: [2] - unicorn/require-array-join-separator: [2] - unicorn/require-number-to-fixed-digits-argument: [2] - unicorn/require-post-message-target-origin: [0] - unicorn/string-content: [0] - unicorn/switch-case-braces: [0] - unicorn/template-indent: [2] - unicorn/text-encoding-identifier-case: [0] - unicorn/throw-new-error: [2] - use-isnan: [2] - valid-typeof: [2, {requireStringLiterals: true}] - vars-on-top: [0] - wc/attach-shadow-constructor: [2] - wc/define-tag-after-class-definition: [0] - wc/expose-class-on-global: [0] - wc/file-name-matches-element: [2] - wc/guard-define-call: [0] - wc/guard-super-call: [2] - wc/max-elements-per-file: [0] - wc/no-child-traversal-in-attributechangedcallback: [2] - wc/no-child-traversal-in-connectedcallback: [2] - wc/no-closed-shadow-root: [2] - wc/no-constructor-attributes: [2] - wc/no-constructor-params: [2] - wc/no-constructor: [2] - wc/no-customized-built-in-elements: [2] - wc/no-exports-with-element: [0] - wc/no-invalid-element-name: [2] - wc/no-invalid-extends: [2] - wc/no-method-prefixed-with-on: [2] - wc/no-self-class: [2] - wc/no-typos: [2] - wc/require-listener-teardown: [2] - wc/tag-name-matches-class: [2] - yoda: [2, never] diff --git a/.forgejo/issue_template/config.yml b/.forgejo/issue_template/config.yml index 0e3caf9280..f2ea8d945a 100644 --- a/.forgejo/issue_template/config.yml +++ b/.forgejo/issue_template/config.yml @@ -1,7 +1,7 @@ contact_links: - name: 🔓 Security Reports url: mailto:security@forgejo.org - about: "Please email (GPG: `A4676E79`) instead of opening a public issue." + about: "Please email (See https://forgejo.org/.well-known/security.txt)." - name: 💬 Matrix Chat Room url: https://matrix.to/#/#forgejo-chat:matrix.org about: Please ask questions and discuss configuration or deployment problems here. diff --git a/.forgejo/testdata/build-release/Dockerfile b/.forgejo/testdata/build-release/Dockerfile index 9c44dedddd..d10564359e 100644 --- a/.forgejo/testdata/build-release/Dockerfile +++ b/.forgejo/testdata/build-release/Dockerfile @@ -1,6 +1,6 @@ -FROM code.forgejo.org/oci/alpine:3.20 +FROM data.forgejo.org/oci/alpine:3.21 ARG RELEASE_VERSION=unkown LABEL maintainer="contact@forgejo.org" \ org.opencontainers.image.version="${RELEASE_VERSION}" RUN mkdir -p /app/gitea -RUN ( echo '#!/bin/sh' ; echo "echo forgejo v$RELEASE_VERSION" ) > /app/gitea/forgejo-cli ; chmod +x /app/gitea/forgejo-cli +RUN ( echo '#!/bin/sh' ; echo "echo forgejo v$RELEASE_VERSION" ) > /app/gitea/gitea ; chmod +x /app/gitea/gitea diff --git a/.forgejo/testdata/build-release/go.mod b/.forgejo/testdata/build-release/go.mod index 697bc87b98..585dcc4f3d 100644 --- a/.forgejo/testdata/build-release/go.mod +++ b/.forgejo/testdata/build-release/go.mod @@ -1,3 +1,3 @@ -module code.gitea.io/gitea +module forgejo.org -go 1.23.1 +go 1.23.3 diff --git a/.forgejo/workflows-composite/apt-install-from/action.yaml b/.forgejo/workflows-composite/apt-install-from/action.yaml new file mode 100644 index 0000000000..615e7cb184 --- /dev/null +++ b/.forgejo/workflows-composite/apt-install-from/action.yaml @@ -0,0 +1,29 @@ +inputs: + packages: + description: 'Packages to install' + required: true + release: + description: 'Release to install from' + default: testing + +runs: + using: "composite" + steps: + - name: setup apt package source + run: | + export DEBIAN_FRONTEND=noninteractive + echo "deb http://deb.debian.org/debian/ ${RELEASE} main" > "/etc/apt/sources.list.d/${RELEASE}.list" + env: + RELEASE: ${{inputs.release}} + - name: install packages + run: | + apt-get update -qq + apt-get -q install -qq -y ${PACKAGES} + env: + PACKAGES: ${{inputs.packages}} + - name: remove temporary package list to prevent using it in other steps + run: | + rm "/etc/apt/sources.list.d/${RELEASE}.list" + apt-get update -qq + env: + RELEASE: ${{inputs.release}} diff --git a/.forgejo/workflows-composite/build-backend/action.yaml b/.forgejo/workflows-composite/build-backend/action.yaml new file mode 100644 index 0000000000..68a99ffaf9 --- /dev/null +++ b/.forgejo/workflows-composite/build-backend/action.yaml @@ -0,0 +1,15 @@ +runs: + using: "composite" + steps: + - run: | + su forgejo -c 'make deps-backend' + - uses: https://data.forgejo.org/actions/cache@v4 + id: cache-backend + with: + path: ${{github.workspace}}/gitea + key: backend-build-${{ github.sha }} + - if: steps.cache-backend.outputs.cache-hit != 'true' + run: | + su forgejo -c 'make backend' + env: + TAGS: bindata diff --git a/.forgejo/workflows-composite/setup-cache-go/action.yaml b/.forgejo/workflows-composite/setup-cache-go/action.yaml new file mode 100644 index 0000000000..f2818a7635 --- /dev/null +++ b/.forgejo/workflows-composite/setup-cache-go/action.yaml @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: MIT +name: 'Forgejo Actions to setup Go and cache dependencies' +author: 'Forgejo authors' +description: | + Wrap the setup-go with improved dependency caching. +inputs: + username: + description: 'User for which to manage the dependency cache' + default: root + +runs: + using: "composite" + steps: + - name: "Install zstd for faster caching" + run: | + apt-get update -qq + apt-get -q install -qq -y zstd + + - name: "Set up Go using setup-go" + uses: https://data.forgejo.org/actions/setup-go@v5 + id: go-version + with: + go-version-file: "go.mod" + # do not cache dependencies, we do this manually + cache: false + + - name: "Get go environment information" + id: go-environment + run: | + chmod 755 $HOME # ensure ${RUN_AS_USER} has permission when go is located in $HOME + export GOROOT="$(go env GOROOT)" + echo "modcache=$(su ${RUN_AS_USER} -c '${GOROOT}/bin/go env GOMODCACHE')" >> "$GITHUB_OUTPUT" + echo "cache=$(su ${RUN_AS_USER} -c '${GOROOT}/bin/go env GOCACHE')" >> "$GITHUB_OUTPUT" + env: + RUN_AS_USER: ${{ inputs.username }} + GO_VERSION: ${{ steps.go-version.outputs.go-version }} + + - name: "Create cache folders with correct permissions (for non-root users)" + if: inputs.username != 'root' + # when the cache is restored, only the permissions of the last part are restored + # so assuming that /home/user exists and we are restoring /home/user/go/pkg/mod, + # both folders will have the correct permissions, but + # /home/user/go and /home/user/go/pkg might be owned by root + run: | + su ${RUN_AS_USER} -c 'mkdir -p "${MODCACHE_DIR}" "${CACHE_DIR}"' + env: + RUN_AS_USER: ${{ inputs.username }} + MODCACHE_DIR: ${{ steps.go-environment.outputs.modcache }} + CACHE_DIR: ${{ steps.go-environment.outputs.cache }} + + - name: "Restore Go dependencies from cache or mark for later caching" + id: cache-deps + uses: https://data.forgejo.org/actions/cache@v4 + with: + key: setup-cache-go-deps-${{ runner.os }}-${{ inputs.username }}-${{ steps.go-version.outputs.go_version }}-${{ hashFiles('go.sum', 'go.mod') }} + restore-keys: | + setup-cache-go-deps-${{ runner.os }}-${{ inputs.username }}-${{ steps.go-version.outputs.go_version }}- + setup-cache-go-deps-${{ runner.os }}-${{ inputs.username }}- + path: | + ${{ steps.go-environment.outputs.modcache }} + ${{ steps.go-environment.outputs.cache }} diff --git a/.forgejo/workflows-composite/setup-env/action.yaml b/.forgejo/workflows-composite/setup-env/action.yaml new file mode 100644 index 0000000000..f19569a137 --- /dev/null +++ b/.forgejo/workflows-composite/setup-env/action.yaml @@ -0,0 +1,25 @@ +# TODO: +# - [ ] prepare a forgejo ci image with the necessary tools and forgejo user +runs: + using: "composite" + steps: + - name: setup user and permissions + run: | + git config --add safe.directory '*' + # ignore if the user already exists (like with the playwright image) + adduser --quiet --comment forgejo --disabled-password forgejo || true + chown -R forgejo:forgejo . + + - uses: ./.forgejo/workflows-composite/setup-cache-go + with: + username: forgejo + + - name: validate go version + run: | + set -ex + toolchain=$(grep -oP '(?<=toolchain ).+' go.mod) + version=$(go version | cut -d' ' -f3) + if dpkg --compare-versions ${version#go} lt ${toolchain#go}; then + echo "go version too low: $toolchain >= $version" + exit 1 + fi diff --git a/.forgejo/workflows/backport.yml b/.forgejo/workflows/backport.yml index 32a93edbc0..573c33f93b 100644 --- a/.forgejo/workflows/backport.yml +++ b/.forgejo/workflows/backport.yml @@ -22,6 +22,8 @@ # `backport/v1.21` label on a merged pull request that can be backported # without conflict. # +name: issue-labels + on: pull_request_target: types: @@ -31,21 +33,21 @@ on: jobs: backporting: if: > - !startsWith(vars.ROLE, 'forgejo-') && ( + ( vars.ROLE == 'forgejo-coding' ) && ( github.event.pull_request.merged && contains(toJSON(github.event.pull_request.labels), 'backport/v') ) runs-on: docker container: - image: 'code.forgejo.org/oci/node:20-bookworm' + image: 'data.forgejo.org/oci/node:22-bookworm' steps: - name: event info run: | cat <<'EOF' ${{ toJSON(github) }} EOF - - uses: https://code.forgejo.org/actions/git-backporting@v4.8.0 + - uses: https://data.forgejo.org/actions/git-backporting@v4.8.5 with: target-branch-pattern: "^backport/(?(v.*))$" strategy: ort diff --git a/.forgejo/workflows/build-release-integration.yml b/.forgejo/workflows/build-release-integration.yml index 610b8f0520..1af6d567dd 100644 --- a/.forgejo/workflows/build-release-integration.yml +++ b/.forgejo/workflows/build-release-integration.yml @@ -22,13 +22,13 @@ on: jobs: release-simulation: - if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} - runs-on: self-hosted + if: vars.ROLE == 'forgejo-coding' + runs-on: lxc-bookworm steps: - - uses: actions/checkout@v3 + - uses: https://data.forgejo.org/actions/checkout@v4 - id: forgejo - uses: https://code.forgejo.org/actions/setup-forgejo@v1 + uses: https://data.forgejo.org/actions/setup-forgejo@v2.0.4 with: user: root password: admin1234 diff --git a/.forgejo/workflows/build-release.yml b/.forgejo/workflows/build-release.yml index ce05f6d8ff..a34f3533fd 100644 --- a/.forgejo/workflows/build-release.yml +++ b/.forgejo/workflows/build-release.yml @@ -14,6 +14,12 @@ # secrets.CASCADE_DESTINATION_TOKEN: scope read:user, write:repository, write:issue # vars.CASCADE_DESTINATION_DOER: forgejo-ci # +# vars.SKIP_END_TO_END: `true` or `false` +# It must be `false` (or absent) so https://code.forgejo.org/forgejo/end-to-end is run +# with the newly built release. +# It must be set to `true` when a release is missing, for instance because it was +# removed and failed to upload. +# on: push: tags: 'v[0-9]+.[0-9]+.*' @@ -23,11 +29,11 @@ on: jobs: release: - runs-on: self-hosted + runs-on: lxc-bookworm # root is used for testing, allow it if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root' steps: - - uses: actions/checkout@v3 + - uses: https://data.forgejo.org/actions/checkout@v4 with: fetch-depth: 0 @@ -37,11 +43,11 @@ jobs: repository="${{ github.repository }}" echo "value=${repository##*/}" >> "$GITHUB_OUTPUT" - - uses: https://code.forgejo.org/actions/setup-node@v3 + - uses: https://data.forgejo.org/actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - - uses: https://code.forgejo.org/actions/setup-go@v4 + - uses: https://data.forgejo.org/actions/setup-go@v5 with: go-version-file: "go.mod" @@ -87,7 +93,7 @@ jobs: - name: cache node_modules id: node - uses: https://code.forgejo.org/actions/cache@v3 + uses: https://data.forgejo.org/actions/cache@v4 with: path: | node_modules @@ -158,7 +164,7 @@ jobs: - name: build container & release if: ${{ secrets.TOKEN != '' }} - uses: https://code.forgejo.org/forgejo/forgejo-build-publish/build@v5.1.1 + uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.4 with: forgejo: "${{ env.GITHUB_SERVER_URL }}" owner: "${{ env.GITHUB_REPOSITORY_OWNER }}" @@ -170,14 +176,14 @@ jobs: platforms: linux/amd64,linux/arm64,linux/arm/v6 release-notes: "${{ steps.release-notes.outputs.value }}" binary-name: forgejo - binary-path: /app/gitea/forgejo-cli + binary-path: /app/gitea/gitea override: "${{ steps.release-info.outputs.override }}" verify-labels: "maintainer=contact@forgejo.org,org.opencontainers.image.version=${{ steps.release-info.outputs.version }}" verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }} - name: build rootless container if: ${{ secrets.TOKEN != '' }} - uses: https://code.forgejo.org/forgejo/forgejo-build-publish/build@v5.1.1 + uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.4 with: forgejo: "${{ env.GITHUB_SERVER_URL }}" owner: "${{ env.GITHUB_REPOSITORY_OWNER }}" @@ -194,8 +200,8 @@ jobs: verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }} - name: end-to-end tests - if: ${{ secrets.TOKEN != '' && vars.ROLE == 'forgejo-integration' }} - uses: https://code.forgejo.org/actions/cascading-pr@v2 + if: ${{ secrets.TOKEN != '' && vars.ROLE == 'forgejo-integration' && vars.SKIP_END_TO_END != 'true' }} + uses: https://data.forgejo.org/actions/cascading-pr@v2.2.0 with: origin-url: ${{ env.GITHUB_SERVER_URL }} origin-repo: ${{ github.repository }} diff --git a/.forgejo/workflows/cascade-setup-end-to-end.yml b/.forgejo/workflows/cascade-setup-end-to-end.yml index 404bbe8fa6..7c8c56de13 100644 --- a/.forgejo/workflows/cascade-setup-end-to-end.yml +++ b/.forgejo/workflows/cascade-setup-end-to-end.yml @@ -12,8 +12,10 @@ # whatever is in the default branch instead # # - after it is merged, double check it works by setting the -# run-end-to-end-test on a pull request (any pull request will doe +# run-end-to-end-test on a pull request (any pull request will do) # +name: issue-labels + on: push: branches: @@ -23,42 +25,23 @@ on: - labeled jobs: - info: - if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} - runs-on: docker - container: - image: code.forgejo.org/oci/node:20-bookworm - steps: - - name: event - run: | - echo github.event.pull_request.head.repo.fork = ${{ github.event.pull_request.head.repo.fork }} - echo github.event.action = ${{ github.event.action }} - echo github.event.pull_request.merged = ${{ github.event.pull_request.merged }} - echo github.event.pull_request.labels.*.name - cat <<'EOF' - ${{ toJSON(github.event.pull_request.labels.*.name) }} - EOF - cat <<'EOF' - ${{ toJSON(github.event) }} - EOF - cascade: if: > - !startsWith(vars.ROLE, 'forgejo-') && ( + vars.ROLE == 'forgejo-coding' && ( github.event_name == 'push' || ( - github.event.action == 'label_updated' && contains(github.event.pull_request.labels.*.name, 'run-end-to-end-tests') + github.event.action == 'label_updated' && github.event.label.name == 'run-end-to-end-tests' ) ) runs-on: docker container: - image: code.forgejo.org/oci/node:20-bookworm + image: data.forgejo.org/oci/node:22-bookworm steps: - - uses: actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v4 with: fetch-depth: '0' show-progress: 'false' - - uses: actions/cascading-pr@v2 + - uses: https://data.forgejo.org/actions/cascading-pr@v2.2.0 with: origin-url: ${{ env.GITHUB_SERVER_URL }} origin-repo: ${{ github.repository }} diff --git a/.forgejo/workflows/e2e.yml b/.forgejo/workflows/e2e.yml deleted file mode 100644 index 9f2fbb0fa2..0000000000 --- a/.forgejo/workflows/e2e.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: e2e - -on: - pull_request: - paths: - - Makefile - - playwright.config.js - - .forgejo/workflows/e2e.yml - - tests/e2e/** - - web_src/js/** - - web_src/css/form.css - - templates/webhook/shared-settings.tmpl - - templates/org/team/new.tmpl - -jobs: - test-e2e: - if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} - runs-on: docker - container: - image: 'code.forgejo.org/oci/playwright:latest' - steps: - - uses: https://code.forgejo.org/actions/checkout@v4 - - uses: https://code.forgejo.org/actions/setup-go@v4 - with: - go-version-file: "go.mod" - - run: | - git config --add safe.directory '*' - chown -R forgejo:forgejo . - - run: | - su forgejo -c 'make deps-frontend frontend deps-backend' - - run: | - su forgejo -c 'make backend' - - run: | - su forgejo -c 'make generate test-e2e-sqlite' - timeout-minutes: 40 - env: - USE_REPO_TEST_DIR: 1 diff --git a/.forgejo/workflows/forgejo-integration-cleanup.yml b/.forgejo/workflows/forgejo-integration-cleanup.yml index 049679a1eb..d490e3b2f2 100644 --- a/.forgejo/workflows/forgejo-integration-cleanup.yml +++ b/.forgejo/workflows/forgejo-integration-cleanup.yml @@ -9,7 +9,7 @@ jobs: if: vars.ROLE == 'forgejo-integration' runs-on: docker container: - image: 'code.forgejo.org/oci/node:20-bookworm' + image: 'data.forgejo.org/oci/node:22-bookworm' steps: - name: apt install curl jq diff --git a/.forgejo/workflows/merge-requirements.yml b/.forgejo/workflows/merge-requirements.yml new file mode 100644 index 0000000000..9aaf2af68d --- /dev/null +++ b/.forgejo/workflows/merge-requirements.yml @@ -0,0 +1,45 @@ +# Copyright 2024 The Forgejo Authors +# SPDX-License-Identifier: MIT + +name: requirements + +on: + pull_request: + types: + - labeled + - edited + - opened + - synchronize + +jobs: + merge-conditions: + if: vars.ROLE == 'forgejo-coding' + runs-on: docker + container: + image: 'data.forgejo.org/oci/node:22-bookworm' + steps: + - name: Debug output + run: | + cat <<'EOF' + ${{ toJSON(github) }} + EOF + - name: Missing test label + if: > + !( + contains(toJSON(github.event.pull_request.labels), 'test/present') + || contains(toJSON(github.event.pull_request.labels), 'test/not-needed') + || contains(toJSON(github.event.pull_request.labels), 'test/manual') + ) + run: | + echo "A team member must set the label to either 'present', 'not-needed' or 'manual'." + exit 1 + - name: Missing manual test instructions + if: > + ( + contains(toJSON(github.event.pull_request.labels), 'test/manual') + && !contains(toJSON(github.event.pull_request.body), '# Test') + ) + run: | + echo "Manual test label is set. The PR description needs to contain test steps introduced by a heading like:" + echo "# Testing" + exit 1 diff --git a/.forgejo/workflows/milestone.yml b/.forgejo/workflows/milestone.yml new file mode 100644 index 0000000000..9a51c515d0 --- /dev/null +++ b/.forgejo/workflows/milestone.yml @@ -0,0 +1,24 @@ +# Copyright 2024 The Forgejo Authors +# SPDX-License-Identifier: MIT +# +name: milestone + +on: + pull_request_target: + types: + - closed + +jobs: + set: + if: vars.ROLE == 'forgejo-coding' && github.event.pull_request.merged + runs-on: docker + container: + image: 'data.forgejo.org/oci/ci:1' + steps: + - uses: https://data.forgejo.org/forgejo/set-milestone@v1.0.0 + with: + forgejo: https://codeberg.org + repository: forgejo/forgejo + token: ${{ secrets.SET_MILESTONE_TOKEN }} + pr-number: ${{ github.event.pull_request.number }} + verbose: ${{ vars.SET_MILESTONE_VERBOSE }} diff --git a/.forgejo/workflows/mirror.yml b/.forgejo/workflows/mirror.yml index fd222115ac..d45a2f6f77 100644 --- a/.forgejo/workflows/mirror.yml +++ b/.forgejo/workflows/mirror.yml @@ -11,7 +11,7 @@ jobs: if: ${{ secrets.MIRROR_TOKEN != '' }} runs-on: docker container: - image: 'code.forgejo.org/oci/node:20-bookworm' + image: 'data.forgejo.org/oci/node:22-bookworm' steps: - name: git push {v*/,}forgejo run: | diff --git a/.forgejo/workflows/publish-release.yml b/.forgejo/workflows/publish-release.yml index 41c884c2d1..27d3b9383e 100644 --- a/.forgejo/workflows/publish-release.yml +++ b/.forgejo/workflows/publish-release.yml @@ -2,6 +2,8 @@ # # See also https://forgejo.org/docs/next/contributor/release/#stable-release-process # +# TOKEN_NEXT_DIGEST is a token with write repository access to https://invisible.forgejo.org/infrastructure/next-digest issued by https://invisible.forgejo.org/forgejo-next-digest +# # https://codeberg.org/forgejo-experimental/forgejo # # Copies a release from codeberg.org/forgejo-integration to codeberg.org/forgejo-experimental @@ -14,7 +16,7 @@ # vars.DOER: forgejo-experimental-ci # secrets.TOKEN: # -# http://private.forgejo.org/forgejo/forgejo +# http://invisible.forgejo.org/forgejo/forgejo # # Copies & sign a release from codeberg.org/forgejo-integration to codeberg.org/forgejo # @@ -36,20 +38,20 @@ on: jobs: publish: - runs-on: self-hosted + runs-on: lxc-bookworm if: vars.DOER != '' && vars.FORGEJO != '' && vars.TO_OWNER != '' && vars.FROM_OWNER != '' && secrets.TOKEN != '' steps: - - uses: actions/checkout@v3 + - uses: https://data.forgejo.org/actions/checkout@v4 - name: copy & sign - uses: https://code.forgejo.org/forgejo/forgejo-build-publish/publish@v5 + uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.3.4 with: from-forgejo: ${{ vars.FORGEJO }} to-forgejo: ${{ vars.FORGEJO }} from-owner: ${{ vars.FROM_OWNER }} to-owner: ${{ vars.TO_OWNER }} repo: ${{ vars.REPO }} - release-notes: "See https://codeberg.org/forgejo/forgejo/src/branch/forgejo/RELEASE-NOTES.md#{ANCHOR}" + release-notes: "See https://codeberg.org/forgejo/forgejo/src/branch/forgejo/release-notes-published/{VERSION}.md" ref-name: ${{ github.ref_name }} sha: ${{ github.sha }} from-token: ${{ secrets.TOKEN }} @@ -59,30 +61,28 @@ jobs: gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }} verbose: ${{ vars.VERBOSE }} - - name: upgrade v*.next.forgejo.org - run: | - export DEBIAN_FRONTEND=noninteractive - apt-get update -qq - apt-get -q install -y -qq curl - version="${{ github.ref_name }}" - version=${version##*v} - major=$(echo $version | sed -E -e 's/^([0-9]+).*/\1/') - # https://forgejo.org/docs/next/developer/infrastructure - curl -o /dev/null -sS https://v$major.next.forgejo.org/.well-known/wakeup-on-logs/forgejo-v$major + - name: get trigger mirror issue + id: mirror + uses: https://data.forgejo.org/infrastructure/issue-action/get@v1.3.0 + with: + forgejo: https://code.forgejo.org + repository: forgejo/forgejo + labels: mirror-trigger - - name: set up go for the DNS update below - if: vars.ROLE == 'forgejo-experimental' && secrets.OVH_APP_KEY != '' - uses: https://code.forgejo.org/actions/setup-go@v4 + - name: trigger the mirror + uses: https://data.forgejo.org/infrastructure/issue-action/set@v1.3.0 with: - go-version-file: "go.mod" - - name: update the _release.experimental DNS record - if: vars.ROLE == 'forgejo-experimental' && secrets.OVH_APP_KEY != '' - uses: https://code.forgejo.org/actions/ovh-dns-update@v1 + forgejo: https://code.forgejo.org + repository: forgejo/forgejo + token: ${{ secrets.LABEL_ISSUE_FORGEJO_MIRROR_TOKEN }} + numbers: ${{ steps.mirror.outputs.numbers }} + label-wait-if-exists: 3600 + label: trigger + + - name: upgrade v*.next.forgejo.org + uses: https://data.forgejo.org/infrastructure/next-digest@v1.1.0 with: - subdomain: _release.experimental - domain: forgejo.com # there is a CNAME from .org to .com (for security reasons) - record-id: 5283602601 - value: v=${{ github.ref_name }} - ovh-app-key: ${{ secrets.OVH_APP_KEY }} - ovh-app-secret: ${{ secrets.OVH_APP_SECRET }} - ovh-consumer-key: ${{ secrets.OVH_CON_KEY }} + url: https://placeholder:${{ secrets.TOKEN_NEXT_DIGEST }}@invisible.forgejo.org/infrastructure/next-digest + ref_name: '${{ github.ref_name }}' + image: 'codeberg.org/forgejo-experimental/forgejo' + tag_suffix: '-rootless' diff --git a/.forgejo/workflows/release-notes-assistant-milestones.yml b/.forgejo/workflows/release-notes-assistant-milestones.yml index fb7bba1d52..7f77098357 100644 --- a/.forgejo/workflows/release-notes-assistant-milestones.yml +++ b/.forgejo/workflows/release-notes-assistant-milestones.yml @@ -4,16 +4,19 @@ on: schedule: - cron: '@daily' +env: + RNA_VERSION: v1.2.5 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org + jobs: release-notes: - if: ${{ !startsWith(vars.ROLE, 'forgejo-') + if: vars.ROLE == 'forgejo-coding' runs-on: docker container: - image: 'code.forgejo.org/oci/node:20-bookworm' + image: 'data.forgejo.org/oci/node:22-bookworm' steps: - - uses: https://code.forgejo.org/actions/checkout@v3 + - uses: https://data.forgejo.org/actions/checkout@v4 - - uses: https://code.forgejo.org/actions/setup-go@v4 + - uses: https://data.forgejo.org/actions/setup-go@v5 with: go-version-file: "go.mod" cache: false @@ -29,5 +32,5 @@ jobs: set -x curl -sS $GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/milestones?state=open | jq -r '.[] | .title' | while read forgejo version ; do milestone="$forgejo $version" - go run code.forgejo.org/forgejo/release-notes-assistant@v1.1.1 --config .release-notes-assistant.yaml --storage milestone --storage-location "$milestone" --forgejo-url $GITHUB_SERVER_URL --repository $GITHUB_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} release $version + go run code.forgejo.org/forgejo/release-notes-assistant@$RNA_VERSION --config .release-notes-assistant.yaml --storage milestone --storage-location "$milestone" --forgejo-url $GITHUB_SERVER_URL --repository $GITHUB_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} release $version done diff --git a/.forgejo/workflows/release-notes-assistant.yml b/.forgejo/workflows/release-notes-assistant.yml index dd67b4e203..cdcd2e6fe4 100644 --- a/.forgejo/workflows/release-notes-assistant.yml +++ b/.forgejo/workflows/release-notes-assistant.yml @@ -1,3 +1,5 @@ +name: issue-labels + on: pull_request_target: types: @@ -5,14 +7,17 @@ on: - synchronize - labeled +env: + RNA_VERSION: v1.2.5 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org + jobs: release-notes: - if: ${{ !startsWith(vars.ROLE, 'forgejo-') && contains(github.event.pull_request.labels.*.name, 'worth a release-note') }} + if: ( vars.ROLE == 'forgejo-coding' ) && contains(github.event.pull_request.labels.*.name, 'worth a release-note') runs-on: docker container: - image: 'code.forgejo.org/oci/node:20-bookworm' + image: 'data.forgejo.org/oci/node:22-bookworm' steps: - - uses: https://code.forgejo.org/actions/checkout@v3 + - uses: https://data.forgejo.org/actions/checkout@v4 - name: event run: | @@ -23,7 +28,7 @@ jobs: ${{ toJSON(github.event) }} EOF - - uses: https://code.forgejo.org/actions/setup-go@v4 + - uses: https://data.forgejo.org/actions/setup-go@v5 with: go-version-file: "go.mod" cache: false @@ -36,4 +41,4 @@ jobs: - name: release-notes-assistant preview run: | - go run code.forgejo.org/forgejo/release-notes-assistant@v1.1.1 --config .release-notes-assistant.yaml --storage pr --storage-location ${{ github.event.pull_request.number }} --forgejo-url $GITHUB_SERVER_URL --repository $GITHUB_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} preview ${{ github.event.pull_request.number }} + go run code.forgejo.org/forgejo/release-notes-assistant@$RNA_VERSION --config .release-notes-assistant.yaml --storage pr --storage-location ${{ github.event.pull_request.number }} --forgejo-url $GITHUB_SERVER_URL --repository $GITHUB_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} preview ${{ github.event.pull_request.number }} diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml index 400cd453b3..1765f58a4e 100644 --- a/.forgejo/workflows/renovate.yml +++ b/.forgejo/workflows/renovate.yml @@ -8,7 +8,9 @@ name: renovate on: push: branches: - - 'renovate/**' # self-test updates + - renovate/** # self-test updates + paths: + - .forgejo/workflows/renovate.yml schedule: - cron: '0 0/2 * * *' workflow_dispatch: @@ -16,18 +18,21 @@ on: env: RENOVATE_DRY_RUN: ${{ (github.event_name != 'schedule' && github.ref_name != github.event.repository.default_branch) && 'full' || '' }} RENOVATE_REPOSITORIES: ${{ github.repository }} + # fix because 10.0.0-58-7e1df53+gitea-1.22.0 < 10.0.0 for semver + # and codeberg api returns such versions from `git describe --tags` + # RENOVATE_X_PLATFORM_VERSION: 10.0.0+gitea-1.22.0 currently not needed jobs: renovate: - if: ${{ secrets.RENOVATE_TOKEN != '' }} + if: vars.ROLE == 'forgejo-coding' && secrets.RENOVATE_TOKEN != '' runs-on: docker container: - image: code.forgejo.org/forgejo-contrib/renovate:38.93.2 + image: data.forgejo.org/renovate/renovate:40.31.0 steps: - name: Load renovate repo cache - uses: https://code.forgejo.org/actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: https://data.forgejo.org/actions/cache/restore@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: | .tmp/cache/renovate/repository @@ -60,7 +65,7 @@ jobs: - name: Save renovate repo cache if: always() && env.RENOVATE_DRY_RUN != 'full' - uses: https://code.forgejo.org/actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: https://data.forgejo.org/actions/cache/save@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: | .tmp/cache/renovate/repository diff --git a/.forgejo/workflows/testing.yml b/.forgejo/workflows/testing.yml index 725cd242ee..4d88d3efb0 100644 --- a/.forgejo/workflows/testing.yml +++ b/.forgejo/workflows/testing.yml @@ -6,319 +6,270 @@ on: branches: - 'forgejo*' - 'v*/forgejo*' + workflow_dispatch: jobs: backend-checks: - if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} + if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker container: - image: 'code.forgejo.org/oci/node:20-bookworm' + image: 'data.forgejo.org/oci/node:22-bookworm' + options: --tmpfs /tmp:exec,noatime steps: - name: event info run: | cat <<'EOF' ${{ toJSON(github) }} EOF - - uses: https://code.forgejo.org/actions/checkout@v3 - - uses: https://code.forgejo.org/actions/setup-go@v4 - with: - go-version-file: "go.mod" - - run: make deps-backend deps-tools - - run: make --always-make -j$(nproc) lint-backend tidy-check swagger-check fmt-check swagger-validate # ensure the "go-licenses" make target runs - - run: | - make backend - env: - TAGS: bindata - - uses: actions/cache@v4 - with: - path: '/workspace/forgejo/forgejo/gitea' - key: backend-build-${{ github.sha }} + - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: ./.forgejo/workflows-composite/setup-env + - run: su forgejo -c 'make deps-backend deps-tools' + - run: su forgejo -c 'make --always-make -j$(nproc) lint-backend tidy-check swagger-check lint-swagger fmt-check swagger-validate' # ensure the "go-licenses" make target runs + - uses: ./.forgejo/workflows-composite/build-backend frontend-checks: - if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} + if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker container: - image: 'code.forgejo.org/oci/node:20-bookworm' + image: 'data.forgejo.org/oci/node:22-bookworm' + options: --tmpfs /tmp:exec,noatime steps: - - uses: https://code.forgejo.org/actions/checkout@v3 + - uses: https://data.forgejo.org/actions/checkout@v4 - run: make deps-frontend - run: make lint-frontend - run: make checks-frontend - run: make test-frontend-coverage - run: make frontend + - name: Install zstd for cache saving + # works around https://github.com/actions/cache/issues/1169, because the + # consuming job has zstd and doesn't restore the cache otherwise + run: | + apt-get update -qq + apt-get -q install -qq -y zstd + - name: "Cache frontend build for playwright testing" + uses: https://data.forgejo.org/actions/cache/save@v4 + with: + path: ${{github.workspace}}/public/assets + key: frontend-build-${{ github.sha }} test-unit: - if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} + if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker needs: [backend-checks, frontend-checks] container: - image: 'code.forgejo.org/oci/node:20-bookworm' + image: 'data.forgejo.org/oci/node:22-bookworm' + options: --tmpfs /tmp:exec,noatime services: elasticsearch: - image: docker.io/bitnami/elasticsearch:7 + image: data.forgejo.org/oci/bitnami/elasticsearch:7 + options: --tmpfs /bitnami/elasticsearch/data env: discovery.type: single-node ES_JAVA_OPTS: "-Xms512m -Xmx512m" minio: - image: docker.io/bitnami/minio:2024.8.17 + image: data.forgejo.org/oci/bitnami/minio:2024.8.17 options: >- - --hostname gitea.minio + --hostname gitea.minio --tmpfs /bitnami/minio/data:noatime env: MINIO_DOMAIN: minio MINIO_ROOT_USER: 123456 MINIO_ROOT_PASSWORD: 12345678 steps: - - uses: https://code.forgejo.org/actions/checkout@v3 - - uses: https://code.forgejo.org/actions/setup-go@v4 - with: - go-version-file: "go.mod" - - run: | - git config --add safe.directory '*' - adduser --quiet --comment forgejo --disabled-password forgejo - chown -R forgejo:forgejo . + - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: ./.forgejo/workflows-composite/setup-env - name: install git >= 2.42 - run: | - export DEBIAN_FRONTEND=noninteractive - echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list - apt-get update -qq - apt-get -q install -qq -y git - rm /etc/apt/sources.list.d/testing.list - apt-get update -qq + uses: ./.forgejo/workflows-composite/apt-install-from + with: + packages: git - name: test release-notes-assistant.sh run: | apt-get -q install -qq -y jq ./release-notes-assistant.sh test_main - - run: | - su forgejo -c 'make deps-backend' - - uses: actions/cache/restore@v4 - id: cache-backend - with: - path: '/workspace/forgejo/forgejo/gitea' - key: backend-build-${{ github.sha }} - - if: steps.cache-backend.outputs.cache-hit != 'true' - run: | - su forgejo -c 'make backend' - env: - TAGS: bindata + - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-backend test-check' - timeout-minutes: 50 + timeout-minutes: 120 env: RACE_ENABLED: 'true' TAGS: bindata TEST_ELASTICSEARCH_URL: http://elasticsearch:9200 - test-remote-cacher: - if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} + test-e2e: + if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker needs: [backend-checks, frontend-checks] container: - image: 'code.forgejo.org/oci/node:20-bookworm' + image: 'data.forgejo.org/oci/playwright:latest' + options: --tmpfs /tmp:exec,noatime + steps: + - uses: https://data.forgejo.org/actions/checkout@v4 + with: + fetch-depth: 20 + - uses: ./.forgejo/workflows-composite/setup-env + - name: "Restore frontend build" + uses: https://data.forgejo.org/actions/cache/restore@v4 + id: cache-frontend + with: + path: ${{github.workspace}}/public/assets + key: frontend-build-${{ github.sha }} + - name: "Build frontend (if not cached)" + if: steps.cache-frontend.outputs.cache-hit != 'true' + run: | + su forgejo -c 'make deps-frontend frontend' + - uses: ./.forgejo/workflows-composite/build-backend + - name: Get changed files + id: changed-files + uses: https://data.forgejo.org/tj-actions/changed-files@v46 + with: + separator: '\n' + - run: | + su forgejo -c 'make generate test-e2e-sqlite' + timeout-minutes: 120 + env: + USE_REPO_TEST_DIR: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + CHANGED_FILES: ${{steps.changed-files.outputs.all_changed_files}} + - name: Upload test artifacts on failure + if: failure() + uses: https://data.forgejo.org/forgejo/upload-artifact@v4 + with: + name: test-artifacts.zip + path: tests/e2e/test-artifacts/ + retention-days: 3 + test-remote-cacher: + if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' + runs-on: docker + needs: [backend-checks, frontend-checks, test-unit] + container: + image: 'data.forgejo.org/oci/node:22-bookworm' + options: --tmpfs /tmp:exec,noatime + name: ${{ format('test-remote-cacher ({0})', matrix.cacher.name) }} strategy: matrix: cacher: - # redis - - image: docker.io/bitnami/redis:7.2 - port: 6379 - # redict - - image: registry.redict.io/redict:7.3.0-scratch - port: 6379 - # valkey - - image: docker.io/bitnami/valkey:7.2 - port: 6379 - # garnet - - image: ghcr.io/microsoft/garnet-alpine:1.0.14 - port: 6379 + - name: redis + image: data.forgejo.org/oci/bitnami/redis:7.2 + options: --tmpfs /bitnami/redis/data:noatime + - name: redict + image: registry.redict.io/redict:7.3.0-scratch + options: --tmpfs /data:noatime + - name: valkey + image: data.forgejo.org/oci/bitnami/valkey:7.2 + options: --tmpfs /bitnami/redis/data:noatime + - name: garnet + image: ghcr.io/microsoft/garnet-alpine:1.0.14 + options: --tmpfs /data:noatime services: cacher: image: ${{ matrix.cacher.image }} options: ${{ matrix.cacher.options }} steps: - - uses: https://code.forgejo.org/actions/checkout@v3 - - uses: https://code.forgejo.org/actions/setup-go@v4 - with: - go-version-file: "go.mod" - - run: | - git config --add safe.directory '*' - adduser --quiet --comment forgejo --disabled-password forgejo - chown -R forgejo:forgejo . + - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: ./.forgejo/workflows-composite/setup-env - name: install git >= 2.42 - run: | - export DEBIAN_FRONTEND=noninteractive - echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list - apt-get update -qq - apt-get -q install -qq -y git - rm /etc/apt/sources.list.d/testing.list - apt-get update -qq - - run: | - su forgejo -c 'make deps-backend' - - uses: actions/cache/restore@v4 - id: cache-backend + uses: ./.forgejo/workflows-composite/apt-install-from with: - path: '/workspace/forgejo/forgejo/gitea' - key: backend-build-${{ github.sha }} - - if: steps.cache-backend.outputs.cache-hit != 'true' - run: | - su forgejo -c 'make backend' - env: - TAGS: bindata + packages: git + - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-remote-cacher test-check' - timeout-minutes: 50 + timeout-minutes: 120 env: RACE_ENABLED: 'true' TAGS: bindata TEST_REDIS_SERVER: cacher:${{ matrix.cacher.port }} test-mysql: - if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} + if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker needs: [backend-checks, frontend-checks] container: - image: 'code.forgejo.org/oci/node:20-bookworm' + image: 'data.forgejo.org/oci/node:22-bookworm' + options: --tmpfs /tmp:exec,noatime services: mysql: - image: 'docker.io/bitnami/mysql:8.4' + image: 'data.forgejo.org/oci/bitnami/mysql:8.4' env: ALLOW_EMPTY_PASSWORD: yes MYSQL_DATABASE: testgitea # # See also https://codeberg.org/forgejo/forgejo/issues/976 # - MYSQL_EXTRA_FLAGS: --innodb-adaptive-flushing=OFF --innodb-buffer-pool-size=4G --innodb-log-buffer-size=128M --innodb-flush-log-at-trx-commit=0 --innodb-flush-log-at-timeout=30 --innodb-flush-method=nosync --innodb-fsync-threshold=1000000000 + MYSQL_EXTRA_FLAGS: --innodb-adaptive-flushing=OFF --innodb-buffer-pool-size=4G --innodb-log-buffer-size=128M --innodb-flush-log-at-trx-commit=0 --innodb-flush-log-at-timeout=30 --innodb-flush-method=nosync --innodb-fsync-threshold=1000000000 --disable-log-bin + options: --tmpfs /bitnami/mysql/data:noatime steps: - - uses: https://code.forgejo.org/actions/checkout@v3 - - uses: https://code.forgejo.org/actions/setup-go@v4 - with: - go-version-file: "go.mod" + - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: ./.forgejo/workflows-composite/setup-env - name: install dependencies & git >= 2.42 - run: | - export DEBIAN_FRONTEND=noninteractive - echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list - apt-get update -qq - apt-get install --no-install-recommends -qq -y git git-lfs - rm /etc/apt/sources.list.d/testing.list - apt-get update -qq - - name: setup user and permissions - run: | - git config --add safe.directory '*' - adduser --quiet --comment forgejo --disabled-password forgejo - chown -R forgejo:forgejo . - - run: | - su forgejo -c 'make deps-backend' - - uses: actions/cache/restore@v4 - id: cache-backend + uses: ./.forgejo/workflows-composite/apt-install-from with: - path: '/workspace/forgejo/forgejo/gitea' - key: backend-build-${{ github.sha }} - - if: steps.cache-backend.outputs.cache-hit != 'true' - run: | - su forgejo -c 'make backend' - env: - TAGS: bindata + packages: git git-lfs + - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-mysql-migration test-mysql' - timeout-minutes: 50 + timeout-minutes: 120 env: USE_REPO_TEST_DIR: 1 test-pgsql: - if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} + if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker needs: [backend-checks, frontend-checks] container: - image: 'code.forgejo.org/oci/node:20-bookworm' + image: 'data.forgejo.org/oci/node:22-bookworm' + options: --tmpfs /tmp:exec,noatime services: minio: - image: docker.io/bitnami/minio:2024.8.17 + image: data.forgejo.org/oci/bitnami/minio:2024.8.17 env: MINIO_ROOT_USER: 123456 MINIO_ROOT_PASSWORD: 12345678 + options: --tmpfs /bitnami/minio/data ldap: - image: docker.io/gitea/test-openldap:latest + image: data.forgejo.org/oci/test-openldap:latest pgsql: - image: 'code.forgejo.org/oci/postgres:15' + image: data.forgejo.org/oci/bitnami/postgresql:16 env: - POSTGRES_DB: test - POSTGRES_PASSWORD: postgres + POSTGRESQL_DATABASE: test + POSTGRESQL_PASSWORD: postgres + POSTGRESQL_FSYNC: off + POSTGRESQL_EXTRA_FLAGS: -c full_page_writes=off + options: --tmpfs /bitnami/postgresql steps: - - uses: https://code.forgejo.org/actions/checkout@v3 - - uses: https://code.forgejo.org/actions/setup-go@v4 - with: - go-version-file: "go.mod" + - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: ./.forgejo/workflows-composite/setup-env - name: install dependencies & git >= 2.42 - run: | - export DEBIAN_FRONTEND=noninteractive - echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list - apt-get update -qq - apt-get install --no-install-recommends -qq -y git git-lfs - rm /etc/apt/sources.list.d/testing.list - apt-get update -qq - - name: setup user and permissions - run: | - git config --add safe.directory '*' - adduser --quiet --comment forgejo --disabled-password forgejo - chown -R forgejo:forgejo . - - run: | - su forgejo -c 'make deps-backend' - - uses: actions/cache/restore@v4 - id: cache-backend + uses: ./.forgejo/workflows-composite/apt-install-from with: - path: '/workspace/forgejo/forgejo/gitea' - key: backend-build-${{ github.sha }} - - if: steps.cache-backend.outputs.cache-hit != 'true' - run: | - su forgejo -c 'make backend' - env: - TAGS: bindata + packages: git git-lfs + - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-pgsql-migration test-pgsql' - timeout-minutes: 50 + timeout-minutes: 120 env: RACE_ENABLED: true USE_REPO_TEST_DIR: 1 TEST_LDAP: 1 test-sqlite: - if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} + if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker needs: [backend-checks, frontend-checks] container: - image: 'code.forgejo.org/oci/node:20-bookworm' + image: 'data.forgejo.org/oci/node:22-bookworm' + options: --tmpfs /tmp:exec,noatime steps: - - uses: https://code.forgejo.org/actions/checkout@v3 - - uses: https://code.forgejo.org/actions/setup-go@v4 - with: - go-version-file: "go.mod" + - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: ./.forgejo/workflows-composite/setup-env - name: install dependencies & git >= 2.42 - run: | - export DEBIAN_FRONTEND=noninteractive - echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list - apt-get update -qq - apt-get install --no-install-recommends -qq -y git git-lfs - rm /etc/apt/sources.list.d/testing.list - apt-get update -qq - - name: setup user and permissions - run: | - git config --add safe.directory '*' - adduser --quiet --comment forgejo --disabled-password forgejo - chown -R forgejo:forgejo . - - run: | - su forgejo -c 'make deps-backend' - - uses: actions/cache/restore@v4 - id: cache-backend + uses: ./.forgejo/workflows-composite/apt-install-from with: - path: '/workspace/forgejo/forgejo/gitea' - key: backend-build-${{ github.sha }} - - if: steps.cache-backend.outputs.cache-hit != 'true' - run: | - su forgejo -c 'make backend' - env: - TAGS: bindata sqlite sqlite_unlock_notify + packages: git git-lfs + - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-sqlite-migration test-sqlite' - timeout-minutes: 50 + timeout-minutes: 120 env: TAGS: sqlite sqlite_unlock_notify RACE_ENABLED: true TEST_TAGS: sqlite sqlite_unlock_notify USE_REPO_TEST_DIR: 1 security-check: - if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} + if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker needs: - test-sqlite @@ -327,11 +278,10 @@ jobs: - test-remote-cacher - test-unit container: - image: 'code.forgejo.org/oci/node:20-bookworm' + image: 'data.forgejo.org/oci/node:22-bookworm' + options: --tmpfs /tmp:exec,noatime steps: - - uses: https://code.forgejo.org/actions/checkout@v3 - - uses: https://code.forgejo.org/actions/setup-go@v4 - with: - go-version-file: "go.mod" - - run: make deps-backend deps-tools - - run: make security-check + - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: ./.forgejo/workflows-composite/setup-env + - run: su forgejo -c 'make deps-backend deps-tools' + - run: su forgejo -c 'make security-check' diff --git a/.gitignore b/.gitignore index 7f40d0ba55..744e24a09a 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ _testmain.go *coverage.out coverage.all +coverage/ cpu.out /modules/migration/bindata.go @@ -56,6 +57,7 @@ cpu.out /gitea-vet /debug /integrations.test +/forgejo /bin /dist @@ -72,6 +74,7 @@ cpu.out /tests/e2e/reports /tests/e2e/test-artifacts /tests/e2e/test-snapshots +/tests/e2e/.auth /tests/*.ini /tests/**/*.git/**/*.sample /node_modules @@ -115,6 +118,9 @@ prime/ *_source.tar.bz2 .DS_Store +# Direnv configuration +/.envrc + # nix-direnv generated files .direnv/ diff --git a/.golangci.yml b/.golangci.yml index 4a20269b0e..532132838d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,7 +1,9 @@ +version: "2" +output: + sort-order: + - file linters: - enable-all: false - disable-all: true - fast: false + default: none enable: - bidichk - depguard @@ -9,140 +11,155 @@ linters: - errcheck - forbidigo - gocritic - - gofmt - - gofumpt - - gosimple - govet - ineffassign - nakedret - nolintlint - revive - staticcheck - - stylecheck - - tenv - testifylint - - typecheck - unconvert - - unused - unparam + - unused + - usetesting - wastedassign - -run: - timeout: 10m - -output: - sort-results: true - sort-order: [file] - show-stats: true - -linters-settings: - stylecheck: - checks: ["all", "-ST1005", "-ST1003"] - nakedret: - max-func-lines: 0 - gocritic: - disabled-checks: - - ifElseChain - revive: - severity: error + settings: + depguard: + rules: + main: + deny: + - pkg: encoding/json + desc: use gitea's modules/json instead of encoding/json + - pkg: github.com/unknwon/com + desc: use gitea's util and replacements + - pkg: io/ioutil + desc: use os or io instead + - pkg: golang.org/x/exp + desc: it's experimental and unreliable + - pkg: forgejo.org/modules/git/internal + desc: do not use the internal package, use AddXxx function instead + - pkg: gopkg.in/ini.v1 + desc: do not use the ini package, use gitea's config system instead + - pkg: github.com/minio/sha256-simd + desc: use crypto/sha256 instead, see https://codeberg.org/forgejo/forgejo/pulls/1528 + gocritic: + disabled-checks: + - ifElseChain + revive: + severity: error + rules: + - name: atomic + - name: bare-return + - name: blank-imports + - name: constant-logical-expr + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: duplicated-imports + - name: empty-lines + - name: error-naming + - name: error-return + - name: error-strings + - name: errorf + - name: exported + - name: identical-branches + - name: if-return + - name: increment-decrement + - name: indent-error-flow + - name: modifies-value-receiver + - name: package-comments + - name: range + - name: receiver-naming + - name: redefines-builtin-id + - name: string-of-int + - name: superfluous-else + - name: time-naming + - name: unconditional-recursion + - name: unexported-return + - name: unreachable-code + - name: var-declaration + - name: var-naming + - name: redefines-builtin-id + disabled: true + staticcheck: + checks: + - all + testifylint: + disable: + - go-require + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling rules: - - name: atomic - - name: bare-return - - name: blank-imports - - name: constant-logical-expr - - name: context-as-argument - - name: context-keys-type - - name: dot-imports - - name: duplicated-imports - - name: empty-lines - - name: error-naming - - name: error-return - - name: error-strings - - name: errorf - - name: exported - - name: identical-branches - - name: if-return - - name: increment-decrement - - name: indent-error-flow - - name: modifies-value-receiver - - name: package-comments - - name: range - - name: receiver-naming - - name: redefines-builtin-id - - name: string-of-int - - name: superfluous-else - - name: time-naming - - name: unconditional-recursion - - name: unexported-return - - name: unreachable-code - - name: var-declaration - - name: var-naming - gofumpt: - extra-rules: true - depguard: - rules: - main: - deny: - - pkg: encoding/json - desc: use gitea's modules/json instead of encoding/json - - pkg: github.com/unknwon/com - desc: use gitea's util and replacements - - pkg: io/ioutil - desc: use os or io instead - - pkg: golang.org/x/exp - desc: it's experimental and unreliable - - pkg: code.gitea.io/gitea/modules/git/internal - desc: do not use the internal package, use AddXxx function instead - - pkg: gopkg.in/ini.v1 - desc: do not use the ini package, use gitea's config system instead - - pkg: github.com/minio/sha256-simd - desc: use crypto/sha256 instead, see https://codeberg.org/forgejo/forgejo/pulls/1528 - testifylint: - disable: - - go-require - + - linters: + - nolintlint + path: models/db/sql_postgres_with_schema.go + - linters: + - dupl + - errcheck + - gocyclo + - gosec + - staticcheck + - unparam + path: _test\.go + - linters: + - dupl + - errcheck + - gocyclo + - gosec + path: models/migrations/v + - linters: + - forbidigo + path: cmd + - linters: + - dupl + text: (?i)webhook + - linters: + - gocritic + text: (?i)`ID' should not be capitalized + - linters: + - deadcode + - unused + text: (?i)swagger + - linters: + - staticcheck + text: (?i)argument x is overwritten before first use + - linters: + - gocritic + text: '(?i)commentFormatting: put a space between `//` and comment text' + - linters: + - gocritic + text: '(?i)exitAfterDefer:' + - linters: + - staticcheck + text: "(ST1005|ST1003|QF1001):" + paths: + - node_modules + - public + - web_src + - third_party$ + - builtin$ + - examples$ issues: max-issues-per-linter: 0 max-same-issues: 0 - exclude-dirs: [node_modules, public, web_src] - exclude-case-sensitive: true - exclude-rules: - - path: models/db/sql_postgres_with_schema.go - linters: - - nolintlint - - path: _test\.go - linters: - - gocyclo - - errcheck - - dupl - - gosec - - unparam - - staticcheck - - path: models/migrations/v - linters: - - gocyclo - - errcheck - - dupl - - gosec - - path: cmd - linters: - - forbidigo - - text: "webhook" - linters: - - dupl - - text: "`ID' should not be capitalized" - linters: - - gocritic - - text: "swagger" - linters: - - unused - - deadcode - - text: "argument x is overwritten before first use" - linters: - - staticcheck - - text: "commentFormatting: put a space between `//` and comment text" - linters: - - gocritic - - text: "exitAfterDefer:" - linters: - - gocritic +formatters: + enable: + - gofmt + - gofumpt + settings: + gofumpt: + extra-rules: true + exclusions: + generated: lax + paths: + - node_modules + - public + - web_src + - third_party$ + - builtin$ + - examples$ diff --git a/.release-notes-assistant.yaml b/.release-notes-assistant.yaml index 15c73f9b39..b3e5a8e665 100644 --- a/.release-notes-assistant.yaml +++ b/.release-notes-assistant.yaml @@ -10,7 +10,7 @@ branch-known: cleanup-line: 'sed -Ee "s/^(feat|fix):\s*//g" -e "s/^\[WIP\] //" -e "s/^WIP: //" -e "s;\[(UI|BUG|FEAT|v.*?/forgejo)\]\s*;;g"' render-header: | - ## Draft release notes + ## Release notes comment: |
Where does that come from? diff --git a/BSDmakefile b/BSDmakefile index 79696eadcf..f4a819ff93 100644 --- a/BSDmakefile +++ b/BSDmakefile @@ -36,10 +36,6 @@ GARGS = "--no-print-directory" JARG = -j$(.MAKE.JOBS) .endif -# bmake prefers out-of-source builds and tries to cd into ./obj (among others) -# where possible. GNU Make doesn't, so override that value. -.OBJDIR: ./ - # The GNU convention is to use the lowercased `prefix` variable/macro to # specify the installation directory. Humor them. GPREFIX = @@ -48,11 +44,12 @@ GPREFIX = .endif .BEGIN: .SILENT - which $(GMAKE) || (printf "Error: GNU Make is required!\n\n" 1>&2 && false) + which $(GMAKE) >/dev/null || (printf "Error: GNU Make is required!\n\n" 1>&2 && false) -.PHONY: FRC -$(.TARGETS): FRC - $(GMAKE) $(GPREFIX) $(GARGS) $(.TARGETS:S,.DONE,,) $(JARG) +.PHONY: EMPTY +EMPTY: .SILENT + $(GMAKE) $(GPREFIX) $(GARGS) $(JARG) -.DONE .DEFAULT: .SILENT - $(GMAKE) $(GPREFIX) $(GARGS) $(.TARGETS:S,.DONE,,) $(JARG) +.PHONY: $(.TARGETS) +$(.TARGETS): .SILENT + $(GMAKE) $(GPREFIX) $(GARGS) $(JARG) $@ diff --git a/CODEOWNERS b/CODEOWNERS index d46efc052b..03b0d8753d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -9,13 +9,16 @@ # Files related to frontend development. # Javascript and CSS code. -web_src/.* @caesar @crystal @gusted +web_src/.* @beowulf @gusted # HTML templates used by the backend. -templates/.* @caesar @crystal @gusted +templates/.* @beowulf @gusted ## the issue sidebar was touched by fnetx templates/repo/issue/view_content/sidebar.* @fnetx +# Playwright tests +tests/e2e/.* @fnetx + # Files related to Go development. # The modules usually don't require much knowledge about Forgejo and could @@ -30,8 +33,9 @@ models/.* @gusted # for code that lives in here. routers/.* @gusted -# Let new strings be checked by the translation team. -options/locale/locale_en-US.ini @0ko +# Let locale changes be checked by the translation team. +options/locale/.* @0ko +options/locale_next/.* @0ko # Personal interest .*/webhook.* @oliverpool diff --git a/Dockerfile b/Dockerfile index 01ab36b711..a94f4d2b46 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx +FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx -FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/golang:1.23-alpine3.20 as build-env +FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.21 AS build-env ARG GOPROXY -ENV GOPROXY=${GOPROXY:-direct} +ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct} ARG RELEASE_VERSION ARG TAGS="sqlite sqlite_unlock_notify" @@ -30,8 +30,8 @@ RUN cp /*-alpine-linux-musl*/lib/ld-musl-*.so.1 /lib || true RUN apk --no-cache add build-base git nodejs npm -COPY . ${GOPATH}/src/code.gitea.io/gitea -WORKDIR ${GOPATH}/src/code.gitea.io/gitea +COPY . ${GOPATH}/src/forgejo.org +WORKDIR ${GOPATH}/src/forgejo.org RUN make clean RUN make frontend @@ -47,11 +47,11 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \ /tmp/local/etc/s6/gitea/* \ /tmp/local/etc/s6/openssh/* \ /tmp/local/etc/s6/.s6-svscan/* \ - /go/src/code.gitea.io/gitea/gitea \ - /go/src/code.gitea.io/gitea/environment-to-ini -RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete + /go/src/forgejo.org/gitea \ + /go/src/forgejo.org/environment-to-ini +RUN chmod 644 /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete -FROM code.forgejo.org/oci/golang:1.23-alpine3.20 +FROM data.forgejo.org/oci/alpine:3.21 ARG RELEASE_VERSION LABEL maintainer="contact@forgejo.org" \ org.opencontainers.image.authors="Forgejo" \ @@ -98,11 +98,11 @@ ENV GITEA_CUSTOM=/data/gitea VOLUME ["/data"] ENTRYPOINT ["/usr/bin/entrypoint"] -CMD ["/bin/s6-svscan", "/etc/s6"] +CMD ["/usr/bin/s6-svscan", "/etc/s6"] COPY --from=build-env /tmp/local / RUN cd /usr/local/bin ; ln -s gitea forgejo -COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea -RUN ln /app/gitea/gitea /app/gitea/forgejo-cli -COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini -COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh +COPY --from=build-env /go/src/forgejo.org/gitea /app/gitea/gitea +RUN ln -s /app/gitea/gitea /app/gitea/forgejo-cli +COPY --from=build-env /go/src/forgejo.org/environment-to-ini /usr/local/bin/environment-to-ini +COPY --from=build-env /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh diff --git a/Dockerfile.rootless b/Dockerfile.rootless index d2f5f71524..36df26c042 100644 --- a/Dockerfile.rootless +++ b/Dockerfile.rootless @@ -1,9 +1,9 @@ -FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx +FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx -FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/golang:1.23-alpine3.20 as build-env +FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.21 AS build-env ARG GOPROXY -ENV GOPROXY=${GOPROXY:-direct} +ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct} ARG RELEASE_VERSION ARG TAGS="sqlite sqlite_unlock_notify" @@ -30,8 +30,8 @@ RUN cp /*-alpine-linux-musl*/lib/ld-musl-*.so.1 /lib || true RUN apk --no-cache add build-base git nodejs npm -COPY . ${GOPATH}/src/code.gitea.io/gitea -WORKDIR ${GOPATH}/src/code.gitea.io/gitea +COPY . ${GOPATH}/src/forgejo.org +WORKDIR ${GOPATH}/src/forgejo.org RUN make clean RUN make frontend @@ -45,11 +45,12 @@ COPY docker/rootless /tmp/local RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \ /tmp/local/usr/local/bin/docker-setup.sh \ /tmp/local/usr/local/bin/gitea \ - /go/src/code.gitea.io/gitea/gitea \ - /go/src/code.gitea.io/gitea/environment-to-ini -RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete + /go/src/forgejo.org/gitea \ + /go/src/forgejo.org/environment-to-ini +RUN chmod 644 /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete -FROM code.forgejo.org/oci/golang:1.23-alpine3.20 +FROM data.forgejo.org/oci/alpine:3.21 +ARG RELEASE_VERSION LABEL maintainer="contact@forgejo.org" \ org.opencontainers.image.authors="Forgejo" \ org.opencontainers.image.url="https://forgejo.org" \ @@ -71,6 +72,7 @@ RUN apk --no-cache add \ git \ curl \ gnupg \ + openssh-client \ && rm -rf /var/cache/apk/* RUN addgroup \ @@ -89,10 +91,10 @@ RUN chown git:git /var/lib/gitea /etc/gitea COPY --from=build-env /tmp/local / RUN cd /usr/local/bin ; ln -s gitea forgejo -COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea -RUN ln /app/gitea/gitea /app/gitea/forgejo-cli -COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini -COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh +COPY --from=build-env --chown=root:root /go/src/forgejo.org/gitea /app/gitea/gitea +RUN ln -s /app/gitea/gitea /app/gitea/forgejo-cli +COPY --from=build-env --chown=root:root /go/src/forgejo.org/environment-to-ini /usr/local/bin/environment-to-ini +COPY --from=build-env /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh #git:git USER 1000:1000 diff --git a/Makefile b/Makefile index a2bfbdc95c..c525b8acf8 100644 --- a/Makefile +++ b/Makefile @@ -16,50 +16,51 @@ else DIST := dist DIST_DIRS := $(DIST)/binaries $(DIST)/release -IMPORT := code.gitea.io/gitea +IMPORT := forgejo.org -GO ?= go +GO ?= $(shell go env GOROOT)/bin/go SHASUM ?= shasum -a 256 HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes) COMMA := , DIFF ?= diff --unified +ifeq ($(USE_GOTESTSUM), yes) + GOTEST ?= gotestsum -- + GOTESTCOMPILEDRUNPREFIX ?= gotestsum --raw-command -- go tool test2json -t + GOTESTCOMPILEDRUNSUFFIX ?= -test.v=test2json +else + GOTEST ?= $(GO) test + GOTESTCOMPILEDRUNPREFIX ?= + GOTESTCOMPILEDRUNSUFFIX ?= +endif + XGO_VERSION := go-1.21.x AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go -EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.0.3 # renovate: datasource=go -GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0 # renovate: datasource=go -GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.61.0 # renovate: datasource=go +EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.2.1 # renovate: datasource=go +GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.8.0 # renovate: datasource=go +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 # renovate: datasource=go GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 # renovate: datasource=go MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0 # renovate: datasource=go SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 # renovate: datasource=go XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go -DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.25.0 # renovate: datasource=go -GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go -GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.16.2 # renovate: datasource=go -RENOVATE_NPM_PACKAGE ?= renovate@38.93.2 # renovate: datasource=docker packageName=code.forgejo.org/forgejo-contrib/renovate +DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.32.0 # renovate: datasource=go +GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.1 # renovate: datasource=go +GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.18.1 # renovate: datasource=go +RENOVATE_NPM_PACKAGE ?= renovate@40.31.0 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate + +# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/ +DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ... ifeq ($(HAS_GO), yes) CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766 CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS) endif -ifeq ($(GOOS),windows) - IS_WINDOWS := yes -else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows) - ifeq ($(GOOS),) - IS_WINDOWS := yes - endif -endif -ifeq ($(IS_WINDOWS),yes) - GOFLAGS := -v -buildmode=exe - EXECUTABLE ?= gitea.exe -else - GOFLAGS := -v - EXECUTABLE ?= gitea -endif +GOFLAGS := -v +EXECUTABLE ?= gitea ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu) SED_INPLACE := sed -i @@ -91,38 +92,36 @@ else FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY} else # drop the "g" prefix prepended by git describe to the commit hash - FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY} + FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always 2>/dev/null | sed 's/^v//' | sed 's/\-g/-/') + ifneq ($(FORGEJO_VERSION),) + ifeq ($(findstring $(GITEA_COMPATIBILITY),$(FORGEJO_VERSION)),) + FORGEJO_VERSION := $(FORGEJO_VERSION)+$(GITEA_COMPATIBILITY) + endif + endif endif endif FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//') FORGEJO_VERSION_MINOR=$(shell echo $(FORGEJO_VERSION) | sed -E -e 's/^([0-9]+\.[0-9]+).*/\1/') -show-version-full: - @echo ${FORGEJO_VERSION} - -show-version-major: - @echo ${FORGEJO_VERSION_MAJOR} - -show-version-minor: - @echo ${FORGEJO_VERSION_MINOR} - RELEASE_VERSION ?= ${FORGEJO_VERSION} VERSION ?= ${RELEASE_VERSION} FORGEJO_VERSION_API ?= ${FORGEJO_VERSION} -show-version-api: - @echo ${FORGEJO_VERSION_API} - +# Strip binaries by default to reduce size, allow overriding for debugging +STRIP ?= 1 +ifeq ($(STRIP),1) + LDFLAGS := $(LDFLAGS) -s -w +endif LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION_API)" LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64 ifeq ($(HAS_GO), yes) - GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) $(shell $(GO) list code.gitea.io/gitea/models/forgejo_migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./...)) + GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list forgejo.org/models/migrations/...) $(shell $(GO) list forgejo.org/models/forgejo_migrations/...) forgejo.org/tests/integration/migration-test forgejo.org/tests forgejo.org/tests/integration forgejo.org/tests/e2e,$(shell $(GO) list ./...)) endif REMOTE_CACHER_MODULES ?= cache nosql session queue -GO_TEST_REMOTE_CACHER_PACKAGES ?= $(addprefix code.gitea.io/gitea/modules/,$(REMOTE_CACHER_MODULES)) +GO_TEST_REMOTE_CACHER_PACKAGES ?= $(addprefix forgejo.org/modules/,$(REMOTE_CACHER_MODULES)) FOMANTIC_WORK_DIR := web_src/fomantic @@ -154,9 +153,8 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN GO_DIRS := build cmd models modules routers services tests WEB_DIRS := web_src/js web_src/css -ESLINT_FILES := web_src/js tools *.js tests/e2e/*.js tests/e2e/shared/*.js STYLELINT_FILES := web_src/css web_src/js/components/*.vue -SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github $(wildcard *.go *.js *.md *.yml *.yaml *.toml) +SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github $(wildcard *.go *.js *.ts *.vue *.md *.yml *.yaml) GO_SOURCES := $(wildcard *.go) GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go) @@ -164,7 +162,7 @@ GO_SOURCES += $(GENERATED_GO_DEST) GO_SOURCES_NO_BINDATA := $(GO_SOURCES) ifeq ($(HAS_GO), yes) - MIGRATION_PACKAGES := $(shell $(GO) list code.gitea.io/gitea/models/migrations/... code.gitea.io/gitea/models/forgejo_migrations/...) + MIGRATION_PACKAGES := $(shell $(GO) list forgejo.org/models/migrations/... forgejo.org/models/forgejo_migrations/...) endif ifeq ($(filter $(TAGS_SPLIT),bindata),bindata) @@ -203,7 +201,7 @@ all: build .PHONY: help help: @echo "Make Routines:" - @echo " - \"\" equivalent to \"build\"" + @echo " - \"\" equivalent to \"build\"" @echo " - build build everything" @echo " - frontend build frontend files" @echo " - backend build backend files" @@ -216,16 +214,12 @@ help: @echo " - deps-frontend install frontend dependencies" @echo " - deps-backend install backend dependencies" @echo " - deps-tools install tool dependencies" - @echo " - deps-py install python dependencies" @echo " - lint lint everything" @echo " - lint-fix lint everything and fix issues" @echo " - lint-frontend lint frontend files" @echo " - lint-frontend-fix lint frontend files and fix issues" @echo " - lint-backend lint backend files" @echo " - lint-backend-fix lint backend files and fix issues" - @echo " - lint-codespell lint typos" - @echo " - lint-codespell-fix lint typos and fix them automatically" - @echo " - lint-codespell-fix-i lint typos and fix them interactively" @echo " - lint-go lint go files" @echo " - lint-go-fix lint go files and fix issues" @echo " - lint-go-vet lint go files with vet" @@ -236,11 +230,7 @@ help: @echo " - lint-css-fix lint css files and fix issues" @echo " - lint-md lint markdown files" @echo " - lint-swagger lint swagger files" - @echo " - lint-templates lint template files" @echo " - lint-renovate lint renovate files" - @echo " - lint-yaml lint yaml files" - @echo " - lint-spell lint spelling" - @echo " - lint-spell-fix lint spelling and fix issues" @echo " - checks run various consistency checks" @echo " - checks-frontend check frontend files" @echo " - checks-backend check backend files" @@ -271,6 +261,30 @@ help: @echo " - test-sqlite[\#TestSpecificName] run integration test for sqlite" @echo " - reproduce-build\#version build a reproducible binary for the specified release version" +.PHONY: verify-version +verify-version: +ifeq ($(FORGEJO_VERSION),) + @echo "Error: Could not determine FORGEJO_VERSION; version file $(STORED_VERSION_FILE) not present and no suitable git tag found" + @echo 'In most cases this likely means you forgot to fetch git tags, you can fix this by executing `git fetch --tags`. If this is not possible and this is part of a custom build process, then you can set a specific version by writing it to $(STORED_VERSION_FILE) (This must be a semver compatible version).' + @false +endif + +.PHONY: show-version-full +show-version-full: verify-version + @echo ${FORGEJO_VERSION} + +.PHONY: show-version-major +show-version-major: verify-version + @echo ${FORGEJO_VERSION_MAJOR} + +.PHONY: show-version-minor +show-version-minor: verify-version + @echo ${FORGEJO_VERSION_MINOR} + +.PHONY: show-version-api +show-version-api: verify-version + @echo ${FORGEJO_VERSION_API} + ### # Check system and environment requirements ### @@ -396,10 +410,10 @@ checks-frontend: lockfile-check svg-check checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check .PHONY: lint -lint: lint-frontend lint-backend lint-spell +lint: lint-frontend lint-backend .PHONY: lint-fix -lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix +lint-fix: lint-frontend-fix lint-backend-fix .PHONY: lint-frontend lint-frontend: lint-js lint-css @@ -408,30 +422,18 @@ lint-frontend: lint-js lint-css lint-frontend-fix: lint-js-fix lint-css-fix .PHONY: lint-backend -lint-backend: lint-go lint-go-vet lint-editorconfig lint-renovate +lint-backend: lint-go lint-go-vet lint-editorconfig lint-renovate lint-locale lint-locale-usage lint-disposable-emails .PHONY: lint-backend-fix -lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig - -.PHONY: lint-codespell -lint-codespell: - codespell - -.PHONY: lint-codespell-fix -lint-codespell-fix: - codespell -w - -.PHONY: lint-codespell-fix-i -lint-codespell-fix-i: - codespell -w -i 3 -C 2 +lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig lint-disposable-emails-fix .PHONY: lint-js lint-js: node_modules - npx eslint --color --max-warnings=0 --ext js,vue $(ESLINT_FILES) + npx eslint --color --max-warnings=0 .PHONY: lint-js-fix lint-js-fix: node_modules - npx eslint --color --max-warnings=0 --ext js,vue $(ESLINT_FILES) --fix + npx eslint --color --max-warnings=0 --fix .PHONY: lint-css lint-css: node_modules @@ -447,23 +449,23 @@ lint-swagger: node_modules .PHONY: lint-renovate lint-renovate: node_modules - npx --yes --package $(RENOVATE_NPM_PACKAGE) -- renovate-config-validator --strict > .lint-renovate 2>&1 || true - @if grep --quiet --extended-regexp -e '^( WARN:|ERROR:)' .lint-renovate ; then cat .lint-renovate ; rm .lint-renovate ; exit 1 ; fi + npx --yes --package $(RENOVATE_NPM_PACKAGE) -- renovate-config-validator > .lint-renovate 2>&1 || true + @if grep --quiet --extended-regexp -e '^( ERROR:)' .lint-renovate ; then cat .lint-renovate ; rm .lint-renovate ; exit 1 ; fi @rm .lint-renovate +.PHONY: lint-locale +lint-locale: + $(GO) run build/lint-locale/lint-locale.go + +.PHONY: lint-locale-usage +lint-locale-usage: + $(GO) run build/lint-locale-usage/lint-locale-usage.go + .PHONY: lint-md lint-md: node_modules npx markdownlint docs *.md -.PHONY: lint-spell -lint-spell: lint-codespell - @go run $(MISSPELL_PACKAGE) -error $(SPELLCHECK_FILES) - -.PHONY: lint-spell-fix -lint-spell-fix: lint-codespell-fix - @go run $(MISSPELL_PACKAGE) -w $(SPELLCHECK_FILES) - -RUN_DEADCODE = $(GO) run $(DEADCODE_PACKAGE) -generated=false -f='{{println .Path}}{{range .Funcs}}{{printf "\t%s\n" .Name}}{{end}}{{println}}' -test code.gitea.io/gitea +RUN_DEADCODE = $(GO) run $(DEADCODE_PACKAGE) -generated=false -f='{{println .Path}}{{range .Funcs}}{{printf "\t%s\n" .Name}}{{end}}{{println}}' -test forgejo.org .PHONY: lint-go lint-go: @@ -477,13 +479,6 @@ lint-go-fix: $(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) --fix $(RUN_DEADCODE) > .deadcode-out -# workaround step for the lint-go-windows CI task because 'go run' can not -# have distinct GOOS/GOARCH for its build and run steps -.PHONY: lint-go-windows -lint-go-windows: - @GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE) - golangci-lint run - .PHONY: lint-go-vet lint-go-vet: @echo "Running go vet..." @@ -498,18 +493,17 @@ lint-go-gopls: lint-editorconfig: $(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates .forgejo/workflows -.PHONY: lint-templates -lint-templates: .venv node_modules - @node tools/lint-templates-svg.js - @poetry run djlint $(shell find templates -type f -iname '*.tmpl') +.PHONY: lint-disposable-emails +lint-disposable-emails: + $(GO) run build/generate-disposable-email.go -check -r $(DISPOSABLE_EMAILS_SHA) -.PHONY: lint-yaml -lint-yaml: .venv - @poetry run yamllint . +.PHONY: lint-disposable-emails-fix +lint-disposable-emails-fix: + $(GO) run build/generate-disposable-email.go -r $(DISPOSABLE_EMAILS_SHA) .PHONY: security-check security-check: - go run $(GOVULNCHECK_PACKAGE) ./... + go run $(GOVULNCHECK_PACKAGE) -show color ./... ### # Development and testing targets @@ -534,12 +528,12 @@ test: test-frontend test-backend .PHONY: test-backend test-backend: @echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." - @$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES) + @$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES) .PHONY: test-remote-cacher test-remote-cacher: @echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." - @$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_REMOTE_CACHER_PACKAGES) + @$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_REMOTE_CACHER_PACKAGES) .PHONY: test-frontend test-frontend: node_modules @@ -563,8 +557,8 @@ test-check: .PHONY: test\#% test\#%: - @echo "Running go test with -tags '$(TEST_TAGS)'..." - @$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES) + @echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." + @$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES) .PHONY: coverage coverage: @@ -575,7 +569,7 @@ coverage: .PHONY: unit-test-coverage unit-test-coverage: @echo "Running unit-test-coverage $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." - @$(GO) test $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1 + @$(GOTEST) $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1 .PHONY: tidy tidy: @@ -596,7 +590,7 @@ tidy-check: tidy go-licenses: $(GO_LICENSE_FILE) $(GO_LICENSE_FILE): go.mod go.sum - -$(shell $(GO) env GOROOT)/bin/go run $(GO_LICENSES_PACKAGE) save . --force --ignore code.gitea.io/gitea --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null + -$(GO) run $(GO_LICENSES_PACKAGE) save . --force --ignore forgejo.org --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null $(GO) run build/generate-go-licenses.go $(GO_LICENSE_TMP_DIR) $(GO_LICENSE_FILE) @rm -rf $(GO_LICENSE_TMP_DIR) @@ -608,11 +602,11 @@ generate-ini-sqlite: .PHONY: test-sqlite test-sqlite: integrations.sqlite.test generate-ini-sqlite - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTESTCOMPILEDRUNPREFIX) ./integrations.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX) .PHONY: test-sqlite\#% test-sqlite\#%: integrations.sqlite.test generate-ini-sqlite - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.run $(subst .,/,$*) + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTESTCOMPILEDRUNPREFIX) ./integrations.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run $(subst .,/,$*) .PHONY: test-sqlite-migration test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test @@ -629,11 +623,11 @@ generate-ini-mysql: .PHONY: test-mysql test-mysql: integrations.mysql.test generate-ini-mysql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTESTCOMPILEDRUNPREFIX) ./integrations.mysql.test $(GOTESTCOMPILEDRUNSUFFIX) .PHONY: test-mysql\#% test-mysql\#%: integrations.mysql.test generate-ini-mysql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test -test.run $(subst .,/,$*) + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTESTCOMPILEDRUNPREFIX) ./integrations.mysql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run $(subst .,/,$*) .PHONY: test-mysql-migration test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test @@ -647,15 +641,16 @@ generate-ini-pgsql: -e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \ -e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \ -e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \ + -e 's|{{TEST_STORAGE_TYPE}}|$(or $(TEST_STORAGE_TYPE),minio)|g' \ tests/pgsql.ini.tmpl > tests/pgsql.ini .PHONY: test-pgsql test-pgsql: integrations.pgsql.test generate-ini-pgsql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTESTCOMPILEDRUNPREFIX) ./integrations.pgsql.test $(GOTESTCOMPILEDRUNSUFFIX) .PHONY: test-pgsql\#% test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.run $(subst .,/,$*) + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTESTCOMPILEDRUNPREFIX) ./integrations.pgsql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run $(subst .,/,$*) .PHONY: test-pgsql-migration test-pgsql-migration: migrations.pgsql.test migrations.individual.pgsql.test @@ -674,35 +669,34 @@ test-e2e: test-e2e-sqlite .PHONY: test-e2e-sqlite test-e2e-sqlite: playwright e2e.sqlite.test generate-ini-sqlite - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test -test.run TestE2e + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e .PHONY: test-e2e-sqlite\#% test-e2e-sqlite\#%: playwright e2e.sqlite.test generate-ini-sqlite - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test -test.run TestE2e/$* + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e/$* .PHONY: test-e2e-sqlite-firefox\#% test-e2e-sqlite-firefox\#%: playwright e2e.sqlite.test generate-ini-sqlite - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini PLAYWRIGHT_PROJECT=firefox ./e2e.sqlite.test -test.run TestE2e/$* + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini PLAYWRIGHT_PROJECT=firefox $(GOTESTCOMPILEDRUNPREFIX) ./e2e.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e/$* .PHONY: test-e2e-mysql test-e2e-mysql: playwright e2e.mysql.test generate-ini-mysql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test -test.run TestE2e + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.mysql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e .PHONY: test-e2e-mysql\#% test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test -test.run TestE2e/$* + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.mysql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e/$* .PHONY: test-e2e-pgsql test-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test -test.run TestE2e + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.pgsql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e .PHONY: test-e2e-pgsql\#% test-e2e-pgsql\#%: playwright e2e.pgsql.test generate-ini-pgsql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test -test.run TestE2e/$* + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.pgsql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e/$* .PHONY: test-e2e-debugserver test-e2e-debugserver: e2e.sqlite.test generate-ini-sqlite - sed -i s/3003/3000/g tests/sqlite.ini GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test -test.run TestDebugserver -test.timeout 24h .PHONY: bench-sqlite @@ -726,73 +720,73 @@ integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sq GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out integrations.mysql.test: git-check $(GO_SOURCES) - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mysql.test + $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -o integrations.mysql.test integrations.pgsql.test: git-check $(GO_SOURCES) - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.pgsql.test + $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -o integrations.pgsql.test integrations.sqlite.test: git-check $(GO_SOURCES) - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.sqlite.test -tags '$(TEST_TAGS)' + $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -o integrations.sqlite.test -tags '$(TEST_TAGS)' integrations.cover.test: git-check $(GO_SOURCES) - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.test + $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.test integrations.cover.sqlite.test: git-check $(GO_SOURCES) - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.sqlite.test -tags '$(TEST_TAGS)' + $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.sqlite.test -tags '$(TEST_TAGS)' .PHONY: migrations.mysql.test migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql.test - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./migrations.mysql.test + $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration/migration-test -o migrations.mysql.test + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTESTCOMPILEDRUNPREFIX) ./migrations.mysql.test $(GOTESTCOMPILEDRUNSUFFIX) .PHONY: migrations.pgsql.test migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./migrations.pgsql.test + $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration/migration-test -o migrations.pgsql.test + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTESTCOMPILEDRUNPREFIX) ./migrations.pgsql.test $(GOTESTCOMPILEDRUNSUFFIX) .PHONY: migrations.sqlite.test migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)' - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./migrations.sqlite.test + $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)' + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTESTCOMPILEDRUNPREFIX) ./migrations.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX) .PHONY: migrations.individual.mysql.test migrations.individual.mysql.test: $(GO_SOURCES) for pkg in $(MIGRATION_PACKAGES); do \ - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1; \ + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1; \ done .PHONY: migrations.individual.sqlite.test\#% migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$* + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$* .PHONY: migrations.individual.pgsql.test migrations.individual.pgsql.test: $(GO_SOURCES) for pkg in $(MIGRATION_PACKAGES); do \ - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1;\ + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1;\ done .PHONY: migrations.individual.pgsql.test\#% migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$* + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$* .PHONY: migrations.individual.sqlite.test migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite for pkg in $(MIGRATION_PACKAGES); do \ - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1; \ + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1; \ done .PHONY: migrations.individual.sqlite.test\#% migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$* + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$* e2e.mysql.test: $(GO_SOURCES) - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.mysql.test + $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/e2e -o e2e.mysql.test e2e.pgsql.test: $(GO_SOURCES) - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.pgsql.test + $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/e2e -o e2e.pgsql.test e2e.sqlite.test: $(GO_SOURCES) - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.sqlite.test -tags '$(TEST_TAGS)' + $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/e2e -o e2e.sqlite.test -tags '$(TEST_TAGS)' .PHONY: check check: test @@ -802,8 +796,8 @@ check: test ### .PHONY: install $(TAGS_PREREQ) -install: $(wildcard *.go) - CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' +install: $(wildcard *.go) | verify-version + CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' .PHONY: build build: frontend backend @@ -830,14 +824,14 @@ generate-go: $(TAGS_PREREQ) merge-locales: @echo "NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY" -$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ) - CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@ +$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ) | verify-version + CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ forgejo: $(EXECUTABLE) ln -f $(EXECUTABLE) forgejo -static-executable: $(GO_SOURCES) $(TAGS_PREREQ) - CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -o $(EXECUTABLE) +static-executable: $(GO_SOURCES) $(TAGS_PREREQ) | verify-version + CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -o $(EXECUTABLE) .PHONY: release release: frontend generate release-linux release-copy release-compress vendor release-sources release-check @@ -848,23 +842,19 @@ sources-tarbal: frontend generate vendor release-sources release-check $(DIST_DIRS): mkdir -p $(DIST_DIRS) -.PHONY: release-windows -release-windows: | $(DIST_DIRS) - CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) . - .PHONY: release-linux -release-linux: | $(DIST_DIRS) +release-linux: | $(DIST_DIRS) verify-version CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out forgejo-$(VERSION) . ifeq ($(CI),true) cp /build/* $(DIST)/binaries endif .PHONY: release-darwin -release-darwin: | $(DIST_DIRS) +release-darwin: | $(DIST_DIRS) verify-version CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) . .PHONY: release-freebsd -release-freebsd: | $(DIST_DIRS) +release-freebsd: | $(DIST_DIRS) verify-version CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) . .PHONY: release-copy @@ -896,7 +886,7 @@ release-docs: | $(DIST_DIRS) docs .PHONY: reproduce-build reproduce-build: # Start building the Dockerfile with the RELEASE_VERSION tag set. GOPROXY is set -# for convience, because the default of the Dockerfile is `direct` which can be +# for convenience, because the default of the Dockerfile is `direct` which can be # quite slow. @docker build --build-arg="RELEASE_VERSION=$(RELEASE_VERSION)" --build-arg="GOPROXY=$(shell $(GO) env GOPROXY)" --tag "forgejo-reproducibility" . @id=$$(docker create forgejo-reproducibility); \ @@ -918,10 +908,7 @@ reproduce-build\#%: ### .PHONY: deps -deps: deps-frontend deps-backend deps-tools deps-py - -.PHONY: deps-py -deps-py: .venv +deps: deps-frontend deps-backend deps-tools .PHONY: deps-frontend deps-frontend: node_modules @@ -1003,12 +990,11 @@ generate-gitignore: .PHONY: generate-gomock generate-gomock: - $(GO) run $(GOMOCK_PACKAGE) -package mock -destination ./modules/queue/mock/redisuniversalclient.go code.gitea.io/gitea/modules/nosql RedisClient + $(GO) run $(GOMOCK_PACKAGE) -package mock -destination ./modules/queue/mock/redisuniversalclient.go forgejo.org/modules/nosql RedisClient .PHONY: generate-images generate-images: | node_modules - npm install --no-save fabric@6 imagemin-zopfli@7 - node tools/generate-images.js $(TAGS) + node tools/generate-images.js .PHONY: generate-manpage generate-manpage: diff --git a/README.md b/README.md index 2edc449177..f95aebadeb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -

Welcome to Forgejo

@@ -16,11 +15,6 @@ Our promise: **Independent Free/Libre Software forever!** ## What does Forgejo offer? - - If you like any of the following, Forgejo is literally meant for you: - Lightweight: Forgejo can easily be hosted on nearly **every machine**. diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index f86e10c21a..32f7b8c264 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -4,19 +4,31 @@ A minor or major Forgejo release is published every [three months](https://forge A [patch or minor release](https://semver.org/spec/v2.0.0.html) (e.g. upgrading from v7.0.0 to v7.0.1 or v7.1.0) does not require manual intervention. But [major releases](https://semver.org/spec/v2.0.0.html#spec-item-8) where the first version number changes (e.g. upgrading from v1.21 to v7.0) contain breaking changes and the release notes explain how to deal with them. -The release notes of each release [are available in the corresponding milestone](https://codeberg.org/forgejo/forgejo/milestones), starting with [Forgejo 7.0.7](https://codeberg.org/forgejo/forgejo/milestone/7683) and [Forgejo 8.0.1](https://codeberg.org/forgejo/forgejo/milestone/7682). +The release notes of each release [are available in the release-notes-published directory of this repository](release-notes-published), starting with [Forgejo 7.0.7](release-notes-published/7.0.7.md) and [Forgejo 8.0.1](release-notes-published/8.0.1.md). + +## 9.0.2 + +See the [Forgejo 9.0.2 release notes](release-notes-published/9.0.2.md). + +## 9.0.1 + +See the [Forgejo 9.0.1 release notes](release-notes-published/9.0.1.md). + +## 9.0.0 + +See the [Forgejo 9.0.0 release notes](release-notes-published/9.0.0.md). ## 8.0.3 -The Forgejo v8.0.3 release notes are [available in the v8.0.3 milestone](https://codeberg.org/forgejo/forgejo/milestone/8231). +See the [Forgejo 8.0.3 release notes](release-notes-published/8.0.3.md). ## 8.0.2 -The Forgejo v8.0.2 release notes are [available in the v8.0.2 milestone](https://codeberg.org/forgejo/forgejo/milestone/7728). +See the [Forgejo 8.0.2 release notes](release-notes-published/8.0.2.md). ## 8.0.1 -The Forgejo v8.0.1 release notes are [available in the v8.0.1 milestone](https://codeberg.org/forgejo/forgejo/milestone/7682). +See the [Forgejo 8.0.1 release notes](release-notes-published/8.0.1.md). ## 8.0.0 @@ -155,17 +167,25 @@ A [companion blog post](https://forgejo.org/2024-07-release-v8-0/) provides addi - [PR](https://codeberg.org/forgejo/forgejo/pulls/2937): 31 March updates +## 7.0.11 + +See the [Forgejo 7.0.11 release notes](release-notes-published/7.0.11.md). + +## 7.0.10 + +See the [Forgejo 7.0.10 release notes](release-notes-published/7.0.10.md). + ## 7.0.9 -The Forgejo v7.0.9 release notes are [available in the v7.0.9 milestone](https://codeberg.org/forgejo/forgejo/milestone/8232). +See the [Forgejo 7.0.9 release notes](release-notes-published/7.0.9.md). ## 7.0.8 -The Forgejo v7.0.8 release notes are [available in the v7.0.8 milestone](https://codeberg.org/forgejo/forgejo/milestone/7729). +See the [Forgejo 7.0.8 release notes](release-notes-published/7.0.8.md). ## 7.0.7 -The Forgejo v7.0.7 release notes are [available in the v7.0.7 milestone](https://codeberg.org/forgejo/forgejo/milestone/7683). +See the [Forgejo 7.0.7 release notes](release-notes-published/7.0.7.md). ## 7.0.6 @@ -1570,7 +1590,7 @@ this situation, [follow the instructions in the companion blog post](https://for The most prominent ones are described here, others can be found in the list of commits included in the release as described above. - * [Fix links to pull request reviews sent via mail](https://codeberg.org/forgejo/forgejo/commit/88e179d5ef8ee41f71d068195685ff098b38ca31). The pull request link was correct but it did not go the the review and stayed at the beginning of the page + * [Fix links to pull request reviews sent via mail](https://codeberg.org/forgejo/forgejo/commit/88e179d5ef8ee41f71d068195685ff098b38ca31). The pull request link was correct but it did not go the review and stayed at the beginning of the page * [Recognize OGG as an audio format](https://codeberg.org/forgejo/forgejo/commit/622ec5c79f299c32ac2667a1aa7b4bf5d7c2d6cf) * [Consistently show the last time a cron job was run in the admin panel](https://codeberg.org/forgejo/forgejo/commit/5f769ef20) * [Fix NuGet registry v2 & v3 API search endpoints](https://codeberg.org/forgejo/forgejo/commit/471138829b0c24fe8c621dbb866ae8bb45ebc674) @@ -1589,7 +1609,7 @@ this situation, [follow the instructions in the companion blog post](https://for * [Fix pull request check list when there are more than 30](https://codeberg.org/forgejo/forgejo/commit/e226b9646) * [Fix attachment clipboard copy on insecure origin](https://codeberg.org/forgejo/forgejo/commit/12ac84c26) * [Fix the profile README rendering](https://codeberg.org/forgejo/forgejo/commit/84c3b60a4) that [was inconsistent with other markdown files renderings](https://codeberg.org/forgejo/forgejo/issues/833) - * [Fix API leaking the user email when the caller is not authentified](https://codeberg.org/forgejo/forgejo/commit/d89003cc1) + * [Fix API leaking the user email when the caller is not authenticated](https://codeberg.org/forgejo/forgejo/commit/d89003cc1) ## 1.20.2-0 @@ -1647,7 +1667,7 @@ $ git -C forgejo log --oneline --no-merges origin/v1.19/forgejo..origin/v1.20/fo The semantic version was updated to `5.0.0+0-gitea-1.20.1` because it contains breaking changes. - **Breaking:** - [Scoped access tokens](https://codeberg.org/forgejo/forgejo/commit/18de83b2a3fc120922096b7348d6375094ae1532) or (Personal Access Tokens), were refactored and although existing tokens are still valid, they may have a different scope than before. To ensure that no tokens have a larger scope than expected they must be removed and recreated. - - If your `app.ini` has one of the the following `[indexer].ISSUE_INDEXER_QUEUE_TYPE`, `[indexer].ISSUE_INDEXER_QUEUE_BATCH_NUMBER`, `[indexer].`, `[indexer].ISSUE_INDEXER_QUEUE_DIR`, `[indexer].ISSUE_INDEXER_QUEUE_CONN_STR`, `[indexer].UPDATE_BUFFER_LEN`, `[mailer].SEND_BUFFER_LEN`, `[repository].PULL_REQUEST_QUEUE_LENGTH` or `[repository].MIRROR_QUEUE_LENGTH`, Forgejo will abort immediately. Unless you know exactly what you're doing, you must comment them out so the default values are used. + - If your `app.ini` has one of the following `[indexer].ISSUE_INDEXER_QUEUE_TYPE`, `[indexer].ISSUE_INDEXER_QUEUE_BATCH_NUMBER`, `[indexer].`, `[indexer].ISSUE_INDEXER_QUEUE_DIR`, `[indexer].ISSUE_INDEXER_QUEUE_CONN_STR`, `[indexer].UPDATE_BUFFER_LEN`, `[mailer].SEND_BUFFER_LEN`, `[repository].PULL_REQUEST_QUEUE_LENGTH` or `[repository].MIRROR_QUEUE_LENGTH`, Forgejo will abort immediately. Unless you know exactly what you're doing, you must comment them out so the default values are used. - The `-p` option of `environment-to-ini` is [no longer supported](https://codeberg.org/forgejo/forgejo/commit/fa0b5b14c2faa6a5f76bb2e7bc9241a5e4354189) - The ".png" suffix for [user and organizations is now reserved](https://codeberg.org/forgejo/forgejo/commit/2b91841cd3e1213ff3e4ed4209d6a4be89c2fa79) - The section `[git.reflog]` is [now obsolete and its keys have been moved](https://codeberg.org/forgejo/forgejo/commit/2f149c5c9db97f20fbbc65e32d1f3133048b11a2) to the following replacements: @@ -1741,7 +1761,7 @@ $ git -C forgejo log --oneline --no-merges origin/v1.19/forgejo..origin/v1.20/fo - [The repository migration can be canceled](https://codeberg.org/forgejo/forgejo/commit/f6e029e6c7849d4361abf7f1d749b5d528364ac4) - [Add button on the diff header to copy the file name](https://codeberg.org/forgejo/forgejo/commit/c5ede35124c8d5280219c24049bb0ad7da9f02ed) - [Add --quiet option to the dump CLI](https://codeberg.org/forgejo/forgejo/commit/cb1536471bcef4d78a3fe5cbd738b9f60fabbcc2) - - [Support searching for an issue with its number in the the list of issues](https://codeberg.org/forgejo/forgejo/commit/1144b1d129de530b2c07dfdfaf55de383cd82212) + - [Support searching for an issue with its number in the list of issues](https://codeberg.org/forgejo/forgejo/commit/1144b1d129de530b2c07dfdfaf55de383cd82212) - [Improve the list of notifications](https://codeberg.org/forgejo/forgejo/commit/f7ede92f82f7f3ec7bb31a1249f9524e5b728f34) - [When editing a file in the web UI, allow for a preview whenever possible](https://codeberg.org/forgejo/forgejo/commit/ac64c8297444ade63a2a364c4afb7e6c1de5a75f) - [Make release download URLs human readable](https://codeberg.org/forgejo/forgejo/commit/42919ccb7cd32ab67d0878baf2bac6cd007899a8) @@ -1778,7 +1798,7 @@ $ git -C forgejo log --oneline --no-merges origin/v1.19/forgejo..origin/v1.20/fo - [Add API for gitignore templates](https://codeberg.org/forgejo/forgejo/commit/36a5d4c2f3b5670e5e921034cd5d25817534a6d4) - [Add API to upuload a file to an empty repository](https://codeberg.org/forgejo/forgejo/commit/cf465b472166ccf6d3e001e3043e4bf43e16e6b3) - [Allow for --not when listing the commits of a repo](https://codeberg.org/forgejo/forgejo/commit/f766b002938b5c81e343c81fda3c0669fa09809f) - - [Add `files` and `verification` parameters to improve performances when listing the commits of a a repo](https://codeberg.org/forgejo/forgejo/commit/1dd83dbb917d55bd253001646d6743f247a4d98b) + - [Add `files` and `verification` parameters to improve performances when listing the commits of a repo](https://codeberg.org/forgejo/forgejo/commit/1dd83dbb917d55bd253001646d6743f247a4d98b) - [Allow for listing a single commit in a repository](https://codeberg.org/forgejo/forgejo/commit/5930ab5fdf7a970fcca3cd50b44cf1cacb615a54) - [Create a branch directly from commit on the create branch API](https://codeberg.org/forgejo/forgejo/commit/cd9a13ebb47d32f46b38439a524e3b2e0c619490) - [Add API for Label templates](https://codeberg.org/forgejo/forgejo/commit/25dc1556cd70b567a4920beb002a0addfbfd6ef2) diff --git a/assets/go-licenses.json b/assets/go-licenses.json index 89fa08074c..d177bc5653 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -19,11 +19,26 @@ "path": "code.forgejo.org/forgejo-contrib/go-libravatar/LICENSE", "licenseText": "Copyright (c) 2016 Sandro Santilli \u003cstrk@kbt.io\u003e\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" }, + { + "name": "code.forgejo.org/forgejo/go-rpmutils", + "path": "code.forgejo.org/forgejo/go-rpmutils/LICENSE", + "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" + }, + { + "name": "code.forgejo.org/forgejo/levelqueue", + "path": "code.forgejo.org/forgejo/levelqueue/LICENSE", + "licenseText": "Copyright (c) 2019 Lunny Xiao\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" + }, { "name": "code.forgejo.org/forgejo/reply", "path": "code.forgejo.org/forgejo/reply/LICENSE", "licenseText": "MIT License\n\nCopyright (c) The Forgejo Authors\nCopyright (c) Discourse\nCopyright (c) Claudemiro\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, + { + "name": "code.forgejo.org/go-chi/binding", + "path": "code.forgejo.org/go-chi/binding/LICENSE", + "licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License." + }, { "name": "code.forgejo.org/go-chi/cache", "path": "code.forgejo.org/go-chi/cache/LICENSE", @@ -75,14 +90,9 @@ "licenseText": "MIT License\n\nCopyright (c) 2019 Go xsd:duration\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, { - "name": "gitea.com/go-chi/binding", - "path": "gitea.com/go-chi/binding/LICENSE", - "licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License." - }, - { - "name": "gitea.com/lunny/levelqueue", - "path": "gitea.com/lunny/levelqueue/LICENSE", - "licenseText": "Copyright (c) 2019 Lunny Xiao\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" + "name": "github.com/42wim/httpsig", + "path": "github.com/42wim/httpsig/LICENSE", + "licenseText": "BSD 3-Clause License\n\nCopyright (c) 2018, go-fed\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, { "name": "github.com/42wim/sshsig", @@ -94,19 +104,14 @@ "path": "github.com/Azure/go-ntlmssp/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2016 Microsoft\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, - { - "name": "github.com/DataDog/zstd", - "path": "github.com/DataDog/zstd/LICENSE", - "licenseText": "Simplified BSD License\n\nCopyright (c) 2016, Datadog \u003cinfo@datadoghq.com\u003e\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n * Redistributions of source code must retain the above copyright notice,\n this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n * Neither the name of the copyright holder nor the names of its contributors\n may be used to endorse or promote products derived from this software\n without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" - }, { "name": "github.com/ProtonMail/go-crypto", "path": "github.com/ProtonMail/go-crypto/LICENSE", "licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, { - "name": "github.com/RoaringBitmap/roaring", - "path": "github.com/RoaringBitmap/roaring/LICENSE", + "name": "github.com/RoaringBitmap/roaring/v2", + "path": "github.com/RoaringBitmap/roaring/v2/LICENSE", "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright 2016 by the authors\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n================================================================================\n\nPortions of runcontainer.go are from the Go standard library, which is licensed\nunder:\n\nCopyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\n copyright notice, this list of conditions and the following disclaimer\n in the documentation and/or other materials provided with the\n distribution.\n * Neither the name of Google Inc. nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, { @@ -292,7 +297,7 @@ { "name": "github.com/cyphar/filepath-securejoin", "path": "github.com/cyphar/filepath-securejoin/LICENSE", - "licenseText": "Copyright (C) 2014-2015 Docker Inc \u0026 Go Authors. All rights reserved.\nCopyright (C) 2017 SUSE LLC. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + "licenseText": "Copyright (C) 2014-2015 Docker Inc \u0026 Go Authors. All rights reserved.\nCopyright (C) 2017-2024 SUSE LLC. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, { "name": "github.com/davecgh/go-spew/spew", @@ -489,11 +494,6 @@ "path": "github.com/golang-jwt/jwt/v5/LICENSE", "licenseText": "Copyright (c) 2012 Dave Grijalva\nCopyright (c) 2021 golang-jwt maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 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.\n\n" }, - { - "name": "github.com/golang/geo", - "path": "github.com/golang/geo/LICENSE", - "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" - }, { "name": "github.com/golang/groupcache/lru", "path": "github.com/golang/groupcache/lru/LICENSE", @@ -567,7 +567,7 @@ { "name": "github.com/gorilla/sessions", "path": "github.com/gorilla/sessions/LICENSE", - "licenseText": "Copyright (c) 2023 The Gorilla Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n\t * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n\t * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n\t * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + "licenseText": "Copyright (c) 2024 The Gorilla Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n\t * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n\t * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n\t * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, { "name": "github.com/hashicorp/go-cleanhttp", @@ -610,8 +610,8 @@ "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2014 Juan Batiz-Benet\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" }, { - "name": "github.com/jhillyerd/enmime", - "path": "github.com/jhillyerd/enmime/LICENSE", + "name": "github.com/jhillyerd/enmime/v2", + "path": "github.com/jhillyerd/enmime/v2/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2012-2016 James Hillyerd, All Rights Reserved\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" }, { @@ -712,11 +712,11 @@ { "name": "github.com/meilisearch/meilisearch-go", "path": "github.com/meilisearch/meilisearch-go/LICENSE", - "licenseText": "MIT License\n\nCopyright (c) 2020-2024 Meili SAS\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" + "licenseText": "MIT License\n\nCopyright (c) 2020-2025 Meili SAS\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, { - "name": "github.com/mholt/acmez/v2", - "path": "github.com/mholt/acmez/v2/LICENSE", + "name": "github.com/mholt/acmez/v3", + "path": "github.com/mholt/acmez/v3/LICENSE", "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, { @@ -734,6 +734,11 @@ "path": "github.com/miekg/dns/LICENSE", "licenseText": "BSD 3-Clause License\n\nCopyright (c) 2009, The Go Authors. Extensions copyright (c) 2011, Miek Gieben. \nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, + { + "name": "github.com/minio/crc64nvme", + "path": "github.com/minio/crc64nvme/LICENSE", + "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" + }, { "name": "github.com/minio/md5-simd", "path": "github.com/minio/md5-simd/LICENSE", @@ -759,6 +764,11 @@ "path": "github.com/modern-go/reflect2/LICENSE", "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, + { + "name": "github.com/munnerz/goautoneg", + "path": "github.com/munnerz/goautoneg/LICENSE", + "licenseText": "Copyright (c) 2011, Open Knowledge Foundation Ltd.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n\n Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in\n the documentation and/or other materials provided with the\n distribution.\n\n Neither the name of the Open Knowledge Foundation Ltd. nor the\n names of its contributors may be used to endorse or promote\n products derived from this software without specific prior written\n permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + }, { "name": "github.com/nektos/act/pkg", "path": "github.com/nektos/act/pkg/LICENSE", @@ -807,7 +817,7 @@ { "name": "github.com/pjbgf/sha1cd", "path": "github.com/pjbgf/sha1cd/LICENSE", - "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" + "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright 2023 pjbgf\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, { "name": "github.com/pkg/errors", @@ -824,6 +834,11 @@ "path": "github.com/pquerna/otp/LICENSE", "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, + { + "name": "github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil", + "path": "github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/LICENSE", + "licenseText": "Copyright (c) 2013 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + }, { "name": "github.com/prometheus/client_golang/prometheus", "path": "github.com/prometheus/client_golang/prometheus/LICENSE", @@ -879,11 +894,6 @@ "path": "github.com/santhosh-tekuri/jsonschema/v6/LICENSE", "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability." }, - { - "name": "github.com/sassoftware/go-rpmutils", - "path": "github.com/sassoftware/go-rpmutils/LICENSE", - "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" - }, { "name": "github.com/sergi/go-diff/diffmatchpatch", "path": "github.com/sergi/go-diff/diffmatchpatch/LICENSE", @@ -924,6 +934,11 @@ "path": "github.com/urfave/cli/v2/LICENSE", "licenseText": "MIT License\n\nCopyright (c) 2022 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, + { + "name": "github.com/urfave/cli/v3", + "path": "github.com/urfave/cli/v3/LICENSE", + "licenseText": "MIT License\n\nCopyright (c) 2023 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" + }, { "name": "github.com/valyala/fastjson", "path": "github.com/valyala/fastjson/LICENSE", @@ -934,11 +949,6 @@ "path": "github.com/x448/float16/LICENSE", "licenseText": "MIT License\n\nCopyright (c) 2019 Montgomery Edwardsâ´â´â¸ and Faye Amacker\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n" }, - { - "name": "github.com/xanzy/go-gitlab", - "path": "github.com/xanzy/go-gitlab/LICENSE", - "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"{}\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" - }, { "name": "github.com/xanzy/ssh-agent", "path": "github.com/xanzy/ssh-agent/LICENSE", @@ -969,6 +979,11 @@ "path": "github.com/zeebo/blake3/LICENSE", "licenseText": "This work is released into the public domain with CC0 1.0.\n\n-------------------------------------------------------------------------------\n\nCreative Commons Legal Code\n\nCC0 1.0 Universal\n\n CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE\n LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN\n ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS\n INFORMATION ON AN \"AS-IS\" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES\n REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS\n PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM\n THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED\n HEREUNDER.\n\nStatement of Purpose\n\nThe laws of most jurisdictions throughout the world automatically confer\nexclusive Copyright and Related Rights (defined below) upon the creator\nand subsequent owner(s) (each and all, an \"owner\") of an original work of\nauthorship and/or a database (each, a \"Work\").\n\nCertain owners wish to permanently relinquish those rights to a Work for\nthe purpose of contributing to a commons of creative, cultural and\nscientific works (\"Commons\") that the public can reliably and without fear\nof later claims of infringement build upon, modify, incorporate in other\nworks, reuse and redistribute as freely as possible in any form whatsoever\nand for any purposes, including without limitation commercial purposes.\nThese owners may contribute to the Commons to promote the ideal of a free\nculture and the further production of creative, cultural and scientific\nworks, or to gain reputation or greater distribution for their Work in\npart through the use and efforts of others.\n\nFor these and/or other purposes and motivations, and without any\nexpectation of additional consideration or compensation, the person\nassociating CC0 with a Work (the \"Affirmer\"), to the extent that he or she\nis an owner of Copyright and Related Rights in the Work, voluntarily\nelects to apply CC0 to the Work and publicly distribute the Work under its\nterms, with knowledge of his or her Copyright and Related Rights in the\nWork and the meaning and intended legal effect of CC0 on those rights.\n\n1. Copyright and Related Rights. A Work made available under CC0 may be\nprotected by copyright and related or neighboring rights (\"Copyright and\nRelated Rights\"). Copyright and Related Rights include, but are not\nlimited to, the following:\n\n i. the right to reproduce, adapt, distribute, perform, display,\n communicate, and translate a Work;\n ii. moral rights retained by the original author(s) and/or performer(s);\niii. publicity and privacy rights pertaining to a person's image or\n likeness depicted in a Work;\n iv. rights protecting against unfair competition in regards to a Work,\n subject to the limitations in paragraph 4(a), below;\n v. rights protecting the extraction, dissemination, use and reuse of data\n in a Work;\n vi. database rights (such as those arising under Directive 96/9/EC of the\n European Parliament and of the Council of 11 March 1996 on the legal\n protection of databases, and under any national implementation\n thereof, including any amended or successor version of such\n directive); and\nvii. other similar, equivalent or corresponding rights throughout the\n world based on applicable law or treaty, and any national\n implementations thereof.\n\n2. Waiver. To the greatest extent permitted by, but not in contravention\nof, applicable law, Affirmer hereby overtly, fully, permanently,\nirrevocably and unconditionally waives, abandons, and surrenders all of\nAffirmer's Copyright and Related Rights and associated claims and causes\nof action, whether now known or unknown (including existing as well as\nfuture claims and causes of action), in the Work (i) in all territories\nworldwide, (ii) for the maximum duration provided by applicable law or\ntreaty (including future time extensions), (iii) in any current or future\nmedium and for any number of copies, and (iv) for any purpose whatsoever,\nincluding without limitation commercial, advertising or promotional\npurposes (the \"Waiver\"). Affirmer makes the Waiver for the benefit of each\nmember of the public at large and to the detriment of Affirmer's heirs and\nsuccessors, fully intending that such Waiver shall not be subject to\nrevocation, rescission, cancellation, termination, or any other legal or\nequitable action to disrupt the quiet enjoyment of the Work by the public\nas contemplated by Affirmer's express Statement of Purpose.\n\n3. Public License Fallback. Should any part of the Waiver for any reason\nbe judged legally invalid or ineffective under applicable law, then the\nWaiver shall be preserved to the maximum extent permitted taking into\naccount Affirmer's express Statement of Purpose. In addition, to the\nextent the Waiver is so judged Affirmer hereby grants to each affected\nperson a royalty-free, non transferable, non sublicensable, non exclusive,\nirrevocable and unconditional license to exercise Affirmer's Copyright and\nRelated Rights in the Work (i) in all territories worldwide, (ii) for the\nmaximum duration provided by applicable law or treaty (including future\ntime extensions), (iii) in any current or future medium and for any number\nof copies, and (iv) for any purpose whatsoever, including without\nlimitation commercial, advertising or promotional purposes (the\n\"License\"). The License shall be deemed effective as of the date CC0 was\napplied by Affirmer to the Work. Should any part of the License for any\nreason be judged legally invalid or ineffective under applicable law, such\npartial invalidity or ineffectiveness shall not invalidate the remainder\nof the License, and in such case Affirmer hereby affirms that he or she\nwill not (i) exercise any of his or her remaining Copyright and Related\nRights in the Work or (ii) assert any associated claims and causes of\naction with respect to the Work, in either case contrary to Affirmer's\nexpress Statement of Purpose.\n\n4. Limitations and Disclaimers.\n\n a. No trademark or patent rights held by Affirmer are waived, abandoned,\n surrendered, licensed or otherwise affected by this document.\n b. Affirmer offers the Work as-is and makes no representations or\n warranties of any kind concerning the Work, express, implied,\n statutory or otherwise, including without limitation warranties of\n title, merchantability, fitness for a particular purpose, non\n infringement, or the absence of latent or other defects, accuracy, or\n the present or absence of errors, whether or not discoverable, all to\n the greatest extent permissible under applicable law.\n c. Affirmer disclaims responsibility for clearing rights of other persons\n that may apply to the Work or any use thereof, including without\n limitation any person's Copyright and Related Rights in the Work.\n Further, Affirmer disclaims responsibility for obtaining any necessary\n consents, permissions or other rights required for any use of the\n Work.\n d. Affirmer understands and acknowledges that Creative Commons is not a\n party to this document and has no duty or obligation with respect to\n this CC0 or use of the Work.\n" }, + { + "name": "gitlab.com/gitlab-org/api/client-go", + "path": "gitlab.com/gitlab-org/api/client-go/LICENSE", + "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"{}\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" + }, { "name": "go.etcd.io/bbolt", "path": "go.etcd.io/bbolt/LICENSE", @@ -989,6 +1004,11 @@ "path": "go.uber.org/zap/LICENSE", "licenseText": "Copyright (c) 2016-2017 Uber Technologies, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" }, + { + "name": "go.uber.org/zap/exp/zapslog", + "path": "go.uber.org/zap/exp/zapslog/LICENSE", + "licenseText": "Copyright (c) 2016-2024 Uber Technologies, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" + }, { "name": "golang.org/x/crypto", "path": "golang.org/x/crypto/LICENSE", @@ -1032,17 +1052,7 @@ { "name": "golang.org/x/time/rate", "path": "golang.org/x/time/rate/LICENSE", - "licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" - }, - { - "name": "google.golang.org/genproto/googleapis/rpc/status", - "path": "google.golang.org/genproto/googleapis/rpc/status/LICENSE", - "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" - }, - { - "name": "google.golang.org/grpc", - "path": "google.golang.org/grpc/LICENSE", - "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" + "licenseText": "Copyright 2009 The Go Authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google LLC nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, { "name": "google.golang.org/protobuf", diff --git a/build.go b/build.go index 234579b514..d410e171c7 100644 --- a/build.go +++ b/build.go @@ -11,13 +11,4 @@ package main import ( // for embed _ "github.com/shurcooL/vfsgen" - - // for cover merge - _ "golang.org/x/tools/cover" - - // for vet - _ "code.gitea.io/gitea-vet" - - // for swagger - _ "github.com/go-swagger/go-swagger/cmd/swagger" ) diff --git a/build/backport-locales.go b/build/backport-locales.go index 3df83ea6d9..3125f19014 100644 --- a/build/backport-locales.go +++ b/build/backport-locales.go @@ -12,8 +12,8 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/container" + "forgejo.org/modules/setting" ) func main() { diff --git a/build/code-batch-process.go b/build/code-batch-process.go index cc2ab68026..516736b65c 100644 --- a/build/code-batch-process.go +++ b/build/code-batch-process.go @@ -15,7 +15,7 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/build/codeformat" + "forgejo.org/build/codeformat" ) // Windows has a limitation for command line arguments, the size can not exceed 32KB. diff --git a/build/codeformat/formatimports.go b/build/codeformat/formatimports.go index c9fc2a27b4..acedd13234 100644 --- a/build/codeformat/formatimports.go +++ b/build/codeformat/formatimports.go @@ -13,8 +13,8 @@ import ( ) var importPackageGroupOrders = map[string]int{ - "": 1, // internal - "code.gitea.io/gitea/": 2, + "": 1, // internal + "forgejo.org/": 2, } var errInvalidCommentBetweenImports = errors.New("comments between imported packages are invalid, please move comments to the end of the package line") diff --git a/build/codeformat/formatimports_test.go b/build/codeformat/formatimports_test.go index 1abc9f8ab7..03c780911f 100644 --- a/build/codeformat/formatimports_test.go +++ b/build/codeformat/formatimports_test.go @@ -58,8 +58,8 @@ import ( "code.gitea.io/other/package" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" "xorm.io/the/package" @@ -82,8 +82,8 @@ import ( _ "image/jpeg" // for processing jpeg images _ "image/png" // for processing png images - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" "code.gitea.io/other/package" "github.com/issue9/identicon" diff --git a/build/generate-disposable-email.go b/build/generate-disposable-email.go new file mode 100644 index 0000000000..f87df088c5 --- /dev/null +++ b/build/generate-disposable-email.go @@ -0,0 +1,203 @@ +// Copyright 2024 James Hatfield +// SPDX-License-Identifier: MIT + +//go:build ignore + +package main + +import ( + "bufio" + "bytes" + "crypto" + "flag" + "fmt" + "go/format" + "io" + "log" + "net/http" + "os" + "regexp" + "strings" +) + +const disposableEmailListURL string = "https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/%s/disposable_email_blocklist.conf" + +var ( + gitRef *string = flag.String("r", "master", "Git reference of the domain list version") + outPat *string = flag.String("o", "modules/setting/disposable_email_domain_data.go", "Output path") + check *bool = flag.Bool("check", false, "Check if the current output file matches the current upstream list") +) + +func main() { + flag.Parse() + + if *check { + // read in the local copy of the domain list + local, err := get_local_file() + if err != nil { + log.Fatalf("File Read Error: %v", err) + } + + // generate the remote copy of the domain list + remote, err := generate() + if err != nil { + log.Fatalf("Generation Error: %v", err) + } + + // strip the comments from both (so we dont fail simply due to git ref difference) + local = strip_comments(local) + remote = strip_comments(remote) + + // generate the hash of the local copy + local_sha, err := hash(local) + if err != nil { + log.Fatalf("Local Hash Generation Error: %v", err) + } + + // generate the hash of the remote copy + remote_sha, err := hash(remote) + if err != nil { + log.Fatalf("Remote Hash Generation Error: %v", err) + } + + // if the hashes dont match then the local copy needs to be updated + if local_sha != remote_sha { + log.Fatalf("Disposable email domain list needs to be updated!! \"make lint-disposable-emails-fix\"") + } + } else { + // generate the source code (array of domains) + res, err := generate() + if err != nil { + log.Fatalf("Generation Error: %v", err) + } + + // write result to a file + err = os.WriteFile(*outPat, res, 0o644) + if err != nil { + log.Fatalf("File Write Error: %v", err) + } + } +} + +func strip_comments(data []byte) []byte { + result := make([]byte, 0, len(data)) + + re := regexp.MustCompile(`^\W*//.*$`) + + for _, line := range bytes.Split(data, []byte("\n")) { + if !re.Match(line) { + result = append(result, line...) + } + } + + return result +} + +func hash(data []byte) (string, error) { + var err error + + hash := crypto.SHA3_256.New() + + _, err = hash.Write(data) + if err != nil { + return "", err + } + + return fmt.Sprintf("%x", hash.Sum(nil)), err +} + +func get_local_file() ([]byte, error) { + var err error + + f, err := os.Open(*outPat) + if err != nil { + return nil, err + } + defer f.Close() + + data, err := io.ReadAll(f) + if err != nil { + return nil, err + } + + return data, err +} + +func get_remote() ([]string, error) { + var err error + var url string = fmt.Sprintf(disposableEmailListURL, *gitRef) + + // download the domain list + res, err := http.Get(url) + if err != nil { + return nil, err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + // go through all entries (1 domain per line) + scanner := bufio.NewScanner(bytes.NewReader(body)) + + var arrDomains []string + for scanner.Scan() { + line := scanner.Text() + arrDomains = append(arrDomains, line) + } + + return arrDomains, err +} + +func generate() ([]byte, error) { + var err error + var url string = fmt.Sprintf(disposableEmailListURL, *gitRef) + + // download the domains list + arrDomains, err := get_remote() + if err != nil { + return nil, err + } + + // build the string in a readable way + var sb strings.Builder + + _, err = sb.WriteString("[]string{\n") + if err != nil { + return nil, err + } + + for _, item := range arrDomains { + _, err = sb.WriteString(fmt.Sprintf("\t%q,\n", item)) + if err != nil { + return nil, err + } + } + + _, err = sb.WriteString("}") + if err != nil { + return nil, err + } + + // insert the values into file + final := fmt.Sprintf(hdr, url, sb.String()) + + return format.Source([]byte(final)) +} + +const hdr = ` +// Copyright 2024 James Hatfield +// SPDX-License-Identifier: MIT +// +// Code generated by build/generate-disposable-email.go. DO NOT EDIT +// Sourced from %s +package setting + +import "sync" + +var DisposableEmailDomains = sync.OnceValue(func() []string { + return %s +}) +` diff --git a/build/generate-emoji.go b/build/generate-emoji.go index 5a88e456ee..0ad49a6541 100644 --- a/build/generate-emoji.go +++ b/build/generate-emoji.go @@ -20,7 +20,7 @@ import ( "strings" "unicode/utf8" - "code.gitea.io/gitea/modules/json" + "forgejo.org/modules/json" ) const ( @@ -53,8 +53,6 @@ func (e Emoji) MarshalJSON() ([]byte, error) { } func main() { - var err error - flag.Parse() // generate data @@ -83,8 +81,6 @@ var replacer = strings.NewReplacer( var emojiRE = regexp.MustCompile(`\{Emoji:"([^"]*)"`) func generate() ([]byte, error) { - var err error - // load gemoji data res, err := http.Get(gemojiURL) if err != nil { diff --git a/build/generate-gitignores.go b/build/generate-gitignores.go index 1e09c83a6a..7acfd6cbe4 100644 --- a/build/generate-gitignores.go +++ b/build/generate-gitignores.go @@ -15,7 +15,7 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/util" ) func main() { diff --git a/build/generate-go-licenses.go b/build/generate-go-licenses.go index 22ef817ebc..3f4d62a2cc 100644 --- a/build/generate-go-licenses.go +++ b/build/generate-go-licenses.go @@ -16,7 +16,7 @@ import ( "sort" "strings" - "code.gitea.io/gitea/modules/container" + "forgejo.org/modules/container" ) // regexp is based on go-license, excluding README and NOTICE @@ -102,9 +102,9 @@ func main() { pkgName := path.Dir(pkgPath) // There might be a bug somewhere in go-licenses that sometimes interprets the - // root package as "." and sometimes as "code.gitea.io/gitea". Workaround by + // root package as "." and sometimes as "forgejo.org". Workaround by // removing both of them for the sake of stable output. - if pkgName == "." || pkgName == "code.gitea.io/gitea" { + if pkgName == "." || pkgName == "forgejo.org" { continue } diff --git a/build/generate-licenses.go b/build/generate-licenses.go index 9a111bc811..e925d8af02 100644 --- a/build/generate-licenses.go +++ b/build/generate-licenses.go @@ -15,7 +15,7 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/util" ) func main() { diff --git a/build/gocovmerge.go b/build/gocovmerge.go deleted file mode 100644 index c6f74ed85c..0000000000 --- a/build/gocovmerge.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// Copyright (c) 2015, Wade Simmons -// SPDX-License-Identifier: MIT - -// gocovmerge takes the results from multiple `go test -coverprofile` runs and -// merges them into one profile - -//go:build ignore - -package main - -import ( - "flag" - "fmt" - "io" - "log" - "os" - "sort" - - "golang.org/x/tools/cover" -) - -func mergeProfiles(p, merge *cover.Profile) { - if p.Mode != merge.Mode { - log.Fatalf("cannot merge profiles with different modes") - } - // Since the blocks are sorted, we can keep track of where the last block - // was inserted and only look at the blocks after that as targets for merge - startIndex := 0 - for _, b := range merge.Blocks { - startIndex = mergeProfileBlock(p, b, startIndex) - } -} - -func mergeProfileBlock(p *cover.Profile, pb cover.ProfileBlock, startIndex int) int { - sortFunc := func(i int) bool { - pi := p.Blocks[i+startIndex] - return pi.StartLine >= pb.StartLine && (pi.StartLine != pb.StartLine || pi.StartCol >= pb.StartCol) - } - - i := 0 - if sortFunc(i) != true { - i = sort.Search(len(p.Blocks)-startIndex, sortFunc) - } - i += startIndex - if i < len(p.Blocks) && p.Blocks[i].StartLine == pb.StartLine && p.Blocks[i].StartCol == pb.StartCol { - if p.Blocks[i].EndLine != pb.EndLine || p.Blocks[i].EndCol != pb.EndCol { - log.Fatalf("OVERLAP MERGE: %v %v %v", p.FileName, p.Blocks[i], pb) - } - switch p.Mode { - case "set": - p.Blocks[i].Count |= pb.Count - case "count", "atomic": - p.Blocks[i].Count += pb.Count - default: - log.Fatalf("unsupported covermode: '%s'", p.Mode) - } - } else { - if i > 0 { - pa := p.Blocks[i-1] - if pa.EndLine >= pb.EndLine && (pa.EndLine != pb.EndLine || pa.EndCol > pb.EndCol) { - log.Fatalf("OVERLAP BEFORE: %v %v %v", p.FileName, pa, pb) - } - } - if i < len(p.Blocks)-1 { - pa := p.Blocks[i+1] - if pa.StartLine <= pb.StartLine && (pa.StartLine != pb.StartLine || pa.StartCol < pb.StartCol) { - log.Fatalf("OVERLAP AFTER: %v %v %v", p.FileName, pa, pb) - } - } - p.Blocks = append(p.Blocks, cover.ProfileBlock{}) - copy(p.Blocks[i+1:], p.Blocks[i:]) - p.Blocks[i] = pb - } - return i + 1 -} - -func addProfile(profiles []*cover.Profile, p *cover.Profile) []*cover.Profile { - i := sort.Search(len(profiles), func(i int) bool { return profiles[i].FileName >= p.FileName }) - if i < len(profiles) && profiles[i].FileName == p.FileName { - mergeProfiles(profiles[i], p) - } else { - profiles = append(profiles, nil) - copy(profiles[i+1:], profiles[i:]) - profiles[i] = p - } - return profiles -} - -func dumpProfiles(profiles []*cover.Profile, out io.Writer) { - if len(profiles) == 0 { - return - } - fmt.Fprintf(out, "mode: %s\n", profiles[0].Mode) - for _, p := range profiles { - for _, b := range p.Blocks { - fmt.Fprintf(out, "%s:%d.%d,%d.%d %d %d\n", p.FileName, b.StartLine, b.StartCol, b.EndLine, b.EndCol, b.NumStmt, b.Count) - } - } -} - -func main() { - flag.Parse() - - var merged []*cover.Profile - - for _, file := range flag.Args() { - profiles, err := cover.ParseProfiles(file) - if err != nil { - log.Fatalf("failed to parse profile '%s': %v", file, err) - } - for _, p := range profiles { - merged = addProfile(merged, p) - } - } - - dumpProfiles(merged, os.Stdout) -} diff --git a/build/lint-locale-usage/lint-locale-usage.go b/build/lint-locale-usage/lint-locale-usage.go new file mode 100644 index 0000000000..88375c1c36 --- /dev/null +++ b/build/lint-locale-usage/lint-locale-usage.go @@ -0,0 +1,383 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package main + +import ( + "fmt" + "go/ast" + goParser "go/parser" + "go/token" + "io/fs" + "os" + "path/filepath" + "strconv" + "strings" + "text/template" + tmplParser "text/template/parse" + + "forgejo.org/modules/container" + fjTemplates "forgejo.org/modules/templates" + "forgejo.org/modules/translation/localeiter" + "forgejo.org/modules/util" +) + +// this works by first gathering all valid source string IDs from `en-US` reference files +// and then checking if all used source strings are actually defined + +type LocatedError struct { + Location string + Kind string + Err error +} + +func (e LocatedError) Error() string { + var sb strings.Builder + + sb.WriteString(e.Location) + sb.WriteString(":\t") + if e.Kind != "" { + sb.WriteString(e.Kind) + sb.WriteString(": ") + } + sb.WriteString("ERROR: ") + sb.WriteString(e.Err.Error()) + + return sb.String() +} + +func InitLocaleTrFunctions() map[string][]uint { + ret := make(map[string][]uint) + + f0 := []uint{0} + ret["Tr"] = f0 + ret["TrString"] = f0 + ret["TrHTML"] = f0 + + ret["TrPluralString"] = []uint{1} + ret["TrN"] = []uint{1, 2} + + return ret +} + +type Handler struct { + OnMsgid func(fset *token.FileSet, pos token.Pos, msgid string) + OnUnexpectedInvoke func(fset *token.FileSet, pos token.Pos, funcname string, argc int) + LocaleTrFunctions map[string][]uint +} + +// the `Handle*File` functions follow the following calling convention: +// * `fname` is the name of the input file +// * `src` is either `nil` (then the function invokes `ReadFile` to read the file) +// or the contents of the file as {`[]byte`, or a `string`} + +func (handler Handler) HandleGoFile(fname string, src any) error { + fset := token.NewFileSet() + node, err := goParser.ParseFile(fset, fname, src, goParser.SkipObjectResolution) + if err != nil { + return LocatedError{ + Location: fname, + Kind: "Go parser", + Err: err, + } + } + + ast.Inspect(node, func(n ast.Node) bool { + // search for function calls of the form `anything.Tr(any-string-lit, ...)` + + call, ok := n.(*ast.CallExpr) + if !ok || len(call.Args) < 1 { + return true + } + + funSel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + + ltf, ok := handler.LocaleTrFunctions[funSel.Sel.Name] + if !ok { + return true + } + + var gotUnexpectedInvoke *int + + for _, argNum := range ltf { + if len(call.Args) >= int(argNum+1) { + argLit, ok := call.Args[int(argNum)].(*ast.BasicLit) + if !ok || argLit.Kind != token.STRING { + continue + } + + // extract string content + arg, err := strconv.Unquote(argLit.Value) + if err == nil { + // found interesting strings + handler.OnMsgid(fset, argLit.ValuePos, arg) + } + } else { + argc := len(call.Args) + gotUnexpectedInvoke = &argc + } + } + + if gotUnexpectedInvoke != nil { + handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke) + } + + return true + }) + + return nil +} + +// derived from source: modules/templates/scopedtmpl/scopedtmpl.go, L169-L213 +func (handler Handler) handleTemplateNode(fset *token.FileSet, node tmplParser.Node) { + switch node.Type() { + case tmplParser.NodeAction: + handler.handleTemplatePipeNode(fset, node.(*tmplParser.ActionNode).Pipe) + case tmplParser.NodeList: + nodeList := node.(*tmplParser.ListNode) + handler.handleTemplateFileNodes(fset, nodeList.Nodes) + case tmplParser.NodePipe: + handler.handleTemplatePipeNode(fset, node.(*tmplParser.PipeNode)) + case tmplParser.NodeTemplate: + handler.handleTemplatePipeNode(fset, node.(*tmplParser.TemplateNode).Pipe) + case tmplParser.NodeIf: + nodeIf := node.(*tmplParser.IfNode) + handler.handleTemplateBranchNode(fset, nodeIf.BranchNode) + case tmplParser.NodeRange: + nodeRange := node.(*tmplParser.RangeNode) + handler.handleTemplateBranchNode(fset, nodeRange.BranchNode) + case tmplParser.NodeWith: + nodeWith := node.(*tmplParser.WithNode) + handler.handleTemplateBranchNode(fset, nodeWith.BranchNode) + + case tmplParser.NodeCommand: + nodeCommand := node.(*tmplParser.CommandNode) + + handler.handleTemplateFileNodes(fset, nodeCommand.Args) + + if len(nodeCommand.Args) < 2 { + return + } + + nodeChain, ok := nodeCommand.Args[0].(*tmplParser.ChainNode) + if !ok { + return + } + + nodeIdent, ok := nodeChain.Node.(*tmplParser.IdentifierNode) + if !ok || nodeIdent.Ident != "ctx" || len(nodeChain.Field) != 2 || nodeChain.Field[0] != "Locale" { + return + } + + ltf, ok := handler.LocaleTrFunctions[nodeChain.Field[1]] + if !ok { + return + } + + var gotUnexpectedInvoke *int + + for _, argNum := range ltf { + if len(nodeCommand.Args) >= int(argNum+2) { + nodeString, ok := nodeCommand.Args[int(argNum+1)].(*tmplParser.StringNode) + if ok { + // found interesting strings + // the column numbers are a bit "off", but much better than nothing + handler.OnMsgid(fset, token.Pos(nodeString.Pos), nodeString.Text) + } + } else { + argc := len(nodeCommand.Args) - 1 + gotUnexpectedInvoke = &argc + } + } + + if gotUnexpectedInvoke != nil { + handler.OnUnexpectedInvoke(fset, token.Pos(nodeChain.Pos), nodeChain.Field[1], *gotUnexpectedInvoke) + } + + default: + } +} + +func (handler Handler) handleTemplatePipeNode(fset *token.FileSet, pipeNode *tmplParser.PipeNode) { + if pipeNode == nil { + return + } + + // NOTE: we can't pass `pipeNode.Cmds` to handleTemplateFileNodes due to incompatible argument types + for _, node := range pipeNode.Cmds { + handler.handleTemplateNode(fset, node) + } +} + +func (handler Handler) handleTemplateBranchNode(fset *token.FileSet, branchNode tmplParser.BranchNode) { + handler.handleTemplatePipeNode(fset, branchNode.Pipe) + handler.handleTemplateFileNodes(fset, branchNode.List.Nodes) + if branchNode.ElseList != nil { + handler.handleTemplateFileNodes(fset, branchNode.ElseList.Nodes) + } +} + +func (handler Handler) handleTemplateFileNodes(fset *token.FileSet, nodes []tmplParser.Node) { + for _, node := range nodes { + handler.handleTemplateNode(fset, node) + } +} + +func (handler Handler) HandleTemplateFile(fname string, src any) error { + var tmplContent []byte + switch src2 := src.(type) { + case nil: + var err error + tmplContent, err = os.ReadFile(fname) + if err != nil { + return LocatedError{ + Location: fname, + Kind: "ReadFile", + Err: err, + } + } + case []byte: + tmplContent = src2 + case string: + // SAFETY: we do not modify tmplContent below + tmplContent = util.UnsafeStringToBytes(src2) + default: + panic("invalid type for 'src'") + } + + fset := token.NewFileSet() + fset.AddFile(fname, 1, len(tmplContent)).SetLinesForContent(tmplContent) + // SAFETY: we do not modify tmplContent2 below + tmplContent2 := util.UnsafeBytesToString(tmplContent) + + tmpl := template.New(fname) + tmpl.Funcs(fjTemplates.NewFuncMap()) + tmplParsed, err := tmpl.Parse(tmplContent2) + if err != nil { + return LocatedError{ + Location: fname, + Kind: "Template parser", + Err: err, + } + } + handler.handleTemplateFileNodes(fset, tmplParsed.Root.Nodes) + return nil +} + +// This command assumes that we get started from the project root directory +// +// Possible command line flags: +// +// --allow-missing-msgids don't return an error code if missing message IDs are found +// +// EXIT CODES: +// +// 0 success, no issues found +// 1 unable to walk directory tree +// 2 unable to parse locale ini/json files +// 3 unable to parse go or text/template files +// 4 found missing message IDs +// +//nolint:forbidigo +func main() { + allowMissingMsgids := false + for _, arg := range os.Args[1:] { + if arg == "--allow-missing-msgids" { + allowMissingMsgids = true + } + } + + onError := func(err error) { + if err == nil { + return + } + fmt.Println(err.Error()) + os.Exit(3) + } + + msgids := make(container.Set[string]) + + localeFile := filepath.Join(filepath.Join("options", "locale"), "locale_en-US.ini") + localeContent, err := os.ReadFile(localeFile) + if err != nil { + fmt.Printf("%s:\tERROR: %s\n", localeFile, err.Error()) + os.Exit(2) + } + + if err = localeiter.IterateMessagesContent(localeContent, func(trKey, trValue string) error { + msgids[trKey] = struct{}{} + return nil + }); err != nil { + fmt.Printf("%s:\tERROR: %s\n", localeFile, err.Error()) + os.Exit(2) + } + + localeFile = filepath.Join(filepath.Join("options", "locale_next"), "locale_en-US.json") + localeContent, err = os.ReadFile(localeFile) + if err != nil { + fmt.Printf("%s:\tERROR: %s\n", localeFile, err.Error()) + os.Exit(2) + } + + if err := localeiter.IterateMessagesNextContent(localeContent, func(trKey, pluralForm, trValue string) error { + // ignore plural form + msgids[trKey] = struct{}{} + return nil + }); err != nil { + fmt.Printf("%s:\tERROR: %s\n", localeFile, err.Error()) + os.Exit(2) + } + + gotAnyMsgidError := false + + handler := Handler{ + OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string) { + if !msgids.Contains(msgid) { + gotAnyMsgidError = true + fmt.Printf("%s:\tmissing msgid: %s\n", fset.Position(pos).String(), msgid) + } + }, + OnUnexpectedInvoke: func(fset *token.FileSet, pos token.Pos, funcname string, argc int) { + gotAnyMsgidError = true + fmt.Printf("%s:\tunexpected invocation of %s with %d arguments\n", fset.Position(pos).String(), funcname, argc) + }, + LocaleTrFunctions: InitLocaleTrFunctions(), + } + + if err := filepath.WalkDir(".", func(fpath string, d fs.DirEntry, err error) error { + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + name := d.Name() + if d.IsDir() { + if name == "docker" || name == ".git" || name == "node_modules" { + return fs.SkipDir + } + } else if name == "bindata.go" || fpath == "modules/translation/i18n/i18n_test.go" { + // skip false positives + } else if strings.HasSuffix(name, ".go") { + onError(handler.HandleGoFile(fpath, nil)) + } else if strings.HasSuffix(name, ".tmpl") { + if strings.HasPrefix(fpath, "tests") && strings.HasSuffix(name, ".ini.tmpl") { + // skip false positives + } else { + onError(handler.HandleTemplateFile(fpath, nil)) + } + } + return nil + }); err != nil { + fmt.Printf("walkdir ERROR: %s\n", err.Error()) + os.Exit(1) + } + + if !allowMissingMsgids && gotAnyMsgidError { + os.Exit(4) + } +} diff --git a/build/lint-locale-usage/lint-locale-usage_test.go b/build/lint-locale-usage/lint-locale-usage_test.go new file mode 100644 index 0000000000..81ca12c6db --- /dev/null +++ b/build/lint-locale-usage/lint-locale-usage_test.go @@ -0,0 +1,50 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package main + +import ( + "go/token" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func buildHandler(ret *[]string) Handler { + return Handler{ + OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string) { + *ret = append(*ret, msgid) + }, + OnUnexpectedInvoke: func(fset *token.FileSet, pos token.Pos, funcname string, argc int) {}, + LocaleTrFunctions: InitLocaleTrFunctions(), + } +} + +func HandleGoFileWrapped(t *testing.T, fname, src string) []string { + var ret []string + handler := buildHandler(&ret) + require.NoError(t, handler.HandleGoFile(fname, src)) + return ret +} + +func HandleTemplateFileWrapped(t *testing.T, fname, src string) []string { + var ret []string + handler := buildHandler(&ret) + require.NoError(t, handler.HandleTemplateFile(fname, src)) + return ret +} + +func TestUsagesParser(t *testing.T) { + t.Run("go, simple", func(t *testing.T) { + assert.Equal(t, + []string{"what.an.example"}, + HandleGoFileWrapped(t, "", "package main\nfunc Render(ctx *context.Context) string { return ctx.Tr(\"what.an.example\"); }\n")) + }) + + t.Run("template, simple", func(t *testing.T) { + assert.Equal(t, + []string{"what.an.example"}, + HandleTemplateFileWrapped(t, "", "{{ ctx.Locale.Tr \"what.an.example\" }}\n")) + }) +} diff --git a/build/lint-locale/lint-locale.go b/build/lint-locale/lint-locale.go new file mode 100644 index 0000000000..dc4088c73c --- /dev/null +++ b/build/lint-locale/lint-locale.go @@ -0,0 +1,195 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +//nolint:forbidigo +package main + +import ( + "fmt" + "html" + "io/fs" + "os" + "path/filepath" + "regexp" + "slices" + "strings" + + "forgejo.org/modules/translation/localeiter" + + "github.com/microcosm-cc/bluemonday" + "github.com/sergi/go-diff/diffmatchpatch" +) + +var ( + policy *bluemonday.Policy + tagRemover *strings.Replacer + safeURL = "https://TO-BE-REPLACED.COM" + + // Matches href="", href="#", href="%s", href="#%s", href="%[1]s" and href="#%[1]s". + placeHolderRegex = regexp.MustCompile(`href="#?(%s|%\[\d\]s)?"`) + + dmp = diffmatchpatch.New() +) + +func initBlueMondayPolicy() { + policy = bluemonday.NewPolicy() + + policy.RequireParseableURLs(true) + policy.AllowURLSchemes("https") + + // Only allow safe URL on href. + // Only allow target="_blank". + // Only allow rel="nopener noreferrer", rel="noopener" and rel="noreferrer". + // Only allow placeholder on id and class. + policy.AllowAttrs("href").Matching(regexp.MustCompile("^" + regexp.QuoteMeta(safeURL) + "$")).OnElements("a") + policy.AllowAttrs("target").Matching(regexp.MustCompile("^_blank$")).OnElements("a") + policy.AllowAttrs("rel").Matching(regexp.MustCompile("^(noopener|noreferrer|noopener noreferrer)$")).OnElements("a") + policy.AllowAttrs("id", "class").Matching(regexp.MustCompile(`^%s|%\[\d\]s$`)).OnElements("a") + + // Only allow positional placeholder as class. + positionalPlaceholderRe := regexp.MustCompile(`^%\[\d\]s$`) + policy.AllowAttrs("class").Matching(positionalPlaceholderRe).OnElements("strong") + policy.AllowAttrs("id").Matching(positionalPlaceholderRe).OnElements("code") + + // Allowed elements with no attributes. Must be a recognized tagname. + policy.AllowElements("strong", "br", "b", "strike", "code", "i", "kbd") + + // TODO: Remove in `actions.workflow.dispatch.trigger_found`. + policy.AllowNoAttrs().OnElements("c") +} + +func initRemoveTags() { + oldnew := []string{} + for _, el := range []string{ + "email@example.com", "correu@example.com", "epasts@domens.lv", "email@exemplo.com", "eposta@ornek.com", "email@példa.hu", "email@esempio.it", + "user", "utente", "lietotÄjs", "gebruiker", "usuário", "Benutzer", "Bruker", "bruger", "użytkownik", + "server", "servidor", "kiszolgáló", "serveris", + "label", "etichetta", "etiÄ·ete", "rótulo", "Label", "utilizador", "etiket", "iezÄ«me", "etykieta", + } { + oldnew = append(oldnew, "<"+el+">", "REPLACED-TAG") + } + + tagRemover = strings.NewReplacer(oldnew...) +} + +func preprocessTranslationValue(value string) string { + // href should be a parsable URL, replace placeholder strings with a safe url. + value = placeHolderRegex.ReplaceAllString(value, `href="`+safeURL+`"`) + + // Remove tags that aren't tags but will be parsed as tags. We already know they are safe and sound. + value = tagRemover.Replace(value) + + return value +} + +func checkValue(trKey, value string) []string { + keyValue := preprocessTranslationValue(value) + + if html.UnescapeString(policy.Sanitize(keyValue)) == keyValue { + return nil + } + + // Create a nice diff of the difference. + diffs := dmp.DiffMain(keyValue, html.UnescapeString(policy.Sanitize(keyValue)), false) + diffs = dmp.DiffCleanupSemantic(diffs) + diffs = dmp.DiffCleanupEfficiency(diffs) + + return []string{trKey + ": " + dmp.DiffPrettyText(diffs)} +} + +func checkLocaleContent(localeContent []byte) []string { + errors := []string{} + + if err := localeiter.IterateMessagesContent(localeContent, func(trKey, trValue string) error { + errors = append(errors, checkValue(trKey, trValue)...) + return nil + }); err != nil { + panic(err) + } + + return errors +} + +func checkLocaleNextContent(localeContent []byte) []string { + errors := []string{} + + if err := localeiter.IterateMessagesNextContent(localeContent, func(trKey, pluralForm, trValue string) error { + fullKey := trKey + if pluralForm != "" { + fullKey = trKey + "." + pluralForm + } + errors = append(errors, checkValue(fullKey, trValue)...) + return nil + }); err != nil { + panic(err) + } + + return errors +} + +func main() { + initBlueMondayPolicy() + initRemoveTags() + + localeDir := filepath.Join("options", "locale") + localeFiles, err := os.ReadDir(localeDir) + if err != nil { + panic(err) + } + + // Safety check that we are not reading the wrong directory. + if !slices.ContainsFunc(localeFiles, func(e fs.DirEntry) bool { return strings.HasSuffix(e.Name(), ".ini") }) { + fmt.Println("No locale files found") + os.Exit(1) + } + + exitCode := 0 + for _, localeFile := range localeFiles { + if !strings.HasSuffix(localeFile.Name(), ".ini") { + continue + } + + localeContent, err := os.ReadFile(filepath.Join(localeDir, localeFile.Name())) + if err != nil { + fmt.Println(localeFile.Name()) + panic(err) + } + + if err := checkLocaleContent(localeContent); len(err) > 0 { + fmt.Println(localeFile.Name()) + fmt.Println(strings.Join(err, "\n")) + fmt.Println() + exitCode = 1 + } + } + + // Check the locale next. + localeDir = filepath.Join("options", "locale_next") + localeFiles, err = os.ReadDir(localeDir) + if err != nil { + panic(err) + } + + // Safety check that we are not reading the wrong directory. + if !slices.ContainsFunc(localeFiles, func(e fs.DirEntry) bool { return strings.HasSuffix(e.Name(), ".json") }) { + fmt.Println("No locale_next files found") + os.Exit(1) + } + + for _, localeFile := range localeFiles { + localeContent, err := os.ReadFile(filepath.Join(localeDir, localeFile.Name())) + if err != nil { + fmt.Println(localeFile.Name()) + panic(err) + } + + if err := checkLocaleNextContent(localeContent); len(err) > 0 { + fmt.Println(localeFile.Name()) + fmt.Println(strings.Join(err, "\n")) + fmt.Println() + exitCode = 1 + } + } + + os.Exit(exitCode) +} diff --git a/build/lint-locale/lint-locale_test.go b/build/lint-locale/lint-locale_test.go new file mode 100644 index 0000000000..9e9a931feb --- /dev/null +++ b/build/lint-locale/lint-locale_test.go @@ -0,0 +1,106 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLocalizationPolicy(t *testing.T) { + initBlueMondayPolicy() + initRemoveTags() + + t.Run("Remove tags", func(t *testing.T) { + assert.Empty(t, checkLocaleContent([]byte(`hidden_comment_types_description = Comment types checked here will not be shown inside issue pages. Checking "Label" for example removes all " added/removed
`+ @@ -754,7 +755,7 @@ func TestRender_FilePreview(t *testing.T) { `gogits/gogs – `+ `path/to/file.go`+ ``+ - ``+ + ``+ `Lines 2 to 3 in gogits/gogs@190d949`+ ``+ ``+ @@ -790,7 +791,7 @@ func TestRender_FilePreview(t *testing.T) { `gogits/gogs – `+ `single-line.txt`+ ``+ - ``+ + ``+ `Line 1 in gogits/gogs@4c1aaf5`+ ``+ ``+ @@ -833,7 +834,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -864,7 +865,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -897,7 +898,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -922,7 +923,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -953,7 +954,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -978,7 +979,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -1003,7 +1004,7 @@ func TestRender_FilePreview(t *testing.T) { ``+ - ``+ + ``+ `Lines 2 to 3 in 190d949`+ ``+ ``+ @@ -1026,4 +1027,138 @@ func TestRender_FilePreview(t *testing.T) { localMetas, ) }) + + commitFileURL := util.URLJoin(markup.TestRepoURL, "src", "commit", "c9913120ed2c1e27c1d7752ecdb7a504dc7cf6be", "path", "to", "file.md") + + t.Run("rendered file with ?display=source", func(t *testing.T) { + testRender( + commitFileURL+"?display=source"+"#L1-L2", + `

`+ + `
`+ + `
`+ + `
`+ + `path/to/file.md`+ + `
`+ + ``+ + `Lines 1 to 2 in c991312`+ + ``+ + `
`+ + `
`+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + `
# A`+"\n"+`
B`+"\n"+`
`+ + `
`+ + `
`+ + `

`, + localMetas, + ) + }) + + t.Run("rendered file without ?display=source", func(t *testing.T) { + testRender( + commitFileURL+"#L1-L2", + `

`+ + `
`+ + `
`+ + `
`+ + `path/to/file.md`+ + `
`+ + ``+ + `Lines 1 to 2 in c991312`+ + ``+ + `
`+ + `
`+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + `
# A`+"\n"+`
B`+"\n"+`
`+ + `
`+ + `
`+ + `

`, + localMetas, + ) + }) + + commitFileURL = util.URLJoin(markup.TestRepoURL, "src", "commit", "190d9492934af498c3f669d6a2431dc5459e5b20", "path", "to", "file.go") + + t.Run("normal file with ?display=source", func(t *testing.T) { + testRender( + commitFileURL+"?display=source"+"#L2-L3", + `

`+ + `
`+ + `
`+ + `
`+ + `path/to/file.go`+ + `
`+ + ``+ + `Lines 2 to 3 in 190d949`+ + ``+ + `
`+ + `
`+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + `
B`+"\n"+`
C`+"\n"+`
`+ + `
`+ + `
`+ + `

`, + localMetas, + ) + }) + + commitFileURL = util.URLJoin(markup.TestRepoURL, "src", "commit", "eeb243c3395e1921c5d90e73bd739827251fc99d", "path", "to", "file%20%23.txt") + + t.Run("file with strange characters in name", func(t *testing.T) { + testRender( + commitFileURL+"#L1", + `

`+ + `
`+ + `
`+ + `
`+ + `path/to/file #.txt`+ + `
`+ + ``+ + `Line 1 in eeb243c`+ + ``+ + `
`+ + `
`+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + `
A`+"\n"+`
`+ + `
`+ + `
`+ + `

`, + localMetas, + ) + }) } diff --git a/modules/markup/markdown/ast.go b/modules/markup/markdown/ast.go index 7f0ac6a92c..c2fbbe6692 100644 --- a/modules/markup/markdown/ast.go +++ b/modules/markup/markdown/ast.go @@ -34,13 +34,6 @@ func NewDetails() *Details { } } -// IsDetails returns true if the given node implements the Details interface, -// otherwise false. -func IsDetails(node ast.Node) bool { - _, ok := node.(*Details) - return ok -} - // Summary is a block that contains the summary of details block type Summary struct { ast.BaseBlock @@ -66,13 +59,6 @@ func NewSummary() *Summary { } } -// IsSummary returns true if the given node implements the Summary interface, -// otherwise false. -func IsSummary(node ast.Node) bool { - _, ok := node.(*Summary) - return ok -} - // TaskCheckBoxListItem is a block that represents a list item of a markdown block with a checkbox type TaskCheckBoxListItem struct { *ast.ListItem @@ -103,13 +89,6 @@ func NewTaskCheckBoxListItem(listItem *ast.ListItem) *TaskCheckBoxListItem { } } -// IsTaskCheckBoxListItem returns true if the given node implements the TaskCheckBoxListItem interface, -// otherwise false. -func IsTaskCheckBoxListItem(node ast.Node) bool { - _, ok := node.(*TaskCheckBoxListItem) - return ok -} - // Icon is an inline for a fomantic icon type Icon struct { ast.BaseInline @@ -139,13 +118,6 @@ func NewIcon(name string) *Icon { } } -// IsIcon returns true if the given node implements the Icon interface, -// otherwise false. -func IsIcon(node ast.Node) bool { - _, ok := node.(*Icon) - return ok -} - // ColorPreview is an inline for a color preview type ColorPreview struct { ast.BaseInline diff --git a/modules/markup/markdown/callout/github.go b/modules/markup/markdown/callout/github.go index 9b8b611d18..49ad249696 100644 --- a/modules/markup/markdown/callout/github.go +++ b/modules/markup/markdown/callout/github.go @@ -7,7 +7,7 @@ package callout import ( "strings" - "code.gitea.io/gitea/modules/svg" + "forgejo.org/modules/svg" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/parser" @@ -50,7 +50,7 @@ func (g *GitHubCalloutTransformer) Transform(node *ast.Document, reader text.Rea return ast.WalkContinue, nil } firstTextNode, ok := firstParagraph.FirstChild().(*ast.Text) - if !ok || string(firstTextNode.Text(reader.Source())) != "[" { + if !ok || string(firstTextNode.Value(reader.Source())) != "[" { return ast.WalkContinue, nil } secondTextNode, ok := firstTextNode.NextSibling().(*ast.Text) @@ -59,14 +59,14 @@ func (g *GitHubCalloutTransformer) Transform(node *ast.Document, reader text.Rea } // If the second node's text isn't one of the supported attention // types, continue walking. - secondTextNodeText := secondTextNode.Text(reader.Source()) + secondTextNodeText := secondTextNode.Value(reader.Source()) attentionType := strings.ToLower(strings.TrimPrefix(string(secondTextNodeText), "!")) if _, has := supportedAttentionTypes[attentionType]; !has { return ast.WalkContinue, nil } thirdTextNode, ok := secondTextNode.NextSibling().(*ast.Text) - if !ok || string(thirdTextNode.Text(reader.Source())) != "]" { + if !ok || string(thirdTextNode.Value(reader.Source())) != "]" { return ast.WalkContinue, nil } diff --git a/modules/markup/markdown/callout/github_legacy.go b/modules/markup/markdown/callout/github_legacy.go index 32a278bc8d..e77da73dd9 100644 --- a/modules/markup/markdown/callout/github_legacy.go +++ b/modules/markup/markdown/callout/github_legacy.go @@ -7,6 +7,8 @@ package callout import ( "strings" + "forgejo.org/modules/markup/markdown/util" + "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/text" @@ -40,7 +42,7 @@ func (g *GitHubLegacyCalloutTransformer) Transform(node *ast.Document, reader te if !ok { return ast.WalkContinue, nil } - calloutText := string(calloutNode.Text(reader.Source())) + calloutText := string(util.Text(calloutNode, reader.Source())) calloutType := strings.ToLower(calloutText) // We only support "Note" and "Warning" callouts in legacy mode, // match only those. @@ -63,6 +65,14 @@ func (g *GitHubLegacyCalloutTransformer) Transform(node *ast.Document, reader te attentionParagraph.AppendChild(attentionParagraph, calloutNode) firstParagraph.Parent().InsertBefore(firstParagraph.Parent(), firstParagraph, attentionParagraph) firstParagraph.RemoveChild(firstParagraph, calloutNode) + + // Remove softbreak line if there's one. + if firstParagraph.ChildCount() >= 1 { + softBreakNode, ok := firstParagraph.FirstChild().(*ast.Text) + if ok && softBreakNode.SoftLineBreak() { + firstParagraph.RemoveChild(firstParagraph, softBreakNode) + } + } } return ast.WalkContinue, nil diff --git a/modules/markup/markdown/color_util.go b/modules/markup/markdown/color_util.go index 355fef3fc0..efbde6b730 100644 --- a/modules/markup/markdown/color_util.go +++ b/modules/markup/markdown/color_util.go @@ -6,7 +6,7 @@ package markdown import "regexp" var ( - hexRGB = regexp.MustCompile(`^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$`) + hexRGB = regexp.MustCompile(`^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$`) hsl = regexp.MustCompile(`^hsl\([ ]*([012]?[0-9]{1,2}|3[0-5][0-9]|360),[ ]*([0-9]{0,2}|100)\%,[ ]*([0-9]{0,2}|100)\%\)$`) hsla = regexp.MustCompile(`^hsla\(([ ]*[012]?[0-9]{1,2}|3[0-5][0-9]|360),[ ]*([0-9]{0,2}|100)\%,[ ]*([0-9]{0,2}|100)\%,[ ]*(1|1\.0|0|(0\.[0-9]+))\)$`) rgb = regexp.MustCompile(`^rgb\(([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))),){2}([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))))\)$`) diff --git a/modules/markup/markdown/color_util_test.go b/modules/markup/markdown/color_util_test.go index c6e0555a35..9f6448cf8c 100644 --- a/modules/markup/markdown/color_util_test.go +++ b/modules/markup/markdown/color_util_test.go @@ -17,6 +17,7 @@ func TestMatchColor(t *testing.T) { {"#ddeeffa0", true}, {"#ddeefe", true}, {"#abcdef", true}, + {"#fffa", true}, {"#abcdeg", false}, {"#abcdefg0", false}, {"black", false}, diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index 0290e1312d..d229afa8e3 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -8,8 +8,8 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/markup" + "forgejo.org/modules/setting" "github.com/yuin/goldmark/ast" east "github.com/yuin/goldmark/extension/ast" @@ -131,7 +131,7 @@ func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast. if entering { _, err = w.WriteString("') @@ -203,8 +203,7 @@ func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node return ast.WalkContinue, nil } - var err error - _, err = w.WriteString(fmt.Sprintf(``, name)) + _, err := fmt.Fprintf(w, ``, name) if err != nil { return ast.WalkStop, err } diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index d249d25014..717da464b9 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -5,19 +5,19 @@ package markdown import ( - "fmt" + "errors" "html/template" "io" "strings" "sync" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/markup/common" - "code.gitea.io/gitea/modules/markup/markdown/callout" - "code.gitea.io/gitea/modules/markup/markdown/math" - "code.gitea.io/gitea/modules/setting" - giteautil "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/log" + "forgejo.org/modules/markup" + "forgejo.org/modules/markup/common" + "forgejo.org/modules/markup/markdown/callout" + "forgejo.org/modules/markup/markdown/math" + "forgejo.org/modules/setting" + giteautil "forgejo.org/modules/util" chromahtml "github.com/alecthomas/chroma/v2/formatters/html" "github.com/yuin/goldmark" @@ -54,7 +54,7 @@ func (l *limitWriter) Write(data []byte) (int, error) { if err != nil { return n, err } - return n, fmt.Errorf("rendered content too large - truncating render") + return n, errors.New("rendered content too large - truncating render") } n, err := l.w.Write(data) l.sum += int64(n) diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index e3dc6c9655..e229ee4c65 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -10,14 +10,14 @@ import ( "strings" "testing" - "code.gitea.io/gitea/models/unittest" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/markup/markdown" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/test" - "code.gitea.io/gitea/modules/util" + "forgejo.org/models/unittest" + "forgejo.org/modules/git" + "forgejo.org/modules/log" + "forgejo.org/modules/markup" + "forgejo.org/modules/markup/markdown" + "forgejo.org/modules/setting" + "forgejo.org/modules/test" + "forgejo.org/modules/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -108,7 +108,7 @@ func TestRender_Images(t *testing.T) { test( "[["+title+"|"+url+"]]", - `

`+title+`

`) + `

`) test( "[!["+title+"]("+url+")]("+href+")", `

`+title+`

`) @@ -119,7 +119,7 @@ func TestRender_Images(t *testing.T) { test( "[["+title+"|"+url+"]]", - `

`+title+`

`) + `

`) test( "[!["+title+"]("+url+")]("+href+")", `

`+title+`

`) @@ -135,8 +135,8 @@ func testAnswers(baseURLContent, baseURLImages string) []string {

See commit 65f1bf27bc

Ideas and codes

    -
  • Bezier widget (by @r-lyeh) ocornut/imgui#786
  • -
  • Bezier widget (by @r-lyeh) #786
  • +
  • Bezier widget (by @r-lyeh) ocornut/imgui#786
  • +
  • Bezier widget (by @r-lyeh) #786
  • Node graph editors https://github.com/ocornut/imgui/issues/306
  • Memory Editor
  • Plot var helper
  • @@ -149,13 +149,13 @@ func testAnswers(baseURLContent, baseURLImages string) []string { - + - + @@ -164,9 +164,9 @@ func testAnswers(baseURLContent, baseURLImages string) []string { `

    Excelsior JET allows you to create native executables for Windows, Linux and Mac OS X.

    1. Package your libGDX application
      -images/1.png
    2. +
    3. Perform a test run by hitting the Run! button.
      -images/2.png
    4. +

    More tests

    (from https://www.markdownguide.org/extended-syntax/)

    @@ -422,7 +422,7 @@ func TestRenderSiblingImages_Issue12925(t *testing.T) { func TestRenderEmojiInLinks_Issue12331(t *testing.T) { testcase := `[Link with emoji :moon: in text](https://gitea.io)` - expected := `

    Link with emoji 🌔 in text

    + expected := `

    Link with emoji 🌔 in text

    ` res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase) require.NoError(t, err) @@ -471,7 +471,7 @@ func TestColorPreview(t *testing.T) { // no backticks "rgb(166, 32, 64)", // typo - "`hsI(0, 100%, 50%)`", // codespell-ignore + "`hsI(0, 100%, 50%)`", // codespell:ignore // looks like a color but not really "`hsl(40, 60, 80)`", } @@ -849,13 +849,13 @@ mail@domain.com local image
    local image
    remote image
    -local image
    -remote link
    +
    +
    https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -ðŸ‘
    +ðŸ‘
    mail@domain.com
    @mention-user test
    #123
    @@ -876,13 +876,13 @@ space

    local image
    local image
    remote image
    -local image
    -remote link
    +
    +
    https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -ðŸ‘
    +ðŸ‘
    mail@domain.com
    @mention-user test
    #123
    @@ -905,13 +905,13 @@ space

    local image
    local image
    remote image
    -local image
    -remote link
    +
    +
    https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -ðŸ‘
    +ðŸ‘
    mail@domain.com
    @mention-user test
    #123
    @@ -934,13 +934,13 @@ space

    local image
    local image
    remote image
    -local image
    -remote link
    +
    +
    https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -ðŸ‘
    +ðŸ‘
    mail@domain.com
    @mention-user test
    #123
    @@ -963,13 +963,13 @@ space

    local image
    local image
    remote image
    -local image
    -remote link
    +
    +
    https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -ðŸ‘
    +ðŸ‘
    mail@domain.com
    @mention-user test
    #123
    @@ -992,13 +992,13 @@ space

    local image
    local image
    remote image
    -local image
    -remote link
    +
    +
    https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -ðŸ‘
    +ðŸ‘
    mail@domain.com
    @mention-user test
    #123
    @@ -1022,13 +1022,13 @@ space

    local image
    local image
    remote image
    -local image
    -remote link
    +
    +
    https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -ðŸ‘
    +ðŸ‘
    mail@domain.com
    @mention-user test
    #123
    @@ -1052,13 +1052,13 @@ space

    local image
    local image
    remote image
    -local image
    -remote link
    +
    +
    https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -ðŸ‘
    +ðŸ‘
    mail@domain.com
    @mention-user test
    #123
    @@ -1082,13 +1082,13 @@ space

    local image
    local image
    remote image
    -local image
    -remote link
    +
    +
    https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -ðŸ‘
    +ðŸ‘
    mail@domain.com
    @mention-user test
    #123
    @@ -1112,13 +1112,13 @@ space

    local image
    local image
    remote image
    -local image
    -remote link
    +
    +
    https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -ðŸ‘
    +ðŸ‘
    mail@domain.com
    @mention-user test
    #123
    @@ -1143,13 +1143,13 @@ space

    local image
    local image
    remote image
    -local image
    -remote link
    +
    +
    https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -ðŸ‘
    +ðŸ‘
    mail@domain.com
    @mention-user test
    #123
    @@ -1174,13 +1174,13 @@ space

    local image
    local image
    remote image
    -local image
    -remote link
    +
    +
    https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
    https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
    com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
    -ðŸ‘
    +ðŸ‘
    mail@domain.com
    @mention-user test
    #123
    @@ -1190,7 +1190,7 @@ space

    } for i, c := range cases { - result, err := markdown.RenderString(&markup.RenderContext{Ctx: context.Background(), Links: c.Links, IsWiki: c.IsWiki}, input) + result, err := markdown.RenderString(&markup.RenderContext{Ctx: t.Context(), Links: c.Links, IsWiki: c.IsWiki}, input) require.NoError(t, err, "Unexpected error in testcase: %v", i) assert.Equal(t, template.HTML(c.Expected), result, "Unexpected result in testcase %v", i) } @@ -1356,4 +1356,10 @@ func TestCallout(t *testing.T) { } test(">\n0", "
    \n
    \n

    0

    ") + test("> **Warning**\n> Bad stuff is brewing here", `

    Warning

    +

    Bad stuff is brewing here

    +
    `) + test("> [!WARNING]\n> Bad stuff is brewing here", `

    Warning

    +

    Bad stuff is brewing here

    +
    `) } diff --git a/modules/markup/markdown/math/inline_parser.go b/modules/markup/markdown/math/inline_parser.go index b11195d551..da28d4fd9c 100644 --- a/modules/markup/markdown/math/inline_parser.go +++ b/modules/markup/markdown/math/inline_parser.go @@ -96,12 +96,12 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser. if len(line) <= pos { break } - suceedingCharacter := line[pos] + succeedingCharacter := line[pos] // check valid ending character - if !isPunctuation(suceedingCharacter) && - !(suceedingCharacter == ' ') && - !(suceedingCharacter == '\n') && - !isBracket(suceedingCharacter) { + if !isPunctuation(succeedingCharacter) && + (succeedingCharacter != ' ') && + (succeedingCharacter != '\n') && + !isBracket(succeedingCharacter) { return nil } if line[ender-1] != '\\' { @@ -139,12 +139,12 @@ func trimBlock(node *Inline, block text.Reader) { // trim first space and last space first := node.FirstChild().(*ast.Text) - if !(!first.Segment.IsEmpty() && block.Source()[first.Segment.Start] == ' ') { + if first.Segment.IsEmpty() || block.Source()[first.Segment.Start] != ' ' { return } last := node.LastChild().(*ast.Text) - if !(!last.Segment.IsEmpty() && block.Source()[last.Segment.Stop-1] == ' ') { + if last.Segment.IsEmpty() || block.Source()[last.Segment.Stop-1] != ' ' { return } diff --git a/modules/markup/markdown/math/math.go b/modules/markup/markdown/math/math.go index 3d9f376bc6..4126dc9ad6 100644 --- a/modules/markup/markdown/math/math.go +++ b/modules/markup/markdown/math/math.go @@ -39,28 +39,6 @@ func Enabled(enable ...bool) Option { }) } -// WithInlineDollarParser enables or disables the parsing of $...$ -func WithInlineDollarParser(enable ...bool) Option { - value := true - if len(enable) > 0 { - value = enable[0] - } - return extensionFunc(func(e *Extension) { - e.parseDollarInline = value - }) -} - -// WithBlockDollarParser enables or disables the parsing of $$...$$ -func WithBlockDollarParser(enable ...bool) Option { - value := true - if len(enable) > 0 { - value = enable[0] - } - return extensionFunc(func(e *Extension) { - e.parseDollarBlock = value - }) -} - // Math represents a math extension with default rendered delimiters var Math = &Extension{ enabled: true, diff --git a/modules/markup/markdown/meta_test.go b/modules/markup/markdown/meta_test.go index d341ae43e4..aaf116ff20 100644 --- a/modules/markup/markdown/meta_test.go +++ b/modules/markup/markdown/meta_test.go @@ -54,7 +54,7 @@ func TestExtractMetadata(t *testing.T) { var meta IssueTemplate body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest), &meta) require.NoError(t, err) - assert.Equal(t, "", body) + assert.Empty(t, body) assert.Equal(t, metaTest, meta) assert.True(t, meta.Valid()) }) @@ -86,7 +86,7 @@ func TestExtractMetadataBytes(t *testing.T) { var meta IssueTemplate body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest)), &meta) require.NoError(t, err) - assert.Equal(t, "", string(body)) + assert.Empty(t, string(body)) assert.Equal(t, metaTest, meta) assert.True(t, meta.Valid()) }) diff --git a/modules/markup/markdown/prefixed_id.go b/modules/markup/markdown/prefixed_id.go index 63d7fadc0a..036481dc05 100644 --- a/modules/markup/markdown/prefixed_id.go +++ b/modules/markup/markdown/prefixed_id.go @@ -7,9 +7,9 @@ import ( "bytes" "fmt" - "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/markup/common" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/container" + "forgejo.org/modules/markup/common" + "forgejo.org/modules/util" "github.com/yuin/goldmark/ast" ) diff --git a/modules/markup/markdown/renderconfig.go b/modules/markup/markdown/renderconfig.go index f4c48d1b3d..5c3eb1beec 100644 --- a/modules/markup/markdown/renderconfig.go +++ b/modules/markup/markdown/renderconfig.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - "code.gitea.io/gitea/modules/markup" + "forgejo.org/modules/markup" "github.com/yuin/goldmark/ast" "gopkg.in/yaml.v3" diff --git a/modules/markup/markdown/toc.go b/modules/markup/markdown/toc.go index 38f744a25f..dbfab3e9dc 100644 --- a/modules/markup/markdown/toc.go +++ b/modules/markup/markdown/toc.go @@ -7,8 +7,8 @@ import ( "fmt" "net/url" - "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/translation" + "forgejo.org/modules/markup" + "forgejo.org/modules/translation" "github.com/yuin/goldmark/ast" ) diff --git a/modules/markup/markdown/transform_codespan.go b/modules/markup/markdown/transform_codespan.go index a2cd4fb5ba..fc88db026b 100644 --- a/modules/markup/markdown/transform_codespan.go +++ b/modules/markup/markdown/transform_codespan.go @@ -8,7 +8,8 @@ import ( "fmt" "strings" - "code.gitea.io/gitea/modules/markup" + "forgejo.org/modules/markup" + mdutil "forgejo.org/modules/markup/markdown/util" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/renderer/html" @@ -39,7 +40,7 @@ func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Nod r.Writer.RawWrite(w, value) } case *ColorPreview: - _, _ = w.WriteString(fmt.Sprintf(``, string(v.Color))) + _, _ = fmt.Fprintf(w, ``, string(v.Color)) } } return ast.WalkSkipChildren, nil @@ -49,7 +50,7 @@ func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Nod } func (g *ASTTransformer) transformCodeSpan(_ *markup.RenderContext, v *ast.CodeSpan, reader text.Reader) { - colorContent := v.Text(reader.Source()) + colorContent := mdutil.Text(v, reader.Source()) if matchColor(strings.ToLower(string(colorContent))) { v.AppendChild(v, NewColorPreview(colorContent)) } diff --git a/modules/markup/markdown/transform_heading.go b/modules/markup/markdown/transform_heading.go index 6d48f34d93..eedaf58556 100644 --- a/modules/markup/markdown/transform_heading.go +++ b/modules/markup/markdown/transform_heading.go @@ -6,8 +6,9 @@ package markdown import ( "fmt" - "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/markup" + mdutil "forgejo.org/modules/markup/markdown/util" + "forgejo.org/modules/util" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/text" @@ -19,7 +20,7 @@ func (g *ASTTransformer) transformHeading(_ *markup.RenderContext, v *ast.Headin v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value))) } } - txt := v.Text(reader.Source()) + txt := mdutil.Text(v, reader.Source()) header := markup.Header{ Text: util.UnsafeBytesToString(txt), Level: v.Level, diff --git a/modules/markup/markdown/transform_image.go b/modules/markup/markdown/transform_image.go index b34a710fed..0f9c69cae6 100644 --- a/modules/markup/markdown/transform_image.go +++ b/modules/markup/markdown/transform_image.go @@ -6,8 +6,8 @@ package markdown import ( "strings" - "code.gitea.io/gitea/modules/markup" - giteautil "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/markup" + giteautil "forgejo.org/modules/util" "github.com/yuin/goldmark/ast" ) diff --git a/modules/markup/markdown/transform_link.go b/modules/markup/markdown/transform_link.go index e6f3836412..48e3479563 100644 --- a/modules/markup/markdown/transform_link.go +++ b/modules/markup/markdown/transform_link.go @@ -7,9 +7,9 @@ import ( "bytes" "slices" - "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/setting" - giteautil "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/markup" + "forgejo.org/modules/setting" + giteautil "forgejo.org/modules/util" "github.com/yuin/goldmark/ast" ) diff --git a/modules/markup/markdown/transform_list.go b/modules/markup/markdown/transform_list.go index b982fd4a83..03b3c4e89c 100644 --- a/modules/markup/markdown/transform_list.go +++ b/modules/markup/markdown/transform_list.go @@ -6,7 +6,7 @@ package markdown import ( "fmt" - "code.gitea.io/gitea/modules/markup" + "forgejo.org/modules/markup" "github.com/yuin/goldmark/ast" east "github.com/yuin/goldmark/extension/ast" diff --git a/modules/markup/markdown/util/text.go b/modules/markup/markdown/util/text.go new file mode 100644 index 0000000000..8a42e5835b --- /dev/null +++ b/modules/markup/markdown/util/text.go @@ -0,0 +1,26 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package util + +import ( + "bytes" + + "github.com/yuin/goldmark/ast" +) + +func textOfChildren(n ast.Node, src []byte, b *bytes.Buffer) { + for c := n.FirstChild(); c != nil; c = c.NextSibling() { + if t, ok := c.(*ast.Text); ok { + b.Write(t.Value(src)) + } else { + textOfChildren(c, src, b) + } + } +} + +func Text(n ast.Node, src []byte) []byte { + var b bytes.Buffer + textOfChildren(n, src, &b) + return b.Bytes() +} diff --git a/modules/markup/mdstripper/mdstripper.go b/modules/markup/mdstripper/mdstripper.go index 2a69d95224..7e5abc2ebe 100644 --- a/modules/markup/mdstripper/mdstripper.go +++ b/modules/markup/mdstripper/mdstripper.go @@ -10,9 +10,9 @@ import ( "strings" "sync" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup/common" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/log" + "forgejo.org/modules/markup/common" + "forgejo.org/modules/setting" "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" @@ -46,7 +46,7 @@ func (r *stripRenderer) Render(w io.Writer, source []byte, doc ast.Node) error { coalesce := prevSibIsText r.processString( w, - v.Text(source), + v.Value(source), coalesce) if v.SoftLineBreak() { r.doubleSpace(w) @@ -107,11 +107,12 @@ func (r *stripRenderer) processAutoLink(w io.Writer, link []byte) { } var sep string - if parts[3] == "issues" { + switch parts[3] { + case "issues": sep = "#" - } else if parts[3] == "pulls" { + case "pulls": sep = "!" - } else { + default: // Process out of band r.links = append(r.links, linkStr) return diff --git a/modules/markup/mdstripper/mdstripper_test.go b/modules/markup/mdstripper/mdstripper_test.go index ea34df0a3b..7fb49c1e01 100644 --- a/modules/markup/mdstripper/mdstripper_test.go +++ b/modules/markup/mdstripper/mdstripper_test.go @@ -79,7 +79,7 @@ A HIDDEN ` + "`" + `GHOST` + "`" + ` IN THIS LINE. lines = append(lines, line) } } - assert.EqualValues(t, test.expectedText, lines) - assert.EqualValues(t, test.expectedLinks, links) + assert.Equal(t, test.expectedText, lines) + assert.Equal(t, test.expectedLinks, links) } } diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go index 391ee6c12b..b9d7b21db0 100644 --- a/modules/markup/orgmode/orgmode.go +++ b/modules/markup/orgmode/orgmode.go @@ -9,11 +9,11 @@ import ( "io" "strings" - "code.gitea.io/gitea/modules/highlight" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/highlight" + "forgejo.org/modules/log" + "forgejo.org/modules/markup" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" "github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma/v2/lexers" diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go index f41d86a8a8..cdaa9f18ce 100644 --- a/modules/markup/orgmode/orgmode_test.go +++ b/modules/markup/orgmode/orgmode_test.go @@ -7,10 +7,10 @@ import ( "strings" "testing" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/git" + "forgejo.org/modules/markup" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -152,8 +152,8 @@ func HelloWorld() { } #+end_src `, `
    -
    // HelloWorld prints "Hello World"
    -func HelloWorld() {
    +
    // HelloWorld prints "Hello World"
    +func HelloWorld() {
     	fmt.Println("Hello World")
     }
    `) diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index 2137302f43..a622d75085 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -14,9 +14,9 @@ import ( "strings" "sync" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/git" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" "github.com/yuin/goldmark/ast" ) diff --git a/modules/markup/sanitizer.go b/modules/markup/sanitizer.go index ddc218c1b8..384dd1fe94 100644 --- a/modules/markup/sanitizer.go +++ b/modules/markup/sanitizer.go @@ -10,7 +10,7 @@ import ( "regexp" "sync" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" "github.com/microcosm-cc/bluemonday" ) @@ -94,10 +94,10 @@ func createDefaultPolicy() *bluemonday.Policy { } // Allow classes for anchors - policy.AllowAttrs("class").Matching(regexp.MustCompile(`ref-issue( ref-external-issue)?`)).OnElements("a") + policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(ref-issue( ref-external-issue)?|mention)$`)).OnElements("a") // Allow classes for task lists - policy.AllowAttrs("class").Matching(regexp.MustCompile(`task-list-item`)).OnElements("li") + policy.AllowAttrs("class").Matching(regexp.MustCompile(`^task-list-item$`)).OnElements("li") // Allow classes for org mode list item status. policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(unchecked|checked|indeterminate)$`)).OnElements("li") @@ -106,10 +106,11 @@ func createDefaultPolicy() *bluemonday.Policy { policy.AllowAttrs("class").Matching(regexp.MustCompile(`^icon(\s+[\p{L}\p{N}_-]+)+$`)).OnElements("i") // Allow classes for emojis - policy.AllowAttrs("class").Matching(regexp.MustCompile(`emoji`)).OnElements("img") + policy.AllowAttrs("class").Matching(regexp.MustCompile(`^emoji$`)).OnElements("img") // Allow icons, emojis, chroma syntax and keyword markup on span policy.AllowAttrs("class").Matching(regexp.MustCompile(`^((icon(\s+[\p{L}\p{N}_-]+)+)|(emoji)|(language-math display)|(language-math inline))$|^([a-z][a-z0-9]{0,2})$|^` + keywordClass + `$`)).OnElements("span") + policy.AllowAttrs("data-alias").Matching(regexp.MustCompile(`^[a-zA-Z0-9-_+]+$`)).OnElements("span") // Allow 'color' and 'background-color' properties for the style attribute on text elements and table cells. policy.AllowStyles("color", "background-color").OnElements("span", "p", "th", "td") @@ -121,14 +122,14 @@ func createDefaultPolicy() *bluemonday.Policy { policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui table$")).OnElements("div") policy.AllowAttrs("class").Matching(regexp.MustCompile("^header$")).OnElements("div") policy.AllowAttrs("data-line-number").Matching(regexp.MustCompile("^[0-9]+$")).OnElements("span") - policy.AllowAttrs("class").Matching(regexp.MustCompile("^text small grey$")).OnElements("span") - policy.AllowAttrs("class").Matching(regexp.MustCompile("^file-preview*")).OnElements("table") + policy.AllowAttrs("class").Matching(regexp.MustCompile("^text grey$")).OnElements("span") + policy.AllowAttrs("class").Matching(regexp.MustCompile("^file-preview$")).OnElements("table") policy.AllowAttrs("class").Matching(regexp.MustCompile("^lines-escape$")).OnElements("td") policy.AllowAttrs("class").Matching(regexp.MustCompile("^toggle-escape-button btn interact-bg$")).OnElements("button") policy.AllowAttrs("title").OnElements("button") policy.AllowAttrs("class").Matching(regexp.MustCompile("^ambiguous-code-point$")).OnElements("span") policy.AllowAttrs("data-tooltip-content").OnElements("span") - policy.AllowAttrs("class").Matching(regexp.MustCompile("muted|(text black)")).OnElements("a") + policy.AllowAttrs("class").Matching(regexp.MustCompile("^muted|(text black)$")).OnElements("a") policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui warning message tw-text-left$")).OnElements("div") // Allow generally safe attributes diff --git a/modules/markup/sanitizer_test.go b/modules/markup/sanitizer_test.go index 4441a41544..9805a34910 100644 --- a/modules/markup/sanitizer_test.go +++ b/modules/markup/sanitizer_test.go @@ -68,6 +68,13 @@ func Test_Sanitizer(t *testing.T) { `bad`, `bad`, `bad`, `bad`, `bad`, `bad`, + + // Mention + `@forgejo/UI`, `@forgejo/UI`, + + // Emoji + `THUMBS UP`, `THUMBS UP`, + `THUMBS UP`, `THUMBS UP`, } for i := 0; i < len(testCases); i += 2 { diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/0b/b53b56d70d253ce75c257d3cd6334a41ef2b6c b/modules/markup/tests/repo/repo1_filepreview/objects/0b/b53b56d70d253ce75c257d3cd6334a41ef2b6c new file mode 100644 index 0000000000..1ab268b76c Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/0b/b53b56d70d253ce75c257d3cd6334a41ef2b6c differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/18/9739e1c2a6cdb8ee094ba1ef8a2e40cf65b2ec b/modules/markup/tests/repo/repo1_filepreview/objects/18/9739e1c2a6cdb8ee094ba1ef8a2e40cf65b2ec new file mode 100644 index 0000000000..c8b99f906b Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/18/9739e1c2a6cdb8ee094ba1ef8a2e40cf65b2ec differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/2a/4032b49cff56d6d4921133e087d9dc0341a2c5 b/modules/markup/tests/repo/repo1_filepreview/objects/2a/4032b49cff56d6d4921133e087d9dc0341a2c5 new file mode 100644 index 0000000000..f799e8a988 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/2a/4032b49cff56d6d4921133e087d9dc0341a2c5 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/2d/2f8eaa17b17359ee1c73222065575d50d9a157 b/modules/markup/tests/repo/repo1_filepreview/objects/2d/2f8eaa17b17359ee1c73222065575d50d9a157 new file mode 100644 index 0000000000..7f4c451d00 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/2d/2f8eaa17b17359ee1c73222065575d50d9a157 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/2f/b9577a8e940a0a84a789cdd4a45d0f172e3fdd b/modules/markup/tests/repo/repo1_filepreview/objects/2f/b9577a8e940a0a84a789cdd4a45d0f172e3fdd new file mode 100644 index 0000000000..fc97712911 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/2f/b9577a8e940a0a84a789cdd4a45d0f172e3fdd differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/2f/f8eb63aad050c3f20e9cb27090ab7378267ab2 b/modules/markup/tests/repo/repo1_filepreview/objects/2f/f8eb63aad050c3f20e9cb27090ab7378267ab2 new file mode 100644 index 0000000000..e230df1343 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/2f/f8eb63aad050c3f20e9cb27090ab7378267ab2 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/35/75ed7948fe86ab56b0a76f796f7995222bec65 b/modules/markup/tests/repo/repo1_filepreview/objects/35/75ed7948fe86ab56b0a76f796f7995222bec65 new file mode 100644 index 0000000000..1493caa3df Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/35/75ed7948fe86ab56b0a76f796f7995222bec65 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/3c/95f14e5a0ab2c5ba9ee9a47ddc261af4968043 b/modules/markup/tests/repo/repo1_filepreview/objects/3c/95f14e5a0ab2c5ba9ee9a47ddc261af4968043 new file mode 100644 index 0000000000..3e9c0c0d8b Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/3c/95f14e5a0ab2c5ba9ee9a47ddc261af4968043 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/3e/2a4f1b9a15ffa15ea7ffdc06acd302442b3eca b/modules/markup/tests/repo/repo1_filepreview/objects/3e/2a4f1b9a15ffa15ea7ffdc06acd302442b3eca new file mode 100644 index 0000000000..78189a52f6 --- /dev/null +++ b/modules/markup/tests/repo/repo1_filepreview/objects/3e/2a4f1b9a15ffa15ea7ffdc06acd302442b3eca @@ -0,0 +1 @@ +x•ŽANÃ0EYû³GB;a U=D9€=þ&–ÚÙÓr} 7èê­ÞÓÓëåÒŒBœ^¬´¤˜yY8Ï:AІ X}R×XkÎs­"î;uìFº®9x” Œ ÊEdÐ’%Í~**Zß3\ºÙvíô9Й>nÿ8Žfxkû=[9K”%L>®ôêÙ{§7Ãs–;aÕvý4ÛhXOûH·Ô“þÕ†ûð`KÑ \ No newline at end of file diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/72/1f0ce13d83f93d431b849a554a62948b85f573 b/modules/markup/tests/repo/repo1_filepreview/objects/72/1f0ce13d83f93d431b849a554a62948b85f573 new file mode 100644 index 0000000000..d781d4d248 --- /dev/null +++ b/modules/markup/tests/repo/repo1_filepreview/objects/72/1f0ce13d83f93d431b849a554a62948b85f573 @@ -0,0 +1 @@ +x•ŽKŠ1@]çµ$¿J¥aæz€JRÁ@w+éØsýõ®ÞâñàåÛ²´ÖÛÃè"@VL&J3%f-ÑGDÒq2>FçjBOEݹË:ÀgÃ\1¤œ¦ê¦’kÀêªEM6DÔ,Ÿ\‚âǸÞ:\6é¾OülmÈ©­;Ï­|ƒ!GäŒE‚£6Z«üzòY¥Î² ¨m¸wÙ›üÂÿi‘.x-o³ò"›úŒLÌ \ No newline at end of file diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/72/e0a44ea5761c9055995db18019e459576b3b27 b/modules/markup/tests/repo/repo1_filepreview/objects/72/e0a44ea5761c9055995db18019e459576b3b27 new file mode 100644 index 0000000000..7b926dc0d8 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/72/e0a44ea5761c9055995db18019e459576b3b27 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/72/e1c77b65c7baa0e848557089148833fb54705e b/modules/markup/tests/repo/repo1_filepreview/objects/72/e1c77b65c7baa0e848557089148833fb54705e new file mode 100644 index 0000000000..0bbca73af2 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/72/e1c77b65c7baa0e848557089148833fb54705e differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/8a/3b1881b5c4e7dc2be7ee1c0f37f93ffbb5ff77 b/modules/markup/tests/repo/repo1_filepreview/objects/8a/3b1881b5c4e7dc2be7ee1c0f37f93ffbb5ff77 new file mode 100644 index 0000000000..0ea93376dc Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/8a/3b1881b5c4e7dc2be7ee1c0f37f93ffbb5ff77 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/8b/ccd5176c25898b57da2551e076f769054e0d8e b/modules/markup/tests/repo/repo1_filepreview/objects/8b/ccd5176c25898b57da2551e076f769054e0d8e new file mode 100644 index 0000000000..394a7bb50d Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/8b/ccd5176c25898b57da2551e076f769054e0d8e differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/95/31b649823095acf5d79ab9e4f8b8d86046352f b/modules/markup/tests/repo/repo1_filepreview/objects/95/31b649823095acf5d79ab9e4f8b8d86046352f new file mode 100644 index 0000000000..ab36311f6f Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/95/31b649823095acf5d79ab9e4f8b8d86046352f differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/ac/769ab4baa91060a4c2f828f53e6c3cc2f708f8 b/modules/markup/tests/repo/repo1_filepreview/objects/ac/769ab4baa91060a4c2f828f53e6c3cc2f708f8 new file mode 100644 index 0000000000..59afaebf4a Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/ac/769ab4baa91060a4c2f828f53e6c3cc2f708f8 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/c5/3110b1957cefc56c4b2d879476ddbe905980bf b/modules/markup/tests/repo/repo1_filepreview/objects/c5/3110b1957cefc56c4b2d879476ddbe905980bf new file mode 100644 index 0000000000..3de089bf6a Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/c5/3110b1957cefc56c4b2d879476ddbe905980bf differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/c9/8762531dd068cd818300a5f5c7dca5da79b510 b/modules/markup/tests/repo/repo1_filepreview/objects/c9/8762531dd068cd818300a5f5c7dca5da79b510 new file mode 100644 index 0000000000..af5b784773 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/c9/8762531dd068cd818300a5f5c7dca5da79b510 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/c9/913120ed2c1e27c1d7752ecdb7a504dc7cf6be b/modules/markup/tests/repo/repo1_filepreview/objects/c9/913120ed2c1e27c1d7752ecdb7a504dc7cf6be new file mode 100644 index 0000000000..9fc2b7c312 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/c9/913120ed2c1e27c1d7752ecdb7a504dc7cf6be differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/e7/99b34ea867a0364d0df33f382562db9ff39084 b/modules/markup/tests/repo/repo1_filepreview/objects/e7/99b34ea867a0364d0df33f382562db9ff39084 new file mode 100644 index 0000000000..ef73ed1791 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/e7/99b34ea867a0364d0df33f382562db9ff39084 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/ee/b243c3395e1921c5d90e73bd739827251fc99d b/modules/markup/tests/repo/repo1_filepreview/objects/ee/b243c3395e1921c5d90e73bd739827251fc99d new file mode 100644 index 0000000000..5515b07d4a Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/ee/b243c3395e1921c5d90e73bd739827251fc99d differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/f7/0f10e4db19068f79bc43844b49f3eece45c4e8 b/modules/markup/tests/repo/repo1_filepreview/objects/f7/0f10e4db19068f79bc43844b49f3eece45c4e8 new file mode 100644 index 0000000000..2e15b4fb0a Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/f7/0f10e4db19068f79bc43844b49f3eece45c4e8 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/refs/heads/master b/modules/markup/tests/repo/repo1_filepreview/refs/heads/master index df25bf45f0..709cffca17 100644 --- a/modules/markup/tests/repo/repo1_filepreview/refs/heads/master +++ b/modules/markup/tests/repo/repo1_filepreview/refs/heads/master @@ -1 +1 @@ -4c1aaf56bcb9f39dcf65f3f250726850aed13cd6 +eeb243c3395e1921c5d90e73bd739827251fc99d diff --git a/modules/mcaptcha/mcaptcha.go b/modules/mcaptcha/mcaptcha.go index 74142aa863..dbcafce29f 100644 --- a/modules/mcaptcha/mcaptcha.go +++ b/modules/mcaptcha/mcaptcha.go @@ -7,7 +7,7 @@ import ( "context" "fmt" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" "codeberg.org/gusted/mcaptcha" ) diff --git a/modules/metrics/collector.go b/modules/metrics/collector.go index 230260ff94..5b6787d2f7 100755 --- a/modules/metrics/collector.go +++ b/modules/metrics/collector.go @@ -6,9 +6,9 @@ package metrics import ( "runtime" - activities_model "code.gitea.io/gitea/models/activities" - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/setting" + activities_model "forgejo.org/models/activities" + "forgejo.org/models/db" + "forgejo.org/modules/setting" "github.com/prometheus/client_golang/prometheus" ) diff --git a/modules/migration/downloader.go b/modules/migration/downloader.go index 08dbbc29a9..48bdf0456d 100644 --- a/modules/migration/downloader.go +++ b/modules/migration/downloader.go @@ -7,7 +7,7 @@ package migration import ( "context" - "code.gitea.io/gitea/modules/structs" + "forgejo.org/modules/structs" ) // Downloader downloads the site repo information diff --git a/modules/migration/file_format.go b/modules/migration/file_format.go index d29d24dd0b..8851ad6de7 100644 --- a/modules/migration/file_format.go +++ b/modules/migration/file_format.go @@ -9,8 +9,8 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/json" + "forgejo.org/modules/log" "github.com/santhosh-tekuri/jsonschema/v6" "gopkg.in/yaml.v3" diff --git a/modules/migration/options.go b/modules/migration/options.go index 234e72c295..63bbe60758 100644 --- a/modules/migration/options.go +++ b/modules/migration/options.go @@ -4,7 +4,7 @@ package migration -import "code.gitea.io/gitea/modules/structs" +import "forgejo.org/modules/structs" // MigrateOptions defines the way a repository gets migrated // this is for internal usage by migrations module and func who interact with it diff --git a/modules/migration/pullrequest.go b/modules/migration/pullrequest.go index 1435991bd2..0861ab24f1 100644 --- a/modules/migration/pullrequest.go +++ b/modules/migration/pullrequest.go @@ -8,7 +8,7 @@ import ( "fmt" "time" - "code.gitea.io/gitea/modules/git" + "forgejo.org/modules/git" ) // PullRequest defines a standard pull request information @@ -34,9 +34,11 @@ type PullRequest struct { Assignees []string IsLocked bool `yaml:"is_locked"` Reactions []*Reaction + Flow int64 ForeignIndex int64 Context DownloaderContext `yaml:"-"` EnsuredSafe bool `yaml:"ensured_safe"` + IsDraft bool `yaml:"is_draft"` } func (p *PullRequest) GetLocalIndex() int64 { return p.Number } diff --git a/modules/migration/repo.go b/modules/migration/repo.go index 22c2cf6fb3..a85d38084d 100644 --- a/modules/migration/repo.go +++ b/modules/migration/repo.go @@ -14,4 +14,5 @@ type Repository struct { CloneURL string `yaml:"clone_url"` // SECURITY: This must be checked to ensure that is safe to be used OriginalURL string `yaml:"original_url"` DefaultBranch string + Website string } diff --git a/modules/nosql/manager.go b/modules/nosql/manager.go index 0ba21585fa..7eea069e09 100644 --- a/modules/nosql/manager.go +++ b/modules/nosql/manager.go @@ -9,7 +9,7 @@ import ( "sync" "time" - "code.gitea.io/gitea/modules/process" + "forgejo.org/modules/process" "github.com/redis/go-redis/v9" "github.com/syndtr/goleveldb/leveldb" diff --git a/modules/nosql/manager_leveldb.go b/modules/nosql/manager_leveldb.go index 4d2c90debc..087aac3e9a 100644 --- a/modules/nosql/manager_leveldb.go +++ b/modules/nosql/manager_leveldb.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/errors" diff --git a/modules/nosql/manager_redis.go b/modules/nosql/manager_redis.go index 79a533bd6b..bdaade1b47 100644 --- a/modules/nosql/manager_redis.go +++ b/modules/nosql/manager_redis.go @@ -11,7 +11,7 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" "github.com/redis/go-redis/v9" ) diff --git a/modules/optional/option.go b/modules/optional/option.go index af9e5ac852..ccbad259c2 100644 --- a/modules/optional/option.go +++ b/modules/optional/option.go @@ -3,6 +3,8 @@ package optional +import "strconv" + type Option[T any] []T func None[T any]() Option[T] { @@ -43,3 +45,12 @@ func (o Option[T]) ValueOrDefault(v T) T { } return v } + +// ParseBool get the corresponding optional.Option[bool] of a string using strconv.ParseBool +func ParseBool(s string) Option[bool] { + v, e := strconv.ParseBool(s) + if e != nil { + return None[bool]() + } + return Some(v) +} diff --git a/modules/optional/option_test.go b/modules/optional/option_test.go index 203e9221e3..a674caf633 100644 --- a/modules/optional/option_test.go +++ b/modules/optional/option_test.go @@ -6,7 +6,7 @@ package optional_test import ( "testing" - "code.gitea.io/gitea/modules/optional" + "forgejo.org/modules/optional" "github.com/stretchr/testify/assert" ) @@ -57,3 +57,16 @@ func TestOption(t *testing.T) { assert.True(t, opt3.Has()) assert.Equal(t, int(1), opt3.Value()) } + +func Test_ParseBool(t *testing.T) { + assert.Equal(t, optional.None[bool](), optional.ParseBool("")) + assert.Equal(t, optional.None[bool](), optional.ParseBool("x")) + + assert.Equal(t, optional.Some(false), optional.ParseBool("0")) + assert.Equal(t, optional.Some(false), optional.ParseBool("f")) + assert.Equal(t, optional.Some(false), optional.ParseBool("False")) + + assert.Equal(t, optional.Some(true), optional.ParseBool("1")) + assert.Equal(t, optional.Some(true), optional.ParseBool("t")) + assert.Equal(t, optional.Some(true), optional.ParseBool("True")) +} diff --git a/modules/optional/serialization.go b/modules/optional/serialization.go index b120a0edf6..86c1c97341 100644 --- a/modules/optional/serialization.go +++ b/modules/optional/serialization.go @@ -4,7 +4,7 @@ package optional import ( - "code.gitea.io/gitea/modules/json" + "forgejo.org/modules/json" "gopkg.in/yaml.v3" ) diff --git a/modules/optional/serialization_test.go b/modules/optional/serialization_test.go index c852b8a70f..14bf3b7814 100644 --- a/modules/optional/serialization_test.go +++ b/modules/optional/serialization_test.go @@ -7,8 +7,8 @@ import ( std_json "encoding/json" //nolint:depguard "testing" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/optional" + "forgejo.org/modules/json" + "forgejo.org/modules/optional" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -52,11 +52,11 @@ func TestOptionalToJson(t *testing.T) { t.Run(tc.name, func(t *testing.T) { b, err := json.Marshal(tc.obj) require.NoError(t, err) - assert.EqualValues(t, tc.want, string(b), "gitea json module returned unexpected") + assert.Equal(t, tc.want, string(b), "gitea json module returned unexpected") b, err = std_json.Marshal(tc.obj) require.NoError(t, err) - assert.EqualValues(t, tc.want, string(b), "std json module returned unexpected") + assert.Equal(t, tc.want, string(b), "std json module returned unexpected") }) } } @@ -90,12 +90,12 @@ func TestOptionalFromJson(t *testing.T) { var obj1 testSerializationStruct err := json.Unmarshal([]byte(tc.data), &obj1) require.NoError(t, err) - assert.EqualValues(t, tc.want, obj1, "gitea json module returned unexpected") + assert.Equal(t, tc.want, obj1, "gitea json module returned unexpected") var obj2 testSerializationStruct err = std_json.Unmarshal([]byte(tc.data), &obj2) require.NoError(t, err) - assert.EqualValues(t, tc.want, obj2, "std json module returned unexpected") + assert.Equal(t, tc.want, obj2, "std json module returned unexpected") }) } } @@ -136,7 +136,7 @@ optional_two_string: null t.Run(tc.name, func(t *testing.T) { b, err := yaml.Marshal(tc.obj) require.NoError(t, err) - assert.EqualValues(t, tc.want, string(b), "yaml module returned unexpected") + assert.Equal(t, tc.want, string(b), "yaml module returned unexpected") }) } } @@ -185,7 +185,7 @@ optional_twostring: null var obj testSerializationStruct err := yaml.Unmarshal([]byte(tc.data), &obj) require.NoError(t, err) - assert.EqualValues(t, tc.want, obj, "yaml module returned unexpected") + assert.Equal(t, tc.want, obj, "yaml module returned unexpected") }) } } diff --git a/modules/options/base.go b/modules/options/base.go index 6c6e3839f4..3ae8c56b79 100644 --- a/modules/options/base.go +++ b/modules/options/base.go @@ -4,8 +4,8 @@ package options import ( - "code.gitea.io/gitea/modules/assetfs" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/assetfs" + "forgejo.org/modules/setting" ) func CustomAssets() *assetfs.Layer { diff --git a/modules/options/dynamic.go b/modules/options/dynamic.go index 085492d11c..8eed8516ab 100644 --- a/modules/options/dynamic.go +++ b/modules/options/dynamic.go @@ -6,8 +6,8 @@ package options import ( - "code.gitea.io/gitea/modules/assetfs" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/assetfs" + "forgejo.org/modules/setting" ) func BuiltinAssets() *assetfs.Layer { diff --git a/modules/options/static.go b/modules/options/static.go index 72b28e990e..02091a2b1c 100644 --- a/modules/options/static.go +++ b/modules/options/static.go @@ -6,7 +6,7 @@ package options import ( - "code.gitea.io/gitea/modules/assetfs" + "forgejo.org/modules/assetfs" ) func BuiltinAssets() *assetfs.Layer { diff --git a/modules/packages/alpine/metadata.go b/modules/packages/alpine/metadata.go index 582c42610d..8562612206 100644 --- a/modules/packages/alpine/metadata.go +++ b/modules/packages/alpine/metadata.go @@ -13,8 +13,8 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/util" + "forgejo.org/modules/validation" ) var ( diff --git a/modules/packages/arch/metadata.go b/modules/packages/arch/metadata.go index 0e08670311..f967bd25a0 100644 --- a/modules/packages/arch/metadata.go +++ b/modules/packages/arch/metadata.go @@ -4,6 +4,7 @@ package arch import ( + "archive/tar" "bufio" "bytes" "encoding/hex" @@ -14,9 +15,9 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/modules/packages" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/packages" + "forgejo.org/modules/util" + "forgejo.org/modules/validation" "github.com/mholt/archiver/v3" ) @@ -25,7 +26,9 @@ import ( // https://man.archlinux.org/man/PKGBUILD.5 const ( - PropertyDescription = "arch.description" + PropertyDescription = "arch.description" + PropertyFiles = "arch.files" + PropertyArch = "arch.architecture" PropertyDistribution = "arch.distribution" @@ -39,8 +42,8 @@ const ( var ( reName = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+$`) reVer = regexp.MustCompile(`^[a-zA-Z0-9:_.+]+-+[0-9]+$`) - reOptDep = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?[a-zA-Z0-9@._+-]+)?(:.*)?$`) - rePkgVer = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?[a-zA-Z0-9@._+-]+)?$`) + reOptDep = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?([0-9]+:)?[a-zA-Z0-9@._+-]+)?(:.*)?$`) + rePkgVer = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?([0-9]+:)?[a-zA-Z0-9@._+-]+)?$`) magicZSTD = []byte{0x28, 0xB5, 0x2F, 0xFD} magicXZ = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A} @@ -71,7 +74,7 @@ type VersionMetadata struct { Conflicts []string `json:"conflicts,omitempty"` Replaces []string `json:"replaces,omitempty"` Backup []string `json:"backup,omitempty"` - Xdata []string `json:"xdata,omitempty"` + XData []string `json:"xdata,omitempty"` } // FileMetadata Metadata related to specific package file. @@ -85,11 +88,13 @@ type FileMetadata struct { Packager string `json:"packager"` Arch string `json:"arch"` PgpSigned string `json:"pgp"` + + Files []string `json:"files,omitempty"` } // ParsePackage Function that receives arch package archive data and returns it's metadata. func ParsePackage(r *packages.HashedBuffer) (*Package, error) { - md5, _, sha256, _ := r.Sums() + md5, _, sha256, _, _ := r.Sums() _, err := r.Seek(0, io.SeekStart) if err != nil { return nil, err @@ -125,7 +130,9 @@ func ParsePackage(r *packages.HashedBuffer) (*Package, error) { defer tarball.Close() var pkg *Package - var mtree bool + var mTree bool + + files := make([]string, 0) for { f, err := tarball.Read() @@ -135,27 +142,32 @@ func ParsePackage(r *packages.HashedBuffer) (*Package, error) { if err != nil { return nil, err } - defer f.Close() + // ref:https://gitlab.archlinux.org/pacman/pacman/-/blob/91546004903eea5d5267d59898a6029ba1d64031/lib/libalpm/add.c#L529-L533 + if !strings.HasPrefix(f.Name(), ".") { + files = append(files, (f.Header.(*tar.Header)).Name) + } switch f.Name() { case ".PKGINFO": pkg, err = ParsePackageInfo(tarballType, f) if err != nil { + _ = f.Close() return nil, err } case ".MTREE": - mtree = true + mTree = true } + _ = f.Close() } if pkg == nil { return nil, util.NewInvalidArgumentErrorf(".PKGINFO file not found") } - if !mtree { + if !mTree { return nil, util.NewInvalidArgumentErrorf(".MTREE file not found") } - + pkg.FileMetadata.Files = files pkg.FileMetadata.CompressedSize = r.Size() pkg.FileMetadata.MD5 = hex.EncodeToString(md5) pkg.FileMetadata.SHA256 = hex.EncodeToString(sha256) @@ -220,7 +232,7 @@ func ParsePackageInfo(compressType string, r io.Reader) (*Package, error) { case "replaces": p.VersionMetadata.Replaces = append(p.VersionMetadata.Replaces, value) case "xdata": - p.VersionMetadata.Xdata = append(p.VersionMetadata.Xdata, value) + p.VersionMetadata.XData = append(p.VersionMetadata.XData, value) case "builddate": bd, err := strconv.ParseInt(value, 10, 64) if err != nil { @@ -260,48 +272,43 @@ func ValidatePackageSpec(p *Package) error { return util.NewInvalidArgumentErrorf("invalid project URL") } } - for _, cd := range p.VersionMetadata.CheckDepends { - if !rePkgVer.MatchString(cd) { - return util.NewInvalidArgumentErrorf("invalid check dependency: %s", cd) + for _, checkDepend := range p.VersionMetadata.CheckDepends { + if !rePkgVer.MatchString(checkDepend) { + return util.NewInvalidArgumentErrorf("invalid check dependency: %s", checkDepend) } } - for _, d := range p.VersionMetadata.Depends { - if !rePkgVer.MatchString(d) { - return util.NewInvalidArgumentErrorf("invalid dependency: %s", d) + for _, depend := range p.VersionMetadata.Depends { + if !rePkgVer.MatchString(depend) { + return util.NewInvalidArgumentErrorf("invalid dependency: %s", depend) } } - for _, md := range p.VersionMetadata.MakeDepends { - if !rePkgVer.MatchString(md) { - return util.NewInvalidArgumentErrorf("invalid make dependency: %s", md) + for _, makeDepend := range p.VersionMetadata.MakeDepends { + if !rePkgVer.MatchString(makeDepend) { + return util.NewInvalidArgumentErrorf("invalid make dependency: %s", makeDepend) } } - for _, p := range p.VersionMetadata.Provides { - if !rePkgVer.MatchString(p) { - return util.NewInvalidArgumentErrorf("invalid provides: %s", p) + for _, provide := range p.VersionMetadata.Provides { + if !rePkgVer.MatchString(provide) { + return util.NewInvalidArgumentErrorf("invalid provides: %s", provide) } } - for _, p := range p.VersionMetadata.Conflicts { - if !rePkgVer.MatchString(p) { - return util.NewInvalidArgumentErrorf("invalid conflicts: %s", p) + for _, conflict := range p.VersionMetadata.Conflicts { + if !rePkgVer.MatchString(conflict) { + return util.NewInvalidArgumentErrorf("invalid conflicts: %s", conflict) } } - for _, p := range p.VersionMetadata.Replaces { - if !rePkgVer.MatchString(p) { - return util.NewInvalidArgumentErrorf("invalid replaces: %s", p) + for _, replace := range p.VersionMetadata.Replaces { + if !rePkgVer.MatchString(replace) { + return util.NewInvalidArgumentErrorf("invalid replaces: %s", replace) } } - for _, p := range p.VersionMetadata.Replaces { - if !rePkgVer.MatchString(p) { - return util.NewInvalidArgumentErrorf("invalid xdata: %s", p) + for _, optDepend := range p.VersionMetadata.OptDepends { + if !reOptDep.MatchString(optDepend) { + return util.NewInvalidArgumentErrorf("invalid optional dependency: %s", optDepend) } } - for _, od := range p.VersionMetadata.OptDepends { - if !reOptDep.MatchString(od) { - return util.NewInvalidArgumentErrorf("invalid optional dependency: %s", od) - } - } - for _, bf := range p.VersionMetadata.Backup { - if strings.HasPrefix(bf, "/") { + for _, b := range p.VersionMetadata.Backup { + if strings.HasPrefix(b, "/") { return util.NewInvalidArgumentErrorf("backup file contains leading forward slash") } } @@ -344,3 +351,12 @@ func (p *Package) Desc() string { } return buf.String() } + +func (p *Package) Files() string { + var buf bytes.Buffer + buf.WriteString("%FILES%\n") + for _, item := range p.FileMetadata.Files { + _, _ = fmt.Fprintf(&buf, "%s\n", item) + } + return buf.String() +} diff --git a/modules/packages/arch/metadata_test.go b/modules/packages/arch/metadata_test.go index ddb35ca837..16c1c1637d 100644 --- a/modules/packages/arch/metadata_test.go +++ b/modules/packages/arch/metadata_test.go @@ -12,7 +12,7 @@ import ( "testing/fstest" "time" - "code.gitea.io/gitea/modules/packages" + "forgejo.org/modules/packages" "github.com/mholt/archiver/v3" "github.com/stretchr/testify/require" @@ -344,8 +344,8 @@ func TestValidatePackageSpec(t *testing.T) { }) } -func TestDescString(t *testing.T) { - const pkgdesc = `%FILENAME% +func TestDescAndFileString(t *testing.T) { + const pkgDesc = `%FILENAME% zstd-1.5.5-1-x86_64.pkg.tar.zst %NAME% @@ -415,6 +415,12 @@ ninja dummy5 dummy6 +` + + const pkgFiles = `%FILES% +usr/ +usr/bin/ +usr/bin/zstd ` md := &Package{ @@ -441,7 +447,9 @@ dummy6 BuildDate: 1681646714, Packager: "Jelle van der Waa ", Arch: "x86_64", + Files: []string{"usr/", "usr/bin/", "usr/bin/zstd"}, }, } - require.Equal(t, pkgdesc, md.Desc()) + require.Equal(t, pkgDesc, md.Desc()) + require.Equal(t, pkgFiles, md.Files()) } diff --git a/modules/packages/cargo/parser.go b/modules/packages/cargo/parser.go index 36cd44df84..f2c75538b5 100644 --- a/modules/packages/cargo/parser.go +++ b/modules/packages/cargo/parser.go @@ -9,8 +9,8 @@ import ( "io" "regexp" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/json" + "forgejo.org/modules/validation" "github.com/hashicorp/go-version" ) @@ -96,7 +96,7 @@ func parsePackage(r io.Reader) (*Package, error) { Target *string `json:"target"` Kind string `json:"kind"` Registry *string `json:"registry"` - ExplicitNameInToml string `json:"explicit_name_in_toml"` + ExplicitNameInToml *string `json:"explicit_name_in_toml"` } `json:"deps"` Features map[string][]string `json:"features"` Authors []string `json:"authors"` @@ -136,8 +136,16 @@ func parsePackage(r io.Reader) (*Package, error) { dependencies := make([]*Dependency, 0, len(meta.Deps)) for _, dep := range meta.Deps { + name := dep.Name + packageName := dep.ExplicitNameInToml + // If the explicit_name_in_toml field is set, the package is renamed and + // should be set accordingly. + if dep.ExplicitNameInToml != nil { + name = *dep.ExplicitNameInToml + packageName = &dep.Name + } dependencies = append(dependencies, &Dependency{ - Name: dep.Name, + Name: name, Req: dep.VersionReq, Features: dep.Features, Optional: dep.Optional, @@ -145,6 +153,7 @@ func parsePackage(r io.Reader) (*Package, error) { Target: dep.Target, Kind: dep.Kind, Registry: dep.Registry, + Package: packageName, }) } diff --git a/modules/packages/cargo/parser_test.go b/modules/packages/cargo/parser_test.go index 4b357cb869..d2470fab42 100644 --- a/modules/packages/cargo/parser_test.go +++ b/modules/packages/cargo/parser_test.go @@ -22,7 +22,7 @@ const ( ) func TestParsePackage(t *testing.T) { - createPackage := func(name, version string) io.Reader { + createPackage := func(name, version, dependency string) io.Reader { metadata := `{ "name":"` + name + `", "vers":"` + version + `", @@ -32,7 +32,7 @@ func TestParsePackage(t *testing.T) { { "name":"dep", "version_req":"1.0" - } + }` + dependency + ` ], "homepage":"` + homepage + `", "license":"` + license + `" @@ -48,7 +48,7 @@ func TestParsePackage(t *testing.T) { t.Run("InvalidName", func(t *testing.T) { for _, name := range []string{"", "0test", "-test", "_test", strings.Repeat("a", 65)} { - data := createPackage(name, "1.0.0") + data := createPackage(name, "1.0.0", "") cp, err := ParsePackage(data) assert.Nil(t, cp) @@ -58,7 +58,7 @@ func TestParsePackage(t *testing.T) { t.Run("InvalidVersion", func(t *testing.T) { for _, version := range []string{"", "1.", "-1.0", "1.0.0/1"} { - data := createPackage("test", version) + data := createPackage("test", version, "") cp, err := ParsePackage(data) assert.Nil(t, cp) @@ -67,7 +67,7 @@ func TestParsePackage(t *testing.T) { }) t.Run("Valid", func(t *testing.T) { - data := createPackage("test", "1.0.0") + data := createPackage("test", "1.0.0", "") cp, err := ParsePackage(data) assert.NotNil(t, cp) @@ -84,4 +84,25 @@ func TestParsePackage(t *testing.T) { content, _ := io.ReadAll(cp.Content) assert.Equal(t, "test", string(content)) }) + + t.Run("Renamed dependency", func(t *testing.T) { + data := createPackage("test", "1.0.0", `, {"name":"v4l2-sys", "version":"0.3.0", "explicit_name_in_toml":"v4l2-sys-mit"}`) + + cp, err := ParsePackage(data) + assert.NotNil(t, cp) + require.NoError(t, err) + + assert.Equal(t, "test", cp.Name) + assert.Equal(t, "1.0.0", cp.Version) + assert.Equal(t, description, cp.Metadata.Description) + assert.Equal(t, []string{author}, cp.Metadata.Authors) + assert.Len(t, cp.Metadata.Dependencies, 2) + assert.Equal(t, "dep", cp.Metadata.Dependencies[0].Name) + assert.Equal(t, "v4l2-sys-mit", cp.Metadata.Dependencies[1].Name) + assert.Equal(t, "v4l2-sys", *cp.Metadata.Dependencies[1].Package) + assert.Equal(t, homepage, cp.Metadata.ProjectURL) + assert.Equal(t, license, cp.Metadata.License) + content, _ := io.ReadAll(cp.Content) + assert.Equal(t, "test", string(content)) + }) } diff --git a/modules/packages/chef/metadata.go b/modules/packages/chef/metadata.go index a1c91870c2..951606bbc5 100644 --- a/modules/packages/chef/metadata.go +++ b/modules/packages/chef/metadata.go @@ -10,9 +10,9 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/json" + "forgejo.org/modules/util" + "forgejo.org/modules/validation" ) const ( diff --git a/modules/packages/composer/metadata.go b/modules/packages/composer/metadata.go index 2c2e9ebf27..940309b769 100644 --- a/modules/packages/composer/metadata.go +++ b/modules/packages/composer/metadata.go @@ -10,9 +10,9 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/json" + "forgejo.org/modules/util" + "forgejo.org/modules/validation" "github.com/hashicorp/go-version" ) @@ -48,6 +48,7 @@ type Metadata struct { Homepage string `json:"homepage,omitempty"` License Licenses `json:"license,omitempty"` Authors []Author `json:"authors,omitempty"` + Bin []string `json:"bin,omitempty"` Autoload map[string]any `json:"autoload,omitempty"` AutoloadDev map[string]any `json:"autoload-dev,omitempty"` Extra map[string]any `json:"extra,omitempty"` diff --git a/modules/packages/composer/metadata_test.go b/modules/packages/composer/metadata_test.go index 2bdb23965b..e2bbff4e58 100644 --- a/modules/packages/composer/metadata_test.go +++ b/modules/packages/composer/metadata_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - "code.gitea.io/gitea/modules/json" + "forgejo.org/modules/json" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/modules/packages/conan/conaninfo_parser.go b/modules/packages/conan/conaninfo_parser.go index de11dbee45..6027e51401 100644 --- a/modules/packages/conan/conaninfo_parser.go +++ b/modules/packages/conan/conaninfo_parser.go @@ -8,7 +8,7 @@ import ( "io" "strings" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/util" ) // Conaninfo represents infos of a Conan package diff --git a/modules/packages/conan/reference.go b/modules/packages/conan/reference.go index 58f268bd48..0b863240cb 100644 --- a/modules/packages/conan/reference.go +++ b/modules/packages/conan/reference.go @@ -8,8 +8,8 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/log" + "forgejo.org/modules/util" ) const ( diff --git a/modules/packages/conda/metadata.go b/modules/packages/conda/metadata.go index 76ba95eace..f61cc61c2a 100644 --- a/modules/packages/conda/metadata.go +++ b/modules/packages/conda/metadata.go @@ -10,10 +10,10 @@ import ( "io" "strings" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/validation" - "code.gitea.io/gitea/modules/zstd" + "forgejo.org/modules/json" + "forgejo.org/modules/util" + "forgejo.org/modules/validation" + "forgejo.org/modules/zstd" ) var ( diff --git a/modules/packages/conda/metadata_test.go b/modules/packages/conda/metadata_test.go index 25b0295157..959f9c4727 100644 --- a/modules/packages/conda/metadata_test.go +++ b/modules/packages/conda/metadata_test.go @@ -10,7 +10,7 @@ import ( "io" "testing" - "code.gitea.io/gitea/modules/zstd" + "forgejo.org/modules/zstd" "github.com/dsnet/compress/bzip2" "github.com/stretchr/testify/assert" diff --git a/modules/packages/container/metadata.go b/modules/packages/container/metadata.go index 2a41fb9105..ec9d834357 100644 --- a/modules/packages/container/metadata.go +++ b/modules/packages/container/metadata.go @@ -8,9 +8,9 @@ import ( "io" "strings" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/packages/container/helm" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/json" + "forgejo.org/modules/packages/container/helm" + "forgejo.org/modules/validation" oci "github.com/opencontainers/image-spec/specs-go/v1" ) diff --git a/modules/packages/container/metadata_test.go b/modules/packages/container/metadata_test.go index 930cf48f68..6c8c6ea5b9 100644 --- a/modules/packages/container/metadata_test.go +++ b/modules/packages/container/metadata_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "code.gitea.io/gitea/modules/packages/container/helm" + "forgejo.org/modules/packages/container/helm" oci "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/assert" diff --git a/modules/packages/content_store.go b/modules/packages/content_store.go index da93e6cf6b..f4578d91e0 100644 --- a/modules/packages/content_store.go +++ b/modules/packages/content_store.go @@ -9,9 +9,9 @@ import ( "path" "strings" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/storage" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/setting" + "forgejo.org/modules/storage" + "forgejo.org/modules/util" ) // BlobHash256Key is the key to address a blob content @@ -37,8 +37,8 @@ func (s *ContentStore) ShouldServeDirect() bool { return setting.Packages.Storage.MinioConfig.ServeDirect } -func (s *ContentStore) GetServeDirectURL(key BlobHash256Key, filename string) (*url.URL, error) { - return s.store.URL(KeyToRelativePath(key), filename) +func (s *ContentStore) GetServeDirectURL(key BlobHash256Key, filename string, reqParams url.Values) (*url.URL, error) { + return s.store.URL(KeyToRelativePath(key), filename, reqParams) } // FIXME: Workaround to be removed in v1.20 diff --git a/modules/packages/cran/metadata.go b/modules/packages/cran/metadata.go index 0b0bfb07c6..547fe87ccb 100644 --- a/modules/packages/cran/metadata.go +++ b/modules/packages/cran/metadata.go @@ -13,7 +13,7 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/util" ) const ( diff --git a/modules/packages/debian/metadata.go b/modules/packages/debian/metadata.go index e76db63975..e44801654b 100644 --- a/modules/packages/debian/metadata.go +++ b/modules/packages/debian/metadata.go @@ -12,9 +12,9 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/validation" - "code.gitea.io/gitea/modules/zstd" + "forgejo.org/modules/util" + "forgejo.org/modules/validation" + "forgejo.org/modules/zstd" "github.com/blakesmith/ar" "github.com/ulikunitz/xz" diff --git a/modules/packages/debian/metadata_test.go b/modules/packages/debian/metadata_test.go index 6f6c469989..cfcbc57ee0 100644 --- a/modules/packages/debian/metadata_test.go +++ b/modules/packages/debian/metadata_test.go @@ -10,7 +10,7 @@ import ( "io" "testing" - "code.gitea.io/gitea/modules/zstd" + "forgejo.org/modules/zstd" "github.com/blakesmith/ar" "github.com/stretchr/testify/assert" diff --git a/modules/packages/goproxy/metadata.go b/modules/packages/goproxy/metadata.go index 40f7d20508..2dae4100e7 100644 --- a/modules/packages/goproxy/metadata.go +++ b/modules/packages/goproxy/metadata.go @@ -10,7 +10,7 @@ import ( "path" "strings" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/util" ) const ( diff --git a/modules/packages/hashed_buffer.go b/modules/packages/hashed_buffer.go index 4ab45edcec..93c693efc9 100644 --- a/modules/packages/hashed_buffer.go +++ b/modules/packages/hashed_buffer.go @@ -6,7 +6,7 @@ package packages import ( "io" - "code.gitea.io/gitea/modules/util/filebuffer" + "forgejo.org/modules/util/filebuffer" ) // HashedSizeReader provide methods to read, sum hashes and a Size method @@ -75,7 +75,7 @@ func (b *HashedBuffer) Write(p []byte) (int, error) { return b.combinedWriter.Write(p) } -// Sums gets the MD5, SHA1, SHA256 and SHA512 checksums of the data -func (b *HashedBuffer) Sums() (hashMD5, hashSHA1, hashSHA256, hashSHA512 []byte) { +// Sums gets the MD5, SHA1, SHA256, SHA512 and BLAKE2B checksums of the data +func (b *HashedBuffer) Sums() (hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b []byte) { return b.hash.Sums() } diff --git a/modules/packages/hashed_buffer_test.go b/modules/packages/hashed_buffer_test.go index ed5267cd6f..879038988f 100644 --- a/modules/packages/hashed_buffer_test.go +++ b/modules/packages/hashed_buffer_test.go @@ -21,9 +21,10 @@ func TestHashedBuffer(t *testing.T) { HashSHA1 string HashSHA256 string HashSHA512 string + hashBlake2b string }{ - {5, "test", "098f6bcd4621d373cade4e832627b4f6", "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff"}, - {5, "testtest", "05a671c66aefea124cc08b76ea6d30bb", "51abb9636078defbf888d8457a7c76f85c8f114c", "37268335dd6931045bdcdf92623ff819a64244b53d0e746d438797349d4da578", "125d6d03b32c84d492747f79cf0bf6e179d287f341384eb5d6d3197525ad6be8e6df0116032935698f99a09e265073d1d6c32c274591bf1d0a20ad67cba921bc"}, + {5, "test", "098f6bcd4621d373cade4e832627b4f6", "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", "a71079d42853dea26e453004338670a53814b78137ffbed07603a41d76a483aa9bc33b582f77d30a65e6f29a896c0411f38312e1d66e0bf16386c86a89bea572"}, + {5, "testtest", "05a671c66aefea124cc08b76ea6d30bb", "51abb9636078defbf888d8457a7c76f85c8f114c", "37268335dd6931045bdcdf92623ff819a64244b53d0e746d438797349d4da578", "125d6d03b32c84d492747f79cf0bf6e179d287f341384eb5d6d3197525ad6be8e6df0116032935698f99a09e265073d1d6c32c274591bf1d0a20ad67cba921bc", "372a53b95f46e775b973031e40b844f24389657019f7b7540a9f0496f4ead4a2e4b050909664611fb0f4b7c7e92c3c04c84787be7f6b8edf7bf6bc31856b6c76"}, } for _, c := range cases { @@ -36,11 +37,12 @@ func TestHashedBuffer(t *testing.T) { require.NoError(t, err) assert.Equal(t, c.Data, string(data)) - hashMD5, hashSHA1, hashSHA256, hashSHA512 := buf.Sums() + hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b := buf.Sums() assert.Equal(t, c.HashMD5, hex.EncodeToString(hashMD5)) assert.Equal(t, c.HashSHA1, hex.EncodeToString(hashSHA1)) assert.Equal(t, c.HashSHA256, hex.EncodeToString(hashSHA256)) assert.Equal(t, c.HashSHA512, hex.EncodeToString(hashSHA512)) + assert.Equal(t, c.hashBlake2b, hex.EncodeToString(hashBlake2b)) require.NoError(t, buf.Close()) } diff --git a/modules/packages/helm/metadata.go b/modules/packages/helm/metadata.go index 421fc5e725..19a30c5ffa 100644 --- a/modules/packages/helm/metadata.go +++ b/modules/packages/helm/metadata.go @@ -9,8 +9,8 @@ import ( "io" "strings" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/util" + "forgejo.org/modules/validation" "github.com/hashicorp/go-version" "gopkg.in/yaml.v3" diff --git a/modules/packages/maven/metadata.go b/modules/packages/maven/metadata.go index 42aa250718..bc0dc0155e 100644 --- a/modules/packages/maven/metadata.go +++ b/modules/packages/maven/metadata.go @@ -7,7 +7,8 @@ import ( "encoding/xml" "io" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/util" + "forgejo.org/modules/validation" "golang.org/x/net/html/charset" ) @@ -49,8 +50,16 @@ type pomStruct struct { Version string `xml:"version"` Scope string `xml:"scope"` } `xml:"dependencies>dependency"` + Parent struct { + GroupID string `xml:"groupId"` + ArtifactID string `xml:"artifactId"` + Version string `xml:"version"` + RelativePath string `xml:"relativePath"` + } `xml:"parent"` } +var ErrNoGroupID = util.NewInvalidArgumentErrorf("group ID is missing") + // ParsePackageMetaData parses the metadata of a pom file func ParsePackageMetaData(r io.Reader) (*Metadata, error) { var pom pomStruct @@ -65,6 +74,17 @@ func ParsePackageMetaData(r io.Reader) (*Metadata, error) { pom.URL = "" } + groupID := pom.GroupID + + if groupID == "" { + // If a project inherits from a parent project, the groupId element is optional. + // Refer to: https://maven.apache.org/pom.html#Inheritance + if pom.Parent.GroupID == "" { + return nil, ErrNoGroupID + } + groupID = pom.Parent.GroupID + } + licenses := make([]string, 0, len(pom.Licenses)) for _, l := range pom.Licenses { if l.Name != "" { @@ -82,7 +102,7 @@ func ParsePackageMetaData(r io.Reader) (*Metadata, error) { } return &Metadata{ - GroupID: pom.GroupID, + GroupID: groupID, ArtifactID: pom.ArtifactID, Name: pom.Name, Description: pom.Description, diff --git a/modules/packages/maven/metadata_test.go b/modules/packages/maven/metadata_test.go index d0093013f9..3b087989e6 100644 --- a/modules/packages/maven/metadata_test.go +++ b/modules/packages/maven/metadata_test.go @@ -14,6 +14,7 @@ import ( const ( groupID = "org.gitea" + parentGroupID = "org.gitea.parent" artifactID = "my-project" version = "1.0.1" name = "My Gitea Project" @@ -27,6 +28,11 @@ const ( const pomContent = ` + + ` + parentGroupID + ` + parent-project + 1.0.0 + ` + groupID + ` ` + artifactID + ` ` + version + ` @@ -47,6 +53,24 @@ const pomContent = ` ` +const pomWithParentGroupID = ` + + + ` + parentGroupID + ` + parent-project + 1.0.0 + + + ` + artifactID + ` + ` + version + ` +` + +const pomWithMissingGroupID = ` + + ` + artifactID + ` + ` + version + ` +` + func TestParsePackageMetaData(t *testing.T) { t.Run("InvalidFile", func(t *testing.T) { m, err := ParsePackageMetaData(strings.NewReader("")) @@ -87,4 +111,19 @@ func TestParsePackageMetaData(t *testing.T) { require.NoError(t, err) assert.NotNil(t, m) }) + + t.Run("UseParentGroupID", func(t *testing.T) { + m, err := ParsePackageMetaData(strings.NewReader(pomWithParentGroupID)) + require.NoError(t, err) + assert.NotNil(t, m) + + assert.Equal(t, parentGroupID, m.GroupID) + }) + + t.Run("MissingGroupIDThrowsError", func(t *testing.T) { + m, err := ParsePackageMetaData(strings.NewReader(pomWithMissingGroupID)) + assert.Nil(t, m) + require.Error(t, err) + assert.Equal(t, ErrNoGroupID, err) + }) } diff --git a/modules/packages/multi_hasher.go b/modules/packages/multi_hasher.go index 83a4b5b7af..d2d9a759a8 100644 --- a/modules/packages/multi_hasher.go +++ b/modules/packages/multi_hasher.go @@ -12,28 +12,32 @@ import ( "errors" "hash" "io" + + "golang.org/x/crypto/blake2b" ) const ( - marshaledSizeMD5 = 92 - marshaledSizeSHA1 = 96 - marshaledSizeSHA256 = 108 - marshaledSizeSHA512 = 204 + marshaledSizeMD5 = 92 + marshaledSizeSHA1 = 96 + marshaledSizeSHA256 = 108 + marshaledSizeSHA512 = 204 + marshaledSizeBlake2b = 213 - marshaledSize = marshaledSizeMD5 + marshaledSizeSHA1 + marshaledSizeSHA256 + marshaledSizeSHA512 + marshaledSize = marshaledSizeMD5 + marshaledSizeSHA1 + marshaledSizeSHA256 + marshaledSizeSHA512 + marshaledSizeBlake2b ) // HashSummer provide a Sums method type HashSummer interface { - Sums() (hashMD5, hashSHA1, hashSHA256, hashSHA512 []byte) + Sums() (hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b []byte) } // MultiHasher calculates multiple checksums type MultiHasher struct { - md5 hash.Hash - sha1 hash.Hash - sha256 hash.Hash - sha512 hash.Hash + md5 hash.Hash + sha1 hash.Hash + sha256 hash.Hash + sha512 hash.Hash + blake2b hash.Hash combinedWriter io.Writer } @@ -44,14 +48,16 @@ func NewMultiHasher() *MultiHasher { sha1 := sha1.New() sha256 := sha256.New() sha512 := sha512.New() + blake2b, _ := blake2b.New512(nil) - combinedWriter := io.MultiWriter(md5, sha1, sha256, sha512) + combinedWriter := io.MultiWriter(md5, sha1, sha256, sha512, blake2b) return &MultiHasher{ md5, sha1, sha256, sha512, + blake2b, combinedWriter, } } @@ -74,12 +80,17 @@ func (h *MultiHasher) MarshalBinary() ([]byte, error) { if err != nil { return nil, err } + blake2bBytes, err := h.blake2b.(encoding.BinaryMarshaler).MarshalBinary() + if err != nil { + return nil, err + } b := make([]byte, 0, marshaledSize) b = append(b, md5Bytes...) b = append(b, sha1Bytes...) b = append(b, sha256Bytes...) b = append(b, sha512Bytes...) + b = append(b, blake2bBytes...) return b, nil } @@ -104,7 +115,12 @@ func (h *MultiHasher) UnmarshalBinary(b []byte) error { } b = b[marshaledSizeSHA256:] - return h.sha512.(encoding.BinaryUnmarshaler).UnmarshalBinary(b[:marshaledSizeSHA512]) + if err := h.sha512.(encoding.BinaryUnmarshaler).UnmarshalBinary(b[:marshaledSizeSHA512]); err != nil { + return err + } + + b = b[marshaledSizeSHA512:] + return h.blake2b.(encoding.BinaryUnmarshaler).UnmarshalBinary(b[:marshaledSizeBlake2b]) } // Write implements io.Writer @@ -113,10 +129,11 @@ func (h *MultiHasher) Write(p []byte) (int, error) { } // Sums gets the MD5, SHA1, SHA256 and SHA512 checksums of the data -func (h *MultiHasher) Sums() (hashMD5, hashSHA1, hashSHA256, hashSHA512 []byte) { +func (h *MultiHasher) Sums() (hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b []byte) { hashMD5 = h.md5.Sum(nil) hashSHA1 = h.sha1.Sum(nil) hashSHA256 = h.sha256.Sum(nil) hashSHA512 = h.sha512.Sum(nil) - return hashMD5, hashSHA1, hashSHA256, hashSHA512 + hashBlake2b = h.blake2b.Sum(nil) + return hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b } diff --git a/modules/packages/multi_hasher_test.go b/modules/packages/multi_hasher_test.go index ca333cb0a4..e5a32fc02c 100644 --- a/modules/packages/multi_hasher_test.go +++ b/modules/packages/multi_hasher_test.go @@ -12,10 +12,11 @@ import ( ) const ( - expectedMD5 = "e3bef03c5f3b7f6b3ab3e3053ed71e9c" - expectedSHA1 = "060b3b99f88e96085b4a68e095bc9e3d1d91e1bc" - expectedSHA256 = "6ccce4863b70f258d691f59609d31b4502e1ba5199942d3bc5d35d17a4ce771d" - expectedSHA512 = "7f70e439ba8c52025c1f06cdf6ae443c4b8ed2e90059cdb9bbbf8adf80846f185a24acca9245b128b226d61753b0d7ed46580a69c8999eeff3bc13a4d0bd816c" + expectedMD5 = "e3bef03c5f3b7f6b3ab3e3053ed71e9c" + expectedSHA1 = "060b3b99f88e96085b4a68e095bc9e3d1d91e1bc" + expectedSHA256 = "6ccce4863b70f258d691f59609d31b4502e1ba5199942d3bc5d35d17a4ce771d" + expectedSHA512 = "7f70e439ba8c52025c1f06cdf6ae443c4b8ed2e90059cdb9bbbf8adf80846f185a24acca9245b128b226d61753b0d7ed46580a69c8999eeff3bc13a4d0bd816c" + expectedBlake2b = "b3c3ad15c7e6cca543d651add9427727ffb525120eb23264ee35f16f408a369b599d4404a52d29f642fc0d869f9b55581b60e4e8b9b74997182705d3dcb01edb" ) func TestMultiHasherSums(t *testing.T) { @@ -23,12 +24,13 @@ func TestMultiHasherSums(t *testing.T) { h := NewMultiHasher() h.Write([]byte("gitea")) - hashMD5, hashSHA1, hashSHA256, hashSHA512 := h.Sums() + hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b := h.Sums() assert.Equal(t, expectedMD5, hex.EncodeToString(hashMD5)) assert.Equal(t, expectedSHA1, hex.EncodeToString(hashSHA1)) assert.Equal(t, expectedSHA256, hex.EncodeToString(hashSHA256)) assert.Equal(t, expectedSHA512, hex.EncodeToString(hashSHA512)) + assert.Equal(t, expectedBlake2b, hex.EncodeToString(hashBlake2b)) }) t.Run("State", func(t *testing.T) { @@ -44,11 +46,12 @@ func TestMultiHasherSums(t *testing.T) { h2.Write([]byte("ea")) - hashMD5, hashSHA1, hashSHA256, hashSHA512 := h2.Sums() + hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b := h2.Sums() assert.Equal(t, expectedMD5, hex.EncodeToString(hashMD5)) assert.Equal(t, expectedSHA1, hex.EncodeToString(hashSHA1)) assert.Equal(t, expectedSHA256, hex.EncodeToString(hashSHA256)) assert.Equal(t, expectedSHA512, hex.EncodeToString(hashSHA512)) + assert.Equal(t, expectedBlake2b, hex.EncodeToString(hashBlake2b)) }) } diff --git a/modules/packages/npm/creator.go b/modules/packages/npm/creator.go index 7d3d7cd6b5..ed163d30ac 100644 --- a/modules/packages/npm/creator.go +++ b/modules/packages/npm/creator.go @@ -14,9 +14,9 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/json" + "forgejo.org/modules/util" + "forgejo.org/modules/validation" "github.com/hashicorp/go-version" ) diff --git a/modules/packages/npm/creator_test.go b/modules/packages/npm/creator_test.go index b2cf1aae0e..5cbaf0d865 100644 --- a/modules/packages/npm/creator_test.go +++ b/modules/packages/npm/creator_test.go @@ -10,7 +10,7 @@ import ( "strings" "testing" - "code.gitea.io/gitea/modules/json" + "forgejo.org/modules/json" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/modules/packages/nuget/metadata.go b/modules/packages/nuget/metadata.go index 1e98ddffde..126a0ad494 100644 --- a/modules/packages/nuget/metadata.go +++ b/modules/packages/nuget/metadata.go @@ -13,8 +13,8 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/util" + "forgejo.org/modules/validation" "github.com/hashicorp/go-version" ) @@ -57,12 +57,21 @@ type Package struct { // Metadata represents the metadata of a Nuget package type Metadata struct { + Title string `json:"title,omitempty"` + Language string `json:"language,omitempty"` Description string `json:"description,omitempty"` ReleaseNotes string `json:"release_notes,omitempty"` Readme string `json:"readme,omitempty"` Authors string `json:"authors,omitempty"` + Owners string `json:"owners,omitempty"` + Copyright string `json:"copyright,omitempty"` ProjectURL string `json:"project_url,omitempty"` RepositoryURL string `json:"repository_url,omitempty"` + LicenseURL string `json:"license_url,omitempty"` + IconURL string `json:"icon_url,omitempty"` + MinClientVersion string `json:"min_client_version,omitempty"` + Tags string `json:"tags,omitempty"` + DevelopmentDependency bool `json:"development_dependency,omitempty"` RequireLicenseAcceptance bool `json:"require_license_acceptance"` Dependencies map[string][]Dependency `json:"dependencies,omitempty"` } @@ -77,13 +86,22 @@ type Dependency struct { type nuspecPackage struct { Metadata struct { ID string `xml:"id"` + Title string `xml:"title"` + Language string `xml:"language"` Version string `xml:"version"` Authors string `xml:"authors"` + Owners string `xml:"owners"` + Copyright string `xml:"copyright"` + DevelopmentDependency bool `xml:"developmentDependency"` RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"` ProjectURL string `xml:"projectUrl"` + LicenseURL string `xml:"licenseUrl"` + IconURL string `xml:"iconUrl"` Description string `xml:"description"` ReleaseNotes string `xml:"releaseNotes"` Readme string `xml:"readme"` + Tags string `xml:"tags"` + MinClientVersion string `xml:"minClientVersion,attr"` PackageTypes struct { PackageType []struct { Name string `xml:"name,attr"` @@ -167,11 +185,20 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) { } m := &Metadata{ + Title: p.Metadata.Title, + Language: p.Metadata.Language, Description: p.Metadata.Description, ReleaseNotes: p.Metadata.ReleaseNotes, Authors: p.Metadata.Authors, + Owners: p.Metadata.Owners, + Copyright: p.Metadata.Copyright, ProjectURL: p.Metadata.ProjectURL, RepositoryURL: p.Metadata.Repository.URL, + LicenseURL: p.Metadata.LicenseURL, + IconURL: p.Metadata.IconURL, + MinClientVersion: p.Metadata.MinClientVersion, + Tags: p.Metadata.Tags, + DevelopmentDependency: p.Metadata.DevelopmentDependency, RequireLicenseAcceptance: p.Metadata.RequireLicenseAcceptance, Dependencies: make(map[string][]Dependency), } diff --git a/modules/packages/nuget/metadata_test.go b/modules/packages/nuget/metadata_test.go index ecce052be4..8a34f1a5ae 100644 --- a/modules/packages/nuget/metadata_test.go +++ b/modules/packages/nuget/metadata_test.go @@ -13,14 +13,22 @@ import ( ) const ( - id = "System.Gitea" + id = "System.Forgejo" + title = "Package Title" + language = "Package Language" semver = "1.0.1" - authors = "Gitea Authors" - projectURL = "https://gitea.io" + authors = "Forgejo Authors" + owners = "Package Owners" + copyright = "Package Copyright" + projectURL = "https://forgejo.org" + licenseURL = "https://forgejo.org/docs/latest/license/" + iconURL = "https://codeberg.org/forgejo/governance/raw/branch/main/branding/logo/forgejo.png" description = "Package Description" releaseNotes = "Package Release Notes" readme = "Readme" - repositoryURL = "https://gitea.io/gitea/gitea" + tags = "tag_1 tag_2 tag_3" + minClientVersion = "1.0.0.0" + repositoryURL = "https://codeberg.org/forgejo" targetFramework = ".NETStandard2.1" dependencyID = "System.Text.Json" dependencyVersion = "5.0.0" @@ -28,16 +36,24 @@ const ( const nuspecContent = ` - + ` + id + ` + ` + title + ` + ` + language + ` ` + semver + ` ` + authors + ` + ` + owners + ` + ` + copyright + ` + true true ` + projectURL + ` + ` + licenseURL + ` + ` + iconURL + ` ` + description + ` ` + releaseNotes + ` README.md + ` + tags + ` @@ -142,12 +158,22 @@ func TestParsePackageMetaData(t *testing.T) { assert.Equal(t, DependencyPackage, np.PackageType) assert.Equal(t, id, np.ID) + assert.Equal(t, title, np.Metadata.Title) + assert.Equal(t, language, np.Metadata.Language) assert.Equal(t, semver, np.Version) assert.Equal(t, authors, np.Metadata.Authors) + assert.Equal(t, owners, np.Metadata.Owners) + assert.Equal(t, copyright, np.Metadata.Copyright) + assert.True(t, np.Metadata.DevelopmentDependency) + assert.True(t, np.Metadata.RequireLicenseAcceptance) assert.Equal(t, projectURL, np.Metadata.ProjectURL) + assert.Equal(t, licenseURL, np.Metadata.LicenseURL) + assert.Equal(t, iconURL, np.Metadata.IconURL) assert.Equal(t, description, np.Metadata.Description) assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes) assert.Equal(t, readme, np.Metadata.Readme) + assert.Equal(t, tags, np.Metadata.Tags) + assert.Equal(t, minClientVersion, np.Metadata.MinClientVersion) assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL) assert.Len(t, np.Metadata.Dependencies, 1) assert.Contains(t, np.Metadata.Dependencies, targetFramework) diff --git a/modules/packages/nuget/symbol_extractor.go b/modules/packages/nuget/symbol_extractor.go index 81bf0371a0..992ade7e8f 100644 --- a/modules/packages/nuget/symbol_extractor.go +++ b/modules/packages/nuget/symbol_extractor.go @@ -13,8 +13,8 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/modules/packages" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/packages" + "forgejo.org/modules/util" ) var ( diff --git a/modules/packages/pub/metadata.go b/modules/packages/pub/metadata.go index afb464e462..f8afdf7218 100644 --- a/modules/packages/pub/metadata.go +++ b/modules/packages/pub/metadata.go @@ -10,8 +10,8 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/util" + "forgejo.org/modules/validation" "github.com/hashicorp/go-version" "gopkg.in/yaml.v3" diff --git a/modules/packages/rpm/metadata.go b/modules/packages/rpm/metadata.go index f4f78c2cab..4af9af620f 100644 --- a/modules/packages/rpm/metadata.go +++ b/modules/packages/rpm/metadata.go @@ -8,10 +8,10 @@ import ( "io" "strings" - "code.gitea.io/gitea/modules/timeutil" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/timeutil" + "forgejo.org/modules/validation" - "github.com/sassoftware/go-rpmutils" + "code.forgejo.org/forgejo/go-rpmutils" ) const ( @@ -78,11 +78,12 @@ type FileMetadata struct { } type Entry struct { - Name string `json:"name" xml:"name,attr"` - Flags string `json:"flags,omitempty" xml:"flags,attr,omitempty"` - Version string `json:"version,omitempty" xml:"ver,attr,omitempty"` - Epoch string `json:"epoch,omitempty" xml:"epoch,attr,omitempty"` - Release string `json:"release,omitempty" xml:"rel,attr,omitempty"` + Name string `json:"name" xml:"name,attr"` + Flags string `json:"flags,omitempty" xml:"flags,attr,omitempty"` + AltFlags uint32 `json:"alt_flags,omitempty" xml:"alt_flags,attr,omitempty"` + Version string `json:"version,omitempty" xml:"ver,attr,omitempty"` + Epoch string `json:"epoch,omitempty" xml:"epoch,attr,omitempty"` + Release string `json:"release,omitempty" xml:"rel,attr,omitempty"` } type File struct { @@ -98,7 +99,7 @@ type Changelog struct { } // ParsePackage parses the RPM package file -func ParsePackage(r io.Reader) (*Package, error) { +func ParsePackage(r io.Reader, repoType string) (*Package, error) { rpm, err := rpmutils.ReadRpm(r) if err != nil { return nil, err @@ -138,10 +139,10 @@ func ParsePackage(r io.Reader) (*Package, error) { InstalledSize: getUInt64(rpm.Header, rpmutils.SIZE), ArchiveSize: getUInt64(rpm.Header, rpmutils.SIG_PAYLOADSIZE), - Provides: getEntries(rpm.Header, rpmutils.PROVIDENAME, rpmutils.PROVIDEVERSION, rpmutils.PROVIDEFLAGS), - Requires: getEntries(rpm.Header, rpmutils.REQUIRENAME, rpmutils.REQUIREVERSION, rpmutils.REQUIREFLAGS), - Conflicts: getEntries(rpm.Header, rpmutils.CONFLICTNAME, rpmutils.CONFLICTVERSION, rpmutils.CONFLICTFLAGS), - Obsoletes: getEntries(rpm.Header, rpmutils.OBSOLETENAME, rpmutils.OBSOLETEVERSION, rpmutils.OBSOLETEFLAGS), + Provides: getEntries(rpm.Header, rpmutils.PROVIDENAME, rpmutils.PROVIDEVERSION, rpmutils.PROVIDEFLAGS, repoType), + Requires: getEntries(rpm.Header, rpmutils.REQUIRENAME, rpmutils.REQUIREVERSION, rpmutils.REQUIREFLAGS, repoType), + Conflicts: getEntries(rpm.Header, rpmutils.CONFLICTNAME, rpmutils.CONFLICTVERSION, rpmutils.CONFLICTFLAGS, repoType), + Obsoletes: getEntries(rpm.Header, rpmutils.OBSOLETENAME, rpmutils.OBSOLETEVERSION, rpmutils.OBSOLETEFLAGS, repoType), Files: getFiles(rpm.Header), Changelogs: getChangelogs(rpm.Header), }, @@ -170,7 +171,7 @@ func getUInt64(h *rpmutils.RpmHeader, tag int) uint64 { return values[0] } -func getEntries(h *rpmutils.RpmHeader, namesTag, versionsTag, flagsTag int) []*Entry { +func getEntries(h *rpmutils.RpmHeader, namesTag, versionsTag, flagsTag int, repoType string) []*Entry { names, err := h.GetStrings(namesTag) if err != nil || len(names) == 0 { return nil @@ -188,43 +189,54 @@ func getEntries(h *rpmutils.RpmHeader, namesTag, versionsTag, flagsTag int) []*E } entries := make([]*Entry, 0, len(names)) - for i := range names { - e := &Entry{ - Name: names[i], - } - flags := flags[i] - if (flags&rpmutils.RPMSENSE_GREATER) != 0 && (flags&rpmutils.RPMSENSE_EQUAL) != 0 { - e.Flags = "GE" - } else if (flags&rpmutils.RPMSENSE_LESS) != 0 && (flags&rpmutils.RPMSENSE_EQUAL) != 0 { - e.Flags = "LE" - } else if (flags & rpmutils.RPMSENSE_GREATER) != 0 { - e.Flags = "GT" - } else if (flags & rpmutils.RPMSENSE_LESS) != 0 { - e.Flags = "LT" - } else if (flags & rpmutils.RPMSENSE_EQUAL) != 0 { - e.Flags = "EQ" - } - - version := versions[i] - if version != "" { - parts := strings.Split(version, "-") - - versionParts := strings.Split(parts[0], ":") - if len(versionParts) == 2 { - e.Version = versionParts[1] - e.Epoch = versionParts[0] - } else { - e.Version = versionParts[0] - e.Epoch = "0" + switch repoType { + case "rpm": + for i := range names { + e := &Entry{ + Name: names[i], } - if len(parts) > 1 { - e.Release = parts[1] + flags := flags[i] + if (flags&rpmutils.RPMSENSE_GREATER) != 0 && (flags&rpmutils.RPMSENSE_EQUAL) != 0 { + e.Flags = "GE" + } else if (flags&rpmutils.RPMSENSE_LESS) != 0 && (flags&rpmutils.RPMSENSE_EQUAL) != 0 { + e.Flags = "LE" + } else if (flags & rpmutils.RPMSENSE_GREATER) != 0 { + e.Flags = "GT" + } else if (flags & rpmutils.RPMSENSE_LESS) != 0 { + e.Flags = "LT" + } else if (flags & rpmutils.RPMSENSE_EQUAL) != 0 { + e.Flags = "EQ" } - } - entries = append(entries, e) + version := versions[i] + if version != "" { + parts := strings.Split(version, "-") + + versionParts := strings.Split(parts[0], ":") + if len(versionParts) == 2 { + e.Version = versionParts[1] + e.Epoch = versionParts[0] + } else { + e.Version = versionParts[0] + e.Epoch = "0" + } + + if len(parts) > 1 { + e.Release = parts[1] + } + } + entries = append(entries, e) + } + case "alt": + for i := range names { + e := &Entry{ + AltFlags: uint32(flags[i]), + } + e.Version = versions[i] + entries = append(entries, e) + } } return entries } diff --git a/modules/packages/rpm/metadata_test.go b/modules/packages/rpm/metadata_test.go index dc9b480723..05f53ea446 100644 --- a/modules/packages/rpm/metadata_test.go +++ b/modules/packages/rpm/metadata_test.go @@ -48,7 +48,7 @@ Mu0UFYgZ/bYnuvn/vz4wtCz8qMwsHUvP0PX3tbYFUctAPdrY6tiiDtcCddDECahx7SuVNP5dpmb5 zr, err := gzip.NewReader(bytes.NewReader(rpmPackageContent)) require.NoError(t, err) - p, err := ParsePackage(zr) + p, err := ParsePackage(zr, "rpm") assert.NotNil(t, p) require.NoError(t, err) diff --git a/modules/packages/rubygems/marshal.go b/modules/packages/rubygems/marshal.go index 4e6a5fc5f8..191efc7c0e 100644 --- a/modules/packages/rubygems/marshal.go +++ b/modules/packages/rubygems/marshal.go @@ -9,7 +9,7 @@ import ( "io" "reflect" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/util" ) const ( diff --git a/modules/packages/rubygems/metadata.go b/modules/packages/rubygems/metadata.go index 8a9794860e..6d021a17ab 100644 --- a/modules/packages/rubygems/metadata.go +++ b/modules/packages/rubygems/metadata.go @@ -10,8 +10,8 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/util" + "forgejo.org/modules/validation" "gopkg.in/yaml.v3" ) diff --git a/modules/packages/swift/metadata.go b/modules/packages/swift/metadata.go index 24c4262ab7..34fc4f1784 100644 --- a/modules/packages/swift/metadata.go +++ b/modules/packages/swift/metadata.go @@ -11,9 +11,9 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/json" + "forgejo.org/modules/util" + "forgejo.org/modules/validation" "github.com/hashicorp/go-version" ) diff --git a/modules/packages/vagrant/metadata.go b/modules/packages/vagrant/metadata.go index 6789533339..24684249b7 100644 --- a/modules/packages/vagrant/metadata.go +++ b/modules/packages/vagrant/metadata.go @@ -9,8 +9,8 @@ import ( "io" "strings" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/json" + "forgejo.org/modules/validation" ) const ( diff --git a/modules/packages/vagrant/metadata_test.go b/modules/packages/vagrant/metadata_test.go index f467781a08..f1950685be 100644 --- a/modules/packages/vagrant/metadata_test.go +++ b/modules/packages/vagrant/metadata_test.go @@ -10,7 +10,7 @@ import ( "io" "testing" - "code.gitea.io/gitea/modules/json" + "forgejo.org/modules/json" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/modules/pprof/pprof.go b/modules/pprof/pprof.go index c611c14270..d46790458e 100644 --- a/modules/pprof/pprof.go +++ b/modules/pprof/pprof.go @@ -9,7 +9,7 @@ import ( "runtime" "runtime/pprof" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) // DumpMemProfileForUsername dumps a memory profile at pprofDataPath as memprofile__ diff --git a/modules/private/actions.go b/modules/private/actions.go index 311a283650..8e4b44c226 100644 --- a/modules/private/actions.go +++ b/modules/private/actions.go @@ -6,7 +6,7 @@ package private import ( "context" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" ) type GenerateTokenRequest struct { diff --git a/modules/private/forgejo_actions.go b/modules/private/forgejo_actions.go deleted file mode 100644 index 133d5e253f..0000000000 --- a/modules/private/forgejo_actions.go +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT - -package private - -import ( - "context" - - "code.gitea.io/gitea/modules/setting" -) - -type ActionsRunnerRegisterRequest struct { - Token string - Scope string - Labels []string - Name string - Version string -} - -func ActionsRunnerRegister(ctx context.Context, token, scope string, labels []string, name, version string) (string, ResponseExtra) { - reqURL := setting.LocalURL + "api/internal/actions/register" - - req := newInternalRequest(ctx, reqURL, "POST", ActionsRunnerRegisterRequest{ - Token: token, - Scope: scope, - Labels: labels, - Name: name, - Version: version, - }) - - resp, extra := requestJSONResp(req, &ResponseText{}) - return resp.Text, extra -} diff --git a/modules/private/hook.go b/modules/private/hook.go index 93cbcd469d..2d64c1dec9 100644 --- a/modules/private/hook.go +++ b/modules/private/hook.go @@ -9,10 +9,10 @@ import ( "net/url" "time" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/git/pushoptions" - "code.gitea.io/gitea/modules/repository" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/git" + "forgejo.org/modules/git/pushoptions" + "forgejo.org/modules/repository" + "forgejo.org/modules/setting" ) // Git environment variables diff --git a/modules/private/internal.go b/modules/private/internal.go index 9c330a24a8..65fddbbe6b 100644 --- a/modules/private/internal.go +++ b/modules/private/internal.go @@ -13,11 +13,11 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/httplib" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/proxyprotocol" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/httplib" + "forgejo.org/modules/json" + "forgejo.org/modules/log" + "forgejo.org/modules/proxyprotocol" + "forgejo.org/modules/setting" ) // Response is used for internal request response (for user message and error message) diff --git a/modules/private/key.go b/modules/private/key.go index dcd1714856..422ff16d9a 100644 --- a/modules/private/key.go +++ b/modules/private/key.go @@ -7,7 +7,7 @@ import ( "context" "fmt" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" ) // UpdatePublicKeyInRepo update public key and if necessary deploy key updates diff --git a/modules/private/mail.go b/modules/private/mail.go index 08de5b7e28..f6054f9c74 100644 --- a/modules/private/mail.go +++ b/modules/private/mail.go @@ -6,7 +6,7 @@ package private import ( "context" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" ) // Email structure holds a data for sending general emails diff --git a/modules/private/manager.go b/modules/private/manager.go index 6055e553bd..fa2e0b0d40 100644 --- a/modules/private/manager.go +++ b/modules/private/manager.go @@ -12,7 +12,7 @@ import ( "strconv" "time" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" ) // Shutdown calls the internal shutdown function diff --git a/modules/private/request.go b/modules/private/request.go index 58cd261239..b80167adb6 100644 --- a/modules/private/request.go +++ b/modules/private/request.go @@ -8,8 +8,8 @@ import ( "io" "net/http" - "code.gitea.io/gitea/modules/httplib" - "code.gitea.io/gitea/modules/json" + "forgejo.org/modules/httplib" + "forgejo.org/modules/json" ) // ResponseText is used to get the response as text, instead of parsing it as JSON. diff --git a/modules/private/restore_repo.go b/modules/private/restore_repo.go index 496209d3cb..2192d3048d 100644 --- a/modules/private/restore_repo.go +++ b/modules/private/restore_repo.go @@ -8,7 +8,7 @@ import ( "fmt" "time" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" ) // RestoreParams structure holds a data for restore repository diff --git a/modules/private/serv.go b/modules/private/serv.go index 480a446954..fb8496930e 100644 --- a/modules/private/serv.go +++ b/modules/private/serv.go @@ -8,10 +8,10 @@ import ( "fmt" "net/url" - asymkey_model "code.gitea.io/gitea/models/asymkey" - "code.gitea.io/gitea/models/perm" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/setting" + asymkey_model "forgejo.org/models/asymkey" + "forgejo.org/models/perm" + user_model "forgejo.org/models/user" + "forgejo.org/modules/setting" ) // KeyAndOwner is the response from ServNoCommand diff --git a/modules/process/manager.go b/modules/process/manager.go index 37098ad92f..062ee1482f 100644 --- a/modules/process/manager.go +++ b/modules/process/manager.go @@ -7,6 +7,7 @@ package process import ( "context" "runtime/pprof" + "runtime/trace" "strconv" "sync" "sync/atomic" @@ -126,6 +127,27 @@ func (pm *Manager) AddTypedContext(parent context.Context, description, processT return ctx, cancel, finished } +// AddTypedContextTimeout creates a new context and adds it as a process. Once the process is finished, finished must be called +// to remove the process from the process table. It should not be called until the process is finished but must always be called. +// +// cancel should be used to cancel the returned context, however it will not remove the process from the process table. +// finished will cancel the returned context and remove it from the process table. +// +// Most processes will not need to use the cancel function but there will be cases whereby you want to cancel the process but not immediately remove it from the +// process table. +func (pm *Manager) AddTypedContextTimeout(parent context.Context, timeout time.Duration, description, processType string, currentlyRunning bool) (ctx context.Context, cancel context.CancelFunc, finished FinishedFunc) { + if timeout <= 0 { + // it's meaningless to use timeout <= 0, and it must be a bug! so we must panic here to tell developers to make the timeout correct + panic("the timeout must be greater than zero, otherwise the context will be cancelled immediately") + } + + ctx, cancel = context.WithTimeout(parent, timeout) + + ctx, _, finished = pm.Add(ctx, description, cancel, processType, currentlyRunning) + + return ctx, cancel, finished +} + // AddContextTimeout creates a new context and add it as a process. Once the process is finished, finished must be called // to remove the process from the process table. It should not be called until the process is finished but must always be called. // @@ -135,16 +157,7 @@ func (pm *Manager) AddTypedContext(parent context.Context, description, processT // Most processes will not need to use the cancel function but there will be cases whereby you want to cancel the process but not immediately remove it from the // process table. func (pm *Manager) AddContextTimeout(parent context.Context, timeout time.Duration, description string) (ctx context.Context, cancel context.CancelFunc, finished FinishedFunc) { - if timeout <= 0 { - // it's meaningless to use timeout <= 0, and it must be a bug! so we must panic here to tell developers to make the timeout correct - panic("the timeout must be greater than zero, otherwise the context will be cancelled immediately") - } - - ctx, cancel = context.WithTimeout(parent, timeout) - - ctx, _, finished = pm.Add(ctx, description, cancel, NormalProcessType, true) - - return ctx, cancel, finished + return pm.AddTypedContextTimeout(parent, timeout, description, NormalProcessType, true) } // Add create a new process @@ -159,6 +172,8 @@ func (pm *Manager) Add(ctx context.Context, description string, cancel context.C parentPID = "" } + ctx, traceTask := trace.NewTask(ctx, processType) + process := &process{ PID: pid, ParentPID: parentPID, @@ -166,6 +181,7 @@ func (pm *Manager) Add(ctx context.Context, description string, cancel context.C Start: start, Cancel: cancel, Type: processType, + TraceTrask: traceTask, } var finished FinishedFunc @@ -218,6 +234,7 @@ func (pm *Manager) nextPID() (start time.Time, pid IDType) { } func (pm *Manager) remove(process *process) { + process.TraceTrask.End() deleted := false pm.mutex.Lock() diff --git a/modules/process/manager_stacktraces.go b/modules/process/manager_stacktraces.go index e260893113..a603e6a9f2 100644 --- a/modules/process/manager_stacktraces.go +++ b/modules/process/manager_stacktraces.go @@ -4,8 +4,8 @@ package process import ( + "bytes" "fmt" - "io" "runtime/pprof" "sort" "time" @@ -175,13 +175,12 @@ func (pm *Manager) ProcessStacktraces(flat, noSystem bool) ([]*Process, int, int // Now from within the lock we need to get the goroutines. // Why? If we release the lock then between between filling the above map and getting // the stacktraces another process could be created which would then look like a dead process below - reader, writer := io.Pipe() - defer reader.Close() - go func() { - err := pprof.Lookup("goroutine").WriteTo(writer, 0) - _ = writer.CloseWithError(err) - }() - stacks, err = profile.Parse(reader) + var buf bytes.Buffer + if err := pprof.Lookup("goroutine").WriteTo(&buf, 0); err != nil { + return nil, 0, 0, err + } + + stacks, err = profile.ParseData(buf.Bytes()) if err != nil { return nil, 0, 0, err } diff --git a/modules/process/manager_stacktraces_test.go b/modules/process/manager_stacktraces_test.go new file mode 100644 index 0000000000..936fab40d8 --- /dev/null +++ b/modules/process/manager_stacktraces_test.go @@ -0,0 +1,92 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package process + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestProcessStacktraces(t *testing.T) { + _, _, finish := GetManager().AddContext(t.Context(), "Normal process") + defer finish() + parentCtx, _, finish := GetManager().AddContext(t.Context(), "Children normal process") + defer finish() + _, _, finish = GetManager().AddContext(parentCtx, "Children process") + defer finish() + _, _, finish = GetManager().AddTypedContext(t.Context(), "System process", SystemProcessType, true) + defer finish() + + t.Run("No flat with no system process", func(t *testing.T) { + processes, processCount, _, err := GetManager().ProcessStacktraces(false, true) + require.NoError(t, err) + assert.Equal(t, 4, processCount) + assert.Len(t, processes, 2) + + assert.Equal(t, "Children normal process", processes[0].Description) + assert.Equal(t, NormalProcessType, processes[0].Type) + assert.Empty(t, processes[0].ParentPID) + assert.Len(t, processes[0].Children, 1) + + assert.Equal(t, "Children process", processes[0].Children[0].Description) + assert.Equal(t, processes[0].PID, processes[0].Children[0].ParentPID) + + assert.Equal(t, "Normal process", processes[1].Description) + assert.Equal(t, NormalProcessType, processes[1].Type) + assert.Empty(t, processes[1].ParentPID) + assert.Empty(t, processes[1].Children) + }) + + t.Run("Flat with no system process", func(t *testing.T) { + processes, processCount, _, err := GetManager().ProcessStacktraces(true, true) + require.NoError(t, err) + assert.Equal(t, 4, processCount) + assert.Len(t, processes, 3) + + assert.Equal(t, "Children process", processes[0].Description) + assert.Equal(t, NormalProcessType, processes[0].Type) + assert.Equal(t, processes[1].PID, processes[0].ParentPID) + assert.Empty(t, processes[0].Children) + + assert.Equal(t, "Children normal process", processes[1].Description) + assert.Equal(t, NormalProcessType, processes[1].Type) + assert.Empty(t, processes[1].ParentPID) + assert.Empty(t, processes[1].Children) + + assert.Equal(t, "Normal process", processes[2].Description) + assert.Equal(t, NormalProcessType, processes[2].Type) + assert.Empty(t, processes[2].ParentPID) + assert.Empty(t, processes[2].Children) + }) + + t.Run("System process", func(t *testing.T) { + processes, processCount, _, err := GetManager().ProcessStacktraces(false, false) + require.NoError(t, err) + assert.Equal(t, 4, processCount) + assert.Len(t, processes, 4) + + assert.Equal(t, "System process", processes[0].Description) + assert.Equal(t, SystemProcessType, processes[0].Type) + assert.Empty(t, processes[0].ParentPID) + assert.Empty(t, processes[0].Children) + + assert.Equal(t, "Children normal process", processes[1].Description) + assert.Equal(t, NormalProcessType, processes[1].Type) + assert.Empty(t, processes[1].ParentPID) + assert.Len(t, processes[1].Children, 1) + + assert.Equal(t, "Normal process", processes[2].Description) + assert.Equal(t, NormalProcessType, processes[2].Type) + assert.Empty(t, processes[2].ParentPID) + assert.Empty(t, processes[2].Children) + + // This is the "main" pid, testing code always runs in a goroutine. + assert.Equal(t, "(unassociated)", processes[3].Description) + assert.Equal(t, NoneProcessType, processes[3].Type) + assert.Empty(t, processes[3].ParentPID) + assert.Empty(t, processes[3].Children) + }) +} diff --git a/modules/process/manager_test.go b/modules/process/manager_test.go index 36b2a912ea..0d637c8acc 100644 --- a/modules/process/manager_test.go +++ b/modules/process/manager_test.go @@ -23,7 +23,7 @@ func TestGetManager(t *testing.T) { func TestManager_AddContext(t *testing.T) { pm := Manager{processMap: make(map[IDType]*process), next: 1} - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() p1Ctx, _, finished := pm.AddContext(ctx, "foo") @@ -42,7 +42,7 @@ func TestManager_AddContext(t *testing.T) { func TestManager_Cancel(t *testing.T) { pm := Manager{processMap: make(map[IDType]*process), next: 1} - ctx, _, finished := pm.AddContext(context.Background(), "foo") + ctx, _, finished := pm.AddContext(t.Context(), "foo") defer finished() pm.Cancel(GetPID(ctx)) @@ -54,7 +54,7 @@ func TestManager_Cancel(t *testing.T) { } finished() - ctx, cancel, finished := pm.AddContext(context.Background(), "foo") + ctx, cancel, finished := pm.AddContext(t.Context(), "foo") defer finished() cancel() @@ -70,7 +70,7 @@ func TestManager_Cancel(t *testing.T) { func TestManager_Remove(t *testing.T) { pm := Manager{processMap: make(map[IDType]*process), next: 1} - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() p1Ctx, _, finished := pm.AddContext(ctx, "foo") diff --git a/modules/process/manager_unix.go b/modules/process/manager_unix.go index c5be906b35..54dd6dc485 100644 --- a/modules/process/manager_unix.go +++ b/modules/process/manager_unix.go @@ -1,8 +1,6 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !windows - package process import ( diff --git a/modules/process/manager_windows.go b/modules/process/manager_windows.go deleted file mode 100644 index 44a84f2203..0000000000 --- a/modules/process/manager_windows.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build windows - -package process - -import ( - "os/exec" -) - -// SetSysProcAttribute sets the common SysProcAttrs for commands -func SetSysProcAttribute(cmd *exec.Cmd) { - // Do nothing -} diff --git a/modules/process/process.go b/modules/process/process.go index 06a28c4a60..8947eb252f 100644 --- a/modules/process/process.go +++ b/modules/process/process.go @@ -5,12 +5,14 @@ package process import ( "context" + "runtime/trace" "time" ) var ( SystemProcessType = "system" RequestProcessType = "request" + GitProcessType = "git" NormalProcessType = "normal" NoneProcessType = "none" ) @@ -23,6 +25,7 @@ type process struct { Start time.Time Cancel context.CancelFunc Type string + TraceTrask *trace.Task } // ToProcess converts a process to a externally usable Process diff --git a/modules/proxy/proxy.go b/modules/proxy/proxy.go index 1a6bdad7fb..8c460dba30 100644 --- a/modules/proxy/proxy.go +++ b/modules/proxy/proxy.go @@ -10,8 +10,8 @@ import ( "strings" "sync" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" "github.com/gobwas/glob" ) diff --git a/modules/proxyprotocol/conn.go b/modules/proxyprotocol/conn.go index f437f13683..beac5de120 100644 --- a/modules/proxyprotocol/conn.go +++ b/modules/proxyprotocol/conn.go @@ -14,7 +14,7 @@ import ( "sync" "time" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) var ( diff --git a/modules/public/public.go b/modules/public/public.go index abc6b46158..174936fd4a 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -12,12 +12,12 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/assetfs" - "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/httpcache" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/assetfs" + "forgejo.org/modules/container" + "forgejo.org/modules/httpcache" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" ) func CustomAssets() *assetfs.Layer { diff --git a/modules/public/public_test.go b/modules/public/public_test.go index 5e4bf5d671..4bfbb7ef31 100644 --- a/modules/public/public_test.go +++ b/modules/public/public_test.go @@ -6,7 +6,7 @@ package public import ( "testing" - "code.gitea.io/gitea/modules/container" + "forgejo.org/modules/container" "github.com/stretchr/testify/assert" ) diff --git a/modules/public/serve_dynamic.go b/modules/public/serve_dynamic.go index a668b17c34..e5bd89b1cd 100644 --- a/modules/public/serve_dynamic.go +++ b/modules/public/serve_dynamic.go @@ -6,8 +6,8 @@ package public import ( - "code.gitea.io/gitea/modules/assetfs" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/assetfs" + "forgejo.org/modules/setting" ) func BuiltinAssets() *assetfs.Layer { diff --git a/modules/public/serve_static.go b/modules/public/serve_static.go index e79085021e..e19bd976eb 100644 --- a/modules/public/serve_static.go +++ b/modules/public/serve_static.go @@ -8,8 +8,8 @@ package public import ( "time" - "code.gitea.io/gitea/modules/assetfs" - "code.gitea.io/gitea/modules/timeutil" + "forgejo.org/modules/assetfs" + "forgejo.org/modules/timeutil" ) var _ GzipBytesProvider = (*vfsgenÛ°CompressedFileInfo)(nil) diff --git a/modules/queue/base_channel.go b/modules/queue/base_channel.go index dd8ccb15f4..1be4edf144 100644 --- a/modules/queue/base_channel.go +++ b/modules/queue/base_channel.go @@ -9,7 +9,7 @@ import ( "sync" "time" - "code.gitea.io/gitea/modules/container" + "forgejo.org/modules/container" ) var errChannelClosed = errors.New("channel is closed") diff --git a/modules/queue/base_levelqueue.go b/modules/queue/base_levelqueue.go index efc57c9c9c..12c805c0be 100644 --- a/modules/queue/base_levelqueue.go +++ b/modules/queue/base_levelqueue.go @@ -7,10 +7,10 @@ import ( "context" "sync/atomic" - "code.gitea.io/gitea/modules/nosql" - "code.gitea.io/gitea/modules/queue/lqinternal" + "forgejo.org/modules/nosql" + "forgejo.org/modules/queue/lqinternal" - "gitea.com/lunny/levelqueue" + "code.forgejo.org/forgejo/levelqueue" "github.com/syndtr/goleveldb/leveldb" ) diff --git a/modules/queue/base_levelqueue_common.go b/modules/queue/base_levelqueue_common.go index 78d3b85a8a..8b4f35c47d 100644 --- a/modules/queue/base_levelqueue_common.go +++ b/modules/queue/base_levelqueue_common.go @@ -11,9 +11,9 @@ import ( "sync" "time" - "code.gitea.io/gitea/modules/nosql" + "forgejo.org/modules/nosql" - "gitea.com/lunny/levelqueue" + "code.forgejo.org/forgejo/levelqueue" "github.com/syndtr/goleveldb/leveldb" ) diff --git a/modules/queue/base_levelqueue_test.go b/modules/queue/base_levelqueue_test.go index b65b570c4b..0f02b9f3ee 100644 --- a/modules/queue/base_levelqueue_test.go +++ b/modules/queue/base_levelqueue_test.go @@ -6,10 +6,10 @@ package queue import ( "testing" - "code.gitea.io/gitea/modules/queue/lqinternal" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/queue/lqinternal" + "forgejo.org/modules/setting" - "gitea.com/lunny/levelqueue" + "code.forgejo.org/forgejo/levelqueue" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/syndtr/goleveldb/leveldb" diff --git a/modules/queue/base_levelqueue_unique.go b/modules/queue/base_levelqueue_unique.go index 968a4e98d4..91d2e68500 100644 --- a/modules/queue/base_levelqueue_unique.go +++ b/modules/queue/base_levelqueue_unique.go @@ -8,10 +8,10 @@ import ( "sync" "sync/atomic" - "code.gitea.io/gitea/modules/nosql" - "code.gitea.io/gitea/modules/queue/lqinternal" + "forgejo.org/modules/nosql" + "forgejo.org/modules/queue/lqinternal" - "gitea.com/lunny/levelqueue" + "code.forgejo.org/forgejo/levelqueue" "github.com/syndtr/goleveldb/leveldb" ) diff --git a/modules/queue/base_redis.go b/modules/queue/base_redis.go index 62df30f68f..ec3c6dc16d 100644 --- a/modules/queue/base_redis.go +++ b/modules/queue/base_redis.go @@ -8,9 +8,9 @@ import ( "sync" "time" - "code.gitea.io/gitea/modules/graceful" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/nosql" + "forgejo.org/modules/graceful" + "forgejo.org/modules/log" + "forgejo.org/modules/nosql" "github.com/redis/go-redis/v9" ) diff --git a/modules/queue/base_redis_test.go b/modules/queue/base_redis_test.go index fa1700dc2e..bf3ad5b97b 100644 --- a/modules/queue/base_redis_test.go +++ b/modules/queue/base_redis_test.go @@ -7,8 +7,8 @@ import ( "context" "testing" - "code.gitea.io/gitea/modules/queue/mock" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/queue/mock" + "forgejo.org/modules/setting" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/suite" diff --git a/modules/queue/base_redis_with_server_test.go b/modules/queue/base_redis_with_server_test.go index b73404f4e5..e1f552bfb2 100644 --- a/modules/queue/base_redis_with_server_test.go +++ b/modules/queue/base_redis_with_server_test.go @@ -7,8 +7,8 @@ import ( "testing" "time" - "code.gitea.io/gitea/modules/nosql" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/nosql" + "forgejo.org/modules/setting" "github.com/stretchr/testify/suite" ) diff --git a/modules/queue/base_test.go b/modules/queue/base_test.go index a5600fea63..caa930158c 100644 --- a/modules/queue/base_test.go +++ b/modules/queue/base_test.go @@ -18,11 +18,11 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error) q, err := newFn(cfg) require.NoError(t, err) - ctx := context.Background() + ctx := t.Context() _ = q.RemoveAll(ctx) cnt, err := q.Len(ctx) require.NoError(t, err) - assert.EqualValues(t, 0, cnt) + assert.Equal(t, 0, cnt) // push the first item err = q.PushItem(ctx, []byte("foo")) @@ -30,7 +30,7 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error) cnt, err = q.Len(ctx) require.NoError(t, err) - assert.EqualValues(t, 1, cnt) + assert.Equal(t, 1, cnt) // push a duplicate item err = q.PushItem(ctx, []byte("foo")) @@ -46,10 +46,10 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error) has, err := q.HasItem(ctx, []byte("foo")) require.NoError(t, err) if !isUnique { - assert.EqualValues(t, 2, cnt) + assert.Equal(t, 2, cnt) assert.False(t, has) // non-unique queues don't check for duplicates } else { - assert.EqualValues(t, 1, cnt) + assert.Equal(t, 1, cnt) assert.True(t, has) } @@ -60,18 +60,18 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error) // pop the first item (and the duplicate if non-unique) it, err := q.PopItem(ctx) require.NoError(t, err) - assert.EqualValues(t, "foo", string(it)) + assert.Equal(t, "foo", string(it)) if !isUnique { it, err = q.PopItem(ctx) require.NoError(t, err) - assert.EqualValues(t, "foo", string(it)) + assert.Equal(t, "foo", string(it)) } // pop another item it, err = q.PopItem(ctx) require.NoError(t, err) - assert.EqualValues(t, "bar", string(it)) + assert.Equal(t, "bar", string(it)) // pop an empty queue (timeout, cancel) ctxTimed, cancel := context.WithTimeout(ctx, 10*time.Millisecond) @@ -108,13 +108,13 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error) // remove all cnt, err = q.Len(ctx) require.NoError(t, err) - assert.EqualValues(t, cfg.Length, cnt) + assert.Equal(t, cfg.Length, cnt) _ = q.RemoveAll(ctx) cnt, err = q.Len(ctx) require.NoError(t, err) - assert.EqualValues(t, 0, cnt) + assert.Equal(t, 0, cnt) }) } @@ -122,12 +122,12 @@ func TestBaseDummy(t *testing.T) { q, err := newBaseDummy(&BaseConfig{}, true) require.NoError(t, err) - ctx := context.Background() + ctx := t.Context() require.NoError(t, q.PushItem(ctx, []byte("foo"))) cnt, err := q.Len(ctx) require.NoError(t, err) - assert.EqualValues(t, 0, cnt) + assert.Equal(t, 0, cnt) has, err := q.HasItem(ctx, []byte("foo")) require.NoError(t, err) diff --git a/modules/queue/config.go b/modules/queue/config.go index c5bc16b6f0..f736a5aa12 100644 --- a/modules/queue/config.go +++ b/modules/queue/config.go @@ -4,7 +4,7 @@ package queue import ( - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" ) type BaseConfig struct { diff --git a/modules/queue/manager.go b/modules/queue/manager.go index 8b964c0c28..8f1a93f273 100644 --- a/modules/queue/manager.go +++ b/modules/queue/manager.go @@ -8,8 +8,8 @@ import ( "sync" "time" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" ) // Manager is a manager for the queues created by "CreateXxxQueue" functions, these queues are called "managed queues". diff --git a/modules/queue/manager_test.go b/modules/queue/manager_test.go index a76c238752..fd5d21570c 100644 --- a/modules/queue/manager_test.go +++ b/modules/queue/manager_test.go @@ -4,22 +4,18 @@ package queue import ( - "context" "path/filepath" "testing" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" + "forgejo.org/modules/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestManager(t *testing.T) { - oldAppDataPath := setting.AppDataPath - setting.AppDataPath = t.TempDir() - defer func() { - setting.AppDataPath = oldAppDataPath - }() + defer test.MockVariableValue(&setting.AppDataPath, t.TempDir())() newQueueFromConfig := func(name, cfg string) (*WorkerPoolQueue[int], error) { cfgProvider, err := setting.NewConfigProviderFromData(cfg) @@ -49,7 +45,7 @@ CONN_STR = redis:// assert.Equal(t, filepath.Join(setting.AppDataPath, "queues/common"), q.baseConfig.DataFullDir) assert.Equal(t, 100000, q.baseConfig.Length) assert.Equal(t, 20, q.batchLength) - assert.Equal(t, "", q.baseConfig.ConnStr) + assert.Empty(t, q.baseConfig.ConnStr) assert.Equal(t, "default_queue", q.baseConfig.QueueFullName) assert.Equal(t, "default_queue_unique", q.baseConfig.SetFullName) assert.NotZero(t, q.GetWorkerMaxNumber()) @@ -81,7 +77,7 @@ MAX_WORKERS = 123 require.NoError(t, err) - q1 := createWorkerPoolQueue[string](context.Background(), "no-such", cfgProvider, nil, false) + q1 := createWorkerPoolQueue[string](t.Context(), "no-such", cfgProvider, nil, false) assert.Equal(t, "no-such", q1.GetName()) assert.Equal(t, "dummy", q1.GetType()) // no handler, so it becomes dummy assert.Equal(t, filepath.Join(setting.AppDataPath, "queues/dir1"), q1.baseConfig.DataFullDir) @@ -97,13 +93,13 @@ MAX_WORKERS = 123 assert.Equal(t, "string", q1.GetItemTypeName()) qid1 := GetManager().qidCounter - q2 := createWorkerPoolQueue(context.Background(), "sub", cfgProvider, func(s ...int) (unhandled []int) { return nil }, false) + q2 := createWorkerPoolQueue(t.Context(), "sub", cfgProvider, func(s ...int) (unhandled []int) { return nil }, false) assert.Equal(t, "sub", q2.GetName()) assert.Equal(t, "level", q2.GetType()) assert.Equal(t, filepath.Join(setting.AppDataPath, "queues/dir2"), q2.baseConfig.DataFullDir) assert.Equal(t, 102, q2.baseConfig.Length) assert.Equal(t, 22, q2.batchLength) - assert.Equal(t, "", q2.baseConfig.ConnStr) + assert.Empty(t, q2.baseConfig.ConnStr) assert.Equal(t, "sub_q2", q2.baseConfig.QueueFullName) assert.Equal(t, "sub_q2_u2", q2.baseConfig.SetFullName) assert.Equal(t, 123, q2.GetWorkerMaxNumber()) @@ -119,7 +115,7 @@ MAX_WORKERS = 123 assert.Equal(t, 120, q1.workerMaxNum) stop := runWorkerPoolQueue(q2) - require.NoError(t, GetManager().GetManagedQueue(qid2).FlushWithContext(context.Background(), 0)) - require.NoError(t, GetManager().FlushAll(context.Background(), 0)) + require.NoError(t, GetManager().GetManagedQueue(qid2).FlushWithContext(t.Context(), 0)) + require.NoError(t, GetManager().FlushAll(t.Context(), 0)) stop() } diff --git a/modules/queue/mock/redisuniversalclient.go b/modules/queue/mock/redisuniversalclient.go index 36e4b7cd5d..a19e639ac1 100644 --- a/modules/queue/mock/redisuniversalclient.go +++ b/modules/queue/mock/redisuniversalclient.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: code.gitea.io/gitea/modules/nosql (interfaces: RedisClient) +// Source: forgejo.org/modules/nosql (interfaces: RedisClient) // // Generated by this command: // -// mockgen -package mock -destination ./modules/queue/mock/redisuniversalclient.go code.gitea.io/gitea/modules/nosql RedisClient +// mockgen -package mock -destination ./modules/queue/mock/redisuniversalclient.go forgejo.org/modules/nosql RedisClient // // Package mock is a generated GoMock package. @@ -22,6 +22,7 @@ import ( type MockRedisClient struct { ctrl *gomock.Controller recorder *MockRedisClientMockRecorder + isgomock struct{} } // MockRedisClientMockRecorder is the mock recorder for MockRedisClient. @@ -56,38 +57,38 @@ func (mr *MockRedisClientMockRecorder) Close() *gomock.Call { } // DBSize mocks base method. -func (m *MockRedisClient) DBSize(arg0 context.Context) *redis.IntCmd { +func (m *MockRedisClient) DBSize(ctx context.Context) *redis.IntCmd { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DBSize", arg0) + ret := m.ctrl.Call(m, "DBSize", ctx) ret0, _ := ret[0].(*redis.IntCmd) return ret0 } // DBSize indicates an expected call of DBSize. -func (mr *MockRedisClientMockRecorder) DBSize(arg0 any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) DBSize(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DBSize", reflect.TypeOf((*MockRedisClient)(nil).DBSize), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DBSize", reflect.TypeOf((*MockRedisClient)(nil).DBSize), ctx) } // Decr mocks base method. -func (m *MockRedisClient) Decr(arg0 context.Context, arg1 string) *redis.IntCmd { +func (m *MockRedisClient) Decr(ctx context.Context, key string) *redis.IntCmd { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Decr", arg0, arg1) + ret := m.ctrl.Call(m, "Decr", ctx, key) ret0, _ := ret[0].(*redis.IntCmd) return ret0 } // Decr indicates an expected call of Decr. -func (mr *MockRedisClientMockRecorder) Decr(arg0, arg1 any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) Decr(ctx, key any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Decr", reflect.TypeOf((*MockRedisClient)(nil).Decr), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Decr", reflect.TypeOf((*MockRedisClient)(nil).Decr), ctx, key) } // Del mocks base method. -func (m *MockRedisClient) Del(arg0 context.Context, arg1 ...string) *redis.IntCmd { +func (m *MockRedisClient) Del(ctx context.Context, keys ...string) *redis.IntCmd { m.ctrl.T.Helper() - varargs := []any{arg0} - for _, a := range arg1 { + varargs := []any{ctx} + for _, a := range keys { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "Del", varargs...) @@ -96,17 +97,17 @@ func (m *MockRedisClient) Del(arg0 context.Context, arg1 ...string) *redis.IntCm } // Del indicates an expected call of Del. -func (mr *MockRedisClientMockRecorder) Del(arg0 any, arg1 ...any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) Del(ctx any, keys ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{arg0}, arg1...) + varargs := append([]any{ctx}, keys...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Del", reflect.TypeOf((*MockRedisClient)(nil).Del), varargs...) } // Exists mocks base method. -func (m *MockRedisClient) Exists(arg0 context.Context, arg1 ...string) *redis.IntCmd { +func (m *MockRedisClient) Exists(ctx context.Context, keys ...string) *redis.IntCmd { m.ctrl.T.Helper() - varargs := []any{arg0} - for _, a := range arg1 { + varargs := []any{ctx} + for _, a := range keys { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "Exists", varargs...) @@ -115,45 +116,45 @@ func (m *MockRedisClient) Exists(arg0 context.Context, arg1 ...string) *redis.In } // Exists indicates an expected call of Exists. -func (mr *MockRedisClientMockRecorder) Exists(arg0 any, arg1 ...any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) Exists(ctx any, keys ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{arg0}, arg1...) + varargs := append([]any{ctx}, keys...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockRedisClient)(nil).Exists), varargs...) } // FlushDB mocks base method. -func (m *MockRedisClient) FlushDB(arg0 context.Context) *redis.StatusCmd { +func (m *MockRedisClient) FlushDB(ctx context.Context) *redis.StatusCmd { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FlushDB", arg0) + ret := m.ctrl.Call(m, "FlushDB", ctx) ret0, _ := ret[0].(*redis.StatusCmd) return ret0 } // FlushDB indicates an expected call of FlushDB. -func (mr *MockRedisClientMockRecorder) FlushDB(arg0 any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) FlushDB(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlushDB", reflect.TypeOf((*MockRedisClient)(nil).FlushDB), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlushDB", reflect.TypeOf((*MockRedisClient)(nil).FlushDB), ctx) } // Get mocks base method. -func (m *MockRedisClient) Get(arg0 context.Context, arg1 string) *redis.StringCmd { +func (m *MockRedisClient) Get(ctx context.Context, key string) *redis.StringCmd { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Get", arg0, arg1) + ret := m.ctrl.Call(m, "Get", ctx, key) ret0, _ := ret[0].(*redis.StringCmd) return ret0 } // Get indicates an expected call of Get. -func (mr *MockRedisClientMockRecorder) Get(arg0, arg1 any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) Get(ctx, key any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRedisClient)(nil).Get), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRedisClient)(nil).Get), ctx, key) } // HDel mocks base method. -func (m *MockRedisClient) HDel(arg0 context.Context, arg1 string, arg2 ...string) *redis.IntCmd { +func (m *MockRedisClient) HDel(ctx context.Context, key string, fields ...string) *redis.IntCmd { m.ctrl.T.Helper() - varargs := []any{arg0, arg1} - for _, a := range arg2 { + varargs := []any{ctx, key} + for _, a := range fields { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "HDel", varargs...) @@ -162,31 +163,31 @@ func (m *MockRedisClient) HDel(arg0 context.Context, arg1 string, arg2 ...string } // HDel indicates an expected call of HDel. -func (mr *MockRedisClientMockRecorder) HDel(arg0, arg1 any, arg2 ...any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) HDel(ctx, key any, fields ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{arg0, arg1}, arg2...) + varargs := append([]any{ctx, key}, fields...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HDel", reflect.TypeOf((*MockRedisClient)(nil).HDel), varargs...) } // HKeys mocks base method. -func (m *MockRedisClient) HKeys(arg0 context.Context, arg1 string) *redis.StringSliceCmd { +func (m *MockRedisClient) HKeys(ctx context.Context, key string) *redis.StringSliceCmd { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "HKeys", arg0, arg1) + ret := m.ctrl.Call(m, "HKeys", ctx, key) ret0, _ := ret[0].(*redis.StringSliceCmd) return ret0 } // HKeys indicates an expected call of HKeys. -func (mr *MockRedisClientMockRecorder) HKeys(arg0, arg1 any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) HKeys(ctx, key any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HKeys", reflect.TypeOf((*MockRedisClient)(nil).HKeys), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HKeys", reflect.TypeOf((*MockRedisClient)(nil).HKeys), ctx, key) } // HSet mocks base method. -func (m *MockRedisClient) HSet(arg0 context.Context, arg1 string, arg2 ...any) *redis.IntCmd { +func (m *MockRedisClient) HSet(ctx context.Context, key string, values ...any) *redis.IntCmd { m.ctrl.T.Helper() - varargs := []any{arg0, arg1} - for _, a := range arg2 { + varargs := []any{ctx, key} + for _, a := range values { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "HSet", varargs...) @@ -195,73 +196,73 @@ func (m *MockRedisClient) HSet(arg0 context.Context, arg1 string, arg2 ...any) * } // HSet indicates an expected call of HSet. -func (mr *MockRedisClientMockRecorder) HSet(arg0, arg1 any, arg2 ...any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) HSet(ctx, key any, values ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{arg0, arg1}, arg2...) + varargs := append([]any{ctx, key}, values...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HSet", reflect.TypeOf((*MockRedisClient)(nil).HSet), varargs...) } // Incr mocks base method. -func (m *MockRedisClient) Incr(arg0 context.Context, arg1 string) *redis.IntCmd { +func (m *MockRedisClient) Incr(ctx context.Context, key string) *redis.IntCmd { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Incr", arg0, arg1) + ret := m.ctrl.Call(m, "Incr", ctx, key) ret0, _ := ret[0].(*redis.IntCmd) return ret0 } // Incr indicates an expected call of Incr. -func (mr *MockRedisClientMockRecorder) Incr(arg0, arg1 any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) Incr(ctx, key any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Incr", reflect.TypeOf((*MockRedisClient)(nil).Incr), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Incr", reflect.TypeOf((*MockRedisClient)(nil).Incr), ctx, key) } // LLen mocks base method. -func (m *MockRedisClient) LLen(arg0 context.Context, arg1 string) *redis.IntCmd { +func (m *MockRedisClient) LLen(ctx context.Context, key string) *redis.IntCmd { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "LLen", arg0, arg1) + ret := m.ctrl.Call(m, "LLen", ctx, key) ret0, _ := ret[0].(*redis.IntCmd) return ret0 } // LLen indicates an expected call of LLen. -func (mr *MockRedisClientMockRecorder) LLen(arg0, arg1 any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) LLen(ctx, key any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LLen", reflect.TypeOf((*MockRedisClient)(nil).LLen), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LLen", reflect.TypeOf((*MockRedisClient)(nil).LLen), ctx, key) } // LPop mocks base method. -func (m *MockRedisClient) LPop(arg0 context.Context, arg1 string) *redis.StringCmd { +func (m *MockRedisClient) LPop(ctx context.Context, key string) *redis.StringCmd { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "LPop", arg0, arg1) + ret := m.ctrl.Call(m, "LPop", ctx, key) ret0, _ := ret[0].(*redis.StringCmd) return ret0 } // LPop indicates an expected call of LPop. -func (mr *MockRedisClientMockRecorder) LPop(arg0, arg1 any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) LPop(ctx, key any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LPop", reflect.TypeOf((*MockRedisClient)(nil).LPop), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LPop", reflect.TypeOf((*MockRedisClient)(nil).LPop), ctx, key) } // Ping mocks base method. -func (m *MockRedisClient) Ping(arg0 context.Context) *redis.StatusCmd { +func (m *MockRedisClient) Ping(ctx context.Context) *redis.StatusCmd { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Ping", arg0) + ret := m.ctrl.Call(m, "Ping", ctx) ret0, _ := ret[0].(*redis.StatusCmd) return ret0 } // Ping indicates an expected call of Ping. -func (mr *MockRedisClientMockRecorder) Ping(arg0 any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) Ping(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockRedisClient)(nil).Ping), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockRedisClient)(nil).Ping), ctx) } // RPush mocks base method. -func (m *MockRedisClient) RPush(arg0 context.Context, arg1 string, arg2 ...any) *redis.IntCmd { +func (m *MockRedisClient) RPush(ctx context.Context, key string, values ...any) *redis.IntCmd { m.ctrl.T.Helper() - varargs := []any{arg0, arg1} - for _, a := range arg2 { + varargs := []any{ctx, key} + for _, a := range values { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "RPush", varargs...) @@ -270,17 +271,17 @@ func (m *MockRedisClient) RPush(arg0 context.Context, arg1 string, arg2 ...any) } // RPush indicates an expected call of RPush. -func (mr *MockRedisClientMockRecorder) RPush(arg0, arg1 any, arg2 ...any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) RPush(ctx, key any, values ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{arg0, arg1}, arg2...) + varargs := append([]any{ctx, key}, values...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RPush", reflect.TypeOf((*MockRedisClient)(nil).RPush), varargs...) } // SAdd mocks base method. -func (m *MockRedisClient) SAdd(arg0 context.Context, arg1 string, arg2 ...any) *redis.IntCmd { +func (m *MockRedisClient) SAdd(ctx context.Context, key string, members ...any) *redis.IntCmd { m.ctrl.T.Helper() - varargs := []any{arg0, arg1} - for _, a := range arg2 { + varargs := []any{ctx, key} + for _, a := range members { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "SAdd", varargs...) @@ -289,31 +290,31 @@ func (m *MockRedisClient) SAdd(arg0 context.Context, arg1 string, arg2 ...any) * } // SAdd indicates an expected call of SAdd. -func (mr *MockRedisClientMockRecorder) SAdd(arg0, arg1 any, arg2 ...any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) SAdd(ctx, key any, members ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{arg0, arg1}, arg2...) + varargs := append([]any{ctx, key}, members...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SAdd", reflect.TypeOf((*MockRedisClient)(nil).SAdd), varargs...) } // SIsMember mocks base method. -func (m *MockRedisClient) SIsMember(arg0 context.Context, arg1 string, arg2 any) *redis.BoolCmd { +func (m *MockRedisClient) SIsMember(ctx context.Context, key string, member any) *redis.BoolCmd { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SIsMember", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "SIsMember", ctx, key, member) ret0, _ := ret[0].(*redis.BoolCmd) return ret0 } // SIsMember indicates an expected call of SIsMember. -func (mr *MockRedisClientMockRecorder) SIsMember(arg0, arg1, arg2 any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) SIsMember(ctx, key, member any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SIsMember", reflect.TypeOf((*MockRedisClient)(nil).SIsMember), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SIsMember", reflect.TypeOf((*MockRedisClient)(nil).SIsMember), ctx, key, member) } // SRem mocks base method. -func (m *MockRedisClient) SRem(arg0 context.Context, arg1 string, arg2 ...any) *redis.IntCmd { +func (m *MockRedisClient) SRem(ctx context.Context, key string, members ...any) *redis.IntCmd { m.ctrl.T.Helper() - varargs := []any{arg0, arg1} - for _, a := range arg2 { + varargs := []any{ctx, key} + for _, a := range members { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "SRem", varargs...) @@ -322,22 +323,22 @@ func (m *MockRedisClient) SRem(arg0 context.Context, arg1 string, arg2 ...any) * } // SRem indicates an expected call of SRem. -func (mr *MockRedisClientMockRecorder) SRem(arg0, arg1 any, arg2 ...any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) SRem(ctx, key any, members ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{arg0, arg1}, arg2...) + varargs := append([]any{ctx, key}, members...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SRem", reflect.TypeOf((*MockRedisClient)(nil).SRem), varargs...) } // Set mocks base method. -func (m *MockRedisClient) Set(arg0 context.Context, arg1 string, arg2 any, arg3 time.Duration) *redis.StatusCmd { +func (m *MockRedisClient) Set(ctx context.Context, key string, value any, expiration time.Duration) *redis.StatusCmd { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Set", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "Set", ctx, key, value, expiration) ret0, _ := ret[0].(*redis.StatusCmd) return ret0 } // Set indicates an expected call of Set. -func (mr *MockRedisClientMockRecorder) Set(arg0, arg1, arg2, arg3 any) *gomock.Call { +func (mr *MockRedisClientMockRecorder) Set(ctx, key, value, expiration any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockRedisClient)(nil).Set), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockRedisClient)(nil).Set), ctx, key, value, expiration) } diff --git a/modules/queue/queue.go b/modules/queue/queue.go index 56835014a5..f16b3c1f34 100644 --- a/modules/queue/queue.go +++ b/modules/queue/queue.go @@ -61,7 +61,7 @@ // func handler(items ...*mypkg.QueueItem) []*mypkg.QueueItem { ... } package queue -import "code.gitea.io/gitea/modules/util" +import "forgejo.org/modules/util" type HandlerFuncT[T any] func(...T) (unhandled []T) diff --git a/modules/queue/workergroup.go b/modules/queue/workergroup.go index ea4c0020c5..3fb821ce69 100644 --- a/modules/queue/workergroup.go +++ b/modules/queue/workergroup.go @@ -10,7 +10,7 @@ import ( "sync/atomic" "time" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) var ( diff --git a/modules/queue/workerqueue.go b/modules/queue/workerqueue.go index 041ce9a3f2..6a71fc4fb4 100644 --- a/modules/queue/workerqueue.go +++ b/modules/queue/workerqueue.go @@ -10,10 +10,10 @@ import ( "sync/atomic" "time" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/process" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/json" + "forgejo.org/modules/log" + "forgejo.org/modules/process" + "forgejo.org/modules/setting" ) // WorkerPoolQueue is a queue that uses a pool of workers to process items diff --git a/modules/queue/workerqueue_test.go b/modules/queue/workerqueue_test.go index 4cfe8ede97..8d907ed8cd 100644 --- a/modules/queue/workerqueue_test.go +++ b/modules/queue/workerqueue_test.go @@ -5,15 +5,14 @@ package queue import ( "bytes" - "context" "runtime" "strconv" "sync" "testing" "time" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/test" + "forgejo.org/modules/setting" + "forgejo.org/modules/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -59,15 +58,15 @@ func TestWorkerPoolQueueUnhandled(t *testing.T) { testRecorder.Record("push:%v", i) require.NoError(t, q.Push(i)) } - require.NoError(t, q.FlushWithContext(context.Background(), 0)) + require.NoError(t, q.FlushWithContext(t.Context(), 0)) stop() ok := true for i := 0; i < queueSetting.Length; i++ { if i%2 == 0 { - ok = ok && assert.EqualValues(t, 2, m[i], "test %s: item %d", t.Name(), i) + ok = ok && assert.Equal(t, 2, m[i], "test %s: item %d", t.Name(), i) } else { - ok = ok && assert.EqualValues(t, 1, m[i], "test %s: item %d", t.Name(), i) + ok = ok && assert.Equal(t, 1, m[i], "test %s: item %d", t.Name(), i) } } if !ok { @@ -167,7 +166,7 @@ func testWorkerPoolQueuePersistence(t *testing.T, queueSetting setting.QueueSett q, _ := newWorkerPoolQueueForTest("pr_patch_checker_test", queueSetting, testHandler, true) stop := runWorkerPoolQueue(q) - require.NoError(t, q.FlushWithContext(context.Background(), 0)) + require.NoError(t, q.FlushWithContext(t.Context(), 0)) stop() } @@ -175,7 +174,7 @@ func testWorkerPoolQueuePersistence(t *testing.T, queueSetting setting.QueueSett assert.NotEmpty(t, tasksQ1) assert.NotEmpty(t, tasksQ2) - assert.EqualValues(t, testCount, len(tasksQ1)+len(tasksQ2)) + assert.Equal(t, testCount, len(tasksQ1)+len(tasksQ2)) } func TestWorkerPoolQueueActiveWorkers(t *testing.T) { @@ -193,13 +192,13 @@ func TestWorkerPoolQueueActiveWorkers(t *testing.T) { } time.Sleep(50 * time.Millisecond) - assert.EqualValues(t, 1, q.GetWorkerNumber()) - assert.EqualValues(t, 1, q.GetWorkerActiveNumber()) + assert.Equal(t, 1, q.GetWorkerNumber()) + assert.Equal(t, 1, q.GetWorkerActiveNumber()) time.Sleep(500 * time.Millisecond) - assert.EqualValues(t, 1, q.GetWorkerNumber()) - assert.EqualValues(t, 0, q.GetWorkerActiveNumber()) + assert.Equal(t, 1, q.GetWorkerNumber()) + assert.Equal(t, 0, q.GetWorkerActiveNumber()) time.Sleep(workerIdleDuration) - assert.EqualValues(t, 1, q.GetWorkerNumber()) // there is at least one worker after the queue begins working + assert.Equal(t, 1, q.GetWorkerNumber()) // there is at least one worker after the queue begins working stop() q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 3, Length: 100}, handler, false) @@ -209,13 +208,13 @@ func TestWorkerPoolQueueActiveWorkers(t *testing.T) { } time.Sleep(50 * time.Millisecond) - assert.EqualValues(t, 3, q.GetWorkerNumber()) - assert.EqualValues(t, 3, q.GetWorkerActiveNumber()) + assert.Equal(t, 3, q.GetWorkerNumber()) + assert.Equal(t, 3, q.GetWorkerActiveNumber()) time.Sleep(500 * time.Millisecond) - assert.EqualValues(t, 3, q.GetWorkerNumber()) - assert.EqualValues(t, 0, q.GetWorkerActiveNumber()) + assert.Equal(t, 3, q.GetWorkerNumber()) + assert.Equal(t, 0, q.GetWorkerActiveNumber()) time.Sleep(workerIdleDuration) - assert.EqualValues(t, 1, q.GetWorkerNumber()) // there is at least one worker after the queue begins working + assert.Equal(t, 1, q.GetWorkerNumber()) // there is at least one worker after the queue begins working stop() } @@ -242,13 +241,13 @@ func TestWorkerPoolQueueShutdown(t *testing.T) { } <-handlerCalled time.Sleep(200 * time.Millisecond) // wait for a while to make sure all workers are active - assert.EqualValues(t, 4, q.GetWorkerActiveNumber()) + assert.Equal(t, 4, q.GetWorkerActiveNumber()) stop() // stop triggers shutdown - assert.EqualValues(t, 0, q.GetWorkerActiveNumber()) + assert.Equal(t, 0, q.GetWorkerActiveNumber()) // no item was ever handled, so we still get all of them again q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", qs, handler, false) - assert.EqualValues(t, 20, q.GetQueueItemNumber()) + assert.Equal(t, 20, q.GetQueueItemNumber()) } func TestWorkerPoolQueueWorkerIdleReset(t *testing.T) { diff --git a/modules/recaptcha/recaptcha.go b/modules/recaptcha/recaptcha.go index 1777d169c1..95b0a77a43 100644 --- a/modules/recaptcha/recaptcha.go +++ b/modules/recaptcha/recaptcha.go @@ -11,9 +11,9 @@ import ( "net/url" "strings" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/json" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" ) // Response is the structure of JSON returned from API diff --git a/modules/references/references.go b/modules/references/references.go index c61d06d5dc..7df5119393 100644 --- a/modules/references/references.go +++ b/modules/references/references.go @@ -11,10 +11,10 @@ import ( "strings" "sync" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup/mdstripper" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/log" + "forgejo.org/modules/markup/mdstripper" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" ) var ( @@ -32,7 +32,7 @@ var ( // issueNumericPattern matches string that references to a numeric issue, e.g. #1287 issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\'|\")([#!][0-9]+)(?:\s|$|\)|\]|\'|\"|[:;,.?!]\s|[:;,.?!]$)`) // issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234 - issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\"|\')([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$)|\"|\')`) + issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\"|\')([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$)|\"|\'|,)`) // crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository // e.g. org/repo#12345 crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) @@ -460,15 +460,17 @@ func findAllIssueReferencesBytes(content []byte, links []string) []*rawReference } parts := strings.Split(u.EscapedPath(), "/") // /user/repo/issues/3 - if len(parts) != 5 || parts[0] != "" { + // /user/repo/pulls/7/files/... + if len(parts) < 5 || parts[0] != "" { continue } var sep string - if parts[3] == "issues" { + switch parts[3] { + case "issues": sep = "#" - } else if parts[3] == "pulls" { + case "pulls": sep = "!" - } else { + default: continue } // Note: closing/reopening keywords not supported with URLs diff --git a/modules/references/references_test.go b/modules/references/references_test.go index ffa7f993e3..bb22c0bd59 100644 --- a/modules/references/references_test.go +++ b/modules/references/references_test.go @@ -7,7 +7,7 @@ import ( "regexp" "testing" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" "github.com/stretchr/testify/assert" ) @@ -46,7 +46,7 @@ owner/repo!123456789 contentBytes := []byte(test) convertFullHTMLReferencesToShortRefs(re, &contentBytes) result := string(contentBytes) - assert.EqualValues(t, expect, result) + assert.Equal(t, expect, result) } func TestFindAllIssueReferences(t *testing.T) { @@ -132,6 +132,30 @@ func TestFindAllIssueReferences(t *testing.T) { {203, "user4", "repo5", "203", true, XRefActionNone, nil, nil, ""}, }, }, + { + "This http://gitea.com:3000/user4/repo5/pulls/202#x yes.", + []testResult{ + {202, "user4", "repo5", "202", true, XRefActionNone, nil, nil, ""}, + }, + }, + { + "This http://gitea.com:3000/user4/repo5/pulls/202/commits yes.", + []testResult{ + {202, "user4", "repo5", "202", true, XRefActionNone, nil, nil, ""}, + }, + }, + { + "This http://gitea.com:3000/user4/repo5/pulls/202/files yes.", + []testResult{ + {202, "user4", "repo5", "202", true, XRefActionNone, nil, nil, ""}, + }, + }, + { + "This http://gitea.com:3000/user4/repo5/pulls/202/files#diff- yes.", + []testResult{ + {202, "user4", "repo5", "202", true, XRefActionNone, nil, nil, ""}, + }, + }, { "This http://GiTeA.COM:3000/user4/repo6/pulls/205 yes.", []testResult{ @@ -284,9 +308,9 @@ func testFixtures(t *testing.T, fixtures []testFixture, context string) { } expref := rawToIssueReferenceList(expraw) refs := FindAllIssueReferencesMarkdown(fixture.input) - assert.EqualValues(t, expref, refs, "[%s] Failed to parse: {%s}", context, fixture.input) + assert.Equal(t, expref, refs, "[%s] Failed to parse: {%s}", context, fixture.input) rawrefs := findAllIssueReferencesMarkdown(fixture.input) - assert.EqualValues(t, expraw, rawrefs, "[%s] Failed to parse: {%s}", context, fixture.input) + assert.Equal(t, expraw, rawrefs, "[%s] Failed to parse: {%s}", context, fixture.input) } // Restore for other tests that may rely on the original value @@ -295,7 +319,7 @@ func testFixtures(t *testing.T, fixtures []testFixture, context string) { func TestFindAllMentions(t *testing.T) { res := FindAllMentionsBytes([]byte("@tasha, @mike; @lucy: @john")) - assert.EqualValues(t, []RefSpan{ + assert.Equal(t, []RefSpan{ {Start: 0, End: 6}, {Start: 8, End: 13}, {Start: 15, End: 20}, @@ -466,6 +490,7 @@ func TestRegExp_issueAlphanumericPattern(t *testing.T) { "ABC-123:", "\"ABC-123\"", "'ABC-123'", + "ABC-123, unknown PR", } falseTestCases := []string{ "RC-08", @@ -557,7 +582,7 @@ func TestParseCloseKeywords(t *testing.T) { res := pat.FindAllStringSubmatch(test.match, -1) assert.Len(t, res, 1) assert.Len(t, res[0], 2) - assert.EqualValues(t, test.expected, res[0][1]) + assert.Equal(t, test.expected, res[0][1]) } } } diff --git a/modules/regexplru/regexplru.go b/modules/regexplru/regexplru.go index 8f66dcf3f7..b452094c16 100644 --- a/modules/regexplru/regexplru.go +++ b/modules/regexplru/regexplru.go @@ -6,7 +6,7 @@ package regexplru import ( "regexp" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" lru "github.com/hashicorp/golang-lru/v2" ) diff --git a/modules/regexplru/regexplru_test.go b/modules/regexplru/regexplru_test.go index 8c0c722336..6e15e88e14 100644 --- a/modules/regexplru/regexplru_test.go +++ b/modules/regexplru/regexplru_test.go @@ -19,9 +19,9 @@ func TestRegexpLru(t *testing.T) { require.NoError(t, err) assert.True(t, r.MatchString("a")) - assert.EqualValues(t, 1, lruCache.Len()) + assert.Equal(t, 1, lruCache.Len()) _, err = GetCompiled("(") require.Error(t, err) - assert.EqualValues(t, 2, lruCache.Len()) + assert.Equal(t, 2, lruCache.Len()) } diff --git a/modules/repository/branch.go b/modules/repository/branch.go index 2bf9930f19..59b5f9e7d5 100644 --- a/modules/repository/branch.go +++ b/modules/repository/branch.go @@ -7,14 +7,14 @@ import ( "context" "fmt" - "code.gitea.io/gitea/models/db" - git_model "code.gitea.io/gitea/models/git" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/gitrepo" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/timeutil" + "forgejo.org/models/db" + git_model "forgejo.org/models/git" + repo_model "forgejo.org/models/repo" + "forgejo.org/modules/container" + "forgejo.org/modules/git" + "forgejo.org/modules/gitrepo" + "forgejo.org/modules/log" + "forgejo.org/modules/timeutil" ) // SyncRepoBranches synchronizes branch table with repository branches diff --git a/modules/repository/branch_test.go b/modules/repository/branch_test.go index b98618a16b..31e27f222f 100644 --- a/modules/repository/branch_test.go +++ b/modules/repository/branch_test.go @@ -6,10 +6,10 @@ package repository import ( "testing" - "code.gitea.io/gitea/models/db" - git_model "code.gitea.io/gitea/models/git" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unittest" + "forgejo.org/models/db" + git_model "forgejo.org/models/git" + repo_model "forgejo.org/models/repo" + "forgejo.org/models/unittest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -28,5 +28,5 @@ func TestSyncRepoBranches(t *testing.T) { assert.Equal(t, "sha1", repo.ObjectFormatName) branch, err := git_model.GetBranch(db.DefaultContext, 1, "master") require.NoError(t, err) - assert.EqualValues(t, "master", branch.Name) + assert.Equal(t, "master", branch.Name) } diff --git a/modules/repository/collaborator.go b/modules/repository/collaborator.go index 17915d34b7..5a0c4451b7 100644 --- a/modules/repository/collaborator.go +++ b/modules/repository/collaborator.go @@ -6,11 +6,11 @@ package repository import ( "context" - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/perm" - access_model "code.gitea.io/gitea/models/perm/access" - repo_model "code.gitea.io/gitea/models/repo" - user_model "code.gitea.io/gitea/models/user" + "forgejo.org/models/db" + "forgejo.org/models/perm" + access_model "forgejo.org/models/perm/access" + repo_model "forgejo.org/models/repo" + user_model "forgejo.org/models/user" "xorm.io/builder" ) diff --git a/modules/repository/collaborator_test.go b/modules/repository/collaborator_test.go index 3844197bf1..dae173506b 100644 --- a/modules/repository/collaborator_test.go +++ b/modules/repository/collaborator_test.go @@ -6,14 +6,14 @@ package repository import ( "testing" - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/organization" - perm_model "code.gitea.io/gitea/models/perm" - access_model "code.gitea.io/gitea/models/perm/access" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unit" - "code.gitea.io/gitea/models/unittest" - user_model "code.gitea.io/gitea/models/user" + "forgejo.org/models/db" + "forgejo.org/models/organization" + perm_model "forgejo.org/models/perm" + access_model "forgejo.org/models/perm/access" + repo_model "forgejo.org/models/repo" + "forgejo.org/models/unit" + "forgejo.org/models/unittest" + user_model "forgejo.org/models/user" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -272,8 +272,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { // update team information and then check permission team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}) - err = organization.UpdateTeamUnits(db.DefaultContext, team, nil) - require.NoError(t, err) + unittest.AssertSuccessfulDelete(t, &organization.TeamUnit{TeamID: team.ID}) perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner) require.NoError(t, err) for _, unit := range repo.Units { diff --git a/modules/repository/commits.go b/modules/repository/commits.go index ede60429a1..261b6f7a22 100644 --- a/modules/repository/commits.go +++ b/modules/repository/commits.go @@ -1,4 +1,5 @@ // Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package repository @@ -9,13 +10,14 @@ import ( "net/url" "time" - "code.gitea.io/gitea/models/avatars" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/cache" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/gitea/modules/structs" + asymkey_model "forgejo.org/models/asymkey" + "forgejo.org/models/avatars" + user_model "forgejo.org/models/user" + "forgejo.org/modules/cache" + "forgejo.org/modules/git" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" + api "forgejo.org/modules/structs" ) // PushCommit represents a commit in a push operation. @@ -26,6 +28,8 @@ type PushCommit struct { AuthorName string CommitterEmail string CommitterName string + Signature *git.ObjectSignature + Verification *asymkey_model.ObjectVerification Timestamp time.Time } @@ -145,6 +149,32 @@ func (pc *PushCommits) AvatarLink(ctx context.Context, email string) string { return v } +// PushCommitToCommit transforms a PushCommit to a git.Commit type on a best effort basis. +// +// Attention: Converting a Commit to a PushCommit and converting back to a Commit will not result in an identical object! +func PushCommitToCommit(commit *PushCommit) (*git.Commit, error) { + id, err := git.NewIDFromString(commit.Sha1) + if err != nil { + return nil, err + } + return &git.Commit{ + ID: id, + Author: &git.Signature{ + Name: commit.AuthorName, + Email: commit.AuthorEmail, + When: commit.Timestamp, + }, + Committer: &git.Signature{ + Name: commit.CommitterName, + Email: commit.CommitterEmail, + When: commit.Timestamp, + }, + CommitMessage: commit.Message, + Signature: commit.Signature, + Parents: []git.ObjectID{}, + }, nil +} + // CommitToPushCommit transforms a git.Commit to PushCommit type. func CommitToPushCommit(commit *git.Commit) *PushCommit { return &PushCommit{ @@ -154,6 +184,7 @@ func CommitToPushCommit(commit *git.Commit) *PushCommit { AuthorName: commit.Author.Name, CommitterEmail: commit.Committer.Email, CommitterName: commit.Committer.Name, + Signature: commit.Signature, Timestamp: commit.Author.When, } } diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go index 82841b3268..4b6d4bfe51 100644 --- a/modules/repository/commits_test.go +++ b/modules/repository/commits_test.go @@ -1,20 +1,19 @@ // Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package repository import ( - "crypto/md5" - "fmt" "strconv" "testing" "time" - "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unittest" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/models/db" + repo_model "forgejo.org/models/repo" + "forgejo.org/models/unittest" + "forgejo.org/modules/git" + "forgejo.org/modules/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -65,9 +64,9 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { assert.Equal(t, "user2", payloadCommits[0].Committer.UserName) assert.Equal(t, "User2", payloadCommits[0].Author.Name) assert.Equal(t, "user2", payloadCommits[0].Author.UserName) - assert.EqualValues(t, []string{}, payloadCommits[0].Added) - assert.EqualValues(t, []string{}, payloadCommits[0].Removed) - assert.EqualValues(t, []string{"readme.md"}, payloadCommits[0].Modified) + assert.Equal(t, []string{}, payloadCommits[0].Added) + assert.Equal(t, []string{}, payloadCommits[0].Removed) + assert.Equal(t, []string{"readme.md"}, payloadCommits[0].Modified) assert.Equal(t, "27566bd", payloadCommits[1].ID) assert.Equal(t, "good signed commit (with not yet validated email)", payloadCommits[1].Message) @@ -76,9 +75,9 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { assert.Equal(t, "user2", payloadCommits[1].Committer.UserName) assert.Equal(t, "User2", payloadCommits[1].Author.Name) assert.Equal(t, "user2", payloadCommits[1].Author.UserName) - assert.EqualValues(t, []string{}, payloadCommits[1].Added) - assert.EqualValues(t, []string{}, payloadCommits[1].Removed) - assert.EqualValues(t, []string{"readme.md"}, payloadCommits[1].Modified) + assert.Equal(t, []string{}, payloadCommits[1].Added) + assert.Equal(t, []string{}, payloadCommits[1].Removed) + assert.Equal(t, []string{"readme.md"}, payloadCommits[1].Modified) assert.Equal(t, "5099b81", payloadCommits[2].ID) assert.Equal(t, "good signed commit", payloadCommits[2].Message) @@ -87,9 +86,9 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { assert.Equal(t, "user2", payloadCommits[2].Committer.UserName) assert.Equal(t, "User2", payloadCommits[2].Author.Name) assert.Equal(t, "user2", payloadCommits[2].Author.UserName) - assert.EqualValues(t, []string{"readme.md"}, payloadCommits[2].Added) - assert.EqualValues(t, []string{}, payloadCommits[2].Removed) - assert.EqualValues(t, []string{}, payloadCommits[2].Modified) + assert.Equal(t, []string{"readme.md"}, payloadCommits[2].Added) + assert.Equal(t, []string{}, payloadCommits[2].Removed) + assert.Equal(t, []string{}, payloadCommits[2].Modified) assert.Equal(t, "69554a6", headCommit.ID) assert.Equal(t, "not signed commit", headCommit.Message) @@ -98,9 +97,9 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { assert.Equal(t, "user2", headCommit.Committer.UserName) assert.Equal(t, "User2", headCommit.Author.Name) assert.Equal(t, "user2", headCommit.Author.UserName) - assert.EqualValues(t, []string{}, headCommit.Added) - assert.EqualValues(t, []string{}, headCommit.Removed) - assert.EqualValues(t, []string{"readme.md"}, headCommit.Modified) + assert.Equal(t, []string{}, headCommit.Added) + assert.Equal(t, []string{}, headCommit.Removed) + assert.Equal(t, []string{"readme.md"}, headCommit.Modified) } func TestPushCommits_AvatarLink(t *testing.T) { @@ -126,18 +125,59 @@ func TestPushCommits_AvatarLink(t *testing.T) { }, } - setting.GravatarSource = "https://secure.gravatar.com/avatar" - setting.OfflineMode = true - assert.Equal(t, - "/avatars/avatar2?size="+strconv.Itoa(28*setting.Avatar.RenderedSizeFactor), + "/avatars/ab53a2911ddf9b4817ac01ddcd3d975f?size="+strconv.Itoa(28*setting.Avatar.RenderedSizeFactor), pushCommits.AvatarLink(db.DefaultContext, "user2@example.com")) assert.Equal(t, - fmt.Sprintf("https://secure.gravatar.com/avatar/%x?d=identicon&s=%d", md5.Sum([]byte("nonexistent@example.com")), 28*setting.Avatar.RenderedSizeFactor), + "/assets/img/avatar_default.png", pushCommits.AvatarLink(db.DefaultContext, "nonexistent@example.com")) } +func TestPushCommitToCommit(t *testing.T) { + now := time.Now() + sig := &git.Signature{ + Email: "example@example.com", + Name: "John Doe", + When: now, + } + const hexString = "0123456789abcdef0123456789abcdef01234567" + sha1, err := git.NewIDFromString(hexString) + require.NoError(t, err) + commit, err := PushCommitToCommit(&PushCommit{ + Sha1: sha1.String(), + Message: "Commit Message", + AuthorEmail: "example@example.com", + AuthorName: "John Doe", + CommitterEmail: "example@example.com", + CommitterName: "John Doe", + Signature: nil, + Timestamp: now, + }) + require.NoError(t, err) + assert.Equal(t, sha1, commit.ID) + assert.Equal(t, "Commit Message", commit.CommitMessage) + assert.Equal(t, sig, commit.Author) + assert.Equal(t, sig, commit.Committer) + assert.Nil(t, commit.Signature) +} + +func TestPushCommitToCommitInvalidSha(t *testing.T) { + now := time.Now() + const hexString = "012" + _, err := PushCommitToCommit(&PushCommit{ + Sha1: hexString, + Message: "Commit Message", + AuthorEmail: "example@example.com", + AuthorName: "John Doe", + CommitterEmail: "example@example.com", + CommitterName: "John Doe", + Signature: nil, + Timestamp: now, + }) + require.Error(t, err) +} + func TestCommitToPushCommit(t *testing.T) { now := time.Now() sig := &git.Signature{ diff --git a/modules/repository/create.go b/modules/repository/create.go index ca2150b972..060b995bc5 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -1,4 +1,5 @@ // Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package repository @@ -11,22 +12,22 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/models" - activities_model "code.gitea.io/gitea/models/activities" - "code.gitea.io/gitea/models/db" - git_model "code.gitea.io/gitea/models/git" - "code.gitea.io/gitea/models/organization" - "code.gitea.io/gitea/models/perm" - access_model "code.gitea.io/gitea/models/perm/access" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unit" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/models/webhook" - issue_indexer "code.gitea.io/gitea/modules/indexer/issues" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" + "forgejo.org/models" + activities_model "forgejo.org/models/activities" + "forgejo.org/models/db" + git_model "forgejo.org/models/git" + "forgejo.org/models/organization" + "forgejo.org/models/perm" + access_model "forgejo.org/models/perm/access" + repo_model "forgejo.org/models/repo" + "forgejo.org/models/unit" + user_model "forgejo.org/models/user" + "forgejo.org/models/webhook" + issue_indexer "forgejo.org/modules/indexer/issues" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" + api "forgejo.org/modules/structs" + "forgejo.org/modules/util" ) // CreateRepositoryByExample creates a repository for the user/organization. @@ -73,7 +74,8 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re } units := make([]repo_model.RepoUnit, 0, len(defaultUnits)) for _, tp := range defaultUnits { - if tp == unit.TypeIssues { + switch tp { + case unit.TypeIssues: units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, Type: tp, @@ -83,17 +85,18 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re EnableDependencies: setting.Service.DefaultEnableDependencies, }, }) - } else if tp == unit.TypePullRequests { + case unit.TypePullRequests: units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, Type: tp, Config: &repo_model.PullRequestsConfig{ AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, AllowFastForwardOnly: true, - DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle), - AllowRebaseUpdate: true, + DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle), + DefaultUpdateStyle: repo_model.UpdateStyle(setting.Repository.PullRequest.DefaultUpdateStyle), + AllowRebaseUpdate: true, }, }) - } else { + default: units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, Type: tp, @@ -239,6 +242,11 @@ func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibili e := db.GetEngine(ctx) + // If the repository was reported as abusive, a shadow copy should be created before first update. + if err := repo_model.IfNeededCreateShadowCopyForRepository(ctx, repo, true); err != nil { + return err + } + if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { return fmt.Errorf("update: %w", err) } diff --git a/modules/repository/create_test.go b/modules/repository/create_test.go index c743271c26..45f7f8e853 100644 --- a/modules/repository/create_test.go +++ b/modules/repository/create_test.go @@ -6,10 +6,10 @@ package repository import ( "testing" - activities_model "code.gitea.io/gitea/models/activities" - "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unittest" + activities_model "forgejo.org/models/activities" + "forgejo.org/models/db" + repo_model "forgejo.org/models/repo" + "forgejo.org/models/unittest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -42,5 +42,5 @@ func TestGetDirectorySize(t *testing.T) { size, err := getDirectorySize(repo.RepoPath()) require.NoError(t, err) - assert.EqualValues(t, size, repo.Size) + assert.Equal(t, size, repo.Size) } diff --git a/modules/repository/delete.go b/modules/repository/delete.go index 04af98beef..6fff16b406 100644 --- a/modules/repository/delete.go +++ b/modules/repository/delete.go @@ -6,9 +6,9 @@ package repository import ( "context" - "code.gitea.io/gitea/models/organization" - repo_model "code.gitea.io/gitea/models/repo" - user_model "code.gitea.io/gitea/models/user" + "forgejo.org/models/organization" + repo_model "forgejo.org/models/repo" + user_model "forgejo.org/models/user" ) // CanUserDelete returns true if user could delete the repository diff --git a/modules/repository/env.go b/modules/repository/env.go index e4f32092fc..110f6ca674 100644 --- a/modules/repository/env.go +++ b/modules/repository/env.go @@ -8,9 +8,9 @@ import ( "os" "strings" - repo_model "code.gitea.io/gitea/models/repo" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/setting" + repo_model "forgejo.org/models/repo" + user_model "forgejo.org/models/user" + "forgejo.org/modules/setting" ) // env keys for git hooks need diff --git a/modules/repository/fork.go b/modules/repository/fork.go index fbf0008716..42801fa80d 100644 --- a/modules/repository/fork.go +++ b/modules/repository/fork.go @@ -6,9 +6,9 @@ package repository import ( "context" - "code.gitea.io/gitea/models/organization" - repo_model "code.gitea.io/gitea/models/repo" - user_model "code.gitea.io/gitea/models/user" + "forgejo.org/models/organization" + repo_model "forgejo.org/models/repo" + user_model "forgejo.org/models/user" ) // CanUserForkRepo returns true if specified user can fork repository. diff --git a/modules/repository/hooks.go b/modules/repository/hooks.go index 95849789ab..0f5e3afc34 100644 --- a/modules/repository/hooks.go +++ b/modules/repository/hooks.go @@ -7,10 +7,9 @@ import ( "fmt" "os" "path/filepath" - "runtime" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" ) func getHookTemplates() (hookNames, hookTpls, giteaHookTpls []string) { @@ -146,10 +145,6 @@ func CreateDelegateHooks(repoPath string) (err error) { } func checkExecutable(filename string) bool { - // windows has no concept of a executable bit - if runtime.GOOS == "windows" { - return true - } fileInfo, err := os.Stat(filename) if err != nil { return false diff --git a/modules/repository/init.go b/modules/repository/init.go index 5f500c5233..7b1442be93 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -10,14 +10,14 @@ import ( "sort" "strings" - issues_model "code.gitea.io/gitea/models/issues" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/label" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/options" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + issues_model "forgejo.org/models/issues" + repo_model "forgejo.org/models/repo" + "forgejo.org/modules/git" + "forgejo.org/modules/label" + "forgejo.org/modules/log" + "forgejo.org/modules/options" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" ) type OptionFile struct { diff --git a/modules/repository/init_test.go b/modules/repository/init_test.go index 227efdc1db..1fa928105c 100644 --- a/modules/repository/init_test.go +++ b/modules/repository/init_test.go @@ -14,17 +14,17 @@ func TestMergeCustomLabels(t *testing.T) { all: []string{"a", "a.yaml", "a.yml"}, custom: nil, }) - assert.EqualValues(t, []string{"a.yaml"}, files, "yaml file should win") + assert.Equal(t, []string{"a.yaml"}, files, "yaml file should win") files = mergeCustomLabelFiles(optionFileList{ all: []string{"a", "a.yaml"}, custom: []string{"a"}, }) - assert.EqualValues(t, []string{"a"}, files, "custom file should win") + assert.Equal(t, []string{"a"}, files, "custom file should win") files = mergeCustomLabelFiles(optionFileList{ all: []string{"a", "a.yml", "a.yaml"}, custom: []string{"a", "a.yml"}, }) - assert.EqualValues(t, []string{"a.yml"}, files, "custom yml file should win if no yaml") + assert.Equal(t, []string{"a.yml"}, files, "custom yml file should win if no yaml") } diff --git a/modules/repository/license.go b/modules/repository/license.go index 07ae92ca70..9776f047af 100644 --- a/modules/repository/license.go +++ b/modules/repository/license.go @@ -1,4 +1,5 @@ // Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package repository @@ -10,7 +11,7 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/options" + "forgejo.org/modules/options" ) type LicenseValues struct { @@ -98,7 +99,8 @@ func getLicensePlaceholder(name string) *licensePlaceholder { // Some special placeholders for specific licenses. // It's unsafe to apply them to all licenses. - if name == "0BSD" { + switch name { + case "0BSD": return &licensePlaceholder{ Owner: []string{"AUTHOR"}, Email: []string{"EMAIL"}, @@ -107,6 +109,9 @@ func getLicensePlaceholder(name string) *licensePlaceholder { } // Other special placeholders can be added here. + case "BSD-4-Clause": + ret.Owner = append(ret.Owner, "COPYRIGHT HOLDER") + ret.Owner = append(ret.Owner, "the organization") } return ret } diff --git a/modules/repository/license_test.go b/modules/repository/license_test.go index a7d77743ac..3195f15dda 100644 --- a/modules/repository/license_test.go +++ b/modules/repository/license_test.go @@ -1,4 +1,5 @@ // Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package repository @@ -170,6 +171,31 @@ Copyright (C) 2023 by Gitea teabot@gitea.io ... ... THE AUTHOR BE LIABLE FOR ... +`, + }, + { + name: "BSD-4-Clause", + args: args{ + name: "BSD-4-Clause", + values: &LicenseValues{Year: "2025", Owner: "Forgejo", Email: "hello@forgejo.org", Repo: "forgejo"}, + origin: ` +Copyright (c) . All rights reserved. + +... includes software developed by the organization. + +... Neither the name of the copyright holder nor + +... PROVIDED BY COPYRIGHT HOLDER "AS IS" ... NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE ... +`, + }, + want: ` +Copyright (c) 2025 Forgejo. All rights reserved. + +... includes software developed by Forgejo. + +... Neither the name of the copyright holder nor + +... PROVIDED BY Forgejo "AS IS" ... NO EVENT SHALL Forgejo BE LIABLE ... `, }, } diff --git a/modules/repository/main_test.go b/modules/repository/main_test.go index f81dfcdafb..5906b10865 100644 --- a/modules/repository/main_test.go +++ b/modules/repository/main_test.go @@ -6,9 +6,10 @@ package repository import ( "testing" - "code.gitea.io/gitea/models/unittest" + "forgejo.org/models/unittest" - _ "code.gitea.io/gitea/models/actions" + _ "forgejo.org/models/actions" + _ "forgejo.org/models/forgefed" ) func TestMain(m *testing.M) { diff --git a/modules/repository/push.go b/modules/repository/push.go index 66d0417caf..d8be0a3e8c 100644 --- a/modules/repository/push.go +++ b/modules/repository/push.go @@ -4,7 +4,7 @@ package repository import ( - "code.gitea.io/gitea/modules/git" + "forgejo.org/modules/git" ) // PushUpdateOptions defines the push update options diff --git a/modules/repository/repo.go b/modules/repository/repo.go index e08bc376b8..c86d48fe52 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -12,17 +12,17 @@ import ( "strings" "time" - "code.gitea.io/gitea/models/db" - git_model "code.gitea.io/gitea/models/git" - repo_model "code.gitea.io/gitea/models/repo" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/gitrepo" - "code.gitea.io/gitea/modules/lfs" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/timeutil" + "forgejo.org/models/db" + git_model "forgejo.org/models/git" + repo_model "forgejo.org/models/repo" + user_model "forgejo.org/models/user" + "forgejo.org/modules/container" + "forgejo.org/modules/git" + "forgejo.org/modules/gitrepo" + "forgejo.org/modules/lfs" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" + "forgejo.org/modules/timeutil" ) /* @@ -90,11 +90,11 @@ func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitR if rel.IsDraft { continue } - commitID, err := gitRepo.GetTagCommitID(rel.TagName) + commit, err := gitRepo.GetTagCommit(rel.TagName) if err != nil && !git.IsErrNotExist(err) { return fmt.Errorf("unable to GetTagCommitID for %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err) } - if git.IsErrNotExist(err) || commitID != rel.Sha1 { + if git.IsErrNotExist(err) || commit.ID.String() != rel.Sha1 { if err := repo_model.PushUpdateDeleteTag(ctx, repo, rel.TagName); err != nil { return fmt.Errorf("unable to PushUpdateDeleteTag: %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err) } @@ -182,11 +182,12 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re downloadObjects := func(pointers []lfs.Pointer) error { err := lfsClient.Download(ctx, pointers, func(p lfs.Pointer, content io.ReadCloser, objectError error) error { + if errors.Is(objectError, lfs.ErrObjectNotExist) { + log.Warn("Ignoring missing upstream LFS object %-v: %v", p, objectError) + return nil + } + if objectError != nil { - if errors.Is(objectError, lfs.ErrObjectNotExist) { - log.Warn("Repo[%-v]: Ignore missing LFS object %-v: %v", repo, p, objectError) - return nil - } return objectError } @@ -341,9 +342,10 @@ func pullMirrorReleaseSync(ctx context.Context, repo *repo_model.Repository, git for _, tag := range updates { if _, err := db.GetEngine(ctx).Where("repo_id = ? AND lower_tag_name = ?", repo.ID, strings.ToLower(tag.Name)). - Cols("sha1"). + Cols("sha1", "created_unix"). Update(&repo_model.Release{ - Sha1: tag.Object.String(), + Sha1: tag.Object.String(), + CreatedUnix: timeutil.TimeStamp(tag.Tagger.When.Unix()), }); err != nil { return fmt.Errorf("unable to update tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err) } diff --git a/modules/repository/repo_test.go b/modules/repository/repo_test.go index f3e7be6d7d..45a650ba42 100644 --- a/modules/repository/repo_test.go +++ b/modules/repository/repo_test.go @@ -6,7 +6,7 @@ package repository import ( "testing" - "code.gitea.io/gitea/modules/git" + "forgejo.org/modules/git" "github.com/stretchr/testify/assert" ) @@ -63,7 +63,7 @@ func Test_calcSync(t *testing.T) { inserts, deletes, updates := calcSync(gitTags, dbReleases) if assert.Len(t, inserts, 1, "inserts") { - assert.EqualValues(t, *gitTags[2], *inserts[0], "inserts equal") + assert.Equal(t, *gitTags[2], *inserts[0], "inserts equal") } if assert.Len(t, deletes, 1, "deletes") { @@ -71,6 +71,6 @@ func Test_calcSync(t *testing.T) { } if assert.Len(t, updates, 1, "updates") { - assert.EqualValues(t, *gitTags[1], *updates[0], "updates equal") + assert.Equal(t, *gitTags[1], *updates[0], "updates equal") } } diff --git a/modules/repository/temp.go b/modules/repository/temp.go index 04faa9db3d..6048c43a8e 100644 --- a/modules/repository/temp.go +++ b/modules/repository/temp.go @@ -9,9 +9,9 @@ import ( "path" "path/filepath" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" ) // LocalCopyPath returns the local repository temporary copy path. diff --git a/modules/secret/secret.go b/modules/secret/secret.go index e70ae1839c..fc63ec521b 100644 --- a/modules/secret/secret.go +++ b/modules/secret/secret.go @@ -27,7 +27,7 @@ func AesEncrypt(key, text []byte) ([]byte, error) { if _, err = io.ReadFull(rand.Reader, iv); err != nil { return nil, fmt.Errorf("AesEncrypt unable to read IV: %w", err) } - cfb := cipher.NewCFBEncrypter(block, iv) + cfb := cipher.NewCFBEncrypter(block, iv) //nolint:staticcheck cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b)) return ciphertext, nil } @@ -43,11 +43,11 @@ func AesDecrypt(key, text []byte) ([]byte, error) { } iv := text[:aes.BlockSize] text = text[aes.BlockSize:] - cfb := cipher.NewCFBDecrypter(block, iv) + cfb := cipher.NewCFBDecrypter(block, iv) //nolint:staticcheck cfb.XORKeyStream(text, text) data, err := base64.StdEncoding.DecodeString(string(text)) if err != nil { - return nil, fmt.Errorf("AesDecrypt invalid decrypted base64 string: %w", err) + return nil, fmt.Errorf("AesDecrypt invalid decrypted base64 string: %w - it can be caused by a change of the [security].SECRET_KEY setting or a database corruption - `forgejo doctor check --run check-db-consistency --fix` will get rid of orphaned rows found in the `two_factor` table and may fix this problem if they are the one with the invalid content", err) } return data, nil } diff --git a/modules/session/db.go b/modules/session/db.go index 3b12b93521..eea7e2136e 100644 --- a/modules/session/db.go +++ b/modules/session/db.go @@ -7,9 +7,9 @@ import ( "log" "sync" - "code.gitea.io/gitea/models/auth" - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/timeutil" + "forgejo.org/models/auth" + "forgejo.org/models/db" + "forgejo.org/modules/timeutil" "code.forgejo.org/go-chi/session" ) diff --git a/modules/session/redis.go b/modules/session/redis.go index 230b501080..cf84ef21d9 100644 --- a/modules/session/redis.go +++ b/modules/session/redis.go @@ -22,8 +22,8 @@ import ( "sync" "time" - "code.gitea.io/gitea/modules/graceful" - "code.gitea.io/gitea/modules/nosql" + "forgejo.org/modules/graceful" + "forgejo.org/modules/nosql" "code.forgejo.org/go-chi/session" ) diff --git a/modules/session/virtual.go b/modules/session/virtual.go index 9cf3683a71..1c3e1c778b 100644 --- a/modules/session/virtual.go +++ b/modules/session/virtual.go @@ -7,8 +7,8 @@ import ( "fmt" "sync" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/json" + "forgejo.org/modules/log" "code.forgejo.org/go-chi/session" memcache "code.forgejo.org/go-chi/session/memcache" diff --git a/modules/setting/actions.go b/modules/setting/actions.go index 8c1b57b649..52a3ad5309 100644 --- a/modules/setting/actions.go +++ b/modules/setting/actions.go @@ -46,7 +46,7 @@ func (url defaultActionsURL) URL() string { } const ( - defaultActionsURLForgejo = "https://code.forgejo.org" + defaultActionsURLForgejo = "https://data.forgejo.org" defaultActionsURLGitHub = "github" // https://github.com defaultActionsURLSelf = "self" // the root URL of the self-hosted instance ) diff --git a/modules/setting/actions_test.go b/modules/setting/actions_test.go index afd76d3bee..a3cd5ced44 100644 --- a/modules/setting/actions_test.go +++ b/modules/setting/actions_test.go @@ -21,9 +21,9 @@ func Test_getStorageInheritNameSectionTypeForActions(t *testing.T) { require.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "minio", Actions.LogStorage.Type) - assert.EqualValues(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) + assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) + assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) iniStr = ` [storage.actions_log] @@ -34,9 +34,9 @@ STORAGE_TYPE = minio require.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "minio", Actions.LogStorage.Type) - assert.EqualValues(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) + assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) assert.EqualValues(t, "local", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) + assert.Equal(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) iniStr = ` [storage.actions_log] @@ -50,9 +50,9 @@ STORAGE_TYPE = minio require.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "minio", Actions.LogStorage.Type) - assert.EqualValues(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) + assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) assert.EqualValues(t, "local", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) + assert.Equal(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) iniStr = ` [storage.actions_artifacts] @@ -66,9 +66,9 @@ STORAGE_TYPE = minio require.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "local", Actions.LogStorage.Type) - assert.EqualValues(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) + assert.Equal(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) + assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) iniStr = ` [storage.actions_artifacts] @@ -82,9 +82,9 @@ STORAGE_TYPE = minio require.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "local", Actions.LogStorage.Type) - assert.EqualValues(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) + assert.Equal(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) + assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) iniStr = `` cfg, err = NewConfigProviderFromData(iniStr) @@ -92,9 +92,9 @@ STORAGE_TYPE = minio require.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "local", Actions.LogStorage.Type) - assert.EqualValues(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) + assert.Equal(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) assert.EqualValues(t, "local", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) + assert.Equal(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) } func Test_getDefaultActionsURLForActions(t *testing.T) { @@ -117,7 +117,7 @@ func Test_getDefaultActionsURLForActions(t *testing.T) { iniStr: ` [actions] `, - wantURL: "https://code.forgejo.org", + wantURL: "https://data.forgejo.org", }, { name: "github", @@ -151,7 +151,7 @@ DEFAULT_ACTIONS_URL = https://example.com require.NoError(t, err) require.NoError(t, loadActionsFrom(cfg)) - assert.EqualValues(t, tt.wantURL, Actions.DefaultActionsURL.URL()) + assert.Equal(t, tt.wantURL, Actions.DefaultActionsURL.URL()) }) } } diff --git a/modules/setting/admin.go b/modules/setting/admin.go index eed3aa22cf..7a1e071bac 100644 --- a/modules/setting/admin.go +++ b/modules/setting/admin.go @@ -4,7 +4,7 @@ package setting import ( - "code.gitea.io/gitea/modules/container" + "forgejo.org/modules/container" ) // Admin settings diff --git a/modules/setting/admin_test.go b/modules/setting/admin_test.go index 0c6c24b038..744f069d82 100644 --- a/modules/setting/admin_test.go +++ b/modules/setting/admin_test.go @@ -6,7 +6,7 @@ package setting import ( "testing" - "code.gitea.io/gitea/modules/container" + "forgejo.org/modules/container" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -26,8 +26,8 @@ func Test_loadAdminFrom(t *testing.T) { loadAdminFrom(cfg) assert.True(t, Admin.DisableRegularOrgCreation) - assert.EqualValues(t, "z", Admin.DefaultEmailNotification) + assert.Equal(t, "z", Admin.DefaultEmailNotification) assert.True(t, Admin.SendNotificationEmailOnNewUser) - assert.EqualValues(t, container.SetOf("a", "b"), Admin.UserDisabledFeatures) - assert.EqualValues(t, container.SetOf("x", "y"), Admin.ExternalUserDisableFeatures) + assert.Equal(t, container.SetOf("a", "b"), Admin.UserDisabledFeatures) + assert.Equal(t, container.SetOf("x", "y"), Admin.ExternalUserDisableFeatures) } diff --git a/modules/setting/api.go b/modules/setting/api.go index c36f05cfd1..18180c3d07 100644 --- a/modules/setting/api.go +++ b/modules/setting/api.go @@ -7,7 +7,7 @@ import ( "net/url" "path" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) // API settings diff --git a/modules/setting/attachment.go b/modules/setting/attachment.go index 4255ac985e..956525f0db 100644 --- a/modules/setting/attachment.go +++ b/modules/setting/attachment.go @@ -12,7 +12,7 @@ var Attachment = struct { Enabled bool }{ Storage: &Storage{}, - AllowedTypes: ".cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip", + AllowedTypes: ".avif,.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip", MaxSize: 2048, MaxFiles: 5, Enabled: true, @@ -25,7 +25,7 @@ func loadAttachmentFrom(rootCfg ConfigProvider) (err error) { return err } - Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip") + Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".avif,.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip") Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(2048) Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5) Attachment.Enabled = sec.Key("ENABLED").MustBool(true) diff --git a/modules/setting/attachment_test.go b/modules/setting/attachment_test.go index f8085c1657..a56fcf1c55 100644 --- a/modules/setting/attachment_test.go +++ b/modules/setting/attachment_test.go @@ -26,9 +26,9 @@ MINIO_ENDPOINT = my_minio:9000 require.NoError(t, loadAttachmentFrom(cfg)) assert.EqualValues(t, "minio", Attachment.Storage.Type) - assert.EqualValues(t, "my_minio:9000", Attachment.Storage.MinioConfig.Endpoint) - assert.EqualValues(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) + assert.Equal(t, "my_minio:9000", Attachment.Storage.MinioConfig.Endpoint) + assert.Equal(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) + assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) } func Test_getStorageTypeSectionOverridesStorageSection(t *testing.T) { @@ -48,8 +48,8 @@ MINIO_BUCKET = gitea require.NoError(t, loadAttachmentFrom(cfg)) assert.EqualValues(t, "minio", Attachment.Storage.Type) - assert.EqualValues(t, "gitea-minio", Attachment.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea-minio", Attachment.Storage.MinioConfig.Bucket) + assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) } func Test_getStorageSpecificOverridesStorage(t *testing.T) { @@ -70,8 +70,8 @@ STORAGE_TYPE = local require.NoError(t, loadAttachmentFrom(cfg)) assert.EqualValues(t, "minio", Attachment.Storage.Type) - assert.EqualValues(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) + assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) } func Test_getStorageGetDefaults(t *testing.T) { @@ -81,7 +81,7 @@ func Test_getStorageGetDefaults(t *testing.T) { require.NoError(t, loadAttachmentFrom(cfg)) // default storage is local, so bucket is empty - assert.EqualValues(t, "", Attachment.Storage.MinioConfig.Bucket) + assert.Empty(t, Attachment.Storage.MinioConfig.Bucket) } func Test_getStorageInheritNameSectionType(t *testing.T) { @@ -116,7 +116,7 @@ MINIO_SECRET_ACCESS_KEY = correct_key storage := Attachment.Storage assert.EqualValues(t, "minio", storage.Type) - assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) } func Test_AttachmentStorage1(t *testing.T) { @@ -129,6 +129,6 @@ STORAGE_TYPE = minio require.NoError(t, loadAttachmentFrom(cfg)) assert.EqualValues(t, "minio", Attachment.Storage.Type) - assert.EqualValues(t, "gitea", Attachment.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", Attachment.Storage.MinioConfig.Bucket) + assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) } diff --git a/modules/setting/cache.go b/modules/setting/cache.go index bfa6ca0e61..cdc7e1a971 100644 --- a/modules/setting/cache.go +++ b/modules/setting/cache.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) // Cache represents cache settings diff --git a/modules/setting/camo.go b/modules/setting/camo.go index 366e9a116c..5d31446a41 100644 --- a/modules/setting/camo.go +++ b/modules/setting/camo.go @@ -3,18 +3,28 @@ package setting -import "code.gitea.io/gitea/modules/log" +import ( + "strconv" + + "forgejo.org/modules/log" +) var Camo = struct { Enabled bool ServerURL string `ini:"SERVER_URL"` HMACKey string `ini:"HMAC_KEY"` - Allways bool + Always bool }{} func loadCamoFrom(rootCfg ConfigProvider) { mustMapSetting(rootCfg, "camo", &Camo) if Camo.Enabled { + oldValue := rootCfg.Section("camo").Key("ALLWAYS").MustString("") + if oldValue != "" { + log.Warn("camo.ALLWAYS is deprecated, use camo.ALWAYS instead") + Camo.Always, _ = strconv.ParseBool(oldValue) + } + if Camo.ServerURL == "" || Camo.HMACKey == "" { log.Fatal(`Camo settings require "SERVER_URL" and HMAC_KEY`) } diff --git a/modules/setting/config.go b/modules/setting/config.go index 03558574c2..6299640e61 100644 --- a/modules/setting/config.go +++ b/modules/setting/config.go @@ -6,8 +6,8 @@ package setting import ( "sync" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting/config" + "forgejo.org/modules/log" + "forgejo.org/modules/setting/config" ) type PictureStruct struct { diff --git a/modules/setting/config/value.go b/modules/setting/config/value.go index f0ec120544..3409f61b76 100644 --- a/modules/setting/config/value.go +++ b/modules/setting/config/value.go @@ -7,9 +7,9 @@ import ( "context" "sync" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/json" + "forgejo.org/modules/log" + "forgejo.org/modules/util" ) type CfgSecKey struct { diff --git a/modules/setting/config_env.go b/modules/setting/config_env.go index fa0100dba2..458dbb51bb 100644 --- a/modules/setting/config_env.go +++ b/modules/setting/config_env.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) const ( @@ -168,3 +168,22 @@ func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) { } return changed } + +// InitGiteaEnvVars initializes the environment variables for gitea +func InitGiteaEnvVars() { + // Ideally Gitea should only accept the environment variables which it clearly knows instead of unsetting the ones it doesn't want, + // but the ideal behavior would be a breaking change, and it seems not bringing enough benefits to end users, + // so at the moment we could still keep "unsetting the unnecessary environments" + + // HOME is managed by Gitea, Gitea's git should use "HOME/.gitconfig". + // But git would try "XDG_CONFIG_HOME/git/config" first if "HOME/.gitconfig" does not exist, + // then our git.InitFull would still write to "XDG_CONFIG_HOME/git/config" if XDG_CONFIG_HOME is set. + _ = os.Unsetenv("XDG_CONFIG_HOME") + + _ = os.Unsetenv("GIT_AUTHOR_NAME") + _ = os.Unsetenv("GIT_AUTHOR_EMAIL") + _ = os.Unsetenv("GIT_AUTHOR_DATE") + _ = os.Unsetenv("GIT_COMMITTER_NAME") + _ = os.Unsetenv("GIT_COMMITTER_EMAIL") + _ = os.Unsetenv("GIT_COMMITTER_DATE") +} diff --git a/modules/setting/config_env_test.go b/modules/setting/config_env_test.go index bec3e584ef..fed0f668aa 100644 --- a/modules/setting/config_env_test.go +++ b/modules/setting/config_env_test.go @@ -30,8 +30,8 @@ func TestDecodeEnvSectionKey(t *testing.T) { ok, section, key = decodeEnvSectionKey("SEC") assert.False(t, ok) - assert.Equal(t, "", section) - assert.Equal(t, "", key) + assert.Empty(t, section) + assert.Empty(t, key) } func TestDecodeEnvironmentKey(t *testing.T) { @@ -40,19 +40,19 @@ func TestDecodeEnvironmentKey(t *testing.T) { ok, section, key, file := decodeEnvironmentKey(prefix, suffix, "SEC__KEY") assert.False(t, ok) - assert.Equal(t, "", section) - assert.Equal(t, "", key) + assert.Empty(t, section) + assert.Empty(t, key) assert.False(t, file) ok, section, key, file = decodeEnvironmentKey(prefix, suffix, "GITEA__SEC") assert.False(t, ok) - assert.Equal(t, "", section) - assert.Equal(t, "", key) + assert.Empty(t, section) + assert.Empty(t, key) assert.False(t, file) ok, section, key, file = decodeEnvironmentKey(prefix, suffix, "GITEA____KEY") assert.True(t, ok) - assert.Equal(t, "", section) + assert.Empty(t, section) assert.Equal(t, "KEY", key) assert.False(t, file) @@ -72,8 +72,8 @@ func TestDecodeEnvironmentKey(t *testing.T) { // but it could be fixed in the future by adding a new suffix like "__VALUE" (no such key VALUE is used in Gitea either) ok, section, key, file = decodeEnvironmentKey(prefix, suffix, "GITEA__SEC__FILE") assert.False(t, ok) - assert.Equal(t, "", section) - assert.Equal(t, "", key) + assert.Empty(t, section) + assert.Empty(t, key) assert.True(t, file) ok, section, key, file = decodeEnvironmentKey(prefix, suffix, "GITEA__SEC__KEY__FILE") diff --git a/modules/setting/config_provider.go b/modules/setting/config_provider.go index 12cf36aa59..19f3b9008a 100644 --- a/modules/setting/config_provider.go +++ b/modules/setting/config_provider.go @@ -12,8 +12,8 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/log" + "forgejo.org/modules/util" "gopkg.in/ini.v1" //nolint:depguard ) @@ -262,7 +262,7 @@ func (p *iniConfigProvider) Save() error { } filename := p.file if filename == "" { - return fmt.Errorf("config file path must not be empty") + return errors.New("config file path must not be empty") } if p.loadedFromEmpty { if err := os.MkdirAll(filepath.Dir(filename), os.ModePerm); err != nil { diff --git a/modules/setting/config_provider_test.go b/modules/setting/config_provider_test.go index 702be80861..3b99911f38 100644 --- a/modules/setting/config_provider_test.go +++ b/modules/setting/config_provider_test.go @@ -63,17 +63,17 @@ key = 123 // test default behavior assert.Equal(t, "123", ConfigSectionKeyString(sec, "key")) - assert.Equal(t, "", ConfigSectionKeyString(secSub, "key")) + assert.Empty(t, ConfigSectionKeyString(secSub, "key")) assert.Equal(t, "def", ConfigSectionKeyString(secSub, "key", "def")) assert.Equal(t, "123", ConfigInheritedKeyString(secSub, "key")) // Workaround for ini package's BuggyKeyOverwritten behavior - assert.Equal(t, "", ConfigSectionKeyString(sec, "empty")) - assert.Equal(t, "", ConfigSectionKeyString(secSub, "empty")) + assert.Empty(t, ConfigSectionKeyString(sec, "empty")) + assert.Empty(t, ConfigSectionKeyString(secSub, "empty")) assert.Equal(t, "def", ConfigInheritedKey(secSub, "empty").MustString("def")) assert.Equal(t, "def", ConfigInheritedKey(secSub, "empty").MustString("xyz")) - assert.Equal(t, "", ConfigSectionKeyString(sec, "empty")) + assert.Empty(t, ConfigSectionKeyString(sec, "empty")) assert.Equal(t, "def", ConfigSectionKeyString(secSub, "empty")) } diff --git a/modules/setting/cors.go b/modules/setting/cors.go index 63daaad60b..5260887d9d 100644 --- a/modules/setting/cors.go +++ b/modules/setting/cors.go @@ -5,8 +5,6 @@ package setting import ( "time" - - "code.gitea.io/gitea/modules/log" ) // CORSConfig defines CORS settings @@ -28,7 +26,4 @@ var CORSConfig = struct { func loadCorsFrom(rootCfg ConfigProvider) { mustMapSetting(rootCfg, "cors", &CORSConfig) - if CORSConfig.Enabled { - log.Info("CORS Service Enabled") - } } diff --git a/modules/setting/cron_test.go b/modules/setting/cron_test.go index 32f8ecffd2..cabfb3a325 100644 --- a/modules/setting/cron_test.go +++ b/modules/setting/cron_test.go @@ -39,6 +39,6 @@ EXTEND = true _, err = getCronSettings(cfg, "test", extended) require.NoError(t, err) assert.True(t, extended.Base) - assert.EqualValues(t, "white rabbit", extended.Second) + assert.Equal(t, "white rabbit", extended.Second) assert.True(t, extended.Extend) } diff --git a/modules/setting/database.go b/modules/setting/database.go index 76fae27164..96859e4cf4 100644 --- a/modules/setting/database.go +++ b/modules/setting/database.go @@ -10,8 +10,13 @@ import ( "net/url" "os" "path/filepath" + "strconv" "strings" "time" + + "forgejo.org/modules/log" + + "xorm.io/xorm" ) var ( @@ -24,35 +29,41 @@ var ( EnableSQLite3 bool // Database holds the database settings - Database = struct { - Type DatabaseType - Host string - Name string - User string - Passwd string - Schema string - SSLMode string - Path string - LogSQL bool - MysqlCharset string - CharsetCollation string - Timeout int // seconds - SQLiteJournalMode string - DBConnectRetries int - DBConnectBackoff time.Duration - MaxIdleConns int - MaxOpenConns int - ConnMaxIdleTime time.Duration - ConnMaxLifetime time.Duration - IterateBufferSize int - AutoMigration bool - SlowQueryThreshold time.Duration - }{ + Database = DatabaseSettings{ Timeout: 500, IterateBufferSize: 50, } ) +type DatabaseSettings struct { + Type DatabaseType + Host string + HostPrimary string + HostReplica string + LoadBalancePolicy string + LoadBalanceWeights string + Name string + User string + Passwd string + Schema string + SSLMode string + Path string + LogSQL bool + MysqlCharset string + CharsetCollation string + Timeout int // seconds + SQLiteJournalMode string + DBConnectRetries int + DBConnectBackoff time.Duration + MaxIdleConns int + MaxOpenConns int + ConnMaxIdleTime time.Duration + ConnMaxLifetime time.Duration + IterateBufferSize int + AutoMigration bool + SlowQueryThreshold time.Duration +} + // LoadDBSetting loads the database settings func LoadDBSetting() { loadDBSetting(CfgProvider) @@ -63,6 +74,10 @@ func loadDBSetting(rootCfg ConfigProvider) { Database.Type = DatabaseType(sec.Key("DB_TYPE").String()) Database.Host = sec.Key("HOST").String() + Database.HostPrimary = sec.Key("HOST_PRIMARY").String() + Database.HostReplica = sec.Key("HOST_REPLICA").String() + Database.LoadBalancePolicy = sec.Key("LOAD_BALANCE_POLICY").String() + Database.LoadBalanceWeights = sec.Key("LOAD_BALANCE_WEIGHTS").String() Database.Name = sec.Key("NAME").String() Database.User = sec.Key("USER").String() if len(Database.Passwd) == 0 { @@ -99,8 +114,114 @@ func loadDBSetting(rootCfg ConfigProvider) { } } -// DBConnStr returns database connection string -func DBConnStr() (string, error) { +// DBMasterConnStr returns the connection string for the master (primary) database. +// If a primary host is defined in the configuration, it is used; +// otherwise, it falls back to Database.Host. +// Returns an error if no master host is provided but a slave is defined. +func DBMasterConnStr() (string, error) { + var host string + if Database.HostPrimary != "" { + host = Database.HostPrimary + } else { + host = Database.Host + } + if host == "" && Database.HostReplica != "" { + return "", errors.New("master host is not defined while slave is defined; cannot proceed") + } + + // For SQLite, no host is needed + if host == "" && !Database.Type.IsSQLite3() { + return "", errors.New("no database host defined") + } + + return dbConnStrWithHost(host) +} + +// DBSlaveConnStrs returns one or more connection strings for the replica databases. +// If a replica host is defined (possibly as a comma-separated list) then those DSNs are returned. +// Otherwise, this function falls back to the master DSN (with a warning log). +func DBSlaveConnStrs() ([]string, error) { + var dsns []string + if Database.HostReplica != "" { + // support multiple replica hosts separated by commas + replicas := strings.SplitSeq(Database.HostReplica, ",") + for r := range replicas { + trimmed := strings.TrimSpace(r) + if trimmed == "" { + continue + } + dsn, err := dbConnStrWithHost(trimmed) + if err != nil { + return nil, err + } + dsns = append(dsns, dsn) + } + } + // Fall back to master if no slave DSN was provided. + if len(dsns) == 0 { + master, err := DBMasterConnStr() + if err != nil { + return nil, err + } + log.Debug("Database: No dedicated replica host defined; falling back to primary DSN for replica connections") + dsns = append(dsns, master) + } + return dsns, nil +} + +func BuildLoadBalancePolicy(settings *DatabaseSettings, slaveEngines []*xorm.Engine) xorm.GroupPolicy { + var policy xorm.GroupPolicy + switch settings.LoadBalancePolicy { // Use the settings parameter directly + case "WeightRandom": + var weights []int + if settings.LoadBalanceWeights != "" { // Use the settings parameter directly + for part := range strings.SplitSeq(settings.LoadBalanceWeights, ",") { + w, err := strconv.Atoi(strings.TrimSpace(part)) + if err != nil { + w = 1 // use a default weight if conversion fails + } + weights = append(weights, w) + } + } + // If no valid weights were provided, default each slave to weight 1 + if len(weights) == 0 { + weights = make([]int, len(slaveEngines)) + for i := range weights { + weights[i] = 1 + } + } + policy = xorm.WeightRandomPolicy(weights) + case "WeightRoundRobin": + var weights []int + if settings.LoadBalanceWeights != "" { + for part := range strings.SplitSeq(settings.LoadBalanceWeights, ",") { + w, err := strconv.Atoi(strings.TrimSpace(part)) + if err != nil { + w = 1 // use a default weight if conversion fails + } + weights = append(weights, w) + } + } + // If no valid weights were provided, default each slave to weight 1 + if len(weights) == 0 { + weights = make([]int, len(slaveEngines)) + for i := range weights { + weights[i] = 1 + } + } + policy = xorm.WeightRoundRobinPolicy(weights) + case "RoundRobin": + policy = xorm.RoundRobinPolicy() + case "LeastConn": + policy = xorm.LeastConnPolicy() + default: + policy = xorm.RandomPolicy() + } + return policy +} + +// dbConnStrWithHost constructs the connection string, given a host value. +func dbConnStrWithHost(host string) (string, error) { var connStr string paramSep := "?" if strings.Contains(Database.Name, paramSep) { @@ -109,23 +230,25 @@ func DBConnStr() (string, error) { switch Database.Type { case "mysql": connType := "tcp" - if len(Database.Host) > 0 && Database.Host[0] == '/' { // looks like a unix socket + // if the host starts with '/' it is assumed to be a unix socket path + if len(host) > 0 && host[0] == '/' { connType = "unix" } tls := Database.SSLMode - if tls == "disable" { // allow (Postgres-inspired) default value to work in MySQL + // allow the "disable" value (borrowed from Postgres defaults) to behave as false + if tls == "disable" { tls = "false" } connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%sparseTime=true&tls=%s", - Database.User, Database.Passwd, connType, Database.Host, Database.Name, paramSep, tls) + Database.User, Database.Passwd, connType, host, Database.Name, paramSep, tls) case "postgres": - connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, Database.SSLMode) + connStr = getPostgreSQLConnectionString(host, Database.User, Database.Passwd, Database.Name, Database.SSLMode) case "sqlite3": if !EnableSQLite3 { return "", errors.New("this Gitea binary was not built with SQLite3 support") } if err := os.MkdirAll(filepath.Dir(Database.Path), os.ModePerm); err != nil { - return "", fmt.Errorf("Failed to create directories: %w", err) + return "", fmt.Errorf("failed to create directories: %w", err) } journalMode := "" if Database.SQLiteJournalMode != "" { @@ -136,7 +259,6 @@ func DBConnStr() (string, error) { default: return "", fmt.Errorf("unknown database type: %s", Database.Type) } - return connStr, nil } diff --git a/modules/setting/database_test.go b/modules/setting/database_test.go index a742d54f8c..ce816d53e8 100644 --- a/modules/setting/database_test.go +++ b/modules/setting/database_test.go @@ -4,6 +4,7 @@ package setting import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -107,3 +108,104 @@ func Test_getPostgreSQLConnectionString(t *testing.T) { assert.Equal(t, test.Output, connStr) } } + +func getPostgreSQLEngineGroupConnectionStrings(primaryHost, replicaHosts, user, passwd, name, sslmode string) (string, []string) { + // Determine the primary connection string. + primary := primaryHost + if strings.TrimSpace(primary) == "" { + primary = "127.0.0.1:5432" + } + primaryConn := getPostgreSQLConnectionString(primary, user, passwd, name, sslmode) + + // Build the replica connection strings. + replicaConns := []string{} + if strings.TrimSpace(replicaHosts) != "" { + // Split comma-separated replica host values. + hosts := strings.Split(replicaHosts, ",") + for _, h := range hosts { + trimmed := strings.TrimSpace(h) + if trimmed != "" { + replicaConns = append(replicaConns, + getPostgreSQLConnectionString(trimmed, user, passwd, name, sslmode)) + } + } + } + + return primaryConn, replicaConns +} + +func Test_getPostgreSQLEngineGroupConnectionStrings(t *testing.T) { + tests := []struct { + primaryHost string // primary host setting (e.g. "localhost" or "[::1]:1234") + replicaHosts string // comma-separated replica hosts (e.g. "replica1,replica2:2345") + user string + passwd string + name string + sslmode string + outputPrimary string + outputReplicas []string + }{ + { + // No primary override (empty => default) and no replicas. + primaryHost: "", + replicaHosts: "", + user: "", + passwd: "", + name: "", + sslmode: "", + outputPrimary: "postgres://:@127.0.0.1:5432?sslmode=", + outputReplicas: []string{}, + }, + { + // Primary set and one replica. + primaryHost: "localhost", + replicaHosts: "replicahost", + user: "user", + passwd: "pass", + name: "gitea", + sslmode: "disable", + outputPrimary: "postgres://user:pass@localhost:5432/gitea?sslmode=disable", + outputReplicas: []string{"postgres://user:pass@replicahost:5432/gitea?sslmode=disable"}, + }, + { + // Primary with explicit port; multiple replicas (one without and one with an explicit port). + primaryHost: "localhost:5433", + replicaHosts: "replica1,replica2:5434", + user: "test", + passwd: "secret", + name: "db", + sslmode: "require", + outputPrimary: "postgres://test:secret@localhost:5433/db?sslmode=require", + outputReplicas: []string{ + "postgres://test:secret@replica1:5432/db?sslmode=require", + "postgres://test:secret@replica2:5434/db?sslmode=require", + }, + }, + { + // IPv6 addresses for primary and replica. + primaryHost: "[::1]:1234", + replicaHosts: "[::2]:2345", + user: "ipv6", + passwd: "ipv6pass", + name: "ipv6db", + sslmode: "disable", + outputPrimary: "postgres://ipv6:ipv6pass@[::1]:1234/ipv6db?sslmode=disable", + outputReplicas: []string{ + "postgres://ipv6:ipv6pass@[::2]:2345/ipv6db?sslmode=disable", + }, + }, + } + + for _, test := range tests { + primary, replicas := getPostgreSQLEngineGroupConnectionStrings( + test.primaryHost, + test.replicaHosts, + test.user, + test.passwd, + test.name, + test.sslmode, + ) + assert.Equal(t, test.outputPrimary, primary) + assert.Equal(t, test.outputReplicas, replicas) + } +} diff --git a/modules/setting/disposable_email_domain_data.go b/modules/setting/disposable_email_domain_data.go new file mode 100644 index 0000000000..5f39f02e4b --- /dev/null +++ b/modules/setting/disposable_email_domain_data.go @@ -0,0 +1,3811 @@ +// Copyright 2024 James Hatfield +// SPDX-License-Identifier: MIT +// +// Code generated by build/generate-disposable-email.go. DO NOT EDIT +// Sourced from https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/0c27e671231d27cf66370034d7f6818037416989/disposable_email_blocklist.conf +package setting + +import "sync" + +var DisposableEmailDomains = sync.OnceValue(func() []string { + return []string{ + "0-mail.com", + "027168.com", + "0815.ru", + "0815.ry", + "0815.su", + "0845.ru", + "0box.eu", + "0clickemail.com", + "0n0ff.net", + "0nelce.com", + "0v.ro", + "0w.ro", + "0wnd.net", + "0wnd.org", + "0x207.info", + "1-8.biz", + "1-tm.com", + "10-minute-mail.com", + "1000rebates.stream", + "100likers.com", + "105kg.ru", + "10dk.email", + "10mail.com", + "10mail.org", + "10mail.tk", + "10mail.xyz", + "10minmail.de", + "10minut.com.pl", + "10minut.xyz", + "10minutemail.be", + "10minutemail.cf", + "10minutemail.co.uk", + "10minutemail.co.za", + "10minutemail.com", + "10minutemail.de", + "10minutemail.ga", + "10minutemail.gq", + "10minutemail.ml", + "10minutemail.net", + "10minutemail.nl", + "10minutemail.pro", + "10minutemail.us", + "10minutemailbox.com", + "10minutemails.in", + "10minutenemail.de", + "10minutenmail.xyz", + "10minutesmail.com", + "10minutesmail.fr", + "10minutmail.pl", + "10x9.com", + "11163.com", + "123-m.com", + "12hosting.net", + "12houremail.com", + "12minutemail.com", + "12minutemail.net", + "12storage.com", + "140unichars.com", + "147.cl", + "14n.co.uk", + "15qm.com", + "1blackmoon.com", + "1ce.us", + "1chuan.com", + "1clck2.com", + "1fsdfdsfsdf.tk", + "1mail.ml", + "1pad.de", + "1s.fr", + "1secmail.com", + "1secmail.net", + "1secmail.org", + "1st-forms.com", + "1to1mail.org", + "1usemail.com", + "1webmail.info", + "1zhuan.com", + "2012-2016.ru", + "20email.eu", + "20email.it", + "20mail.eu", + "20mail.in", + "20mail.it", + "20minutemail.com", + "20minutemail.it", + "20mm.eu", + "2120001.net", + "21cn.com", + "247web.net", + "24hinbox.com", + "24hourmail.com", + "24hourmail.net", + "2anom.com", + "2chmail.net", + "2ether.net", + "2fdgdfgdfgdf.tk", + "2odem.com", + "2prong.com", + "2wc.info", + "300book.info", + "30mail.ir", + "30minutemail.com", + "30wave.com", + "3202.com", + "36ru.com", + "3d-painting.com", + "3l6.com", + "3mail.ga", + "3trtretgfrfe.tk", + "4-n.us", + "4057.com", + "418.dk", + "42o.org", + "4gfdsgfdgfd.tk", + "4k5.net", + "4mail.cf", + "4mail.ga", + "4nextmail.com", + "4nmv.ru", + "4tb.host", + "4warding.com", + "4warding.net", + "4warding.org", + "50set.ru", + "55hosting.net", + "5ghgfhfghfgh.tk", + "5gramos.com", + "5july.org", + "5mail.cf", + "5mail.ga", + "5minutemail.net", + "5oz.ru", + "5tb.in", + "5x25.com", + "5ymail.com", + "60minutemail.com", + "672643.net", + "675hosting.com", + "675hosting.net", + "675hosting.org", + "6hjgjhgkilkj.tk", + "6ip.us", + "6mail.cf", + "6mail.ga", + "6mail.ml", + "6paq.com", + "6somok.ru", + "6url.com", + "75hosting.com", + "75hosting.net", + "75hosting.org", + "7days-printing.com", + "7mail.ga", + "7mail.ml", + "7tags.com", + "80665.com", + "8127ep.com", + "8mail.cf", + "8mail.ga", + "8mail.ml", + "99.com", + "99cows.com", + "99experts.com", + "9mail.cf", + "9me.site", + "9mot.ru", + "9ox.net", + "9q.ro", + "a-bc.net", + "a45.in", + "a7996.com", + "aa5zy64.com", + "aaqwe.ru", + "aaqwe.store", + "abacuswe.us", + "abakiss.com", + "abatido.com", + "abcmail.email", + "abevw.com", + "abilitywe.us", + "abovewe.us", + "absolutewe.us", + "abundantwe.us", + "abusemail.de", + "abuser.eu", + "abyssmail.com", + "ac20mail.in", + "academiccommunity.com", + "academywe.us", + "acceleratewe.us", + "accentwe.us", + "acceptwe.us", + "acclaimwe.us", + "accordwe.us", + "accreditedwe.us", + "achievementwe.us", + "achievewe.us", + "acornwe.us", + "acrossgracealley.com", + "acrylicwe.us", + "activatewe.us", + "activitywe.us", + "acucre.com", + "acuitywe.us", + "acumenwe.us", + "adaptivewe.us", + "adaptwe.us", + "add3000.pp.ua", + "addictingtrailers.com", + "adeptwe.us", + "adfskj.com", + "adios.email", + "adiq.eu", + "aditus.info", + "admiralwe.us", + "ado888.biz", + "adobeccepdm.com", + "adoniswe.us", + "adpugh.org", + "adroh.com", + "adsd.org", + "adubiz.info", + "adult-work.info", + "advantagewe.us", + "advantimo.com", + "adventurewe.us", + "adventwe.us", + "advisorwe.us", + "advocatewe.us", + "adwaterandstir.com", + "aegde.com", + "aegia.net", + "aegiscorp.net", + "aegiswe.us", + "aelo.es", + "aeonpsi.com", + "afarek.com", + "affiliate-nebenjob.info", + "affiliatedwe.us", + "affilikingz.de", + "affinitywe.us", + "affluentwe.us", + "affordablewe.us", + "afia.pro", + "afrobacon.com", + "afterhourswe.us", + "agedmail.com", + "agendawe.us", + "agger.ro", + "agilewe.us", + "agorawe.us", + "agtx.net", + "aheadwe.us", + "ahem.email", + "ahk.jp", + "ahmedkhlef.com", + "air2token.com", + "airmailbox.website", + "airsi.de", + "aiworldx.com", + "ajaxapp.net", + "akapost.com", + "akerd.com", + "akgq701.com", + "akmail.in", + "akugu.com", + "al-qaeda.us", + "albionwe.us", + "alchemywe.us", + "alfaceti.com", + "aliaswe.us", + "alienware13.com", + "aligamel.com", + "alina-schiesser.ch", + "alisongamel.com", + "alivance.com", + "alivewe.us", + "all-cats.ru", + "allaccesswe.us", + "allamericanwe.us", + "allaroundwe.us", + "alldirectbuy.com", + "allegiancewe.us", + "allegrowe.us", + "allemojikeyboard.com", + "allgoodwe.us", + "alliancewe.us", + "allinonewe.us", + "allofthem.net", + "alloutwe.us", + "allowed.org", + "alloywe.us", + "allprowe.us", + "allseasonswe.us", + "allstarwe.us", + "allthegoodnamesaretaken.org", + "allurewe.us", + "almondwe.us", + "alph.wtf", + "alpha-web.net", + "alphaomegawe.us", + "alpinewe.us", + "altairwe.us", + "altitudewe.us", + "altuswe.us", + "ama-trade.de", + "ama-trans.de", + "amadeuswe.us", + "amail.club", + "amail.com", + "amail1.com", + "amail4.me", + "amazon-aws.org", + "amberwe.us", + "ambiancewe.us", + "ambitiouswe.us", + "amelabs.com", + "americanawe.us", + "americasbestwe.us", + "americaswe.us", + "amicuswe.us", + "amilegit.com", + "amiri.net", + "amiriindustries.com", + "amplewe.us", + "amplifiedwe.us", + "amplifywe.us", + "ampsylike.com", + "analogwe.us", + "analysiswe.us", + "analyticalwe.us", + "analyticswe.us", + "analyticwe.us", + "anappfor.com", + "anappthat.com", + "andreihusanu.ro", + "andthen.us", + "animesos.com", + "anit.ro", + "ano-mail.net", + "anon-mail.de", + "anonbox.net", + "anonmail.top", + "anonmails.de", + "anonymail.dk", + "anonymbox.com", + "anonymized.org", + "anonymousness.com", + "anotherdomaincyka.tk", + "ansibleemail.com", + "anthony-junkmail.com", + "antireg.com", + "antireg.ru", + "antispam.de", + "antispam24.de", + "antispammail.de", + "any.pink", + "anyalias.com", + "aoeuhtns.com", + "apfelkorps.de", + "aphlog.com", + "apkmd.com", + "appc.se", + "appinventor.nl", + "appixie.com", + "apps.dj", + "appzily.com", + "arduino.hk", + "ariaz.jetzt", + "armyspy.com", + "aron.us", + "arroisijewellery.com", + "art-en-ligne.pro", + "artman-conception.com", + "arur01.tk", + "arurgitu.gq", + "arvato-community.de", + "aschenbrandt.net", + "asdasd.nl", + "asdasd.ru", + "ashleyandrew.com", + "ask-mail.com", + "asorent.com", + "ass.pp.ua", + "astonut.tk", + "astroempires.info", + "asu.mx", + "asu.su", + "at.hm", + "at0mik.org", + "atnextmail.com", + "attnetwork.com", + "augmentationtechnology.com", + "ausgefallen.info", + "auti.st", + "autorobotica.com", + "autosouvenir39.ru", + "autotwollow.com", + "autowb.com", + "averdov.com", + "avia-tonic.fr", + "avls.pt", + "awatum.de", + "awdrt.org", + "awiki.org", + "awsoo.com", + "axiz.org", + "axon7zte.com", + "axsup.net", + "ayakamail.cf", + "azazazatashkent.tk", + "azcomputerworks.com", + "azmeil.tk", + "b1of96u.com", + "b2bx.net", + "b2cmail.de", + "badgerland.eu", + "badoop.com", + "badpotato.tk", + "balaket.com", + "bangban.uk", + "banit.club", + "banit.me", + "bank-opros1.ru", + "bareed.ws", + "barooko.com", + "barryogorman.com", + "bartdevos.be", + "basscode.org", + "bauwerke-online.com", + "bazaaboom.com", + "bbbbyyzz.info", + "bbhost.us", + "bbitf.com", + "bbitj.com", + "bbitq.com", + "bcaoo.com", + "bcast.ws", + "bcb.ro", + "bccto.me", + "bdmuzic.pw", + "beaconmessenger.com", + "bearsarefuzzy.com", + "beddly.com", + "beefmilk.com", + "belamail.org", + "belgianairways.com", + "belljonestax.com", + "beluckygame.com", + "benipaula.org", + "bepureme.com", + "beribase.ru", + "beribaza.ru", + "berirabotay.ru", + "best-john-boats.com", + "bestchoiceusedcar.com", + "bestlistbase.com", + "bestoption25.club", + "bestparadize.com", + "bestsoundeffects.com", + "besttempmail.com", + "betr.co", + "bgtmail.com", + "bgx.ro", + "bheps.com", + "bidourlnks.com", + "big1.us", + "bigprofessor.so", + "bigstring.com", + "bigwhoop.co.za", + "bij.pl", + "binka.me", + "binkmail.com", + "binnary.com", + "bio-muesli.info", + "bio-muesli.net", + "bione.co", + "bitwhites.top", + "bitymails.us", + "blackgoldagency.ru", + "blackmarket.to", + "bladesmail.net", + "blip.ch", + "blnkt.net", + "block521.com", + "blogmyway.org", + "blogos.net", + "blogspam.ro", + "blondemorkin.com", + "blondmail.com", + "bluedumpling.info", + "bluewerks.com", + "bnote.com", + "boatmail.us", + "bobgf.ru", + "bobgf.store", + "bobmail.info", + "bobmurchison.com", + "bofthew.com", + "bonobo.email", + "boofx.com", + "bookthemmore.com", + "bootybay.de", + "borged.com", + "borged.net", + "borged.org", + "bot.nu", + "boun.cr", + "bouncr.com", + "box-mail.ru", + "box-mail.store", + "boxem.ru", + "boxem.store", + "boxformail.in", + "boximail.com", + "boxlet.ru", + "boxlet.store", + "boxmail.lol", + "boxomail.live", + "boxtemp.com.br", + "bptfp.net", + "brand-app.biz", + "brandallday.net", + "brasx.org", + "breakthru.com", + "brefmail.com", + "brennendesreich.de", + "briggsmarcus.com", + "broadbandninja.com", + "bsnow.net", + "bspamfree.org", + "bspooky.com", + "bst-72.com", + "btb-notes.com", + "btc.email", + "btcmail.pw", + "btcmod.com", + "btizet.pl", + "buccalmassage.ru", + "budaya-tionghoa.com", + "budayationghoa.com", + "buffemail.com", + "bugfoo.com", + "bugmenever.com", + "bugmenot.com", + "bukhariansiddur.com", + "bulrushpress.com", + "bum.net", + "bumpymail.com", + "bunchofidiots.com", + "bund.us", + "bundes-li.ga", + "bunsenhoneydew.com", + "burnthespam.info", + "burstmail.info", + "businessbackend.com", + "businesssuccessislifesuccess.com", + "buspad.org", + "bussitussi.com", + "buymoreplays.com", + "buyordie.info", + "buyusdomain.com", + "buyusedlibrarybooks.org", + "buzzcluby.com", + "byebyemail.com", + "byespm.com", + "byom.de", + "c01.kr", + "c51vsgq.com", + "cachedot.net", + "californiafitnessdeals.com", + "cam4you.cc", + "camping-grill.info", + "candymail.de", + "cane.pw", + "capitalistdilemma.com", + "car101.pro", + "carbtc.net", + "cars2.club", + "carsencyclopedia.com", + "cartelera.org", + "caseedu.tk", + "cashflow35.com", + "casualdx.com", + "catgroup.uk", + "cavi.mx", + "cbair.com", + "cbes.net", + "cbty.ru", + "cbty.store", + "cc.liamria", + "ccmail.uk", + "cdfaq.com", + "cdpa.cc", + "ceed.se", + "cek.pm", + "cellurl.com", + "centermail.com", + "centermail.net", + "cetpass.com", + "cfo2go.ro", + "chacuo.net", + "chaichuang.com", + "chalupaurybnicku.cz", + "chammy.info", + "chapsmail.com", + "chasefreedomactivate.com", + "chatich.com", + "cheaphub.net", + "cheatmail.de", + "chenbot.email", + "chewydonut.com", + "chibakenma.ml", + "chickenkiller.com", + "chielo.com", + "childsavetrust.org", + "chilkat.com", + "chinamkm.com", + "chithinh.com", + "chitthi.in", + "choco.la", + "chogmail.com", + "choicemail1.com", + "chong-mail.com", + "chong-mail.net", + "chong-mail.org", + "chumpstakingdumps.com", + "cigar-auctions.com", + "civikli.com", + "civx.org", + "ckaazaza.tk", + "ckiso.com", + "cl-cl.org", + "cl0ne.net", + "claimab.com", + "clandest.in", + "classesmail.com", + "clearwatermail.info", + "click-email.com", + "clickdeal.co", + "clipmail.eu", + "clixser.com", + "clonemoi.tk", + "cloud-mail.top", + "clout.wiki", + "clowmail.com", + "clrmail.com", + "cmail.club", + "cmail.com", + "cmail.net", + "cmail.org", + "cnamed.com", + "cndps.com", + "cnew.ir", + "cnmsg.net", + "cnsds.de", + "co.cc", + "cobarekyo1.ml", + "cocoro.uk", + "cocovpn.com", + "codeandscotch.com", + "codivide.com", + "coffeetimer24.com", + "coieo.com", + "coin-host.net", + "coinlink.club", + "coldemail.info", + "compareshippingrates.org", + "completegolfswing.com", + "comwest.de", + "conf.work", + "consumerriot.com", + "contbay.com", + "cooh-2.site", + "coolandwacky.us", + "coolimpool.org", + "copyhome.win", + "coreclip.com", + "cosmorph.com", + "courrieltemporaire.com", + "coza.ro", + "crankhole.com", + "crapmail.org", + "crastination.de", + "crazespaces.pw", + "crazymailing.com", + "cream.pink", + "crepeau12.com", + "cringemonster.com", + "cross-law.ga", + "cross-law.gq", + "crossmailjet.com", + "crossroadsmail.com", + "crunchcompass.com", + "crusthost.com", + "cs.email", + "csh.ro", + "cszbl.com", + "ctmailing.us", + "ctos.ch", + "cu.cc", + "cubene.com", + "cubiclink.com", + "cuendita.com", + "cuirushi.org", + "cuoly.com", + "cupbest.com", + "curlhph.tk", + "currentmail.com", + "curryworld.de", + "cust.in", + "cutout.club", + "cutradition.com", + "cuvox.de", + "cyber-innovation.club", + "cyber-phone.eu", + "cylab.org", + "d1yun.com", + "d3p.dk", + "daabox.com", + "dab.ro", + "dacoolest.com", + "daemsteam.com", + "daibond.info", + "daily-email.com", + "daintly.com", + "damai.webcam", + "dammexe.net", + "damnthespam.com", + "dandikmail.com", + "darkharvestfilms.com", + "daryxfox.net", + "dasdasdascyka.tk", + "dash-pads.com", + "dataarca.com", + "datarca.com", + "datazo.ca", + "datenschutz.ru", + "datum2.com", + "davidkoh.net", + "davidlcreative.com", + "dawin.com", + "daymail.life", + "daymailonline.com", + "dayrep.com", + "dbunker.com", + "dcctb.com", + "dcemail.com", + "ddcrew.com", + "de-a.org", + "dea-21olympic.com", + "deadaddress.com", + "deadchildren.org", + "deadfake.cf", + "deadfake.ga", + "deadfake.ml", + "deadfake.tk", + "deadspam.com", + "deagot.com", + "dealja.com", + "dealrek.com", + "deekayen.us", + "defomail.com", + "degradedfun.net", + "deinbox.com", + "delayload.com", + "delayload.net", + "delikkt.de", + "delivrmail.com", + "demen.ml", + "dengekibunko.ga", + "dengekibunko.gq", + "dengekibunko.ml", + "der-kombi.de", + "derkombi.de", + "derluxuswagen.de", + "desoz.com", + "despam.it", + "despammed.com", + "dev-null.cf", + "dev-null.ga", + "dev-null.gq", + "dev-null.ml", + "developermail.com", + "devnullmail.com", + "deyom.com", + "dharmatel.net", + "dhm.ro", + "dhy.cc", + "dialogus.com", + "diapaulpainting.com", + "dicopto.com", + "digdig.org", + "digital-message.com", + "digitalesbusiness.info", + "digitalmail.info", + "digitalmariachis.com", + "digitalsanctuary.com", + "dildosfromspace.com", + "dim-coin.com", + "dingbone.com", + "diolang.com", + "directmail24.net", + "disaq.com", + "disbox.net", + "disbox.org", + "discard.cf", + "discard.email", + "discard.ga", + "discard.gq", + "discard.ml", + "discard.tk", + "discardmail.com", + "discardmail.de", + "discos4.com", + "dishcatfish.com", + "disign-concept.eu", + "disign-revelation.com", + "dispo.in", + "dispomail.eu", + "disposable-e.ml", + "disposable-email.ml", + "disposable.cf", + "disposable.ga", + "disposable.ml", + "disposable.site", + "disposableaddress.com", + "disposableemailaddresses.com", + "disposableinbox.com", + "disposablemails.com", + "dispose.it", + "disposeamail.com", + "disposemail.com", + "disposemymail.com", + "dispostable.com", + "divad.ga", + "divermail.com", + "divismail.ru", + "diwaq.com", + "dlemail.ru", + "dmarc.ro", + "dndent.com", + "dnses.ro", + "doanart.com", + "dob.jp", + "dodgeit.com", + "dodgemail.de", + "dodgit.com", + "dodgit.org", + "dodsi.com", + "doiea.com", + "dolphinnet.net", + "domforfb1.tk", + "domforfb18.tk", + "domforfb19.tk", + "domforfb2.tk", + "domforfb23.tk", + "domforfb27.tk", + "domforfb29.tk", + "domforfb3.tk", + "domforfb4.tk", + "domforfb5.tk", + "domforfb6.tk", + "domforfb7.tk", + "domforfb8.tk", + "domforfb9.tk", + "domozmail.com", + "donebyngle.com", + "donemail.ru", + "dongqing365.com", + "dontreg.com", + "dontsendmespam.de", + "doojazz.com", + "doquier.tk", + "dotman.de", + "dotmsg.com", + "dotslashrage.com", + "doublemail.de", + "douchelounge.com", + "dozvon-spb.ru", + "dp76.com", + "dpptd.com", + "dr69.site", + "drdrb.com", + "drdrb.net", + "dred.ru", + "drevo.si", + "drivetagdev.com", + "drmail.in", + "droolingfanboy.de", + "dropcake.de", + "dropjar.com", + "droplar.com", + "dropmail.me", + "dropsin.net", + "drowblock.com", + "dsgvo.party", + "dsgvo.ru", + "dshfjdafd.cloud", + "dsiay.com", + "dspwebservices.com", + "duam.net", + "duck2.club", + "dudmail.com", + "duk33.com", + "dukedish.com", + "dump-email.info", + "dumpandjunk.com", + "dumpmail.de", + "dumpyemail.com", + "durandinterstellar.com", + "duskmail.com", + "dwse.edu.pl", + "dyceroprojects.com", + "dz17.net", + "e-mail.com", + "e-mail.org", + "e-marketstore.ru", + "e-tomarigi.com", + "e3z.de", + "e4ward.com", + "eanok.com", + "easy-trash-mail.com", + "easynetwork.info", + "easytrashmail.com", + "eatmea2z.club", + "eay.jp", + "ebbob.com", + "ebeschlussbuch.de", + "ecallheandi.com", + "ecolo-online.fr", + "edgex.ru", + "edinburgh-airporthotels.com", + "edupolska.edu.pl", + "edv.to", + "ee1.pl", + "ee2.pl", + "eeedv.de", + "eelmail.com", + "efxs.ca", + "egzones.com", + "einmalmail.de", + "einrot.com", + "einrot.de", + "eintagsmail.de", + "elearningjournal.org", + "electro.mn", + "elitevipatlantamodels.com", + "elki-mkzn.ru", + "email-fake.cf", + "email-fake.com", + "email-fake.ga", + "email-fake.gq", + "email-fake.ml", + "email-fake.tk", + "email-jetable.fr", + "email-lab.com", + "email-temp.com", + "email.edu.pl", + "email.net", + "email1.pro", + "email60.com", + "emailage.cf", + "emailage.ga", + "emailage.gq", + "emailage.ml", + "emailage.tk", + "emailate.com", + "emailbin.net", + "emailcbox.pro", + "emailcu.icu", + "emaildienst.de", + "emaildrop.io", + "emailfake.com", + "emailfake.ml", + "emailfoxi.pro", + "emailfreedom.ml", + "emailgenerator.de", + "emailgo.de", + "emailias.com", + "emailigo.de", + "emailinfive.com", + "emailisvalid.com", + "emaillime.com", + "emailmiser.com", + "emailna.co", + "emailnax.com", + "emailo.pro", + "emailondeck.com", + "emailportal.info", + "emailproxsy.com", + "emailresort.com", + "emails.ga", + "emailsecurer.com", + "emailsensei.com", + "emailsingularity.net", + "emailspam.cf", + "emailspam.ga", + "emailspam.gq", + "emailspam.ml", + "emailspam.tk", + "emailsy.info", + "emailtech.info", + "emailtemporanea.com", + "emailtemporanea.net", + "emailtemporar.ro", + "emailtemporario.com.br", + "emailthe.net", + "emailtmp.com", + "emailto.de", + "emailure.net", + "emailwarden.com", + "emailxfer.com", + "emailz.cf", + "emailz.ga", + "emailz.gq", + "emailz.ml", + "emeil.in", + "emeil.ir", + "emeraldwebmail.com", + "emkei.cf", + "emkei.ga", + "emkei.gq", + "emkei.ml", + "emkei.tk", + "eml.pp.ua", + "emlhub.com", + "emlpro.com", + "emltmp.com", + "empireanime.ga", + "emstjzh.com", + "emz.net", + "enayu.com", + "enterto.com", + "envy17.com", + "eoffice.top", + "eoopy.com", + "epb.ro", + "epbox.ru", + "epbox.store", + "ephemail.net", + "ephemeral.email", + "eposta.buzz", + "eposta.work", + "epostal.ru", + "epostal.store", + "eqiluxspam.ga", + "ereplyzy.com", + "ericjohnson.ml", + "eripo.net", + "ero-tube.org", + "esadverse.com", + "esbano-ru.ru", + "esc.la", + "escapehatchapp.com", + "esemay.com", + "esgeneri.com", + "esiix.com", + "esprity.com", + "estate-invest.fr", + "esterace.com", + "eth2btc.info", + "ether123.net", + "ethereum1.top", + "ethersports.org", + "ethersportz.info", + "etotvibor.ru", + "etranquil.com", + "etranquil.net", + "etranquil.org", + "euaqa.com", + "evanfox.info", + "eveav.com", + "evilcomputer.com", + "evopo.com", + "evvgo.com", + "evyush.com", + "exdonuts.com", + "exelica.com", + "existiert.net", + "exitstageleft.net", + "explodemail.com", + "express.net.ua", + "extracurricularsociety.com", + "extremail.ru", + "exweme.com", + "eyepaste.com", + "ez.lv", + "ezehe.com", + "ezfill.com", + "ezstest.com", + "ezztt.com", + "f4k.es", + "facebook-email.cf", + "facebook-email.ga", + "facebook-email.ml", + "facebookmail.gq", + "facebookmail.ml", + "fackme.gq", + "fadingemail.com", + "faecesmail.me", + "fag.wf", + "failbone.com", + "faithkills.com", + "fake-box.com", + "fake-email.pp.ua", + "fake-mail.cf", + "fake-mail.ga", + "fake-mail.ml", + "fakedemail.com", + "fakeinbox.cf", + "fakeinbox.com", + "fakeinbox.ga", + "fakeinbox.info", + "fakeinbox.ml", + "fakeinbox.tk", + "fakeinformation.com", + "fakemail.fr", + "fakemail.io", + "fakemailgenerator.com", + "fakemailz.com", + "fallinhay.com", + "fammix.com", + "fanclub.pm", + "fangoh.com", + "fansworldwide.de", + "fantasymail.de", + "farrse.co.uk", + "fasssd.ru", + "fasssd.store", + "fast-email.info", + "fast-mail.fr", + "fastacura.com", + "fastchevy.com", + "fastchrysler.com", + "fasternet.biz", + "fastkawasaki.com", + "fastmazda.com", + "fastmitsubishi.com", + "fastnissan.com", + "fastsubaru.com", + "fastsuzuki.com", + "fasttoyota.com", + "fastyamaha.com", + "fatflap.com", + "fbma.tk", + "fddns.ml", + "fdfdsfds.com", + "femailtor.com", + "fer-gabon.org", + "fermaxxi.ru", + "fettometern.com", + "fexbox.org", + "fexbox.ru", + "fexpost.com", + "fextemp.com", + "ficken.de", + "fictionsite.com", + "fightallspam.com", + "figjs.com", + "figshot.com", + "figurescoin.com", + "fiifke.de", + "filbert4u.com", + "filberts4u.com", + "film-blog.biz", + "filzmail.com", + "findemail.info", + "findu.pl", + "finews.biz", + "fir.hk", + "firemailbox.club", + "fitnesrezink.ru", + "fivemail.de", + "fivermail.com", + "fixmail.tk", + "fizmail.com", + "fleckens.hu", + "flemail.ru", + "flexvio.com", + "fliegender.fish", + "flowu.com", + "flu.cc", + "fluidsoft.us", + "flurred.com", + "fly-ts.de", + "flyinggeek.net", + "flymail.tk", + "flyspam.com", + "fncp.ru", + "fncp.store", + "foobarbot.net", + "footard.com", + "foreastate.com", + "forecastertests.com", + "foreskin.cf", + "foreskin.ga", + "foreskin.gq", + "foreskin.ml", + "foreskin.tk", + "forgetmail.com", + "fornow.eu", + "forspam.net", + "forward.cat", + "fosil.pro", + "foxja.com", + "foxtrotter.info", + "fr.cr", + "fr.nf", + "fr33mail.info", + "fragolina2.tk", + "frapmail.com", + "frappina.tk", + "free-email.cf", + "free-email.ga", + "free-temp.net", + "freebabysittercam.com", + "freeblackbootytube.com", + "freecat.net", + "freedom4you.info", + "freedompop.us", + "freefattymovies.com", + "freehotmail.net", + "freeinbox.email", + "freelance-france.eu", + "freeletter.me", + "freemail.ms", + "freemails.cf", + "freemails.ga", + "freemails.ml", + "freemeil.ga", + "freemeil.gq", + "freemeil.ml", + "freeml.net", + "freeplumpervideos.com", + "freerubli.ru", + "freeschoolgirlvids.com", + "freesistercam.com", + "freeteenbums.com", + "freundin.ru", + "friendlymail.co.uk", + "front14.org", + "frwdmail.com", + "ftp.sh", + "ftpinc.ca", + "fuckedupload.com", + "fuckingduh.com", + "fuckme69.club", + "fucknloveme.top", + "fuckxxme.top", + "fudgerub.com", + "fuirio.com", + "fukaru.com", + "fukurou.ch", + "fullangle.org", + "fulvie.com", + "fun64.com", + "funnycodesnippets.com", + "funnymail.de", + "furzauflunge.de", + "futuramind.com", + "fuvk.ru", + "fuvk.store", + "fuwa.be", + "fuwa.li", + "fuwamofu.com", + "fuwari.be", + "fux0ringduh.com", + "fxnxs.com", + "fyii.de", + "g14l71lb.com", + "g1xmail.top", + "g2xmail.top", + "g3xmail.top", + "g4hdrop.us", + "gafy.net", + "gage.ga", + "galaxy.tv", + "gally.jp", + "gamail.top", + "gamegregious.com", + "gamgling.com", + "garasikita.pw", + "garbagecollector.org", + "garbagemail.org", + "gardenscape.ca", + "garizo.com", + "garliclife.com", + "garrymccooey.com", + "gav0.com", + "gawab.com", + "gbcmail.win", + "gbmail.top", + "gcmail.top", + "gdmail.top", + "gedmail.win", + "geekforex.com", + "geew.ru", + "gehensiemirnichtaufdensack.de", + "geldwaschmaschine.de", + "gelitik.in", + "genderfuck.net", + "geronra.com", + "geschent.biz", + "get-mail.cf", + "get-mail.ga", + "get-mail.ml", + "get-mail.tk", + "get.pp.ua", + "get1mail.com", + "get2mail.fr", + "getairmail.cf", + "getairmail.com", + "getairmail.ga", + "getairmail.gq", + "getairmail.ml", + "getairmail.tk", + "geteit.com", + "getfun.men", + "getmails.eu", + "getmule.com", + "getnada.com", + "getnowtoday.cf", + "getonemail.com", + "getonemail.net", + "getover.de", + "getsimpleemail.com", + "gett.icu", + "gexik.com", + "ggmal.ml", + "ggvk.ru", + "ggvk.store", + "ghosttexter.de", + "giacmosuaviet.info", + "giaiphapmuasam.com", + "giantmail.de", + "gifto12.com", + "gimpmail.com", + "ginzi.be", + "ginzi.co.uk", + "ginzi.es", + "ginzi.net", + "ginzy.co.uk", + "ginzy.eu", + "giratex.com", + "girlfriend.ru", + "girlmail.win", + "girlsindetention.com", + "girlsundertheinfluence.com", + "gishpuppy.com", + "giveh2o.info", + "givememail.club", + "givmail.com", + "gixenmixen.com", + "glitch.sx", + "globaltouron.com", + "glubex.com", + "glucosegrin.com", + "gmal.com", + "gmatch.org", + "gmial.com", + "gmx1mail.top", + "gmxmail.top", + "gmxmail.win", + "gnctr-calgary.com", + "go2usa.info", + "go2vpn.net", + "goatmail.uk", + "goemailgo.com", + "golemico.com", + "gomail.in", + "goonby.com", + "goplaygame.ru", + "gorillaswithdirtyarmpits.com", + "goround.info", + "gosarlar.com", + "gosuslugi-spravka.ru", + "gothere.biz", + "gotmail.com", + "gotmail.net", + "gotmail.org", + "gowikibooks.com", + "gowikicampus.com", + "gowikicars.com", + "gowikifilms.com", + "gowikigames.com", + "gowikimusic.com", + "gowikinetwork.com", + "gowikitravel.com", + "gowikitv.com", + "grandmamail.com", + "grandmasmail.com", + "grassdev.com", + "great-host.in", + "greencafe24.com", + "greendike.com", + "greenhousemail.com", + "greensloth.com", + "greggamel.com", + "greggamel.net", + "gregorsky.zone", + "gregorygamel.com", + "gregorygamel.net", + "grish.de", + "griuc.schule", + "grn.cc", + "groupbuff.com", + "grr.la", + "gruene-no-thanks.xyz", + "grugrug.ru", + "gruz-m.ru", + "gs-arc.org", + "gsredcross.org", + "gsrv.co.uk", + "gsxstring.ga", + "gudanglowongan.com", + "guerillamail.biz", + "guerillamail.com", + "guerillamail.de", + "guerillamail.info", + "guerillamail.net", + "guerillamail.org", + "guerillamailblock.com", + "guerrillamail.biz", + "guerrillamail.com", + "guerrillamail.de", + "guerrillamail.info", + "guerrillamail.net", + "guerrillamail.org", + "guerrillamailblock.com", + "gufum.com", + "gustr.com", + "guysmail.com", + "gxemail.men", + "gynzi.co.uk", + "gynzi.es", + "gynzy.at", + "gynzy.es", + "gynzy.eu", + "gynzy.gr", + "gynzy.info", + "gynzy.lt", + "gynzy.mobi", + "gynzy.pl", + "gynzy.ro", + "gynzy.sk", + "gzb.ro", + "h8s.org", + "habitue.net", + "hacccc.com", + "hackersquad.tk", + "hackthatbit.ch", + "hahawrong.com", + "haida-edu.cn", + "hairs24.ru", + "haltospam.com", + "hamham.uk", + "hangxomcuatoilatotoro.ml", + "happy2023year.com", + "happydomik.ru", + "harakirimail.com", + "haribu.com", + "hartbot.de", + "hasanmail.ml", + "hat-geld.de", + "hatespam.org", + "hawrong.com", + "haydoo.com", + "hazelnut4u.com", + "hazelnuts4u.com", + "hazmatshipping.org", + "hccmail.win", + "headstrong.de", + "heathenhammer.com", + "heathenhero.com", + "hecat.es", + "heisei.be", + "hellodream.mobi", + "helloricky.com", + "helpinghandtaxcenter.org", + "helpjobs.ru", + "heros3.com", + "herp.in", + "herpderp.nl", + "hezll.com", + "hi2.in", + "hi5.si", + "hiddentragedy.com", + "hidebox.org", + "hidebusiness.xyz", + "hidemail.de", + "hidemail.pro", + "hidemail.us", + "hidzz.com", + "highbros.org", + "hiltonvr.com", + "himail.online", + "hmail.us", + "hmamail.com", + "hmh.ro", + "hoanggiaanh.com", + "hoanglong.tech", + "hochsitze.com", + "hola.org", + "holl.ga", + "honeys.be", + "honor-8.com", + "hopemail.biz", + "hornyalwary.top", + "host1s.com", + "hostcalls.com", + "hostguru.top", + "hostingmail.me", + "hostlaba.com", + "hot-mail.cf", + "hot-mail.ga", + "hot-mail.gq", + "hot-mail.ml", + "hot-mail.tk", + "hotmai.com", + "hotmailproduct.com", + "hotmial.com", + "hotpop.com", + "hotprice.co", + "hotsoup.be", + "housat.com", + "hpc.tw", + "hs.vc", + "ht.cx", + "hthlm.com", + "huangniu8.com", + "huizk.com", + "hukkmu.tk", + "hulapla.de", + "humaility.com", + "hungpackage.com", + "hushmail.cf", + "huskion.net", + "hvastudiesucces.nl", + "hwsye.net", + "hxopi.ru", + "hxopi.store", + "hypenated-domain.com", + "i2pmail.org", + "i6.cloudns.cc", + "iaoss.com", + "ibnuh.bz", + "icantbelieveineedtoexplainthisshit.com", + "icemail.club", + "ich-essen-fleisch.bio", + "ichigo.me", + "icx.in", + "icx.ro", + "icznn.com", + "idx4.com", + "idxue.com", + "ieatspam.eu", + "ieatspam.info", + "ieh-mail.de", + "iencm.com", + "iffymedia.com", + "ige.es", + "igg.biz", + "ignoremail.com", + "ihateyoualot.info", + "ihazspam.ca", + "iheartspam.org", + "ikbenspamvrij.nl", + "ikuromi.com", + "illistnoise.com", + "ilovespam.com", + "imail1.net", + "imails.info", + "imailt.com", + "imgof.com", + "imgv.de", + "immo-gerance.info", + "imperialcnk.com", + "imstations.com", + "imul.info", + "in-ulm.de", + "in2reach.com", + "inactivemachine.com", + "inbax.tk", + "inbound.plus", + "inbox.si", + "inbox2.info", + "inboxalias.com", + "inboxbear.com", + "inboxclean.com", + "inboxclean.org", + "inboxdesign.me", + "inboxed.im", + "inboxed.pw", + "inboxkitten.com", + "inboxnow.ru", + "inboxnow.store", + "inboxproxy.com", + "inboxstore.me", + "inclusiveprogress.com", + "incognitomail.com", + "incognitomail.net", + "incognitomail.org", + "incq.com", + "ind.st", + "indieclad.com", + "indirect.ws", + "indomaed.pw", + "indomina.cf", + "indoserver.stream", + "indosukses.press", + "ineec.net", + "infocom.zp.ua", + "inggo.org", + "inkiny.com", + "inkomail.com", + "inmynetwork.tk", + "inoutmail.de", + "inoutmail.eu", + "inoutmail.info", + "inoutmail.net", + "inpwa.com", + "insanumingeniumhomebrew.com", + "insorg-mail.info", + "instaddr.ch", + "instaddr.uk", + "instaddr.win", + "instance-email.com", + "instant-mail.de", + "instantblingmail.info", + "instantemailaddress.com", + "instantmail.fr", + "instmail.uk", + "internet-v-stavropole.ru", + "internetkeno.com", + "internetoftags.com", + "interstats.org", + "intersteller.com", + "intopwa.com", + "intopwa.net", + "intopwa.org", + "investore.co", + "iozak.com", + "ip4.pp.ua", + "ip6.li", + "ip6.pp.ua", + "ipoo.org", + "ippandansei.tk", + "ipsur.org", + "irabops.com", + "iralborz.bid", + "irc.so", + "irish2me.com", + "irishspringrealty.com", + "iroid.com", + "ironiebehindert.de", + "irssi.tv", + "is.af", + "isdaq.com", + "ishop2k.com", + "isosq.com", + "istii.ro", + "isukrainestillacountry.com", + "it7.ovh", + "italy-mail.com", + "itcompu.com", + "itfast.net", + "itsjiff.com", + "itunesgiftcodegenerator.com", + "iubridge.com", + "iuemail.men", + "iwi.net", + "ixaks.com", + "ixx.io", + "j-p.us", + "jafps.com", + "jaga.email", + "jajxz.com", + "jakemsr.com", + "janproz.com", + "jaqis.com", + "jdmadventures.com", + "jdz.ro", + "je-recycle.info", + "jellow.ml", + "jellyrolls.com", + "jeoce.com", + "jet-renovation.fr", + "jetable.com", + "jetable.net", + "jetable.org", + "jetable.pp.ua", + "ji5.de", + "ji6.de", + "ji7.de", + "jiooq.com", + "jmail.ovh", + "jmail.ro", + "jnxjn.com", + "jobbikszimpatizans.hu", + "jobbrett.com", + "jobposts.net", + "jobs-to-be-done.net", + "joelpet.com", + "joetestalot.com", + "jofuso.com", + "jopho.com", + "joseihorumon.info", + "josse.ltd", + "jourrapide.com", + "jpco.org", + "jsrsolutions.com", + "jumonji.tk", + "jungkamushukum.com", + "junk.to", + "junk1e.com", + "junkmail.ga", + "junkmail.gq", + "just-email.com", + "justemail.ml", + "juyouxi.com", + "jwork.ru", + "kademen.com", + "kadokawa.cf", + "kadokawa.ga", + "kadokawa.gq", + "kadokawa.ml", + "kadokawa.tk", + "kaengu.ru", + "kagi.be", + "kakadua.net", + "kalapi.org", + "kamen-market.ru", + "kamsg.com", + "kaovo.com", + "kappala.info", + "kara-turk.net", + "karatraman.ml", + "kariplan.com", + "karta-kykyruza.ru", + "kartvelo.com", + "kasmail.com", + "kaspop.com", + "katztube.com", + "kazelink.ml", + "kbox.li", + "kcrw.de", + "keepmymail.com", + "keinhirn.de", + "keipino.de", + "kekita.com", + "kellychibale-researchgroup-uct.com", + "kemptvillebaseball.com", + "kiani.com", + "killmail.com", + "killmail.net", + "kimsdisk.com", + "kinda.email", + "kindamail.com", + "kingsq.ga", + "kino-100.ru", + "kiois.com", + "kismail.ru", + "kisstwink.com", + "kitnastar.com", + "kjkszpjcompany.com", + "kkmail.be", + "kkoup.com", + "kksm.be", + "klassmaster.com", + "klassmaster.net", + "klick-tipp.us", + "klipschx12.com", + "kloap.com", + "klovenode.com", + "kludgemush.com", + "klzlk.com", + "kmail.li", + "kmail.live", + "kmhow.com", + "knickerbockerban.de", + "knol-power.nl", + "kobrandly.com", + "kommunity.biz", + "kon42.com", + "konican.com", + "konultant-jurist.ru", + "kook.ml", + "kopagas.com", + "kopaka.net", + "korona-nedvizhimosti.ru", + "koshu.ru", + "kosmetik-obatkuat.com", + "kostenlosemailadresse.de", + "koszmail.pl", + "kpay.be", + "kpooa.com", + "kpost.be", + "krd.ag", + "krsw.tk", + "kruay.com", + "krypton.tk", + "ksmtrck.tk", + "kuhrap.com", + "kuku.lu", + "kulmeo.com", + "kulturbetrieb.info", + "kumli.racing", + "kurzepost.de", + "kutakbisajauhjauh.gq", + "kvhrr.com", + "kvhrs.com", + "kvhrw.com", + "kwift.net", + "kwilco.net", + "kyal.pl", + "kyois.com", + "kzccv.com", + "l-c-a.us", + "l33r.eu", + "l6factors.com", + "laafd.com", + "labetteraverouge.at", + "labworld.org", + "lacedmail.com", + "lackmail.net", + "lackmail.ru", + "lacto.info", + "lags.us", + "lain.ch", + "lak.pp.ua", + "lakelivingstonrealestate.com", + "lakqs.com", + "lamasticots.com", + "lambsauce.de", + "landmail.co", + "laoeq.com", + "larisia.com", + "larland.com", + "last-chance.pro", + "laste.ml", + "lastmail.co", + "lastmail.com", + "lawlita.com", + "laxex.ru", + "laxex.store", + "laymro.com", + "lazyinbox.com", + "lazyinbox.us", + "ldaho.biz", + "ldop.com", + "ldtp.com", + "le-tim.ru", + "lee.mx", + "leeching.net", + "leetmail.co", + "legalrc.loan", + "lellno.gq", + "lenovog4.com", + "lerbhe.com", + "letmeinonthis.com", + "letthemeatspam.com", + "lez.se", + "lgxscreen.com", + "lhsdv.com", + "liamcyrus.com", + "lifebyfood.com", + "lifetimefriends.info", + "lifetotech.com", + "ligsb.com", + "lillemap.net", + "lilo.me", + "lilspam.com", + "lindenbaumjapan.com", + "link2mail.net", + "linkedintuts2016.pw", + "linshiyou.com", + "linshiyouxiang.net", + "linuxmail.so", + "lista.cc", + "litedrop.com", + "liveradio.tk", + "lkgn.se", + "llogin.ru", + "loadby.us", + "loan101.pro", + "loaoa.com", + "loapq.com", + "locanto1.club", + "locantofuck.top", + "locantowsite.club", + "locomodev.net", + "login-email.cf", + "login-email.ga", + "login-email.ml", + "login-email.tk", + "logular.com", + "loh.pp.ua", + "loin.in", + "lolfreak.net", + "lolmail.biz", + "lookugly.com", + "lordsofts.com", + "lortemail.dk", + "losemymail.com", + "lovemeet.faith", + "lovemeleaveme.com", + "lpfmgmtltd.com", + "lr7.us", + "lr78.com", + "lroid.com", + "lru.me", + "ls-server.ru", + "lsyx24.com", + "luckymail.org", + "lukecarriere.com", + "lukemail.info", + "lukop.dk", + "luv2.us", + "lyfestylecreditsolutions.com", + "lyft.live", + "lyricspad.net", + "lzoaq.com", + "m21.cc", + "m4ilweb.info", + "maboard.com", + "mac-24.com", + "macr2.com", + "macromaid.com", + "macromice.info", + "magamail.com", + "maggotymeat.ga", + "magicbox.ro", + "magim.be", + "magspam.net", + "maidlow.info", + "mail-card.net", + "mail-easy.fr", + "mail-filter.com", + "mail-help.net", + "mail-hosting.co", + "mail-hub.info", + "mail-now.top", + "mail-owl.com", + "mail-share.com", + "mail-temporaire.com", + "mail-temporaire.fr", + "mail-tester.com", + "mail.by", + "mail.wtf", + "mail0.ga", + "mail1.top", + "mail114.net", + "mail1a.de", + "mail1web.org", + "mail21.cc", + "mail22.club", + "mail2rss.org", + "mail333.com", + "mail4trash.com", + "mail666.ru", + "mail7.io", + "mail707.com", + "mail72.com", + "mailapp.top", + "mailback.com", + "mailbidon.com", + "mailbiscuit.com", + "mailbiz.biz", + "mailblocks.com", + "mailbox.in.ua", + "mailbox.zip", + "mailbox52.ga", + "mailbox80.biz", + "mailbox82.biz", + "mailbox87.de", + "mailbox92.biz", + "mailboxify.ru", + "mailboxify.store", + "mailboxly.ru", + "mailboxly.store", + "mailboxy.fun", + "mailboxy.ru", + "mailboxy.store", + "mailbucket.org", + "mailcat.biz", + "mailcatch.com", + "mailchop.com", + "mailcker.com", + "maildax.me", + "mailde.de", + "mailde.info", + "maildrop.cc", + "maildrop.cf", + "maildrop.ga", + "maildrop.gq", + "maildrop.ml", + "maildu.de", + "maildx.com", + "maileater.com", + "mailed.in", + "mailed.ro", + "maileimer.de", + "maileme101.com", + "mailers.edu.pl", + "mailexpire.com", + "mailf5.com", + "mailfa.tk", + "mailfall.com", + "mailfast.pro", + "mailfirst.icu", + "mailforspam.com", + "mailfree.ga", + "mailfree.gq", + "mailfree.ml", + "mailfreeonline.com", + "mailfs.com", + "mailguard.me", + "mailgutter.com", + "mailhazard.com", + "mailhazard.us", + "mailhex.com", + "mailhub.pro", + "mailhz.me", + "mailimate.com", + "mailin8r.com", + "mailinatar.com", + "mailinater.com", + "mailinator.co.uk", + "mailinator.com", + "mailinator.gq", + "mailinator.info", + "mailinator.net", + "mailinator.org", + "mailinator.us", + "mailinator0.com", + "mailinator1.com", + "mailinator2.com", + "mailinator2.net", + "mailinator3.com", + "mailinator4.com", + "mailinator5.com", + "mailinator6.com", + "mailinator7.com", + "mailinator8.com", + "mailinator9.com", + "mailincubator.com", + "mailisia.com", + "mailismagic.com", + "mailita.tk", + "mailjunk.cf", + "mailjunk.ga", + "mailjunk.gq", + "mailjunk.ml", + "mailjunk.tk", + "mailmate.com", + "mailme.gq", + "mailme.ir", + "mailme.lv", + "mailme24.com", + "mailmenot.io", + "mailmetrash.com", + "mailmoat.com", + "mailmoth.com", + "mailms.com", + "mailna.biz", + "mailna.co", + "mailna.in", + "mailna.me", + "mailnator.com", + "mailnesia.com", + "mailnull.com", + "mailnuo.com", + "mailonaut.com", + "mailorc.com", + "mailorg.org", + "mailosaur.net", + "mailox.fun", + "mailpick.biz", + "mailpluss.com", + "mailpooch.com", + "mailpoof.com", + "mailpress.gq", + "mailproxsy.com", + "mailquack.com", + "mailrock.biz", + "mailsac.com", + "mailscrap.com", + "mailseal.de", + "mailshell.com", + "mailshiv.com", + "mailsiphon.com", + "mailslapping.com", + "mailslite.com", + "mailsucker.net", + "mailt.net", + "mailt.top", + "mailtechx.com", + "mailtemp.info", + "mailtemporaire.com", + "mailtemporaire.fr", + "mailto.plus", + "mailtome.de", + "mailtothis.com", + "mailtraps.com", + "mailtrash.net", + "mailtrix.net", + "mailtv.net", + "mailtv.tv", + "mailuniverse.co.uk", + "mailzi.ru", + "mailzilla.com", + "mailzilla.org", + "mainerfolg.info", + "makemenaughty.club", + "makemetheking.com", + "malahov.de", + "malayalamdtp.com", + "mama3.org", + "mamulenok.ru", + "mandraghen.cf", + "manifestgenerator.com", + "mannawo.com", + "mansiondev.com", + "manybrain.com", + "mark-compressoren.ru", + "marketlink.info", + "markmurfin.com", + "mask03.ru", + "maskmy.id", + "masonline.info", + "maswae.world", + "matamuasu.ga", + "matchpol.net", + "matra.site", + "max-mail.org", + "maxturns.com", + "mbox.re", + "mbx.cc", + "mcache.net", + "mciek.com", + "mdhc.tk", + "mdz.email", + "meantinc.com", + "mebelnu.info", + "mechanicalresumes.com", + "medkabinet-uzi.ru", + "meepsheep.eu", + "mehr-bitcoin.de", + "meidecn.com", + "meinspamschutz.de", + "meltedbrownies.com", + "meltmail.com", + "memsg.site", + "mentonit.net", + "mepost.pw", + "merepost.com", + "merry.pink", + "meruado.uk", + "messagebeamer.de", + "messwiththebestdielikethe.rest", + "metadownload.org", + "metaintern.net", + "metalunits.com", + "mezimages.net", + "mfsa.info", + "mfsa.ru", + "mfunza.com", + "mhzayt.online", + "miaferrari.com", + "miauj.com", + "midcoastcustoms.com", + "midcoastcustoms.net", + "midcoastsolutions.com", + "midcoastsolutions.net", + "midiharmonica.com", + "midlertidig.com", + "midlertidig.net", + "midlertidig.org", + "mierdamail.com", + "migmail.net", + "migmail.pl", + "migumail.com", + "mihep.com", + "mijnhva.nl", + "minimail.gq", + "ministry-of-silly-walks.de", + "minsmail.com", + "mintemail.com", + "mirai.re", + "misterpinball.de", + "miucce.com", + "mji.ro", + "mjj.edu.ge", + "mjukglass.nu", + "mkpfilm.com", + "ml8.ca", + "mliok.com", + "mm.my", + "mm5.se", + "mnode.me", + "moakt.cc", + "moakt.co", + "moakt.com", + "moakt.ws", + "mobileninja.co.uk", + "mobilevpn.top", + "moburl.com", + "mockmyid.com", + "moeri.org", + "mofu.be", + "mohmal.com", + "mohmal.im", + "mohmal.in", + "mohmal.tech", + "moimoi.re", + "molms.com", + "momentics.ru", + "monachat.tk", + "monadi.ml", + "moneypipe.net", + "monumentmail.com", + "moonwake.com", + "moot.es", + "moreawesomethanyou.com", + "moreorcs.com", + "morriesworld.ml", + "morsin.com", + "moruzza.com", + "motique.de", + "mountainregionallibrary.net", + "mox.pp.ua", + "moy-elektrik.ru", + "moza.pl", + "mozej.com", + "mp-j.ga", + "mr24.co", + "mrvpm.net", + "mrvpt.com", + "msgos.com", + "mspeciosa.com", + "msrc.ml", + "mswork.ru", + "msxd.com", + "mt2009.com", + "mt2014.com", + "mt2015.com", + "mtmdev.com", + "muathegame.com", + "muchomail.com", + "mucincanon.com", + "muehlacker.tk", + "muell.icu", + "muell.io", + "muell.monster", + "muell.xyz", + "muellemail.com", + "muellmail.com", + "munoubengoshi.gq", + "musiccode.me", + "mutant.me", + "mvrht.com", + "mvrht.net", + "mwarner.org", + "mxclip.com", + "mxfuel.com", + "my-pomsies.ru", + "my-teddyy.ru", + "my10minutemail.com", + "mybitti.de", + "mycleaninbox.net", + "mycorneroftheinter.net", + "myde.ml", + "mydefipet.live", + "mydemo.equipment", + "myecho.es", + "myemailboxy.com", + "mygeoweb.info", + "myindohome.services", + "myinfoinc.com", + "myinterserver.ml", + "mykickassideas.com", + "mymail-in.net", + "mymail90.com", + "mymailoasis.com", + "mymaily.lol", + "mynetstore.de", + "myopang.com", + "mypacks.net", + "mypartyclip.de", + "myphantomemail.com", + "mysamp.de", + "myspaceinc.com", + "myspaceinc.net", + "myspaceinc.org", + "myspacepimpedup.com", + "myspamless.com", + "mystvpn.com", + "mysugartime.ru", + "mytemp.email", + "mytempemail.com", + "mytempmail.com", + "mytrashmail.com", + "mywarnernet.net", + "mywrld.site", + "mywrld.top", + "myzx.com", + "mzico.com", + "n1nja.org", + "na-cat.com", + "naah.ru", + "naah.store", + "nabuma.com", + "nada.email", + "nada.ltd", + "nagi.be", + "nakedtruth.biz", + "namewok.com", + "nanonym.ch", + "naslazhdai.ru", + "nationalgardeningclub.com", + "navalcadets.com", + "nawmin.info", + "naymedia.com", + "nbzmr.com", + "negated.com", + "neko2.net", + "nekochan.fr", + "nekosan.uk", + "neomailbox.com", + "neotlozhniy-zaim.ru", + "nepwk.com", + "nervmich.net", + "nervtmich.net", + "net1mail.com", + "netcom.ws", + "netmails.com", + "netmails.net", + "netricity.nl", + "netris.net", + "netviewer-france.com", + "netzidiot.de", + "nevermail.de", + "newbpotato.tk", + "newfilm24.ru", + "newideasfornewpeople.info", + "newmail.top", + "next.ovh", + "nextmail.info", + "nextstopvalhalla.com", + "nezdiro.org", + "nezid.com", + "nezumi.be", + "nezzart.com", + "nfast.net", + "nguyenusedcars.com", + "nh3.ro", + "nice-4u.com", + "nicknassar.com", + "nincsmail.com", + "nincsmail.hu", + "niseko.be", + "niwl.net", + "nm123.com", + "nm7.cc", + "nmail.cf", + "nnh.com", + "nnot.net", + "nnoway.ru", + "no-spam.ws", + "no-trash.ru", + "no-ux.com", + "noblepioneer.com", + "nobugmail.com", + "nobulk.com", + "nobuma.com", + "noclickemail.com", + "nocp.ru", + "nocp.store", + "nodezine.com", + "nogmailspam.info", + "noicd.com", + "nokiamail.com", + "nolemail.ga", + "nomail.cf", + "nomail.ga", + "nomail.pw", + "nomail2me.com", + "nomorespamemails.com", + "nonspam.eu", + "nonspammer.de", + "nonze.ro", + "noref.in", + "norseforce.com", + "norwegischlernen.info", + "nospam4.us", + "nospamfor.us", + "nospamthanks.info", + "nothingtoseehere.ca", + "notif.me", + "notmailinator.com", + "notrnailinator.com", + "notsharingmy.info", + "now.im", + "nowhere.org", + "nowmymail.com", + "nowmymail.net", + "nproxi.com", + "nthrl.com", + "ntlhelp.net", + "nubescontrol.com", + "nullbox.info", + "nurfuerspam.de", + "nut.cc", + "nutpa.net", + "nuts2trade.com", + "nvhrw.com", + "nwldx.com", + "nwytg.com", + "nwytg.net", + "ny7.me", + "nyasan.com", + "nypato.com", + "nyrmusic.com", + "o2stk.org", + "o7i.net", + "oalsp.com", + "obfusko.com", + "objectmail.com", + "obobbo.com", + "oborudovanieizturcii.ru", + "obxpestcontrol.com", + "octovie.com", + "odaymail.com", + "odem.com", + "odnorazovoe.ru", + "oepia.com", + "oerpub.org", + "offshore-proxies.net", + "ofisher.net", + "ohaaa.de", + "ohi.tw", + "oida.icu", + "oing.cf", + "okclprojects.com", + "okinawa.li", + "okrent.us", + "okzk.com", + "olimp-case.ru", + "oloh.ru", + "oloh.store", + "olypmall.ru", + "omail.pro", + "omnievents.org", + "omtecha.com", + "one-mail.top", + "one-time.email", + "one2mail.info", + "onekisspresave.com", + "onemail.host", + "oneoffemail.com", + "oneoffmail.com", + "onetm.jp", + "onewaymail.com", + "onlatedotcom.info", + "online.ms", + "onlineidea.info", + "onlyapp.net", + "onqin.com", + "ontyne.biz", + "oohioo.com", + "oolus.com", + "oonies-shoprus.ru", + "oopi.org", + "oosln.com", + "oovk.ru", + "oovk.store", + "opayq.com", + "openavz.com", + "opendns.ro", + "opentrash.com", + "opmmedia.ga", + "opp24.com", + "optimaweb.me", + "opwebw.com", + "oranek.com", + "ordinaryamerican.net", + "oreidresume.com", + "orgmbx.cc", + "oroki.de", + "orsbap.com", + "oshietechan.link", + "otherinbox.com", + "ourklips.com", + "ourpreviewdomain.com", + "outlawspam.com", + "outlook.edu.pl", + "outmail.win", + "ovomail.co", + "ovpn.to", + "owleyes.ch", + "owlpic.com", + "ownsyou.de", + "oxopoha.com", + "ozatvn.com", + "ozyl.de", + "p-banlis.ru", + "p33.org", + "p71ce1m.com", + "pa9e.com", + "pachilly.com", + "packiu.com", + "pagamenti.tk", + "paharpurmim.ga", + "pakadebu.ga", + "pamaweb.com", + "pancakemail.com", + "papierkorb.me", + "paplease.com", + "para2019.ru", + "parlimentpetitioner.tk", + "pastebitch.com", + "patonce.com", + "pavilionx2.com", + "payperex2.com", + "payspun.com", + "pe.hu", + "pecinan.com", + "pecinan.net", + "pecinan.org", + "penisgoes.in", + "penoto.tk", + "pepbot.com", + "peterdethier.com", + "petloca.com", + "petrzilka.net", + "pewpewpewpew.pw", + "pflege-schoene-haut.de", + "pfui.ru", + "phone-elkey.ru", + "photo-impact.eu", + "photomark.net", + "pi.vu", + "piaa.me", + "pig.pp.ua", + "pii.at", + "piki.si", + "pimpedupmyspace.com", + "pinehill-seattle.org", + "pingir.com", + "pipemail.space", + "pisls.com", + "pitaniezdorovie.ru", + "pivo-bar.ru", + "pixiil.com", + "pizu.ru", + "pizu.store", + "pizzajunk.com", + "pjjkp.com", + "placebomail10.com", + "pleasenoham.org", + "plexfirm.com", + "plexolan.de", + "plhk.ru", + "ploae.com", + "ploncy.com", + "plw.me", + "poehali-otdihat.ru", + "pojok.ml", + "pokemail.net", + "pokiemobile.com", + "polarkingxx.ml", + "politikerclub.de", + "polyfaust.net", + "pooae.com", + "poofy.org", + "pookmail.com", + "poopiebutt.club", + "popcornfarm7.com", + "popcornfly.com", + "popesodomy.com", + "popgx.com", + "porjoton.com", + "porsh.net", + "posdz.com", + "posta.store", + "postacin.com", + "postbx.ru", + "postbx.store", + "postonline.me", + "poutineyourface.com", + "powered.name", + "powerencry.com", + "powlearn.com", + "pp7rvv.com", + "ppetw.com", + "pptrvv.com", + "pqoia.com", + "pratikmail.com", + "pratikmail.net", + "pratikmail.org", + "prazdnik-37.ru", + "predatorrat.cf", + "predatorrat.ga", + "predatorrat.gq", + "predatorrat.ml", + "predatorrat.tk", + "premium-mail.fr", + "primabananen.net", + "prin.be", + "privacy.net", + "privatdemail.net", + "privmail.edu.pl", + "privy-mail.com", + "privy-mail.de", + "privymail.de", + "pro-tag.org", + "pro5g.com", + "procrackers.com", + "profast.top", + "projectcl.com", + "promailt.com", + "proprietativalcea.ro", + "propscore.com", + "protempmail.com", + "proxymail.eu", + "proxyparking.com", + "prtnx.com", + "prtshr.com", + "prtz.eu", + "psh.me", + "psles.com", + "psnator.com", + "psoxs.com", + "puglieisi.com", + "puji.pro", + "punkass.com", + "puppetmail.de", + "purcell.email", + "purelogistics.org", + "pursip.com", + "put2.net", + "puttanamaiala.tk", + "putthisinyourspamdatabase.com", + "pwpwa.com", + "pwrby.com", + "qabq.com", + "qasti.com", + "qbfree.us", + "qc.to", + "qibl.at", + "qiott.com", + "qipmail.net", + "qiq.us", + "qisdo.com", + "qisoa.com", + "qmrbe.com", + "qodiq.com", + "qoika.com", + "qopow.com", + "qq.my", + "qsl.ro", + "qtum-ico.com", + "quadrafit.com", + "quick-mail.cc", + "quickemail.info", + "quickinbox.com", + "quickmail.nl", + "quicksend.ch", + "quipas.com", + "ququb.com", + "qvy.me", + "qwickmail.com", + "r4nd0m.de", + "ra3.us", + "rabin.ca", + "rabiot.reisen", + "rackabzar.com", + "raetp9.com", + "rainbowly.ml", + "raketenmann.de", + "ramenmail.de", + "ramin200.site", + "rancidhome.net", + "randomail.io", + "randomail.net", + "rapt.be", + "raqid.com", + "rax.la", + "raxtest.com", + "razemail.com", + "razuz.com", + "rbb.org", + "rcasd.com", + "rcpt.at", + "rdklcrv.xyz", + "re-gister.com", + "reality-concept.club", + "reallymymail.com", + "realquickemail.com", + "realtyalerts.ca", + "rebates.stream", + "receiveee.com", + "recipeforfailure.com", + "recode.me", + "reconmail.com", + "recyclemail.dk", + "redfeathercrow.com", + "reftoken.net", + "regapts.com", + "regbypass.com", + "regspaces.tk", + "reimondo.com", + "rejectmail.com", + "rejo.technology", + "reliable-mail.com", + "remail.cf", + "remail.ga", + "remarkable.rocks", + "remote.li", + "rentaen.com", + "replyloop.com", + "reptilegenetics.com", + "resgedvgfed.tk", + "revolvingdoorhoax.org", + "rfc822.org", + "rhyta.com", + "richfinances.pw", + "riddermark.de", + "rifkian.ga", + "rinseart.com", + "rippb.com", + "risingsuntouch.com", + "riski.cf", + "risu.be", + "rklips.com", + "rkomo.com", + "rm2rf.com", + "rma.ec", + "rmqkr.net", + "rnailinator.com", + "ro.lt", + "robertspcrepair.com", + "roborena.com", + "robot-mail.com", + "rollindo.agency", + "ronnierage.net", + "rootfest.net", + "rosebearmylove.ru", + "rotaniliam.com", + "rover.info", + "rowe-solutions.com", + "royal.net", + "royaldoodles.org", + "royalmarket.life", + "royandk.com", + "rppkn.com", + "rsvhr.com", + "rteet.com", + "rtrtr.com", + "rtskiya.xyz", + "rudymail.ml", + "rumgel.com", + "runi.ca", + "rupayamail.com", + "ruru.be", + "rustydoor.com", + "rustyload.com", + "ruu.kr", + "rvb.ro", + "ryteto.me", + "ryyr.ru", + "ryyr.store", + "s0ny.net", + "s33db0x.com", + "sabrestlouis.com", + "sackboii.com", + "saeoil.com", + "safaat.cf", + "safermail.info", + "safersignup.de", + "safetymail.info", + "safetypost.de", + "saharanightstempe.com", + "sailmail.io", + "salmeow.tk", + "samsclass.info", + "sandcars.net", + "sandelf.de", + "sandwhichvideo.com", + "sanfinder.com", + "sanim.net", + "sanstr.com", + "sast.ro", + "satisfyme.club", + "satukosong.com", + "sausen.com", + "saynotospams.com", + "scatmail.com", + "scay.net", + "schachrol.com", + "schafmail.de", + "schmeissweg.tk", + "schrott-email.de", + "scrsot.com", + "sd3.in", + "sdvft.com", + "sdvgeft.com", + "sdvrecft.com", + "secmail.pw", + "secretemail.de", + "secure-mail.biz", + "secure-mail.cc", + "secured-link.net", + "securehost.com.es", + "seekapps.com", + "seekjobs4u.com", + "sejaa.lv", + "selfdestructingmail.com", + "selfdestructingmail.org", + "send22u.info", + "sendapp.uk", + "sendfree.org", + "sendingspecialflyers.com", + "sendnow.win", + "sendspamhere.com", + "senseless-entertainment.com", + "seosnaps.com", + "server.ms", + "services391.com", + "sexforswingers.com", + "sexical.com", + "sexyalwasmi.top", + "sfolkar.com", + "sgatra.com", + "shadap.org", + "shalar.net", + "sharedmailbox.org", + "sharkfaces.com", + "sharklasers.com", + "shchiba.uk", + "sheryli.com", + "shhmail.com", + "shhuut.org", + "shieldedmail.com", + "shieldemail.com", + "shiftmail.com", + "shipfromto.com", + "shiphazmat.org", + "shipping-regulations.com", + "shippingterms.org", + "shitaway.tk", + "shitmail.de", + "shitmail.me", + "shitmail.org", + "shmeriously.com", + "shopxda.com", + "shortmail.net", + "shotmail.ru", + "showslow.de", + "shrib.com", + "shut.name", + "shut.ws", + "siberpay.com", + "sidelka-mytischi.ru", + "siftportal.ru", + "sify.com", + "sika3.com", + "sikux.com", + "silenceofthespam.com", + "siliwangi.ga", + "silvercoin.life", + "sim-simka.ru", + "simaenaga.com", + "simpleitsecurity.info", + "sin.cl", + "sinaite.net", + "sinema.ml", + "sinfiltro.cl", + "singlespride.com", + "sinnlos-mail.de", + "sino.tw", + "siteposter.net", + "sizzlemctwizzle.com", + "sjuaq.com", + "skeefmail.com", + "skrak.com", + "skrx.tk", + "sky-inbox.com", + "sky-ts.de", + "skygazerhub.com", + "skyrt.de", + "slapsfromlastnight.com", + "slaskpost.se", + "slave-auctions.net", + "slippery.email", + "slipry.net", + "slopsbox.com", + "slothmail.net", + "slushmail.com", + "sluteen.com", + "sly.io", + "smallker.tk", + "smapfree24.com", + "smapfree24.de", + "smapfree24.eu", + "smapfree24.info", + "smapfree24.org", + "smartemailbox.co", + "smartnator.com", + "smarttalent.pw", + "smashmail.de", + "smellfear.com", + "smellrear.com", + "smellypotato.tk", + "smtp99.com", + "smwg.info", + "snakebutt.com", + "snakemail.com", + "snapmail.cc", + "snapwet.com", + "sneakmail.de", + "snece.com", + "social-mailer.tk", + "socialfurry.org", + "sociallymediocre.com", + "sofia.re", + "sofimail.com", + "sofort-mail.de", + "sofortmail.de", + "sofrge.com", + "softkey-office.ru", + "softpls.asia", + "sogetthis.com", + "sohai.ml", + "sohus.cn", + "soioa.com", + "soisz.com", + "solar-impact.pro", + "solvemail.info", + "solventtrap.wiki", + "songsign.com", + "sonshi.cf", + "soodmail.com", + "soodomail.com", + "soodonims.com", + "soombo.com", + "soon.it", + "spacebazzar.ru", + "spam-be-gone.com", + "spam.care", + "spam.ceo", + "spam.la", + "spam.org.es", + "spam.su", + "spam4.me", + "spamail.de", + "spamarrest.com", + "spamavert.com", + "spambob.com", + "spambob.net", + "spambob.org", + "spambog.com", + "spambog.de", + "spambog.net", + "spambog.ru", + "spambooger.com", + "spambox.info", + "spambox.me", + "spambox.org", + "spambox.us", + "spamcero.com", + "spamcon.org", + "spamcorptastic.com", + "spamcowboy.com", + "spamcowboy.net", + "spamcowboy.org", + "spamday.com", + "spamdecoy.net", + "spamex.com", + "spamfellas.com", + "spamfighter.cf", + "spamfighter.ga", + "spamfighter.gq", + "spamfighter.ml", + "spamfighter.tk", + "spamfree.eu", + "spamfree24.com", + "spamfree24.de", + "spamfree24.eu", + "spamfree24.info", + "spamfree24.net", + "spamfree24.org", + "spamgoes.in", + "spamherelots.com", + "spamhereplease.com", + "spamhole.com", + "spamify.com", + "spaminator.de", + "spamkill.info", + "spaml.com", + "spaml.de", + "spamlot.net", + "spammer.fail", + "spammotel.com", + "spammy.host", + "spamobox.com", + "spamoff.de", + "spamsalad.in", + "spamsandwich.com", + "spamslicer.com", + "spamsphere.com", + "spamspot.com", + "spamstack.net", + "spamthis.co.uk", + "spamthis.network", + "spamthisplease.com", + "spamtrail.com", + "spamtrap.ro", + "spamtroll.net", + "spamwc.cf", + "spamwc.ga", + "spamwc.gq", + "spamwc.ml", + "speedgaus.net", + "sperma.cf", + "spicysoda.com", + "spikio.com", + "spindl-e.com", + "spoofmail.de", + "sportrid.com", + "spr.io", + "spritzzone.de", + "spruzme.com", + "spybox.de", + "spymail.com", + "spymail.one", + "squizzy.de", + "squizzy.net", + "sroff.com", + "sry.li", + "ssoia.com", + "stanfordujjain.com", + "starlight-breaker.net", + "starmail.net", + "starpower.space", + "startfu.com", + "startkeys.com", + "statdvr.com", + "stathost.net", + "statiix.com", + "stayhome.li", + "steam-area.ru", + "steambot.net", + "stexsy.com", + "stinkefinger.net", + "stop-my-spam.cf", + "stop-my-spam.com", + "stop-my-spam.ga", + "stop-my-spam.ml", + "stop-my-spam.pp.ua", + "stop-my-spam.tk", + "stopspam.app", + "storiqax.top", + "storj99.com", + "storj99.top", + "streetwisemail.com", + "stromox.com", + "stuckmail.com", + "stuffmail.de", + "stumpfwerk.com", + "stylist-volos.ru", + "submic.com", + "suburbanthug.com", + "suckmyd.com", + "sudern.de", + "sueshaw.com", + "suexamplesb.com", + "suioe.com", + "super-auswahl.de", + "superblohey.com", + "supergreatmail.com", + "supermailer.jp", + "superplatyna.com", + "superrito.com", + "supersave.net", + "superstachel.de", + "superyp.com", + "suremail.info", + "sute.jp", + "svip520.cn", + "svk.jp", + "svxr.org", + "sweetpotato.ml", + "sweetxxx.de", + "swift-mail.net", + "swift10minutemail.com", + "syinxun.com", + "sylvannet.com", + "symphonyresume.com", + "syosetu.gq", + "syujob.accountants", + "szerz.com", + "tafmail.com", + "tafoi.gr", + "taglead.com", + "tagmymedia.com", + "tagyourself.com", + "talkinator.com", + "talmetry.com", + "tanlanav.com", + "tanukis.org", + "taobudao.com", + "tapchicuoihoi.com", + "taphear.com", + "tapi.re", + "tarzanmail.cf", + "tastrg.com", + "tatsu.uk", + "taukah.com", + "tb-on-line.net", + "tcwlm.com", + "tcwlx.com", + "tdtda.com", + "tech69.com", + "techblast.ch", + "techemail.com", + "techgroup.me", + "technoproxy.ru", + "teerest.com", + "teewars.org", + "tefl.ro", + "telecomix.pl", + "teleg.eu", + "telegmail.com", + "teleworm.com", + "teleworm.us", + "tellos.xyz", + "telvetto.com", + "teml.net", + "temp-link.net", + "temp-mail.com", + "temp-mail.de", + "temp-mail.org", + "temp-mail.pp.ua", + "temp-mail.ru", + "temp-mails.com", + "tempail.com", + "tempalias.com", + "tempe-mail.com", + "tempemail.biz", + "tempemail.co.za", + "tempemail.com", + "tempemail.net", + "tempinbox.co.uk", + "tempinbox.com", + "tempmail.cn", + "tempmail.co", + "tempmail.de", + "tempmail.eu", + "tempmail.it", + "tempmail.pp.ua", + "tempmail.us", + "tempmail.ws", + "tempmail2.com", + "tempmaildemo.com", + "tempmailer.com", + "tempmailer.de", + "tempmailer.net", + "tempmailo.com", + "tempomail.fr", + "tempomail.org", + "temporarily.de", + "temporarioemail.com.br", + "temporary-mail.net", + "temporaryemail.net", + "temporaryemail.us", + "temporaryforwarding.com", + "temporaryinbox.com", + "temporarymailaddress.com", + "tempr.email", + "tempsky.com", + "temptami.com", + "tempthe.net", + "tempymail.com", + "tensi.org", + "ternaklele.ga", + "testore.co", + "testudine.com", + "thanksnospam.info", + "thankyou2010.com", + "thatim.info", + "thc.st", + "theaviors.com", + "thebearshark.com", + "thecarinformation.com", + "thechildrensfocus.com", + "thecity.biz", + "thecloudindex.com", + "thediamants.org", + "thedirhq.info", + "theeyeoftruth.com", + "thejoker5.com", + "thelightningmail.net", + "thelimestones.com", + "thembones.com.au", + "themegreview.com", + "themostemail.com", + "thereddoors.online", + "theroyalweb.club", + "thescrappermovie.com", + "thespamfather.com", + "theteastory.info", + "thex.ro", + "thichanthit.com", + "thietbivanphong.asia", + "thisisnotmyrealemail.com", + "thismail.net", + "thisurl.website", + "thnikka.com", + "thoas.ru", + "thraml.com", + "thrma.com", + "throam.com", + "thrott.com", + "throwam.com", + "throwawayemailaddress.com", + "throwawaymail.com", + "throwawaymail.pp.ua", + "throya.com", + "thrubay.com", + "thunderbolt.science", + "thunkinator.org", + "thxmate.com", + "tiapz.com", + "tic.ec", + "tilien.com", + "timgiarevn.com", + "timkassouf.com", + "tinoza.org", + "tinyurl24.com", + "tipsb.com", + "tittbit.in", + "tiv.cc", + "tizi.com", + "tkitc.de", + "tlpn.org", + "tmail.com", + "tmail.io", + "tmail.link", + "tmail.ws", + "tmail3.com", + "tmail9.com", + "tmailinator.com", + "tmails.net", + "tmmbt.net", + "tmpbox.net", + "tmpemails.com", + "tmpeml.com", + "tmpeml.info", + "tmpjr.me", + "tmpmail.net", + "tmpmail.org", + "tmpmailtor.com", + "tmpnator.live", + "tmpx.sa.com", + "toddsbighug.com", + "tofeat.com", + "toiea.com", + "tokem.co", + "tokenmail.de", + "tonaeto.com", + "tonne.to", + "tonymanso.com", + "toomail.biz", + "toon.ml", + "top-shop-tovar.ru", + "top101.de", + "top1mail.ru", + "top1post.ru", + "topinrock.cf", + "topmail2.com", + "topmail2.net", + "topofertasdehoy.com", + "topranklist.de", + "toprumours.com", + "tormail.org", + "tospage.com", + "toss.pw", + "tosunkaya.com", + "totallynotfake.net", + "totalvista.com", + "totesmail.com", + "totoan.info", + "tourcc.com", + "tp-qa-mail.com", + "tpwlb.com", + "tqoai.com", + "tqosi.com", + "trackden.com", + "tradermail.info", + "tranceversal.com", + "trap-mail.de", + "trash-amil.com", + "trash-mail.at", + "trash-mail.cf", + "trash-mail.com", + "trash-mail.de", + "trash-mail.ga", + "trash-mail.gq", + "trash-mail.ml", + "trash-mail.tk", + "trash-me.com", + "trash2009.com", + "trash2010.com", + "trash2011.com", + "trashcanmail.com", + "trashdevil.com", + "trashdevil.de", + "trashemail.de", + "trashemails.de", + "trashinbox.com", + "trashmail.at", + "trashmail.com", + "trashmail.de", + "trashmail.gq", + "trashmail.io", + "trashmail.me", + "trashmail.net", + "trashmail.org", + "trashmail.ws", + "trashmailer.com", + "trashmailgenerator.de", + "trashmails.com", + "trashymail.com", + "trashymail.net", + "trasz.com", + "trayna.com", + "trbvm.com", + "trbvn.com", + "trbvo.com", + "trend-maker.ru", + "trgfu.com", + "trgovinanaveliko.info", + "trialmail.de", + "trickmail.net", + "trillianpro.com", + "triots.com", + "trixtrux1.ru", + "trollproject.com", + "tropicalbass.info", + "trungtamtoeic.com", + "truthfinderlogin.com", + "tryalert.com", + "tryninja.io", + "tryzoe.com", + "tsderp.com", + "ttirv.org", + "ttszuo.xyz", + "tualias.com", + "tuofs.com", + "tupmail.com", + "turoid.com", + "turual.com", + "turuma.com", + "tutuapp.bid", + "tvchd.com", + "tverya.com", + "twinmail.de", + "twkly.ml", + "twocowmail.net", + "twoweirdtricks.com", + "twzhhq.online", + "txcct.com", + "txen.de", + "txtadvertise.com", + "tyhe.ro", + "tyldd.com", + "tympe.net", + "uacro.com", + "uber-mail.com", + "ubinert.com", + "ubismail.net", + "ubm.md", + "ucche.us", + "ucupdong.ml", + "uemail99.com", + "ufacturing.com", + "uggsrock.com", + "uguuchantele.com", + "uhe2.com", + "uhhu.ru", + "uiu.us", + "ujijima1129.gq", + "uk.to", + "ultra.fyi", + "ultrada.ru", + "uma3.be", + "umail.net", + "undo.it", + "unicodeworld.com", + "unids.com", + "unimark.org", + "unit7lahaina.com", + "unmail.ru", + "uooos.com", + "uorak.com", + "upliftnow.com", + "uplipht.com", + "uploadnolimit.com", + "upozowac.info", + "urfunktion.se", + "urhen.com", + "uroid.com", + "us.af", + "us.to", + "usa.cc", + "usako.net", + "usbc.be", + "used-product.fr", + "ushijima1129.cf", + "ushijima1129.ga", + "ushijima1129.gq", + "ushijima1129.ml", + "ushijima1129.tk", + "utiket.us", + "uu.gl", + "uu2.ovh", + "uuf.me", + "uwork4.us", + "uyhip.com", + "vaasfc4.tk", + "vaati.org", + "valemail.net", + "valhalladev.com", + "vankin.de", + "vasteron.com", + "vctel.com", + "vda.ro", + "vddaz.com", + "vdig.com", + "veanlo.com", + "vemomail.win", + "venompen.com", + "veo.kr", + "ver0.cf", + "ver0.ga", + "ver0.gq", + "ver0.ml", + "ver0.tk", + "vercelli.cf", + "vercelli.ga", + "vercelli.gq", + "vercelli.ml", + "verdejo.com", + "vermutlich.net", + "veryday.ch", + "veryday.eu", + "veryday.info", + "veryrealemail.com", + "vesa.pw", + "vevs.de", + "vfemail.net", + "via.tokyo.jp", + "vickaentb.tk", + "victime.ninja", + "victoriantwins.com", + "vidchart.com", + "viditag.com", + "viewcastmedia.com", + "viewcastmedia.net", + "viewcastmedia.org", + "vikingsonly.com", + "vinernet.com", + "vintomaper.com", + "vipepe.com", + "vipmail.name", + "vipmail.pw", + "vipxm.net", + "viralplays.com", + "virtualemail.info", + "visal007.tk", + "visal168.cf", + "visal168.ga", + "visal168.gq", + "visal168.ml", + "visal168.tk", + "visignal.com", + "vixletdev.com", + "vixtricks.com", + "vjoid.ru", + "vjoid.store", + "vjuum.com", + "vkbb.ru", + "vkbb.store", + "vkbt.ru", + "vkbt.store", + "vkcbt.ru", + "vkcbt.store", + "vkcode.ru", + "vkfu.ru", + "vkfu.store", + "vkpr.store", + "vkr1.com", + "vkrr.ru", + "vkrr.store", + "vmailing.info", + "vmani.com", + "vmpanda.com", + "vnedu.me", + "voidbay.com", + "volaj.com", + "voltaer.com", + "vomoto.com", + "vorga.org", + "votiputox.org", + "voxelcore.com", + "vpn.st", + "vps30.com", + "vps911.net", + "vradportal.com", + "vremonte24-store.ru", + "vrmtr.com", + "vsimcard.com", + "vssms.com", + "vtxmail.us", + "vubby.com", + "vuiy.pw", + "vusra.com", + "vztc.com", + "w-asertun.ru", + "w3internet.co.uk", + "wakingupesther.com", + "walala.org", + "walkmail.net", + "walkmail.ru", + "wallm.com", + "wanko.be", + "watch-harry-potter.com", + "watchever.biz", + "watchfull.net", + "watchironman3onlinefreefullmovie.com", + "waterisgone.com", + "watrf.com", + "wazabi.club", + "wbdev.tech", + "wbml.net", + "web-contact.info", + "web-ideal.fr", + "web-inc.net", + "web-mail.pp.ua", + "web2mailco.com", + "webcontact-france.eu", + "webemail.me", + "webhook.site", + "webm4il.info", + "webmail24.top", + "webtrip.ch", + "webuser.in", + "wecp.ru", + "wecp.store", + "wee.my", + "wef.gr", + "weg-werf-email.de", + "wegwerf-email-addressen.de", + "wegwerf-email-adressen.de", + "wegwerf-email.at", + "wegwerf-email.de", + "wegwerf-email.net", + "wegwerf-emails.de", + "wegwerfadresse.de", + "wegwerfemail.com", + "wegwerfemail.de", + "wegwerfemail.info", + "wegwerfemail.net", + "wegwerfemail.org", + "wegwerfemailadresse.com", + "wegwerfmail.de", + "wegwerfmail.info", + "wegwerfmail.net", + "wegwerfmail.org", + "wegwerpmailadres.nl", + "wegwrfmail.de", + "wegwrfmail.net", + "wegwrfmail.org", + "weizixu.com", + "wekawa.com", + "welikecookies.com", + "wellsfargocomcardholders.com", + "wemel.top", + "wenkuu.com", + "wentcity.com", + "wetrainbayarea.com", + "wetrainbayarea.org", + "wfgdfhj.tk", + "wg0.com", + "wh4f.org", + "whaaaaaaaaaat.com", + "whatiaas.com", + "whatifanalytics.com", + "whatpaas.com", + "whatsaas.com", + "whiffles.org", + "whopy.com", + "whyspam.me", + "wibblesmith.com", + "wickmail.net", + "widaryanto.info", + "widget.gg", + "wiemei.com", + "wierie.tk", + "wifimaple.com", + "wifioak.com", + "wikfee.com", + "wikidocuslava.ru", + "wilemail.com", + "willhackforfood.biz", + "willselfdestruct.com", + "wimsg.com", + "winemaven.info", + "wins.com.br", + "wlist.ro", + "wmail.cf", + "wmail.club", + "wokcy.com", + "wolfmail.ml", + "wolfsmail.tk", + "wollan.info", + "worldspace.link", + "wpdork.com", + "wpg.im", + "wralawfirm.com", + "writeme.us", + "wronghead.com", + "ws.gy", + "wsym.de", + "wudet.men", + "wuespdj.xyz", + "wupics.com", + "wuuvo.com", + "wuzak.com", + "wuzup.net", + "wuzupmail.net", + "wwjmp.com", + "wwvk.ru", + "wwvk.store", + "wwwnew.eu", + "wxnw.net", + "x24.com", + "xagloo.co", + "xagloo.com", + "xbaby69.top", + "xcode.ro", + "xcodes.net", + "xcompress.com", + "xcoxc.com", + "xcpy.com", + "xemaps.com", + "xemne.com", + "xents.com", + "xepa.ru", + "xjoi.com", + "xkx.me", + "xl.cx", + "xmail.com", + "xmailer.be", + "xmaily.com", + "xn--9kq967o.com", + "xn--d-bga.net", + "xojxe.com", + "xost.us", + "xoxox.cc", + "xperiae5.com", + "xrap.de", + "xrho.com", + "xvx.us", + "xww.ro", + "xxhamsterxx.ga", + "xxi2.com", + "xxlocanto.us", + "xxolocanto.us", + "xxqx3802.com", + "xxvk.ru", + "xxvk.store", + "xxxhi.cc", + "xy9ce.tk", + "xylar.ru", + "xylar.store", + "xyzfree.net", + "xzsok.com", + "yabai-oppai.tk", + "yahmail.top", + "yahooproduct.net", + "yamail.win", + "yanet.me", + "yannmail.win", + "yapped.net", + "yaqp.com", + "yarnpedia.ga", + "ycare.de", + "ycn.ro", + "ye.vc", + "yecp.ru", + "yecp.store", + "yedi.org", + "yeezus.ru", + "yep.it", + "yermail.net", + "yhg.biz", + "ynmrealty.com", + "yodx.ro", + "yogamaven.com", + "yoggm.com", + "yomail.info", + "yoo.ro", + "yopmail.com", + "yopmail.fr", + "yopmail.gq", + "yopmail.net", + "yopmail.pp.ua", + "yordanmail.cf", + "you-spam.com", + "yougotgoated.com", + "youmail.ga", + "youmailr.com", + "youneedmore.info", + "youpymail.com", + "your5.ru", + "your5.store", + "yourdomain.com", + "youremail.cf", + "yourewronghereswhy.com", + "yourlms.biz", + "yourspamgoesto.space", + "yourtube.ml", + "youxiang.dev", + "yroid.com", + "yspend.com", + "ytpayy.com", + "yugasandrika.com", + "yui.it", + "yuoia.com", + "yuurok.com", + "yxdad.ru", + "yxdad.store", + "yxzx.net", + "yyolf.net", + "z-o-e-v-a.ru", + "z0d.eu", + "z1p.biz", + "z86.ru", + "zain.site", + "zainmax.net", + "zaktouni.fr", + "zarabotokdoma11.ru", + "zasod.com", + "zaym-zaym.ru", + "zcovz.ru", + "zcovz.store", + "zcrcd.com", + "zdenka.net", + "ze.tc", + "zebins.com", + "zebins.eu", + "zehnminuten.de", + "zehnminutenmail.de", + "zemzar.net", + "zepp.dk", + "zetmail.com", + "zfymail.com", + "zhaoqian.ninja", + "zhaoyuanedu.cn", + "zhcne.com", + "zhewei88.com", + "zhorachu.com", + "zik.dj", + "zipcad.com", + "zipcatfish.com", + "zipo1.gq", + "zippymail.info", + "zipsendtest.com", + "ziragold.com", + "zoaxe.com", + "zoemail.com", + "zoemail.net", + "zoemail.org", + "zoetropes.org", + "zombie-hive.com", + "zomg.info", + "zsero.com", + "zumpul.com", + "zv68.com", + "zxcv.com", + "zxcvbnm.com", + "zymuying.com", + "zzi.us", + "zzrgg.com", + "zzz.com", + } +}) diff --git a/modules/setting/f3.go b/modules/setting/f3.go index 8669b70562..31d12294b8 100644 --- a/modules/setting/f3.go +++ b/modules/setting/f3.go @@ -3,7 +3,7 @@ package setting import ( - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) // Friendly Forge Format (F3) settings diff --git a/modules/setting/federation.go b/modules/setting/federation.go index aeb30683ea..510ac128ee 100644 --- a/modules/setting/federation.go +++ b/modules/setting/federation.go @@ -4,9 +4,9 @@ package setting import ( - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" - "github.com/go-fed/httpsig" + "github.com/42wim/httpsig" ) // Federation settings @@ -15,18 +15,20 @@ var ( Enabled bool ShareUserStatistics bool MaxSize int64 - Algorithms []string + SignatureAlgorithms []string DigestAlgorithm string GetHeaders []string PostHeaders []string + SignatureEnforced bool }{ Enabled: false, ShareUserStatistics: true, MaxSize: 4, - Algorithms: []string{"rsa-sha256", "rsa-sha512", "ed25519"}, + SignatureAlgorithms: []string{"rsa-sha256", "rsa-sha512", "ed25519"}, DigestAlgorithm: "SHA-256", GetHeaders: []string{"(request-target)", "Date", "Host"}, PostHeaders: []string{"(request-target)", "Date", "Host", "Digest"}, + SignatureEnforced: true, } ) @@ -44,8 +46,8 @@ func loadFederationFrom(rootCfg ConfigProvider) { // Get MaxSize in bytes instead of MiB Federation.MaxSize = 1 << 20 * Federation.MaxSize - HttpsigAlgs = make([]httpsig.Algorithm, len(Federation.Algorithms)) - for i, alg := range Federation.Algorithms { + HttpsigAlgs = make([]httpsig.Algorithm, len(Federation.SignatureAlgorithms)) + for i, alg := range Federation.SignatureAlgorithms { HttpsigAlgs[i] = httpsig.Algorithm(alg) } } diff --git a/modules/setting/forgejo_storage_test.go b/modules/setting/forgejo_storage_test.go index d91bff59e9..42c46beb77 100644 --- a/modules/setting/forgejo_storage_test.go +++ b/modules/setting/forgejo_storage_test.go @@ -259,6 +259,6 @@ func testStoragePathMatch(t *testing.T, iniStr string, storageType StorageType, cfg, err := NewConfigProviderFromData(iniStr) require.NoError(t, err, iniStr) require.NoError(t, loadCommonSettingsFrom(cfg), iniStr) - assert.EqualValues(t, testSectionToPath(storageType, section), testStorageGetPath(*storage), iniStr) - assert.EqualValues(t, storageType, (*storage).Type, iniStr) + assert.Equal(t, testSectionToPath(storageType, section), testStorageGetPath(*storage), iniStr) + assert.Equal(t, storageType, (*storage).Type, iniStr) } diff --git a/modules/setting/git.go b/modules/setting/git.go index 812c4fe6c9..f35592a924 100644 --- a/modules/setting/git.go +++ b/modules/setting/git.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) // Git settings diff --git a/modules/setting/git_test.go b/modules/setting/git_test.go index 34427f908f..5604151907 100644 --- a/modules/setting/git_test.go +++ b/modules/setting/git_test.go @@ -6,17 +6,15 @@ package setting import ( "testing" + "forgejo.org/modules/test" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGitConfig(t *testing.T) { - oldGit := Git - oldGitConfig := GitConfig - defer func() { - Git = oldGit - GitConfig = oldGitConfig - }() + defer test.MockProtect(&Git)() + defer test.MockProtect(&GitConfig)() cfg, err := NewConfigProviderFromData(` [git.config] @@ -24,8 +22,8 @@ a.b = 1 `) require.NoError(t, err) loadGitFrom(cfg) - assert.EqualValues(t, "1", GitConfig.Options["a.b"]) - assert.EqualValues(t, "histogram", GitConfig.Options["diff.algorithm"]) + assert.Equal(t, "1", GitConfig.Options["a.b"]) + assert.Equal(t, "histogram", GitConfig.Options["diff.algorithm"]) cfg, err = NewConfigProviderFromData(` [git.config] @@ -33,24 +31,20 @@ diff.algorithm = other `) require.NoError(t, err) loadGitFrom(cfg) - assert.EqualValues(t, "other", GitConfig.Options["diff.algorithm"]) + assert.Equal(t, "other", GitConfig.Options["diff.algorithm"]) } func TestGitReflog(t *testing.T) { - oldGit := Git - oldGitConfig := GitConfig - defer func() { - Git = oldGit - GitConfig = oldGitConfig - }() + defer test.MockProtect(&Git)() + defer test.MockProtect(&GitConfig)() // default reflog config without legacy options cfg, err := NewConfigProviderFromData(``) require.NoError(t, err) loadGitFrom(cfg) - assert.EqualValues(t, "true", GitConfig.GetOption("core.logAllRefUpdates")) - assert.EqualValues(t, "90", GitConfig.GetOption("gc.reflogExpire")) + assert.Equal(t, "true", GitConfig.GetOption("core.logAllRefUpdates")) + assert.Equal(t, "90", GitConfig.GetOption("gc.reflogExpire")) // custom reflog config by legacy options cfg, err = NewConfigProviderFromData(` @@ -61,6 +55,6 @@ EXPIRATION = 123 require.NoError(t, err) loadGitFrom(cfg) - assert.EqualValues(t, "false", GitConfig.GetOption("core.logAllRefUpdates")) - assert.EqualValues(t, "123", GitConfig.GetOption("gc.reflogExpire")) + assert.Equal(t, "false", GitConfig.GetOption("core.logAllRefUpdates")) + assert.Equal(t, "123", GitConfig.GetOption("gc.reflogExpire")) } diff --git a/modules/setting/i18n.go b/modules/setting/i18n.go index 889e52beb6..a400cf844c 100644 --- a/modules/setting/i18n.go +++ b/modules/setting/i18n.go @@ -9,7 +9,9 @@ var defaultI18nLangNames = []string{ "zh-CN", "简体中文", "zh-HK", "ç¹é«”中文(香港)", "zh-TW", "ç¹é«”中文(å°ç£ï¼‰", + "da", "Dansk", "de-DE", "Deutsch", + "nds", "Plattdüütsch", "fr-FR", "Français", "nl-NL", "Nederlands", "lv-LV", "LatvieÅ¡u", diff --git a/modules/setting/incoming_email.go b/modules/setting/incoming_email.go index 287e72941c..e592220de6 100644 --- a/modules/setting/incoming_email.go +++ b/modules/setting/incoming_email.go @@ -4,11 +4,12 @@ package setting import ( + "errors" "fmt" "net/mail" "strings" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) var IncomingEmail = struct { @@ -68,7 +69,7 @@ func checkReplyToAddress() error { } if parsed.Name != "" { - return fmt.Errorf("name must not be set") + return errors.New("name must not be set") } c := strings.Count(IncomingEmail.ReplyToAddress, IncomingEmail.TokenPlaceholder) diff --git a/modules/setting/incoming_email_test.go b/modules/setting/incoming_email_test.go index 0fdd44d333..6d181cae3c 100644 --- a/modules/setting/incoming_email_test.go +++ b/modules/setting/incoming_email_test.go @@ -31,8 +31,8 @@ func Test_loadIncomingEmailFrom(t *testing.T) { loadIncomingEmailFrom(cfg) - assert.EqualValues(t, "jane.doe@example.com", IncomingEmail.Username) - assert.EqualValues(t, "y0u'll n3v3r gUess th1S!!1", IncomingEmail.Password) + assert.Equal(t, "jane.doe@example.com", IncomingEmail.Username) + assert.Equal(t, "y0u'll n3v3r gUess th1S!!1", IncomingEmail.Password) }) t.Run("Port settings", func(t *testing.T) { @@ -45,7 +45,7 @@ func Test_loadIncomingEmailFrom(t *testing.T) { loadIncomingEmailFrom(cfg) - assert.EqualValues(t, 143, IncomingEmail.Port) + assert.Equal(t, 143, IncomingEmail.Port) }) t.Run("no port, with tls", func(t *testing.T) { @@ -56,7 +56,7 @@ func Test_loadIncomingEmailFrom(t *testing.T) { loadIncomingEmailFrom(cfg) - assert.EqualValues(t, 993, IncomingEmail.Port) + assert.Equal(t, 993, IncomingEmail.Port) }) t.Run("port overrides tls", func(t *testing.T) { @@ -68,7 +68,7 @@ func Test_loadIncomingEmailFrom(t *testing.T) { loadIncomingEmailFrom(cfg) - assert.EqualValues(t, 1993, IncomingEmail.Port) + assert.Equal(t, 1993, IncomingEmail.Port) }) }) } diff --git a/modules/setting/indexer.go b/modules/setting/indexer.go index 3c96b58740..6a464ee0de 100644 --- a/modules/setting/indexer.go +++ b/modules/setting/indexer.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" "github.com/gobwas/glob" ) @@ -109,7 +109,7 @@ func IndexerGlobFromString(globstr string) []Glob { expr = strings.TrimSpace(expr) if expr != "" { if g, err := glob.Compile(expr, '.', '/'); err != nil { - log.Info("Invalid glob expression '%s' (skipped): %v", expr, err) + log.Warn("Invalid glob expression '%s' (skipped): %v", expr, err) } else { extarr = append(extarr, Glob{glob: g, pattern: expr}) } diff --git a/modules/setting/lfs.go b/modules/setting/lfs.go index 750101747f..452bfae737 100644 --- a/modules/setting/lfs.go +++ b/modules/setting/lfs.go @@ -7,25 +7,35 @@ import ( "fmt" "time" - "code.gitea.io/gitea/modules/generate" + "forgejo.org/modules/generate" ) -// LFS represents the configuration for Git LFS +// LFS represents the server-side configuration for Git LFS. +// Ideally these options should be in a section like "[lfs_server]", +// but they are in "[server]" section due to historical reasons. +// Could be refactored in the future while keeping backwards compatibility. var LFS = struct { StartServer bool `ini:"LFS_START_SERVER"` 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"` + MaxBatchSize int `ini:"LFS_MAX_BATCH_SIZE"` Storage *Storage }{} +// LFSClient represents configuration for Gitea's LFS clients, for example: mirroring upstream Git LFS +var LFSClient = struct { + BatchSize int `ini:"BATCH_SIZE"` + BatchOperationConcurrency int `ini:"BATCH_OPERATION_CONCURRENCY"` +}{} + func loadLFSFrom(rootCfg ConfigProvider) error { + mustMapSetting(rootCfg, "lfs_client", &LFSClient) + + mustMapSetting(rootCfg, "server", &LFS) sec := rootCfg.Section("server") - if err := sec.MapTo(&LFS); err != nil { - return fmt.Errorf("failed to map LFS settings: %v", err) - } lfsSec, _ := rootCfg.GetSection("lfs") @@ -52,6 +62,15 @@ func loadLFSFrom(rootCfg ConfigProvider) error { LFS.LocksPagingNum = 50 } + if LFSClient.BatchSize < 1 { + LFSClient.BatchSize = 20 + } + + if LFSClient.BatchOperationConcurrency < 1 { + // match the default git-lfs's `lfs.concurrenttransfers` https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-config.adoc#upload-and-download-transfer-settings + LFSClient.BatchOperationConcurrency = 8 + } + LFS.HTTPAuthExpiry = sec.Key("LFS_HTTP_AUTH_EXPIRY").MustDuration(24 * time.Hour) if !LFS.StartServer || !InstallLock { @@ -61,10 +80,7 @@ func loadLFSFrom(rootCfg ConfigProvider) error { jwtSecretBase64 := loadSecret(rootCfg.Section("server"), "LFS_JWT_SECRET_URI", "LFS_JWT_SECRET") LFS.JWTSecretBytes, err = generate.DecodeJwtSecret(jwtSecretBase64) if err != nil { - LFS.JWTSecretBytes, jwtSecretBase64, err = generate.NewJwtSecret() - if err != nil { - return fmt.Errorf("error generating JWT Secret for custom config: %v", err) - } + LFS.JWTSecretBytes, jwtSecretBase64 = generate.NewJwtSecret() // Save secret saveCfg, err := rootCfg.PrepareSaving() diff --git a/modules/setting/lfs_test.go b/modules/setting/lfs_test.go index c7f16379b2..0abf401fa0 100644 --- a/modules/setting/lfs_test.go +++ b/modules/setting/lfs_test.go @@ -20,7 +20,7 @@ func Test_getStorageInheritNameSectionTypeForLFS(t *testing.T) { require.NoError(t, loadLFSFrom(cfg)) assert.EqualValues(t, "minio", LFS.Storage.Type) - assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) iniStr = ` [server] @@ -55,7 +55,7 @@ STORAGE_TYPE = minio require.NoError(t, loadLFSFrom(cfg)) assert.EqualValues(t, "minio", LFS.Storage.Type) - assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) iniStr = ` [lfs] @@ -69,7 +69,7 @@ STORAGE_TYPE = minio require.NoError(t, loadLFSFrom(cfg)) assert.EqualValues(t, "minio", LFS.Storage.Type) - assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) iniStr = ` [lfs] @@ -84,7 +84,7 @@ STORAGE_TYPE = minio require.NoError(t, loadLFSFrom(cfg)) assert.EqualValues(t, "minio", LFS.Storage.Type) - assert.EqualValues(t, "my_lfs/", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "my_lfs/", LFS.Storage.MinioConfig.BasePath) } func Test_LFSStorage1(t *testing.T) { @@ -97,6 +97,35 @@ STORAGE_TYPE = minio require.NoError(t, loadLFSFrom(cfg)) assert.EqualValues(t, "minio", LFS.Storage.Type) - assert.EqualValues(t, "gitea", LFS.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", LFS.Storage.MinioConfig.Bucket) + assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) +} + +func Test_LFSClientServerConfigs(t *testing.T) { + iniStr := ` +[server] +LFS_MAX_BATCH_SIZE = 100 +[lfs_client] +# will default to 20 +BATCH_SIZE = 0 +` + cfg, err := NewConfigProviderFromData(iniStr) + assert.NoError(t, err) + + assert.NoError(t, loadLFSFrom(cfg)) + assert.Equal(t, 100, LFS.MaxBatchSize) + assert.Equal(t, 20, LFSClient.BatchSize) + assert.Equal(t, 8, LFSClient.BatchOperationConcurrency) + + iniStr = ` +[lfs_client] +BATCH_SIZE = 50 +BATCH_OPERATION_CONCURRENCY = 10 +` + cfg, err = NewConfigProviderFromData(iniStr) + assert.NoError(t, err) + + assert.NoError(t, loadLFSFrom(cfg)) + assert.Equal(t, 50, LFSClient.BatchSize) + assert.Equal(t, 10, LFSClient.BatchOperationConcurrency) } diff --git a/modules/setting/log.go b/modules/setting/log.go index a141188c0c..0747ac4dac 100644 --- a/modules/setting/log.go +++ b/modules/setting/log.go @@ -11,8 +11,8 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/log" + "forgejo.org/modules/util" ) type LogGlobalConfig struct { diff --git a/modules/setting/log_test.go b/modules/setting/log_test.go index 3134d3e75c..eda6dc36af 100644 --- a/modules/setting/log_test.go +++ b/modules/setting/log_test.go @@ -8,8 +8,8 @@ import ( "strings" "testing" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/json" + "forgejo.org/modules/log" "github.com/stretchr/testify/require" ) diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go index 136d932b8d..9c004c6ce0 100644 --- a/modules/setting/mailer.go +++ b/modules/setting/mailer.go @@ -11,7 +11,7 @@ import ( "text/template" "time" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" shellquote "github.com/kballard/go-shellquote" ) @@ -215,6 +215,11 @@ func loadMailerFrom(rootCfg ConfigProvider) { if err != nil { log.Error("Failed to parse Sendmail args: '%s' with error %v", sec.Key("SENDMAIL_ARGS").String(), err) } + + if len(MailService.SendmailArgs) == 0 || MailService.SendmailArgs[len(MailService.SendmailArgs)-1] != "--" { + log.Warn("SENDMAIL_ARGS setting does not end in \"--\", appending it to prevent argument injection") + MailService.SendmailArgs = append(MailService.SendmailArgs, "--") + } case "smtp", "smtps", "smtp+starttls", "smtp+unix": ips := tryResolveAddr(MailService.SMTPAddr) if MailService.Protocol == "smtp" { @@ -263,8 +268,6 @@ func loadMailerFrom(rootCfg ConfigProvider) { MailService.OverrideEnvelopeFrom = true MailService.EnvelopeFrom = parsed.Address } - - log.Info("Mail Service Enabled") } func loadRegisterMailFrom(rootCfg ConfigProvider) { @@ -275,7 +278,6 @@ func loadRegisterMailFrom(rootCfg ConfigProvider) { return } Service.RegisterEmailConfirm = true - log.Info("Register Mail Service Enabled") } func loadNotifyMailFrom(rootCfg ConfigProvider) { @@ -286,7 +288,6 @@ func loadNotifyMailFrom(rootCfg ConfigProvider) { return } Service.EnableNotifyMail = true - log.Info("Notify Mail Service Enabled") } func tryResolveAddr(addr string) []net.IPAddr { diff --git a/modules/setting/mailer_test.go b/modules/setting/mailer_test.go index f8af4a78c1..4523cc91dd 100644 --- a/modules/setting/mailer_test.go +++ b/modules/setting/mailer_test.go @@ -34,8 +34,8 @@ func Test_loadMailerFrom(t *testing.T) { // Check mailer setting loadMailerFrom(cfg) - assert.EqualValues(t, kase.SMTPAddr, MailService.SMTPAddr) - assert.EqualValues(t, kase.SMTPPort, MailService.SMTPPort) + assert.Equal(t, kase.SMTPAddr, MailService.SMTPAddr) + assert.Equal(t, kase.SMTPPort, MailService.SMTPPort) }) } @@ -48,7 +48,31 @@ func Test_loadMailerFrom(t *testing.T) { loadMailerFrom(cfg) - assert.EqualValues(t, "jane.doe@example.com", MailService.User) - assert.EqualValues(t, "y0u'll n3v3r gUess th1S!!1", MailService.Passwd) + assert.Equal(t, "jane.doe@example.com", MailService.User) + assert.Equal(t, "y0u'll n3v3r gUess th1S!!1", MailService.Passwd) + }) + + t.Run("sendmail argument sanitization", func(t *testing.T) { + cfg, _ := NewConfigProviderFromData("") + sec := cfg.Section("mailer") + sec.NewKey("ENABLED", "true") + sec.NewKey("PROTOCOL", "sendmail") + sec.NewKey("SENDMAIL_ARGS", "-B 8BITMIME") + + loadMailerFrom(cfg) + + assert.Equal(t, []string{"-B", "8BITMIME", "--"}, MailService.SendmailArgs) + }) + + t.Run("empty sendmail args", func(t *testing.T) { + cfg, _ := NewConfigProviderFromData("") + sec := cfg.Section("mailer") + sec.NewKey("ENABLED", "true") + sec.NewKey("PROTOCOL", "sendmail") + sec.NewKey("SENDMAIL_ARGS", "") + + loadMailerFrom(cfg) + + assert.Equal(t, []string{"--"}, MailService.SendmailArgs) }) } diff --git a/modules/setting/markup.go b/modules/setting/markup.go index e893c1c2f1..4ab9e7b2d1 100644 --- a/modules/setting/markup.go +++ b/modules/setting/markup.go @@ -7,7 +7,7 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) // ExternalMarkupRenderers represents the external markup renderers @@ -62,7 +62,7 @@ type MarkupSanitizerRule struct { func loadMarkupFrom(rootCfg ConfigProvider) { mustMapSetting(rootCfg, "markdown", &Markdown) - MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000) + MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(50000) FilePreviewMaxLines = rootCfg.Section("markup").Key("FILEPREVIEW_MAX_LINES").MustInt(50) ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10) ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10) diff --git a/modules/setting/mirror.go b/modules/setting/mirror.go index 3aa530a1f4..58c57c5c95 100644 --- a/modules/setting/mirror.go +++ b/modules/setting/mirror.go @@ -6,7 +6,7 @@ package setting import ( "time" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) // Mirror settings diff --git a/modules/setting/moderation.go b/modules/setting/moderation.go new file mode 100644 index 0000000000..5f35a284d6 --- /dev/null +++ b/modules/setting/moderation.go @@ -0,0 +1,15 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package setting + +// Moderation settings +var Moderation = struct { + Enabled bool `ini:"ENABLED"` +}{ + Enabled: false, +} + +func loadModerationFrom(rootCfg ConfigProvider) { + mustMapSetting(rootCfg, "moderation", &Moderation) +} diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go index 49288e2639..9e95e1c6a9 100644 --- a/modules/setting/oauth2.go +++ b/modules/setting/oauth2.go @@ -8,8 +8,8 @@ import ( "path/filepath" "sync/atomic" - "code.gitea.io/gitea/modules/generate" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/generate" + "forgejo.org/modules/log" ) // OAuth2UsernameType is enum describing the way gitea 'name' should be generated from oauth2 data @@ -138,10 +138,7 @@ func loadOAuth2From(rootCfg ConfigProvider) { if InstallLock { jwtSecretBytes, err := generate.DecodeJwtSecret(jwtSecretBase64) if err != nil { - jwtSecretBytes, jwtSecretBase64, err = generate.NewJwtSecret() - if err != nil { - log.Fatal("error generating JWT secret: %v", err) - } + jwtSecretBytes, jwtSecretBase64 = generate.NewJwtSecret() saveCfg, err := rootCfg.PrepareSaving() if err != nil { log.Fatal("save oauth2.JWT_SECRET failed: %v", err) @@ -161,10 +158,7 @@ var generalSigningSecret atomic.Pointer[[]byte] func GetGeneralTokenSigningSecret() []byte { old := generalSigningSecret.Load() if old == nil || len(*old) == 0 { - jwtSecret, _, err := generate.NewJwtSecret() - if err != nil { - log.Fatal("Unable to generate general JWT secret: %v", err) - } + jwtSecret, _ := generate.NewJwtSecret() if generalSigningSecret.CompareAndSwap(old, &jwtSecret) { return jwtSecret } diff --git a/modules/setting/oauth2_test.go b/modules/setting/oauth2_test.go index 18252b2447..7a1f4842a4 100644 --- a/modules/setting/oauth2_test.go +++ b/modules/setting/oauth2_test.go @@ -7,8 +7,8 @@ import ( "os" "testing" - "code.gitea.io/gitea/modules/generate" - "code.gitea.io/gitea/modules/test" + "forgejo.org/modules/generate" + "forgejo.org/modules/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -32,7 +32,7 @@ JWT_SECRET = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB actual := GetGeneralTokenSigningSecret() expected, _ := generate.DecodeJwtSecret("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB") assert.Len(t, actual, 32) - assert.EqualValues(t, expected, actual) + assert.Equal(t, expected, actual) } func TestGetGeneralSigningSecretSave(t *testing.T) { diff --git a/modules/setting/other.go b/modules/setting/other.go index 4ba494765b..db60cd2205 100644 --- a/modules/setting/other.go +++ b/modules/setting/other.go @@ -3,7 +3,7 @@ package setting -import "code.gitea.io/gitea/modules/log" +import "forgejo.org/modules/log" type OtherConfig struct { ShowFooterVersion bool diff --git a/modules/setting/packages.go b/modules/setting/packages.go index b3f50617d2..87e41fb5a0 100644 --- a/modules/setting/packages.go +++ b/modules/setting/packages.go @@ -42,6 +42,7 @@ var ( LimitSizePub int64 LimitSizePyPI int64 LimitSizeRpm int64 + LimitSizeAlt int64 LimitSizeRubyGems int64 LimitSizeSwift int64 LimitSizeVagrant int64 @@ -106,6 +107,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) { Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT") Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT") Packages.DefaultRPMSignEnabled = sec.Key("DEFAULT_RPM_SIGN_ENABLED").MustBool(false) + Packages.LimitSizeAlt = mustBytes(sec, "LIMIT_SIZE_ALT") return nil } diff --git a/modules/setting/packages_test.go b/modules/setting/packages_test.go index 78eb4b4bbc..85a4656da0 100644 --- a/modules/setting/packages_test.go +++ b/modules/setting/packages_test.go @@ -42,7 +42,7 @@ STORAGE_TYPE = minio require.NoError(t, loadPackagesFrom(cfg)) assert.EqualValues(t, "minio", Packages.Storage.Type) - assert.EqualValues(t, "packages/", Packages.Storage.MinioConfig.BasePath) + assert.Equal(t, "packages/", Packages.Storage.MinioConfig.BasePath) // we can also configure packages storage directly iniStr = ` @@ -54,7 +54,7 @@ STORAGE_TYPE = minio require.NoError(t, loadPackagesFrom(cfg)) assert.EqualValues(t, "minio", Packages.Storage.Type) - assert.EqualValues(t, "packages/", Packages.Storage.MinioConfig.BasePath) + assert.Equal(t, "packages/", Packages.Storage.MinioConfig.BasePath) // or we can indicate the storage type in the packages section iniStr = ` @@ -69,7 +69,7 @@ STORAGE_TYPE = minio require.NoError(t, loadPackagesFrom(cfg)) assert.EqualValues(t, "minio", Packages.Storage.Type) - assert.EqualValues(t, "packages/", Packages.Storage.MinioConfig.BasePath) + assert.Equal(t, "packages/", Packages.Storage.MinioConfig.BasePath) // or we can indicate the storage type and minio base path in the packages section iniStr = ` @@ -85,7 +85,7 @@ STORAGE_TYPE = minio require.NoError(t, loadPackagesFrom(cfg)) assert.EqualValues(t, "minio", Packages.Storage.Type) - assert.EqualValues(t, "my_packages/", Packages.Storage.MinioConfig.BasePath) + assert.Equal(t, "my_packages/", Packages.Storage.MinioConfig.BasePath) } func Test_PackageStorage1(t *testing.T) { @@ -110,8 +110,8 @@ MINIO_SECRET_ACCESS_KEY = correct_key storage := Packages.Storage assert.EqualValues(t, "minio", storage.Type) - assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket) - assert.EqualValues(t, "packages/", storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, "packages/", storage.MinioConfig.BasePath) assert.True(t, storage.MinioConfig.ServeDirect) } @@ -137,8 +137,8 @@ MINIO_SECRET_ACCESS_KEY = correct_key storage := Packages.Storage assert.EqualValues(t, "minio", storage.Type) - assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket) - assert.EqualValues(t, "packages/", storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, "packages/", storage.MinioConfig.BasePath) assert.True(t, storage.MinioConfig.ServeDirect) } @@ -165,8 +165,8 @@ MINIO_SECRET_ACCESS_KEY = correct_key storage := Packages.Storage assert.EqualValues(t, "minio", storage.Type) - assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket) - assert.EqualValues(t, "my_packages/", storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, "my_packages/", storage.MinioConfig.BasePath) assert.True(t, storage.MinioConfig.ServeDirect) } @@ -193,7 +193,7 @@ MINIO_SECRET_ACCESS_KEY = correct_key storage := Packages.Storage assert.EqualValues(t, "minio", storage.Type) - assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket) - assert.EqualValues(t, "my_packages/", storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, "my_packages/", storage.MinioConfig.BasePath) assert.True(t, storage.MinioConfig.ServeDirect) } diff --git a/modules/setting/path.go b/modules/setting/path.go index 85d0e06302..33f27db8fd 100644 --- a/modules/setting/path.go +++ b/modules/setting/path.go @@ -10,7 +10,7 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) var ( @@ -34,11 +34,7 @@ var ( func getAppPath() (string, error) { var appPath string var err error - if IsWindows && filepath.IsAbs(os.Args[0]) { - appPath = filepath.Clean(os.Args[0]) - } else { - appPath, err = exec.LookPath(os.Args[0]) - } + appPath, err = exec.LookPath(os.Args[0]) if err != nil { if !errors.Is(err, exec.ErrDot) { return "", err diff --git a/modules/setting/proxy.go b/modules/setting/proxy.go index 4ff420d090..7a9de9568b 100644 --- a/modules/setting/proxy.go +++ b/modules/setting/proxy.go @@ -6,7 +6,7 @@ package setting import ( "net/url" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) // Proxy settings diff --git a/modules/setting/queue.go b/modules/setting/queue.go index 251a6c1e30..06d007c140 100644 --- a/modules/setting/queue.go +++ b/modules/setting/queue.go @@ -7,8 +7,8 @@ import ( "path/filepath" "runtime" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/json" + "forgejo.org/modules/log" ) // QueueSettings represent the settings for a queue from the ini diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 6086dd1d57..c9e70560d0 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -4,12 +4,15 @@ package setting import ( + "os" "os/exec" "path" "path/filepath" "strings" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" + + "golang.org/x/crypto/ssh" ) // enumerates all the policy repository creating @@ -26,6 +29,8 @@ var MaxUserCardsPerPage = 36 // MaxForksPerPage sets maximum amount of forks shown per page var MaxForksPerPage = 40 +var SSHInstanceKey ssh.PublicKey + // Repository settings var ( Repository = struct { @@ -87,9 +92,9 @@ var ( DefaultMergeMessageAllAuthors bool DefaultMergeMessageMaxApprovers int DefaultMergeMessageOfficialApproversOnly bool + DefaultUpdateStyle string PopulateSquashCommentWithCommitMessages bool AddCoCommitterTrailers bool - TestConflictingPatchesWithGitApply bool RetargetChildrenOnMerge bool } `ini:"repository.pull-request"` @@ -108,6 +113,7 @@ var ( SigningKey string SigningName string SigningEmail string + Format string InitialCommit []string CRUDActions []string `ini:"CRUD_ACTIONS"` Merges []string @@ -216,9 +222,9 @@ var ( DefaultMergeMessageAllAuthors bool DefaultMergeMessageMaxApprovers int DefaultMergeMessageOfficialApproversOnly bool + DefaultUpdateStyle string PopulateSquashCommentWithCommitMessages bool AddCoCommitterTrailers bool - TestConflictingPatchesWithGitApply bool RetargetChildrenOnMerge bool }{ WorkInProgressPrefixes: []string{"WIP:", "[WIP]"}, @@ -232,6 +238,7 @@ var ( DefaultMergeMessageAllAuthors: false, DefaultMergeMessageMaxApprovers: 10, DefaultMergeMessageOfficialApproversOnly: true, + DefaultUpdateStyle: "merge", PopulateSquashCommentWithCommitMessages: false, AddCoCommitterTrailers: true, RetargetChildrenOnMerge: true, @@ -259,6 +266,7 @@ var ( SigningKey string SigningName string SigningEmail string + Format string InitialCommit []string CRUDActions []string `ini:"CRUD_ACTIONS"` Merges []string @@ -268,6 +276,7 @@ var ( SigningKey: "default", SigningName: "", SigningEmail: "", + Format: "openpgp", InitialCommit: []string{"always"}, CRUDActions: []string{"pubkey", "twofa", "parentsigned"}, Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"}, @@ -286,7 +295,7 @@ func loadRepositoryFrom(rootCfg ConfigProvider) { // Determine and create root git repository path. sec := rootCfg.Section("repository") Repository.DisableHTTPGit = sec.Key("DISABLE_HTTP_GIT").MustBool() - Repository.UseCompatSSHURI = sec.Key("USE_COMPAT_SSH_URI").MustBool() + Repository.UseCompatSSHURI = sec.Key("USE_COMPAT_SSH_URI").MustBool(true) Repository.GoGetCloneURLProtocol = sec.Key("GO_GET_CLONE_URL_PROTOCOL").MustString("https") Repository.MaxCreationLimit = sec.Key("MAX_CREATION_LIMIT").MustInt(-1) Repository.DefaultBranch = sec.Key("DEFAULT_BRANCH").MustString(Repository.DefaultBranch) @@ -373,4 +382,15 @@ func loadRepositoryFrom(rootCfg ConfigProvider) { log.Fatal("loadRepoArchiveFrom: %v", err) } Repository.EnableFlags = sec.Key("ENABLE_FLAGS").MustBool() + + if Repository.Signing.Format == "ssh" && Repository.Signing.SigningKey != "none" && Repository.Signing.SigningKey != "" { + sshPublicKey, err := os.ReadFile(Repository.Signing.SigningKey) + if err != nil { + log.Fatal("Could not read repository signing key in %q: %v", Repository.Signing.SigningKey, err) + } + SSHInstanceKey, _, _, _, err = ssh.ParseAuthorizedKey(sshPublicKey) + if err != nil { + log.Fatal("Could not parse the SSH signing key %q: %v", sshPublicKey, err) + } + } } diff --git a/modules/setting/repository_archive_test.go b/modules/setting/repository_archive_test.go index d3901b6e47..cff59f3663 100644 --- a/modules/setting/repository_archive_test.go +++ b/modules/setting/repository_archive_test.go @@ -21,7 +21,7 @@ STORAGE_TYPE = minio require.NoError(t, loadRepoArchiveFrom(cfg)) assert.EqualValues(t, "minio", RepoArchive.Storage.Type) - assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) // we can also configure packages storage directly iniStr = ` @@ -33,7 +33,7 @@ STORAGE_TYPE = minio require.NoError(t, loadRepoArchiveFrom(cfg)) assert.EqualValues(t, "minio", RepoArchive.Storage.Type) - assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) // or we can indicate the storage type in the packages section iniStr = ` @@ -48,7 +48,7 @@ STORAGE_TYPE = minio require.NoError(t, loadRepoArchiveFrom(cfg)) assert.EqualValues(t, "minio", RepoArchive.Storage.Type) - assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) // or we can indicate the storage type and minio base path in the packages section iniStr = ` @@ -64,7 +64,7 @@ STORAGE_TYPE = minio require.NoError(t, loadRepoArchiveFrom(cfg)) assert.EqualValues(t, "minio", RepoArchive.Storage.Type) - assert.EqualValues(t, "my_archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "my_archive/", RepoArchive.Storage.MinioConfig.BasePath) } func Test_RepoArchiveStorage(t *testing.T) { @@ -86,7 +86,7 @@ MINIO_SECRET_ACCESS_KEY = correct_key storage := RepoArchive.Storage assert.EqualValues(t, "minio", storage.Type) - assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) iniStr = ` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -108,5 +108,5 @@ MINIO_SECRET_ACCESS_KEY = correct_key storage = RepoArchive.Storage assert.EqualValues(t, "minio", storage.Type) - assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) } diff --git a/modules/setting/repository_test.go b/modules/setting/repository_test.go new file mode 100644 index 0000000000..d36e739f6b --- /dev/null +++ b/modules/setting/repository_test.go @@ -0,0 +1,59 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package setting + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/ssh" +) + +func TestSSHInstanceKey(t *testing.T) { + sshSigningKeyPath, err := filepath.Abs("../../tests/integration/ssh-signing-key.pub") + require.NoError(t, err) + + t.Run("None value", func(t *testing.T) { + cfg, err := NewConfigProviderFromData(` +[repository.signing] +FORMAT = ssh +SIGNING_KEY = none +`) + require.NoError(t, err) + + loadRepositoryFrom(cfg) + + assert.Nil(t, SSHInstanceKey) + }) + + t.Run("No value", func(t *testing.T) { + cfg, err := NewConfigProviderFromData(` +[repository.signing] +FORMAT = ssh +`) + require.NoError(t, err) + + loadRepositoryFrom(cfg) + + assert.Nil(t, SSHInstanceKey) + }) + t.Run("Normal", func(t *testing.T) { + iniStr := fmt.Sprintf(` +[repository.signing] +FORMAT = ssh +SIGNING_KEY = %s +`, sshSigningKeyPath) + cfg, err := NewConfigProviderFromData(iniStr) + require.NoError(t, err) + + loadRepositoryFrom(cfg) + + assert.NotNil(t, SSHInstanceKey) + assert.Equal(t, "ssh-ed25519", SSHInstanceKey.Type()) + assert.EqualValues(t, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFeRC8GfFyXtiy0f1E7hLv77BXW7e68tFvIcs8/29YqH\n", ssh.MarshalAuthorizedKey(SSHInstanceKey)) + }) +} diff --git a/modules/setting/security.go b/modules/setting/security.go index 678a57cb30..c38d8dae79 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -8,10 +8,10 @@ import ( "os" "strings" - "code.gitea.io/gitea/modules/auth/password/hash" - "code.gitea.io/gitea/modules/generate" - "code.gitea.io/gitea/modules/keying" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/auth/password/hash" + "forgejo.org/modules/generate" + "forgejo.org/modules/keying" + "forgejo.org/modules/log" ) var ( @@ -35,7 +35,6 @@ var ( PasswordHashAlgo string PasswordCheckPwn bool SuccessfulTokensCacheSize int - DisableQueryAuthToken bool CSRFCookieName = "_csrf" CSRFCookieHTTPOnly = true ) @@ -160,14 +159,4 @@ func loadSecurityFrom(rootCfg ConfigProvider) { PasswordComplexity = append(PasswordComplexity, name) } } - - sectionHasDisableQueryAuthToken := sec.HasKey("DISABLE_QUERY_AUTH_TOKEN") - - // TODO: default value should be true in future releases - DisableQueryAuthToken = sec.Key("DISABLE_QUERY_AUTH_TOKEN").MustBool(false) - - // warn if the setting is set to false explicitly - if sectionHasDisableQueryAuthToken && !DisableQueryAuthToken { - log.Warn("Enabling Query API Auth tokens is not recommended. DISABLE_QUERY_AUTH_TOKEN will default to true in gitea 1.23 and will be removed in gitea 1.24.") - } } diff --git a/modules/setting/server.go b/modules/setting/server.go index 5cc33f6fc4..bff51f787d 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -13,9 +13,9 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/json" + "forgejo.org/modules/log" + "forgejo.org/modules/util" ) // Scheme describes protocol types @@ -265,7 +265,7 @@ func loadServerFrom(rootCfg ConfigProvider) { } UnixSocketPermission = uint32(UnixSocketPermissionParsed) - if !filepath.IsAbs(HTTPAddr) { + if HTTPAddr[0] != '@' && !filepath.IsAbs(HTTPAddr) { HTTPAddr = filepath.Join(AppWorkPath, HTTPAddr) } } diff --git a/modules/setting/server_test.go b/modules/setting/server_test.go index 8db8168854..3c6faa2311 100644 --- a/modules/setting/server_test.go +++ b/modules/setting/server_test.go @@ -6,9 +6,10 @@ package setting import ( "testing" - "code.gitea.io/gitea/modules/test" + "forgejo.org/modules/test" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestDisplayNameDefault(t *testing.T) { @@ -34,3 +35,54 @@ func TestDisplayNameCustomFormat(t *testing.T) { displayName := generateDisplayName() assert.Equal(t, "Forgejo - Beyond coding. We Forge.", displayName) } + +func TestMaxUserRedirectsDefault(t *testing.T) { + iniStr := `` + cfg, err := NewConfigProviderFromData(iniStr) + require.NoError(t, err) + loadServiceFrom(cfg) + + assert.EqualValues(t, 0, Service.UsernameCooldownPeriod) + assert.EqualValues(t, 0, Service.MaxUserRedirects) + + iniStr = `[service] +MAX_USER_REDIRECTS = 8` + cfg, err = NewConfigProviderFromData(iniStr) + require.NoError(t, err) + loadServiceFrom(cfg) + + assert.EqualValues(t, 0, Service.UsernameCooldownPeriod) + assert.EqualValues(t, 8, Service.MaxUserRedirects) + + iniStr = `[service] +USERNAME_COOLDOWN_PERIOD = 3` + cfg, err = NewConfigProviderFromData(iniStr) + require.NoError(t, err) + loadServiceFrom(cfg) + + assert.EqualValues(t, 3, Service.UsernameCooldownPeriod) + assert.EqualValues(t, 5, Service.MaxUserRedirects) + + iniStr = `[service] +USERNAME_COOLDOWN_PERIOD = 3 +MAX_USER_REDIRECTS = 8` + cfg, err = NewConfigProviderFromData(iniStr) + require.NoError(t, err) + loadServiceFrom(cfg) + + assert.EqualValues(t, 3, Service.UsernameCooldownPeriod) + assert.EqualValues(t, 8, Service.MaxUserRedirects) +} + +func TestUnixSocketAbstractNamespace(t *testing.T) { + iniStr := ` + [server] + PROTOCOL=http+unix + HTTP_ADDR=@forgejo + ` + cfg, err := NewConfigProviderFromData(iniStr) + require.NoError(t, err) + loadServerFrom(cfg) + + assert.Equal(t, "@forgejo", HTTPAddr) +} diff --git a/modules/setting/service.go b/modules/setting/service.go index afaee18101..a04d1a70ba 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -1,15 +1,18 @@ // Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved // SPDX-License-Identifier: MIT package setting import ( + "net/url" "regexp" + "slices" "strings" "time" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/structs" + "forgejo.org/modules/log" + "forgejo.org/modules/structs" "github.com/gobwas/glob" ) @@ -37,10 +40,12 @@ var Service = struct { RegisterManualConfirm bool EmailDomainAllowList []glob.Glob EmailDomainBlockList []glob.Glob + EmailDomainBlockDisposable bool DisableRegistration bool AllowOnlyInternalRegistration bool AllowOnlyExternalRegistration bool ShowRegistrationButton bool + EnableInternalSignIn bool ShowMilestonesDashboardPage bool RequireSignInView bool EnableNotifyMail bool @@ -82,6 +87,8 @@ var Service = struct { DefaultOrgMemberVisible bool UserDeleteWithCommentsMaxTime time.Duration ValidSiteURLSchemes []string + UsernameCooldownPeriod int64 + MaxUserRedirects int64 // OpenID settings EnableOpenIDSignIn bool @@ -91,8 +98,10 @@ var Service = struct { // Explore page settings Explore struct { - RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"` - DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"` + RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"` + DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"` + DisableOrganizationsPage bool `ini:"DISABLE_ORGANIZATIONS_PAGE"` + DisableCodePage bool `ini:"DISABLE_CODE_PAGE"` } `ini:"service.explore"` }{ AllowedUserVisibilityModesSlice: []bool{true, true, true}, @@ -133,6 +142,21 @@ func CompileEmailGlobList(sec ConfigSection, keys ...string) (globs []glob.Glob) return globs } + +func appURLAsGlob(fqdn string) (glob.Glob, error) { + localFqdn, err := url.ParseRequestURI(fqdn) + if err != nil { + log.Error("Error in EmailDomainAllowList: %v", err) + return nil, err + } + appFqdn, err := glob.Compile(localFqdn.Hostname(), ',') + if err != nil { + log.Error("Error in EmailDomainAllowList: %v", err) + return nil, err + } + return appFqdn, nil +} + func loadServiceFrom(rootCfg ConfigProvider) { sec := rootCfg.Section("service") Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180) @@ -152,9 +176,34 @@ func loadServiceFrom(rootCfg ConfigProvider) { if sec.HasKey("EMAIL_DOMAIN_WHITELIST") { deprecatedSetting(rootCfg, "service", "EMAIL_DOMAIN_WHITELIST", "service", "EMAIL_DOMAIN_ALLOWLIST", "1.21") } - Service.EmailDomainAllowList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_WHITELIST", "EMAIL_DOMAIN_ALLOWLIST") + emailDomainAllowList := CompileEmailGlobList(sec, "EMAIL_DOMAIN_WHITELIST", "EMAIL_DOMAIN_ALLOWLIST") + + if len(emailDomainAllowList) > 0 && Federation.Enabled { + appURL, err := appURLAsGlob(AppURL) + if err == nil { + emailDomainAllowList = append(emailDomainAllowList, appURL) + } + } + Service.EmailDomainAllowList = emailDomainAllowList Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST") - Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration)) + Service.EmailDomainBlockDisposable = sec.Key("EMAIL_DOMAIN_BLOCK_DISPOSABLE").MustBool(false) + if Service.EmailDomainBlockDisposable { + toAdd := make([]glob.Glob, 0, len(DisposableEmailDomains())) + for _, domain := range DisposableEmailDomains() { + domain = strings.ToLower(domain) + // Only add domains that aren't blocked yet. + if !slices.ContainsFunc(Service.EmailDomainBlockList, func(g glob.Glob) bool { return g.Match(domain) }) { + if g, err := glob.Compile(domain); err != nil { + log.Error("Error in disposable domain %s: %v", domain, err) + } else { + toAdd = append(toAdd, g) + } + } + } + Service.EmailDomainBlockList = append(Service.EmailDomainBlockList, toAdd...) + } + Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!Service.DisableRegistration && !Service.AllowOnlyExternalRegistration) + Service.EnableInternalSignIn = sec.Key("ENABLE_INTERNAL_SIGNIN").MustBool(true) Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true) Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool() Service.EnableBasicAuth = sec.Key("ENABLE_BASIC_AUTHENTICATION").MustBool(true) @@ -235,6 +284,14 @@ func loadServiceFrom(rootCfg ConfigProvider) { } } Service.ValidSiteURLSchemes = schemes + Service.UsernameCooldownPeriod = sec.Key("USERNAME_COOLDOWN_PERIOD").MustInt64(0) + + // Only set a default if USERNAME_COOLDOWN_PERIOD's feature is active. + maxUserRedirectsDefault := int64(0) + if Service.UsernameCooldownPeriod > 0 { + maxUserRedirectsDefault = 5 + } + Service.MaxUserRedirects = sec.Key("MAX_USER_REDIRECTS").MustInt64(maxUserRedirectsDefault) mustMapSetting(rootCfg, "service.explore", &Service.Explore) diff --git a/modules/setting/service_test.go b/modules/setting/service_test.go index 7a13e39238..4fc09021b6 100644 --- a/modules/setting/service_test.go +++ b/modules/setting/service_test.go @@ -4,15 +4,28 @@ package setting import ( + "fmt" + "sort" + "strings" "testing" - "code.gitea.io/gitea/modules/structs" + "forgejo.org/modules/structs" "github.com/gobwas/glob" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/net/publicsuffix" ) +func match(globs []glob.Glob, s string) bool { + for _, g := range globs { + if g.Match(s) { + return true + } + } + return false +} + func TestLoadServices(t *testing.T) { oldService := Service defer func() { @@ -28,15 +41,6 @@ EMAIL_DOMAIN_BLOCKLIST = d3, *.b require.NoError(t, err) loadServiceFrom(cfg) - match := func(globs []glob.Glob, s string) bool { - for _, g := range globs { - if g.Match(s) { - return true - } - } - return false - } - assert.True(t, match(Service.EmailDomainAllowList, "d1")) assert.True(t, match(Service.EmailDomainAllowList, "foo.w")) assert.True(t, match(Service.EmailDomainAllowList, "d2")) @@ -48,6 +52,121 @@ EMAIL_DOMAIN_BLOCKLIST = d3, *.b assert.False(t, match(Service.EmailDomainBlockList, "d1")) } +func TestLoadServiceBlockDisposable(t *testing.T) { + oldService := Service + defer func() { + Service = oldService + }() + + cfg, err := NewConfigProviderFromData(` +[service] +EMAIL_DOMAIN_BLOCK_DISPOSABLE = true +`) + + require.NoError(t, err) + loadServiceFrom(cfg) + + for _, domain := range DisposableEmailDomains() { + require.True(t, match(Service.EmailDomainBlockList, domain)) + } + + require.Len(t, Service.EmailDomainBlockList, len(DisposableEmailDomains())) + + knownGood := [...]string{ + "aol.com", + "gmx.com", + "mail.com", + "zoho.com", + "proton.me", + "gmail.com", + "yahoo.com", + "icloud.com", + "outlook.com", + "protonmail.com", + } + + for _, domain := range knownGood { + require.False(t, match(Service.EmailDomainBlockList, domain)) + } +} + +func TestLoadServiceBlockDisposableWithExistingGlobs(t *testing.T) { + oldService := Service + defer func() { + Service = oldService + }() + + tldCounts := make(map[string]int) + for _, domain := range DisposableEmailDomains() { + tld, _ := publicsuffix.PublicSuffix(domain) + tldCounts[tld]++ + } + + type tldkv struct { + Tld string + Count int + } + + sortedTldCounts := make([]tldkv, 0) + for tld, count := range tldCounts { + sortedTldCounts = append(sortedTldCounts, tldkv{tld, count}) + } + + sort.Slice(sortedTldCounts, func(i, j int) bool { + return sortedTldCounts[i].Count > sortedTldCounts[j].Count + }) + require.GreaterOrEqual(t, len(sortedTldCounts), 2) + + blockString := fmt.Sprintf("*.%s,*.%s", sortedTldCounts[0].Tld, sortedTldCounts[1].Tld) + + cfg, err := NewConfigProviderFromData(fmt.Sprintf(` +[service] +EMAIL_DOMAIN_BLOCKLIST = %s +EMAIL_DOMAIN_BLOCK_DISPOSABLE = true +`, blockString)) + + require.NoError(t, err) + loadServiceFrom(cfg) + + for _, domain := range DisposableEmailDomains() { + require.True(t, match(Service.EmailDomainBlockList, domain)) + } + + redundant := 0 + for _, val := range DisposableEmailDomains() { + if strings.HasSuffix(val, sortedTldCounts[0].Tld) || + strings.HasSuffix(val, sortedTldCounts[1].Tld) { + redundant++ + } + } + + expected := len(DisposableEmailDomains()) - redundant + 2 + require.Len(t, Service.EmailDomainBlockList, expected) +} + +func TestLoadServiceBlockDisposableWithComplementGlobs(t *testing.T) { + oldService := Service + defer func() { + Service = oldService + }() + + cfg, err := NewConfigProviderFromData(` +[service] +EMAIL_DOMAIN_BLOCKLIST = *.random +EMAIL_DOMAIN_BLOCK_DISPOSABLE = true +`) + + require.NoError(t, err) + loadServiceFrom(cfg) + + for _, domain := range DisposableEmailDomains() { + require.True(t, match(Service.EmailDomainBlockList, domain)) + } + + expected := len(DisposableEmailDomains()) + 1 + require.Len(t, Service.EmailDomainBlockList, expected) +} + func TestLoadServiceVisibilityModes(t *testing.T) { oldService := Service defer func() { diff --git a/modules/setting/session.go b/modules/setting/session.go index e9637fdfc5..e9ff9bf0bc 100644 --- a/modules/setting/session.go +++ b/modules/setting/session.go @@ -9,8 +9,8 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/json" + "forgejo.org/modules/log" ) // SessionConfig defines Session settings @@ -73,6 +73,4 @@ func loadSessionFrom(rootCfg ConfigProvider) { SessionConfig.ProviderConfig = string(shadowConfig) SessionConfig.OriginalProvider = SessionConfig.Provider SessionConfig.Provider = "VirtualSession" - - log.Info("Session Service Enabled") } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index c9d30836ac..75c24580b2 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1,5 +1,6 @@ // Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2017 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved // SPDX-License-Identifier: MIT package setting @@ -7,13 +8,12 @@ package setting import ( "fmt" "os" - "runtime" "strings" "time" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/user" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/log" + "forgejo.org/modules/optional" + "forgejo.org/modules/user" ) var ForgejoVersion = "1.0.0" @@ -33,7 +33,6 @@ var ( RunMode string RunUser string IsProd bool - IsWindows bool // IsInTesting indicates whether the testing is running. A lot of unreliable code causes a lot of nonsense error logs during testing // TODO: this is only a temporary solution, we should make the test code more reliable @@ -41,22 +40,18 @@ var ( ) func init() { - IsWindows = runtime.GOOS == "windows" if AppVer == "" { AppVer = "dev" } - // We can rely on log.CanColorStdout being set properly because modules/log/console_windows.go comes before modules/setting/setting.go lexicographically // By default set this logger at Info - we'll change it later, but we need to start with something. log.SetConsoleLogger(log.DEFAULT, "console", log.INFO) } // IsRunUserMatchCurrentUser returns false if configured run user does not match // actual user that runs the app. The first return value is the actual user name. -// This check is ignored under Windows since SSH remote login is not the main -// method to login on Windows. func IsRunUserMatchCurrentUser(runUser string) (string, bool) { - if IsWindows || SSH.StartBuiltinServer { + if SSH.StartBuiltinServer { return "", true } @@ -167,7 +162,7 @@ func loadRunModeFrom(rootCfg ConfigProvider) { // The following is a purposefully undocumented option. Please do not run Forgejo as root. It will only cause future headaches. // Please don't use root as a bandaid to "fix" something that is broken, instead the broken thing should instead be fixed properly. unsafeAllowRunAsRoot := ConfigSectionKeyBool(rootSec, "I_AM_BEING_UNSAFE_RUNNING_AS_ROOT") - unsafeAllowRunAsRoot = unsafeAllowRunAsRoot || util.OptionalBoolParse(os.Getenv("GITEA_I_AM_BEING_UNSAFE_RUNNING_AS_ROOT")).Value() + unsafeAllowRunAsRoot = unsafeAllowRunAsRoot || optional.ParseBool(os.Getenv("GITEA_I_AM_BEING_UNSAFE_RUNNING_AS_ROOT")).Value() RunMode = os.Getenv("GITEA_RUN_MODE") if RunMode == "" { RunMode = rootSec.Key("RUN_MODE").MustString("prod") @@ -210,6 +205,7 @@ func LoadSettings() { initAllLoggers() loadDBSetting(CfgProvider) + loadFederationFrom(CfgProvider) loadServiceFrom(CfgProvider) loadOAuth2ClientFrom(CfgProvider) loadCacheFrom(CfgProvider) @@ -224,8 +220,8 @@ func LoadSettings() { LoadQueueSettings() loadProjectFrom(CfgProvider) loadMimeTypeMapFrom(CfgProvider) - loadFederationFrom(CfgProvider) loadF3From(CfgProvider) + loadModerationFrom(CfgProvider) } // LoadSettingsForInstall initializes the settings for install diff --git a/modules/setting/setting_test.go b/modules/setting/setting_test.go index f77ee65974..1fef9e068a 100644 --- a/modules/setting/setting_test.go +++ b/modules/setting/setting_test.go @@ -1,4 +1,5 @@ // Copyright 2020 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved // SPDX-License-Identifier: MIT package setting @@ -6,9 +7,10 @@ package setting import ( "testing" - "code.gitea.io/gitea/modules/json" + "forgejo.org/modules/json" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMakeAbsoluteAssetURL(t *testing.T) { @@ -30,3 +32,84 @@ func TestMakeManifestData(t *testing.T) { jsonBytes := MakeManifestData(`Example App '\"`, "https://example.com", "https://example.com/foo/bar") assert.True(t, json.Valid(jsonBytes)) } + +func TestLoadServiceDomainListsForFederation(t *testing.T) { + oldAppURL := AppURL + oldFederation := Federation + oldService := Service + + defer func() { + AppURL = oldAppURL + Federation = oldFederation + Service = oldService + }() + + cfg, err := NewConfigProviderFromData(` +[federation] +ENABLED = true +[service] +EMAIL_DOMAIN_ALLOWLIST = *.allow.random +EMAIL_DOMAIN_BLOCKLIST = *.block.random +`) + + require.NoError(t, err) + loadServerFrom(cfg) + loadFederationFrom(cfg) + loadServiceFrom(cfg) + + assert.True(t, match(Service.EmailDomainAllowList, "d1.allow.random")) + assert.True(t, match(Service.EmailDomainAllowList, "localhost")) +} + +func TestLoadServiceDomainListsNoFederation(t *testing.T) { + oldAppURL := AppURL + oldFederation := Federation + oldService := Service + + defer func() { + AppURL = oldAppURL + Federation = oldFederation + Service = oldService + }() + + cfg, err := NewConfigProviderFromData(` +[federation] +ENABLED = false +[service] +EMAIL_DOMAIN_ALLOWLIST = *.allow.random +EMAIL_DOMAIN_BLOCKLIST = *.block.random +`) + + require.NoError(t, err) + loadServerFrom(cfg) + loadFederationFrom(cfg) + loadServiceFrom(cfg) + + assert.True(t, match(Service.EmailDomainAllowList, "d1.allow.random")) +} + +func TestLoadServiceDomainListsFederationEmptyAllowList(t *testing.T) { + oldAppURL := AppURL + oldFederation := Federation + oldService := Service + + defer func() { + AppURL = oldAppURL + Federation = oldFederation + Service = oldService + }() + + cfg, err := NewConfigProviderFromData(` +[federation] +ENABLED = true +[service] +EMAIL_DOMAIN_BLOCKLIST = *.block.random +`) + + require.NoError(t, err) + loadServerFrom(cfg) + loadFederationFrom(cfg) + loadServiceFrom(cfg) + + assert.Empty(t, Service.EmailDomainAllowList) +} diff --git a/modules/setting/ssh.go b/modules/setting/ssh.go index ea387e521f..86193bddb9 100644 --- a/modules/setting/ssh.go +++ b/modules/setting/ssh.go @@ -11,8 +11,8 @@ import ( "text/template" "time" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/log" + "forgejo.org/modules/util" gossh "golang.org/x/crypto/ssh" ) diff --git a/modules/setting/storage.go b/modules/setting/storage.go index 8ee5c0f0ab..532842064c 100644 --- a/modules/setting/storage.go +++ b/modules/setting/storage.go @@ -170,8 +170,8 @@ func getStorageTargetSection(rootCfg ConfigProvider, name, typ string, sec Confi targetSec, _ := rootCfg.GetSection(storageSectionName + "." + name) if targetSec != nil { targetType := targetSec.Key("STORAGE_TYPE").String() - switch { - case targetType == "": + switch targetType { + case "": if targetSec.Key("PATH").String() == "" { // both storage type and path are empty, use default return getDefaultStorageSection(rootCfg), targetSecIsDefault, nil } diff --git a/modules/setting/storage_test.go b/modules/setting/storage_test.go index 271607914c..a1b687ba5b 100644 --- a/modules/setting/storage_test.go +++ b/modules/setting/storage_test.go @@ -27,16 +27,16 @@ MINIO_BUCKET = gitea-storage require.NoError(t, err) require.NoError(t, loadAttachmentFrom(cfg)) - assert.EqualValues(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) + assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) require.NoError(t, loadLFSFrom(cfg)) - assert.EqualValues(t, "gitea-lfs", LFS.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea-lfs", LFS.Storage.MinioConfig.Bucket) + assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) require.NoError(t, loadAvatarsFrom(cfg)) - assert.EqualValues(t, "gitea-storage", Avatar.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "avatars/", Avatar.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea-storage", Avatar.Storage.MinioConfig.Bucket) + assert.Equal(t, "avatars/", Avatar.Storage.MinioConfig.BasePath) } func Test_getStorageUseOtherNameAsType(t *testing.T) { @@ -52,12 +52,12 @@ MINIO_BUCKET = gitea-storage require.NoError(t, err) require.NoError(t, loadAttachmentFrom(cfg)) - assert.EqualValues(t, "gitea-storage", Attachment.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea-storage", Attachment.Storage.MinioConfig.Bucket) + assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) require.NoError(t, loadLFSFrom(cfg)) - assert.EqualValues(t, "gitea-storage", LFS.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea-storage", LFS.Storage.MinioConfig.Bucket) + assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) } func Test_getStorageInheritStorageType(t *testing.T) { @@ -70,32 +70,32 @@ STORAGE_TYPE = minio require.NoError(t, loadPackagesFrom(cfg)) assert.EqualValues(t, "minio", Packages.Storage.Type) - assert.EqualValues(t, "gitea", Packages.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "packages/", Packages.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", Packages.Storage.MinioConfig.Bucket) + assert.Equal(t, "packages/", Packages.Storage.MinioConfig.BasePath) require.NoError(t, loadRepoArchiveFrom(cfg)) assert.EqualValues(t, "minio", RepoArchive.Storage.Type) - assert.EqualValues(t, "gitea", RepoArchive.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", RepoArchive.Storage.MinioConfig.Bucket) + assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) require.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "minio", Actions.LogStorage.Type) - assert.EqualValues(t, "gitea", Actions.LogStorage.MinioConfig.Bucket) - assert.EqualValues(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) + assert.Equal(t, "gitea", Actions.LogStorage.MinioConfig.Bucket) + assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "gitea", Actions.ArtifactStorage.MinioConfig.Bucket) - assert.EqualValues(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) + assert.Equal(t, "gitea", Actions.ArtifactStorage.MinioConfig.Bucket) + assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) require.NoError(t, loadAvatarsFrom(cfg)) assert.EqualValues(t, "minio", Avatar.Storage.Type) - assert.EqualValues(t, "gitea", Avatar.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "avatars/", Avatar.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", Avatar.Storage.MinioConfig.Bucket) + assert.Equal(t, "avatars/", Avatar.Storage.MinioConfig.BasePath) require.NoError(t, loadRepoAvatarFrom(cfg)) assert.EqualValues(t, "minio", RepoAvatar.Storage.Type) - assert.EqualValues(t, "gitea", RepoAvatar.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "repo-avatars/", RepoAvatar.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", RepoAvatar.Storage.MinioConfig.Bucket) + assert.Equal(t, "repo-avatars/", RepoAvatar.Storage.MinioConfig.BasePath) } type testLocalStoragePathCase struct { @@ -114,7 +114,7 @@ func testLocalStoragePath(t *testing.T, appDataPath, iniStr string, cases []test assert.EqualValues(t, "local", storage.Type) assert.True(t, filepath.IsAbs(storage.Path)) - assert.EqualValues(t, filepath.Clean(c.expectedPath), filepath.Clean(storage.Path)) + assert.Equal(t, filepath.Clean(c.expectedPath), filepath.Clean(storage.Path)) } } @@ -352,8 +352,8 @@ MINIO_SECRET_ACCESS_KEY = my_secret_key require.NoError(t, loadRepoArchiveFrom(cfg)) cp := RepoArchive.Storage.ToShadowCopy() - assert.EqualValues(t, "******", cp.MinioConfig.AccessKeyID) - assert.EqualValues(t, "******", cp.MinioConfig.SecretAccessKey) + assert.Equal(t, "******", cp.MinioConfig.AccessKeyID) + assert.Equal(t, "******", cp.MinioConfig.SecretAccessKey) } func Test_getStorageConfiguration24(t *testing.T) { @@ -408,10 +408,10 @@ MINIO_USE_SSL = true `) require.NoError(t, err) require.NoError(t, loadRepoArchiveFrom(cfg)) - assert.EqualValues(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID) - assert.EqualValues(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey) + assert.Equal(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID) + assert.Equal(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey) assert.True(t, RepoArchive.Storage.MinioConfig.UseSSL) - assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) } func Test_getStorageConfiguration28(t *testing.T) { @@ -425,10 +425,10 @@ MINIO_BASE_PATH = /prefix `) require.NoError(t, err) require.NoError(t, loadRepoArchiveFrom(cfg)) - assert.EqualValues(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID) - assert.EqualValues(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey) + assert.Equal(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID) + assert.Equal(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey) assert.True(t, RepoArchive.Storage.MinioConfig.UseSSL) - assert.EqualValues(t, "/prefix/repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "/prefix/repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) cfg, err = NewConfigProviderFromData(` [storage] @@ -443,10 +443,10 @@ MINIO_BASE_PATH = /lfs `) require.NoError(t, err) require.NoError(t, loadLFSFrom(cfg)) - assert.EqualValues(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID) - assert.EqualValues(t, "my_secret_key", LFS.Storage.MinioConfig.SecretAccessKey) - assert.True(t, true, LFS.Storage.MinioConfig.UseSSL) - assert.EqualValues(t, "/lfs", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID) + assert.Equal(t, "my_secret_key", LFS.Storage.MinioConfig.SecretAccessKey) + assert.True(t, LFS.Storage.MinioConfig.UseSSL) + assert.Equal(t, "/lfs", LFS.Storage.MinioConfig.BasePath) cfg, err = NewConfigProviderFromData(` [storage] @@ -461,8 +461,8 @@ MINIO_BASE_PATH = /lfs `) require.NoError(t, err) require.NoError(t, loadLFSFrom(cfg)) - assert.EqualValues(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID) - assert.EqualValues(t, "my_secret_key", LFS.Storage.MinioConfig.SecretAccessKey) + assert.Equal(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID) + assert.Equal(t, "my_secret_key", LFS.Storage.MinioConfig.SecretAccessKey) assert.True(t, LFS.Storage.MinioConfig.UseSSL) - assert.EqualValues(t, "/lfs", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "/lfs", LFS.Storage.MinioConfig.BasePath) } diff --git a/modules/setting/time.go b/modules/setting/time.go index 39acba12ef..1211fd475a 100644 --- a/modules/setting/time.go +++ b/modules/setting/time.go @@ -6,7 +6,7 @@ package setting import ( "time" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) // DefaultUILocation is the location on the UI, so that we can display the time on UI. @@ -20,7 +20,6 @@ func loadTimeFrom(rootCfg ConfigProvider) { if err != nil { log.Fatal("Load time zone failed: %v", err) } - log.Info("Default UI Location is %v", zone) } if DefaultUILocation == nil { DefaultUILocation = time.Local diff --git a/modules/setting/ui.go b/modules/setting/ui.go index 056d670ba6..2e6a3df4c6 100644 --- a/modules/setting/ui.go +++ b/modules/setting/ui.go @@ -6,8 +6,8 @@ package setting import ( "time" - "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/container" + "forgejo.org/modules/log" ) // UI settings @@ -88,6 +88,7 @@ var UI = struct { Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`, `forgejo`}, CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:", "forgejo": ":forgejo:"}, + ExploreDefaultSort: "recentupdate", PreferredTimestampTense: "mixed", AmbiguousUnicodeDetection: true, diff --git a/modules/setting/webhook.go b/modules/setting/webhook.go index 7b1ab4db1f..071b729aa1 100644 --- a/modules/setting/webhook.go +++ b/modules/setting/webhook.go @@ -6,26 +6,28 @@ package setting import ( "net/url" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) // Webhook settings var Webhook = struct { - QueueLength int - DeliverTimeout int - SkipTLSVerify bool - AllowedHostList string - PagingNum int - ProxyURL string - ProxyURLFixed *url.URL - ProxyHosts []string + QueueLength int + DeliverTimeout int + SkipTLSVerify bool + AllowedHostList string + PagingNum int + ProxyURL string + ProxyURLFixed *url.URL + ProxyHosts []string + PayloadCommitLimit int }{ - QueueLength: 1000, - DeliverTimeout: 5, - SkipTLSVerify: false, - PagingNum: 10, - ProxyURL: "", - ProxyHosts: []string{}, + QueueLength: 1000, + DeliverTimeout: 5, + SkipTLSVerify: false, + PagingNum: 10, + ProxyURL: "", + ProxyHosts: []string{}, + PayloadCommitLimit: 15, } func loadWebhookFrom(rootCfg ConfigProvider) { @@ -45,4 +47,5 @@ func loadWebhookFrom(rootCfg ConfigProvider) { } } Webhook.ProxyHosts = sec.Key("PROXY_HOSTS").Strings(",") + Webhook.PayloadCommitLimit = sec.Key("PAYLOAD_COMMIT_LIMIT").MustInt(15) } diff --git a/modules/ssh/init.go b/modules/ssh/init.go index 21d4f89936..1ccd95b18b 100644 --- a/modules/ssh/init.go +++ b/modules/ssh/init.go @@ -11,8 +11,8 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" ) func Init() error { diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go index f8e4f569b8..19cac0b603 100644 --- a/modules/ssh/ssh.go +++ b/modules/ssh/ssh.go @@ -11,7 +11,6 @@ import ( "crypto/x509" "encoding/pem" "errors" - "fmt" "io" "net" "os" @@ -22,21 +21,17 @@ import ( "sync" "syscall" - asymkey_model "code.gitea.io/gitea/models/asymkey" - "code.gitea.io/gitea/modules/graceful" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/process" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + asymkey_model "forgejo.org/models/asymkey" + "forgejo.org/modules/graceful" + "forgejo.org/modules/log" + "forgejo.org/modules/process" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" "github.com/gliderlabs/ssh" gossh "golang.org/x/crypto/ssh" ) -type contextKey string - -const giteaKeyID = contextKey("gitea-key-id") - func getExitStatusFromError(err error) int { if err == nil { return 0 @@ -62,7 +57,7 @@ func getExitStatusFromError(err error) int { } func sessionHandler(session ssh.Session) { - keyID := fmt.Sprintf("%d", session.Context().Value(giteaKeyID).(int64)) + keyID := session.ConnPermissions().Extensions["forgejo-key-id"] command := session.RawCommand() @@ -238,7 +233,10 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary log.Debug("Successfully authenticated: %s Certificate Fingerprint: %s Principal: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key), principal) } - ctx.SetValue(giteaKeyID, pkey.ID) + if ctx.Permissions().Extensions == nil { + ctx.Permissions().Extensions = map[string]string{} + } + ctx.Permissions().Extensions["forgejo-key-id"] = strconv.FormatInt(pkey.ID, 10) return true } @@ -266,7 +264,10 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary log.Debug("Successfully authenticated: %s Public Key Fingerprint: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key)) } - ctx.SetValue(giteaKeyID, pkey.ID) + if ctx.Permissions().Extensions == nil { + ctx.Permissions().Extensions = map[string]string{} + } + ctx.Permissions().Extensions["forgejo-key-id"] = strconv.FormatInt(pkey.ID, 10) return true } diff --git a/modules/ssh/ssh_graceful.go b/modules/ssh/ssh_graceful.go index cad2c685bd..825313ab1c 100644 --- a/modules/ssh/ssh_graceful.go +++ b/modules/ssh/ssh_graceful.go @@ -4,9 +4,9 @@ package ssh import ( - "code.gitea.io/gitea/modules/graceful" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/graceful" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" "github.com/gliderlabs/ssh" ) diff --git a/modules/storage/helper.go b/modules/storage/helper.go index 95f1c7b9a8..8bec3a0042 100644 --- a/modules/storage/helper.go +++ b/modules/storage/helper.go @@ -30,7 +30,7 @@ func (s DiscardStorage) Delete(_ string) error { return fmt.Errorf("%s", s) } -func (s DiscardStorage) URL(_, _ string) (*url.URL, error) { +func (s DiscardStorage) URL(_, _ string, _ url.Values) (*url.URL, error) { return nil, fmt.Errorf("%s", s) } diff --git a/modules/storage/helper_test.go b/modules/storage/helper_test.go index 60a7c61289..dd30c9b8ac 100644 --- a/modules/storage/helper_test.go +++ b/modules/storage/helper_test.go @@ -38,7 +38,7 @@ func Test_discardStorage(t *testing.T) { require.Error(t, err, string(tt)) } { - got, err := tt.URL("path", "name") + got, err := tt.URL("path", "name", nil) assert.Nil(t, got) require.Errorf(t, err, string(tt)) } diff --git a/modules/storage/local.go b/modules/storage/local.go index 9bb532f1df..6f851983b1 100644 --- a/modules/storage/local.go +++ b/modules/storage/local.go @@ -11,9 +11,9 @@ import ( "os" "path/filepath" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" ) var _ ObjectStorage = &LocalStorage{} @@ -114,7 +114,7 @@ func (l *LocalStorage) Delete(path string) error { } // URL gets the redirect URL to a file -func (l *LocalStorage) URL(path, name string) (*url.URL, error) { +func (l *LocalStorage) URL(path, name string, reqParams url.Values) (*url.URL, error) { return nil, ErrURLNotSupported } diff --git a/modules/storage/local_test.go b/modules/storage/local_test.go index e230323f67..d74c6bbc41 100644 --- a/modules/storage/local_test.go +++ b/modules/storage/local_test.go @@ -8,7 +8,7 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" "github.com/stretchr/testify/assert" ) @@ -50,7 +50,7 @@ func TestBuildLocalPath(t *testing.T) { t.Run(k.path, func(t *testing.T) { l := LocalStorage{dir: k.localDir} - assert.EqualValues(t, k.expected, l.buildLocalPath(k.path)) + assert.Equal(t, k.expected, l.buildLocalPath(k.path)) }) } } diff --git a/modules/storage/minio.go b/modules/storage/minio.go index d0c2dec65b..bf51a1642a 100644 --- a/modules/storage/minio.go +++ b/modules/storage/minio.go @@ -15,9 +15,9 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" @@ -231,7 +231,7 @@ type minioFileInfo struct { } func (m minioFileInfo) Name() string { - return path.Base(m.ObjectInfo.Key) + return path.Base(m.Key) } func (m minioFileInfo) Size() int64 { @@ -243,7 +243,7 @@ func (m minioFileInfo) ModTime() time.Time { } func (m minioFileInfo) IsDir() bool { - return strings.HasSuffix(m.ObjectInfo.Key, "/") + return strings.HasSuffix(m.Key, "/") } func (m minioFileInfo) Mode() os.FileMode { @@ -276,8 +276,12 @@ func (m *MinioStorage) Delete(path string) error { } // URL gets the redirect URL to a file. The presigned link is valid for 5 minutes. -func (m *MinioStorage) URL(path, name string) (*url.URL, error) { - reqParams := make(url.Values) +func (m *MinioStorage) URL(path, name string, serveDirectReqParams url.Values) (*url.URL, error) { + // copy serveDirectReqParams + reqParams, err := url.ParseQuery(serveDirectReqParams.Encode()) + if err != nil { + return nil, err + } // 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)+"\"") u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(path), 5*time.Minute, reqParams) diff --git a/modules/storage/minio_test.go b/modules/storage/minio_test.go index 9ce1dbc7b4..e168a2efbb 100644 --- a/modules/storage/minio_test.go +++ b/modules/storage/minio_test.go @@ -10,7 +10,7 @@ import ( "os" "testing" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" "github.com/minio/minio-go/v7" "github.com/stretchr/testify/assert" @@ -52,19 +52,19 @@ func TestVirtualHostMinioStorage(t *testing.T) { func TestMinioStoragePath(t *testing.T) { m := &MinioStorage{basePath: ""} - assert.Equal(t, "", m.buildMinioPath("/")) - assert.Equal(t, "", m.buildMinioPath(".")) + assert.Empty(t, m.buildMinioPath("/")) + assert.Empty(t, m.buildMinioPath(".")) assert.Equal(t, "a", m.buildMinioPath("/a")) assert.Equal(t, "a/b", m.buildMinioPath("/a/b/")) - assert.Equal(t, "", m.buildMinioDirPrefix("")) + assert.Empty(t, m.buildMinioDirPrefix("")) assert.Equal(t, "a/", m.buildMinioDirPrefix("/a/")) m = &MinioStorage{basePath: "/"} - assert.Equal(t, "", m.buildMinioPath("/")) - assert.Equal(t, "", m.buildMinioPath(".")) + assert.Empty(t, m.buildMinioPath("/")) + assert.Empty(t, m.buildMinioPath(".")) assert.Equal(t, "a", m.buildMinioPath("/a")) assert.Equal(t, "a/b", m.buildMinioPath("/a/b/")) - assert.Equal(t, "", m.buildMinioDirPrefix("")) + assert.Empty(t, m.buildMinioDirPrefix("")) assert.Equal(t, "a/", m.buildMinioDirPrefix("/a/")) m = &MinioStorage{basePath: "/base"} diff --git a/modules/storage/storage.go b/modules/storage/storage.go index b83b1c7929..db081e0768 100644 --- a/modules/storage/storage.go +++ b/modules/storage/storage.go @@ -11,32 +11,13 @@ import ( "net/url" "os" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" ) // ErrURLNotSupported represents url is not supported var ErrURLNotSupported = errors.New("url method not supported") -// ErrInvalidConfiguration is called when there is invalid configuration for a storage -type ErrInvalidConfiguration struct { - cfg any - 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 Type = setting.StorageType // NewStorageFunc is a function that creates a storage @@ -63,7 +44,7 @@ type ObjectStorage interface { Save(path string, r io.Reader, size int64) (int64, error) Stat(path string) (os.FileInfo, error) Delete(path string) error - URL(path, name string) (*url.URL, error) + URL(path, name string, reqParams url.Values) (*url.URL, error) IterateObjects(path string, iterator func(path string, obj Object) error) error } @@ -131,7 +112,7 @@ var ( ActionsArtifacts ObjectStorage = UninitializedStorage ) -// Init init the stoarge +// Init init the storage func Init() error { for _, f := range []func() error{ initAttachments, diff --git a/modules/storage/storage_test.go b/modules/storage/storage_test.go index 70bcd3155a..af3dd9520e 100644 --- a/modules/storage/storage_test.go +++ b/modules/storage/storage_test.go @@ -7,7 +7,7 @@ import ( "bytes" "testing" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/modules/structs/action.go b/modules/structs/action.go new file mode 100644 index 0000000000..df9f845adc --- /dev/null +++ b/modules/structs/action.go @@ -0,0 +1,25 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// ActionRunJob represents a job of a run +// swagger:model +type ActionRunJob struct { + // the action run job id + ID int64 `json:"id"` + // the repository id + RepoID int64 `json:"repo_id"` + // the owner id + OwnerID int64 `json:"owner_id"` + // the action run job name + Name string `json:"name"` + // the action run job needed ids + Needs []string `json:"needs"` + // the action run job labels to run on + RunsOn []string `json:"runs_on"` + // the action run job latest task id + TaskID int64 `json:"task_id"` + // the action run job status + Status string `json:"status"` +} diff --git a/modules/structs/admin_user.go b/modules/structs/admin_user.go index 5b7df127b4..8a4d066d72 100644 --- a/modules/structs/admin_user.go +++ b/modules/structs/admin_user.go @@ -15,7 +15,7 @@ type CreateUserOption struct { FullName string `json:"full_name" binding:"MaxSize(100)"` // required: true // swagger:strfmt email - Email string `json:"email" binding:"Required;Email;MaxSize(254)"` + Email string `json:"email" binding:"Required;EmailForAdmin;MaxSize(254)"` Password string `json:"password" binding:"MaxSize(255)"` MustChangePassword *bool `json:"must_change_password"` SendNotify bool `json:"send_notify"` diff --git a/modules/structs/attachment.go b/modules/structs/attachment.go index c97cdcb83c..0a3d4140c2 100644 --- a/modules/structs/attachment.go +++ b/modules/structs/attachment.go @@ -1,7 +1,7 @@ // Copyright 2017 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package structs // import "code.gitea.io/gitea/modules/structs" +package structs // import "forgejo.org/modules/structs" import ( "time" diff --git a/modules/structs/fork.go b/modules/structs/fork.go index eb7774afbc..8fc73647bd 100644 --- a/modules/structs/fork.go +++ b/modules/structs/fork.go @@ -10,3 +10,11 @@ type CreateForkOption struct { // name of the forked repository Name *string `json:"name"` } + +// SyncForkInfo information about syncing a fork +type SyncForkInfo struct { + Allowed bool `json:"allowed"` + ForkCommit string `json:"fork_commit"` + BaseCommit string `json:"base_commit"` + CommitsBehind int `json:"commits_behind"` +} diff --git a/modules/structs/hook.go b/modules/structs/hook.go index b7f8861b76..28c2e00588 100644 --- a/modules/structs/hook.go +++ b/modules/structs/hook.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/json" + "forgejo.org/modules/json" ) // ErrInvalidReceiveHook FIXME @@ -53,7 +53,8 @@ type CreateHookOption struct { BranchFilter string `json:"branch_filter" binding:"GlobPattern"` AuthorizationHeader string `json:"authorization_header"` // default: false - Active bool `json:"active"` + Active bool `json:"active"` + IsSystemWebhook bool `json:"is_system_webhook"` } // EditHookOption options when modify one hook @@ -141,26 +142,6 @@ func (p *CreatePayload) JSONPayload() ([]byte, error) { return json.MarshalIndent(p, "", " ") } -// ParseCreateHook parses create event hook content. -func ParseCreateHook(raw []byte) (*CreatePayload, error) { - hook := new(CreatePayload) - if err := json.Unmarshal(raw, hook); err != nil { - return nil, err - } - - // it is possible the JSON was parsed, however, - // was not from Gogs (maybe was from Bitbucket) - // So we'll check to be sure certain key fields - // were populated - switch { - case hook.Repo == nil: - return nil, ErrInvalidReceiveHook - case len(hook.Ref) == 0: - return nil, ErrInvalidReceiveHook - } - return hook, nil -} - // ________ .__ __ // \______ \ ____ | | _____/ |_ ____ // | | \_/ __ \| | _/ __ \ __\/ __ \ @@ -221,13 +202,14 @@ const ( // IssueCommentPayload represents a payload information of issue comment event. type IssueCommentPayload struct { - Action HookIssueCommentAction `json:"action"` - Issue *Issue `json:"issue"` - Comment *Comment `json:"comment"` - Changes *ChangesPayload `json:"changes,omitempty"` - Repository *Repository `json:"repository"` - Sender *User `json:"sender"` - IsPull bool `json:"is_pull"` + Action HookIssueCommentAction `json:"action"` + Issue *Issue `json:"issue"` + PullRequest *PullRequest `json:"pull_request,omitempty"` + Comment *Comment `json:"comment"` + Changes *ChangesPayload `json:"changes,omitempty"` + Repository *Repository `json:"repository"` + Sender *User `json:"sender"` + IsPull bool `json:"is_pull"` } // JSONPayload implements Payload @@ -291,22 +273,6 @@ func (p *PushPayload) JSONPayload() ([]byte, error) { return json.MarshalIndent(p, "", " ") } -// ParsePushHook parses push event hook content. -func ParsePushHook(raw []byte) (*PushPayload, error) { - hook := new(PushPayload) - if err := json.Unmarshal(raw, hook); err != nil { - return nil, err - } - - switch { - case hook.Repo == nil: - return nil, ErrInvalidReceiveHook - case len(hook.Ref) == 0: - return nil, ErrInvalidReceiveHook - } - return hook, nil -} - // Branch returns branch name from a payload func (p *PushPayload) Branch() string { return strings.ReplaceAll(p.Ref, "refs/heads/", "") @@ -362,6 +328,7 @@ type IssuePayload struct { Repository *Repository `json:"repository"` Sender *User `json:"sender"` CommitID string `json:"commit_id"` + Label *Label `json:"label,omitempty"` } // JSONPayload encodes the IssuePayload to JSON, with an indentation of two spaces. @@ -399,6 +366,7 @@ type PullRequestPayload struct { Sender *User `json:"sender"` CommitID string `json:"commit_id"` Review *ReviewPayload `json:"review"` + Label *Label `json:"label,omitempty"` } // JSONPayload FIXME diff --git a/modules/structs/miscellaneous.go b/modules/structs/miscellaneous.go index bff10f95b7..7866cb5fc0 100644 --- a/modules/structs/miscellaneous.go +++ b/modules/structs/miscellaneous.go @@ -37,6 +37,10 @@ type MarkupOption struct { // // in: body FilePath string + // The current branch path where the form gets posted + // + // in: body + BranchPath string } // MarkupRender is a rendered markup document diff --git a/modules/structs/org.go b/modules/structs/org.go index b2b2c61a01..451153b620 100644 --- a/modules/structs/org.go +++ b/modules/structs/org.go @@ -47,13 +47,22 @@ type CreateOrgOption struct { // EditOrgOption options for editing an organization type EditOrgOption struct { - FullName string `json:"full_name" binding:"MaxSize(100)"` - Email string `json:"email" binding:"MaxSize(255)"` - Description string `json:"description" binding:"MaxSize(255)"` - Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` - Location string `json:"location" binding:"MaxSize(50)"` + FullName string `json:"full_name" binding:"MaxSize(100)"` + Email *string `json:"email" binding:"MaxSize(255)"` + Description string `json:"description" binding:"MaxSize(255)"` + Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` + Location string `json:"location" binding:"MaxSize(50)"` // possible values are `public`, `limited` or `private` // enum: ["public", "limited", "private"] Visibility string `json:"visibility" binding:"In(,public,limited,private)"` RepoAdminChangeTeamAccess *bool `json:"repo_admin_change_team_access"` } + +// RenameOrgOption options when renaming an organization +type RenameOrgOption struct { + // New username for this org. This name cannot be in use yet by any other user. + // + // required: true + // unique: true + NewName string `json:"new_name" binding:"Required"` +} diff --git a/modules/structs/pull.go b/modules/structs/pull.go index ab627666c9..1ce7550e19 100644 --- a/modules/structs/pull.go +++ b/modules/structs/pull.go @@ -57,7 +57,8 @@ type PullRequest struct { // swagger:strfmt date-time Closed *time.Time `json:"closed_at"` - PinOrder int `json:"pin_order"` + PinOrder int `json:"pin_order"` + Flow int64 `json:"flow"` } // PRBranchInfo information about a branch diff --git a/modules/structs/pull_review.go b/modules/structs/pull_review.go index c77ebea07d..f89c1f2a63 100644 --- a/modules/structs/pull_review.go +++ b/modules/structs/pull_review.go @@ -89,7 +89,6 @@ type CreatePullReviewComment struct { NewLineNum int64 `json:"new_position"` } -// CreatePullReviewCommentOptions are options to create a pull review comment type CreatePullReviewCommentOptions CreatePullReviewComment // SubmitPullReviewOptions are options to submit a pending pull review diff --git a/modules/structs/repo.go b/modules/structs/repo.go index f2fe9c7ac3..c9cd729cf3 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -105,6 +105,7 @@ type Repository struct { DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"` DefaultMergeStyle string `json:"default_merge_style"` DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"` + DefaultUpdateStyle string `json:"default_update_style"` AvatarURL string `json:"avatar_url"` Internal bool `json:"internal"` MirrorInterval string `json:"mirror_interval"` @@ -223,8 +224,10 @@ type EditRepoOption struct { AllowRebaseUpdate *bool `json:"allow_rebase_update,omitempty"` // set to `true` to delete pr branch after merge by default DefaultDeleteBranchAfterMerge *bool `json:"default_delete_branch_after_merge,omitempty"` - // set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", "squash", or "fast-forward-only". - DefaultMergeStyle *string `json:"default_merge_style,omitempty"` + // set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", "squash", "fast-forward-only", "manually-merged", or "rebase-update-only". + DefaultMergeStyle *string `json:"default_merge_style,omitempty" binding:"In(merge,rebase,rebase-merge,squash,fast-forward-only,manually-merged,rebase-update-only)"` + // set to a update style to be used by this repository: "rebase" or "merge" + DefaultUpdateStyle *string `json:"default_update_style,omitempty" binding:"In(merge,rebase)"` // set to `true` to allow edits from maintainers by default DefaultAllowMaintainerEdit *bool `json:"default_allow_maintainer_edit,omitempty"` // set to `true` to archive this repository. @@ -290,6 +293,16 @@ type CreateBranchRepoOption struct { OldRefName string `json:"old_ref_name" binding:"GitRefName;MaxSize(100)"` } +// UpdateBranchRepoOption options when updating a branch in a repository +// swagger:model +type UpdateBranchRepoOption struct { + // New branch name + // + // required: true + // unique: true + Name string `json:"name" binding:"Required;GitRefName;MaxSize(100)"` +} + // TransferRepoOption options when transfer a repository's ownership // swagger:model type TransferRepoOption struct { diff --git a/modules/structs/repo_compare.go b/modules/structs/repo_compare.go index 8a12498705..6e77a813d3 100644 --- a/modules/structs/repo_compare.go +++ b/modules/structs/repo_compare.go @@ -5,6 +5,7 @@ package structs // Compare represents a comparison between two commits. type Compare struct { - TotalCommits int `json:"total_commits"` // Total number of commits in the comparison. - Commits []*Commit `json:"commits"` // List of commits in the comparison. + TotalCommits int `json:"total_commits"` // Total number of commits in the comparison. + Commits []*Commit `json:"commits"` // List of commits in the comparison. + Files []*CommitAffectedFiles `json:"files"` // Total files modified in this comparison. } diff --git a/modules/structs/repo_file.go b/modules/structs/repo_file.go index 00c804146a..242343493b 100644 --- a/modules/structs/repo_file.go +++ b/modules/structs/repo_file.go @@ -4,6 +4,8 @@ package structs +import "time" + // FileOptions options for all file APIs type FileOptions struct { // message (optional) for the commit of this file. if not supplied, a default message will be used @@ -31,7 +33,7 @@ type CreateFileOptions struct { // Branch returns branch name func (o *CreateFileOptions) Branch() string { - return o.FileOptions.BranchName + return o.BranchName } // DeleteFileOptions options for deleting files (used for other File structs below) @@ -45,7 +47,7 @@ type DeleteFileOptions struct { // Branch returns branch name func (o *DeleteFileOptions) Branch() string { - return o.FileOptions.BranchName + return o.BranchName } // UpdateFileOptions options for updating files @@ -61,7 +63,7 @@ type UpdateFileOptions struct { // Branch returns branch name func (o *UpdateFileOptions) Branch() string { - return o.FileOptions.BranchName + return o.BranchName } // ChangeFileOperation for creating, updating or deleting a file @@ -92,7 +94,7 @@ type ChangeFilesOptions struct { // Branch returns branch name func (o *ChangeFilesOptions) Branch() string { - return o.FileOptions.BranchName + return o.BranchName } // FileOptionInterface provides a unified interface for the different file options @@ -121,6 +123,8 @@ type ContentsResponse struct { Path string `json:"path"` SHA string `json:"sha"` LastCommitSHA string `json:"last_commit_sha"` + // swagger:strfmt date-time + LastCommitWhen time.Time `json:"last_commit_when"` // `type` will be `file`, `dir`, `symlink`, or `submodule` Type string `json:"type"` Size int64 `json:"size"` diff --git a/modules/structs/repo_note.go b/modules/structs/repo_note.go index 4eaf5a255d..76c6c17898 100644 --- a/modules/structs/repo_note.go +++ b/modules/structs/repo_note.go @@ -8,3 +8,7 @@ type Note struct { Message string `json:"message"` Commit *Commit `json:"commit"` } + +type NoteOptions struct { + Message string `json:"message"` +} diff --git a/modules/structs/user.go b/modules/structs/user.go index f2747b1473..49e4c495cf 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -6,7 +6,7 @@ package structs import ( "time" - "code.gitea.io/gitea/modules/json" + "forgejo.org/modules/json" ) // User represents a user @@ -27,7 +27,7 @@ type User struct { Email string `json:"email"` // URL to the user's avatar AvatarURL string `json:"avatar_url"` - // URL to the user's gitea page + // URL to the user's profile page HTMLURL string `json:"html_url"` // User locale Language string `json:"language"` @@ -84,6 +84,7 @@ type UserSettings struct { EnableRepoUnitHints bool `json:"enable_repo_unit_hints"` // Privacy HideEmail bool `json:"hide_email"` + HidePronouns bool `json:"hide_pronouns"` HideActivity bool `json:"hide_activity"` } @@ -101,6 +102,7 @@ type UserSettingsOptions struct { EnableRepoUnitHints *bool `json:"enable_repo_unit_hints"` // Privacy HideEmail *bool `json:"hide_email"` + HidePronouns *bool `json:"hide_pronouns"` HideActivity *bool `json:"hide_activity"` } diff --git a/modules/structs/user_app.go b/modules/structs/user_app.go index 7f78fbd495..6592c1cd48 100644 --- a/modules/structs/user_app.go +++ b/modules/structs/user_app.go @@ -23,9 +23,11 @@ type AccessToken struct { type AccessTokenList []*AccessToken // CreateAccessTokenOption options when create access token +// swagger:model CreateAccessTokenOption type CreateAccessTokenOption struct { // required: true - Name string `json:"name" binding:"Required"` + Name string `json:"name" binding:"Required"` + // example: ["all", "read:activitypub","read:issue", "write:misc", "read:notification", "read:organization", "read:package", "read:repository", "read:user"] Scopes []string `json:"scopes"` } diff --git a/modules/structs/user_email.go b/modules/structs/user_email.go index 9319667e8f..485d0de1af 100644 --- a/modules/structs/user_email.go +++ b/modules/structs/user_email.go @@ -7,7 +7,7 @@ package structs // Email an email address belonging to a user type Email struct { // swagger:strfmt email - Email string `json:"email"` + Email string `json:"email" binding:"EmailWithAllowedDomain"` Verified bool `json:"verified"` Primary bool `json:"primary"` UserID int64 `json:"user_id"` diff --git a/modules/structs/workflow.go b/modules/structs/workflow.go index c4429ea0a2..704ed0e65b 100644 --- a/modules/structs/workflow.go +++ b/modules/structs/workflow.go @@ -12,4 +12,18 @@ type DispatchWorkflowOption struct { Ref string `json:"ref"` // Input keys and values configured in the workflow file. Inputs map[string]string `json:"inputs"` + // Flag to return the run info + // default: false + ReturnRunInfo bool `json:"return_run_info"` +} + +// DispatchWorkflowRun represents a workflow run +// swagger:model +type DispatchWorkflowRun struct { + // the workflow run id + ID int64 `json:"id"` + // a unique number for each run of a repository + RunNumber int64 `json:"run_number"` + // the jobs name + Jobs []string `json:"jobs"` } diff --git a/modules/svg/svg.go b/modules/svg/svg.go index 016e1dc08b..e00d8de2d1 100644 --- a/modules/svg/svg.go +++ b/modules/svg/svg.go @@ -9,9 +9,9 @@ import ( "path" "strings" - gitea_html "code.gitea.io/gitea/modules/html" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/public" + gitea_html "forgejo.org/modules/html" + "forgejo.org/modules/log" + "forgejo.org/modules/public" ) var svgIcons map[string]string diff --git a/modules/sync/status_pool.go b/modules/sync/status_pool.go index 6f075d54b7..f22e3e155b 100644 --- a/modules/sync/status_pool.go +++ b/modules/sync/status_pool.go @@ -6,7 +6,7 @@ package sync import ( "sync" - "code.gitea.io/gitea/modules/container" + "forgejo.org/modules/container" ) // StatusTable is a table maintains true/false values. diff --git a/modules/system/appstate_test.go b/modules/system/appstate_test.go index 2f44c7b845..d19900c03d 100644 --- a/modules/system/appstate_test.go +++ b/modules/system/appstate_test.go @@ -6,8 +6,8 @@ package system import ( "testing" - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/unittest" + "forgejo.org/models/db" + "forgejo.org/models/unittest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -43,8 +43,8 @@ func TestAppStateDB(t *testing.T) { item1 := new(testItem1) require.NoError(t, as.Get(db.DefaultContext, item1)) - assert.Equal(t, "", item1.Val1) - assert.EqualValues(t, 0, item1.Val2) + assert.Empty(t, item1.Val1) + assert.Equal(t, 0, item1.Val2) item1 = new(testItem1) item1.Val1 = "a" @@ -58,7 +58,7 @@ func TestAppStateDB(t *testing.T) { item1 = new(testItem1) require.NoError(t, as.Get(db.DefaultContext, item1)) assert.Equal(t, "a", item1.Val1) - assert.EqualValues(t, 2, item1.Val2) + assert.Equal(t, 2, item1.Val2) item2 = new(testItem2) require.NoError(t, as.Get(db.DefaultContext, item2)) diff --git a/modules/system/db.go b/modules/system/db.go index 17178283d9..384087ab4f 100644 --- a/modules/system/db.go +++ b/modules/system/db.go @@ -6,9 +6,9 @@ package system import ( "context" - "code.gitea.io/gitea/models/system" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/util" + "forgejo.org/models/system" + "forgejo.org/modules/json" + "forgejo.org/modules/util" ) // DBStore can be used to store app state items in local filesystem diff --git a/modules/templates/base.go b/modules/templates/base.go index 2c2f35bbed..76d8e3271e 100644 --- a/modules/templates/base.go +++ b/modules/templates/base.go @@ -7,8 +7,8 @@ import ( "slices" "strings" - "code.gitea.io/gitea/modules/assetfs" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/assetfs" + "forgejo.org/modules/setting" ) func AssetFS() *assetfs.LayeredFS { diff --git a/modules/templates/dynamic.go b/modules/templates/dynamic.go index e1babd83c9..c5752c8c72 100644 --- a/modules/templates/dynamic.go +++ b/modules/templates/dynamic.go @@ -6,8 +6,8 @@ package templates import ( - "code.gitea.io/gitea/modules/assetfs" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/assetfs" + "forgejo.org/modules/setting" ) func BuiltinAssets() *assetfs.Layer { diff --git a/modules/templates/eval/eval.go b/modules/templates/eval/eval.go index 5d4ac915b9..487a1de4b0 100644 --- a/modules/templates/eval/eval.go +++ b/modules/templates/eval/eval.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/util" ) type Num struct { diff --git a/modules/templates/helper.go b/modules/templates/helper.go index aeae8204ad..02b175e6f6 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -14,15 +14,14 @@ import ( "strings" "time" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/svg" - "code.gitea.io/gitea/modules/templates/eval" - "code.gitea.io/gitea/modules/timeutil" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/services/gitdiff" + user_model "forgejo.org/models/user" + "forgejo.org/modules/base" + "forgejo.org/modules/markup" + "forgejo.org/modules/setting" + "forgejo.org/modules/svg" + "forgejo.org/modules/templates/eval" + "forgejo.org/modules/util" + "forgejo.org/services/gitdiff" ) // NewFuncMap returns functions for injecting to templates @@ -52,6 +51,7 @@ func NewFuncMap() template.FuncMap { "StringUtils": NewStringUtils, "SliceUtils": NewSliceUtils, "JsonUtils": NewJsonUtils, + "DateUtils": NewDateUtils, // ----------------------------------------------------------------- // svg / avatar / icon / color @@ -64,16 +64,18 @@ func NewFuncMap() template.FuncMap { // ----------------------------------------------------------------- // time / number / format - "FileSize": FileSizePanic, - "CountFmt": base.FormatNumberSI, - "TimeSince": timeutil.TimeSince, - "TimeSinceUnix": timeutil.TimeSinceUnix, - "DateTime": timeutil.DateTime, - "Sec2Time": util.SecToTime, + "FileSize": FileSizePanic, + "CountFmt": base.FormatNumberSI, + "Sec2Time": util.SecToTime, "LoadTimes": func(startTime time.Time) string { return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms" }, + // for backward compatibility only, do not use them anymore + "TimeSince": timeSinceLegacy, + "TimeSinceUnix": timeSinceLegacy, + "DateTime": dateTimeLegacy, + // ----------------------------------------------------------------- // setting "AppName": func() string { @@ -101,6 +103,10 @@ func NewFuncMap() template.FuncMap { "AppVer": func() string { return setting.AppVer }, + "AppVerNoMetadata": func() string { + version, _, _ := strings.Cut(setting.AppVer, "+") + return version + }, "AppDomain": func() string { // documented in mail-templates.md return setting.Domain }, @@ -182,6 +188,7 @@ func NewFuncMap() template.FuncMap { "RenderMarkdownToHtml": RenderMarkdownToHtml, "RenderLabel": RenderLabel, "RenderLabels": RenderLabels, + "RenderReviewRequest": RenderReviewRequest, // ----------------------------------------------------------------- // misc diff --git a/modules/templates/htmlrenderer.go b/modules/templates/htmlrenderer.go index 55a55dd7f4..d60397df08 100644 --- a/modules/templates/htmlrenderer.go +++ b/modules/templates/htmlrenderer.go @@ -19,12 +19,12 @@ import ( "sync/atomic" texttemplate "text/template" - "code.gitea.io/gitea/modules/assetfs" - "code.gitea.io/gitea/modules/graceful" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/templates/scopedtmpl" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/assetfs" + "forgejo.org/modules/graceful" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" + "forgejo.org/modules/templates/scopedtmpl" + "forgejo.org/modules/util" ) type TemplateExecutor scopedtmpl.TemplateExecutor diff --git a/modules/templates/htmlrenderer_test.go b/modules/templates/htmlrenderer_test.go index a1d3783a75..d7a4cd7161 100644 --- a/modules/templates/htmlrenderer_test.go +++ b/modules/templates/htmlrenderer_test.go @@ -10,7 +10,7 @@ import ( "strings" "testing" - "code.gitea.io/gitea/modules/assetfs" + "forgejo.org/modules/assetfs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -66,7 +66,7 @@ func TestHandleError(t *testing.T) { _, err = tmpl.Parse(s) require.Error(t, err) msg := h(err) - assert.EqualValues(t, strings.TrimSpace(expect), strings.TrimSpace(msg)) + assert.Equal(t, strings.TrimSpace(expect), strings.TrimSpace(msg)) } test("{{", p.handleGenericTemplateError, ` @@ -103,5 +103,5 @@ god knows XXX ---------------------------------------------------------------------- ` actualMsg := p.handleExpectedEndError(errors.New("template: test:1: expected end; found XXX")) - assert.EqualValues(t, strings.TrimSpace(expectedMsg), strings.TrimSpace(actualMsg)) + assert.Equal(t, strings.TrimSpace(expectedMsg), strings.TrimSpace(actualMsg)) } diff --git a/modules/templates/mailer.go b/modules/templates/mailer.go index ee79755dbb..a40728d7c7 100644 --- a/modules/templates/mailer.go +++ b/modules/templates/mailer.go @@ -11,9 +11,9 @@ import ( "strings" texttmpl "text/template" - "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/base" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" ) var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}\s*$`) diff --git a/modules/templates/main_test.go b/modules/templates/main_test.go index bbdf5d2f99..946bc603f6 100644 --- a/modules/templates/main_test.go +++ b/modules/templates/main_test.go @@ -7,11 +7,12 @@ import ( "context" "testing" - "code.gitea.io/gitea/models/unittest" - "code.gitea.io/gitea/modules/markup" + "forgejo.org/models/unittest" + "forgejo.org/modules/markup" - _ "code.gitea.io/gitea/models" - _ "code.gitea.io/gitea/models/issues" + _ "forgejo.org/models" + _ "forgejo.org/models/forgefed" + _ "forgejo.org/models/issues" ) func TestMain(m *testing.M) { diff --git a/modules/templates/scopedtmpl/scopedtmpl.go b/modules/templates/scopedtmpl/scopedtmpl.go index 2722ba97a2..41a8ca86e9 100644 --- a/modules/templates/scopedtmpl/scopedtmpl.go +++ b/modules/templates/scopedtmpl/scopedtmpl.go @@ -192,21 +192,21 @@ func newScopedTemplateSet(all *template.Template, name string) (*scopedTemplateS collectTemplates(nodeList.Nodes) } else if node.Type() == parse.NodeIf { nodeIf := node.(*parse.IfNode) - collectTemplates(nodeIf.BranchNode.List.Nodes) - if nodeIf.BranchNode.ElseList != nil { - collectTemplates(nodeIf.BranchNode.ElseList.Nodes) + collectTemplates(nodeIf.List.Nodes) + if nodeIf.ElseList != nil { + collectTemplates(nodeIf.ElseList.Nodes) } } else if node.Type() == parse.NodeRange { nodeRange := node.(*parse.RangeNode) - collectTemplates(nodeRange.BranchNode.List.Nodes) - if nodeRange.BranchNode.ElseList != nil { - collectTemplates(nodeRange.BranchNode.ElseList.Nodes) + collectTemplates(nodeRange.List.Nodes) + if nodeRange.ElseList != nil { + collectTemplates(nodeRange.ElseList.Nodes) } } else if node.Type() == parse.NodeWith { nodeWith := node.(*parse.WithNode) - collectTemplates(nodeWith.BranchNode.List.Nodes) - if nodeWith.BranchNode.ElseList != nil { - collectTemplates(nodeWith.BranchNode.ElseList.Nodes) + collectTemplates(nodeWith.List.Nodes) + if nodeWith.ElseList != nil { + collectTemplates(nodeWith.ElseList.Nodes) } } } diff --git a/modules/templates/static.go b/modules/templates/static.go index b5a7e561ec..776548c853 100644 --- a/modules/templates/static.go +++ b/modules/templates/static.go @@ -8,8 +8,8 @@ package templates import ( "time" - "code.gitea.io/gitea/modules/assetfs" - "code.gitea.io/gitea/modules/timeutil" + "forgejo.org/modules/assetfs" + "forgejo.org/modules/timeutil" ) // GlobalModTime provide a global mod time for embedded asset files diff --git a/modules/templates/util_avatar.go b/modules/templates/util_avatar.go index 85832cf264..93ebec51e4 100644 --- a/modules/templates/util_avatar.go +++ b/modules/templates/util_avatar.go @@ -9,13 +9,13 @@ import ( "html" "html/template" - activities_model "code.gitea.io/gitea/models/activities" - "code.gitea.io/gitea/models/avatars" - "code.gitea.io/gitea/models/organization" - repo_model "code.gitea.io/gitea/models/repo" - user_model "code.gitea.io/gitea/models/user" - gitea_html "code.gitea.io/gitea/modules/html" - "code.gitea.io/gitea/modules/setting" + activities_model "forgejo.org/models/activities" + "forgejo.org/models/avatars" + "forgejo.org/models/organization" + repo_model "forgejo.org/models/repo" + user_model "forgejo.org/models/user" + gitea_html "forgejo.org/modules/html" + "forgejo.org/modules/setting" ) type AvatarUtils struct { @@ -34,7 +34,7 @@ func AvatarHTML(src string, size int, class, name string) template.HTML { name = "avatar" } - return template.HTML(``) + return template.HTML(``) } // Avatar renders user avatars. args: user, size (int), class (string) diff --git a/modules/templates/util_date.go b/modules/templates/util_date.go new file mode 100644 index 0000000000..bb83bf692a --- /dev/null +++ b/modules/templates/util_date.go @@ -0,0 +1,151 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package templates + +import ( + "fmt" + "html" + "html/template" + "strings" + "time" + + "forgejo.org/modules/setting" + "forgejo.org/modules/timeutil" + "forgejo.org/modules/translation" +) + +type DateUtils struct{} + +func NewDateUtils() *DateUtils { + return (*DateUtils)(nil) // the util is stateless, and we do not need to create an instance +} + +// AbsoluteShort renders in "Jan 01, 2006" format +func (du *DateUtils) AbsoluteShort(time any) template.HTML { + return dateTimeFormat("short", time) +} + +// AbsoluteLong renders in "January 01, 2006" format +func (du *DateUtils) AbsoluteLong(time any) template.HTML { + return dateTimeFormat("long", time) +} + +// FullTime renders in "Jan 01, 2006 20:33:44" format +func (du *DateUtils) FullTime(time any) template.HTML { + return dateTimeFormat("full", time) +} + +func (du *DateUtils) TimeSince(time any) template.HTML { + return TimeSince(time) +} + +// ParseLegacy parses the datetime in legacy format, eg: "2016-01-02" in server's timezone. +// It shouldn't be used in new code. New code should use Time or TimeStamp as much as possible. +func (du *DateUtils) ParseLegacy(datetime string) time.Time { + return parseLegacy(datetime) +} + +func parseLegacy(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + t, _ = time.ParseInLocation(time.DateOnly, datetime, setting.DefaultUILocation) + } + return t +} + +func dateTimeLegacy(format string, datetime any, _ ...string) template.HTML { + if !setting.IsProd || setting.IsInTesting { + panic("dateTimeLegacy is for backward compatibility only, do not use it in new code") + } + if s, ok := datetime.(string); ok { + datetime = parseLegacy(s) + } + return dateTimeFormat(format, datetime) +} + +func timeSinceLegacy(time any, _ translation.Locale) template.HTML { + if !setting.IsProd || setting.IsInTesting { + panic("timeSinceLegacy is for backward compatibility only, do not use it in new code") + } + return TimeSince(time) +} + +func anyToTime(any any) (t time.Time, isZero bool) { + switch v := any.(type) { + case nil: + // it is zero + case *time.Time: + if v != nil { + t = *v + } + case time.Time: + t = v + case timeutil.TimeStamp: + t = v.AsTime() + case timeutil.TimeStampNano: + t = v.AsTime() + case int: + t = timeutil.TimeStamp(v).AsTime() + case int64: + t = timeutil.TimeStamp(v).AsTime() + default: + panic(fmt.Sprintf("Unsupported time type %T", any)) + } + return t, t.IsZero() || t.Unix() == 0 +} + +func dateTimeFormat(format string, datetime any) template.HTML { + t, isZero := anyToTime(datetime) + if isZero { + return "-" + } + var textEscaped string + datetimeEscaped := html.EscapeString(t.Format(time.RFC3339)) + if format == "full" { + textEscaped = html.EscapeString(t.Format("2006-01-02 15:04:05 -07:00")) + } else { + textEscaped = html.EscapeString(t.Format("2006-01-02")) + } + + attrs := []string{`weekday=""`, `year="numeric"`} + switch format { + case "short", "long": // date only + attrs = append(attrs, `month="`+format+`"`, `day="numeric"`) + return template.HTML(fmt.Sprintf(`%s`, strings.Join(attrs, " "), datetimeEscaped, textEscaped)) + case "full": // full date including time + attrs = append(attrs, `format="datetime"`, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`, `data-tooltip-content`, `data-tooltip-interactive="true"`) + return template.HTML(fmt.Sprintf(`%s`, strings.Join(attrs, " "), datetimeEscaped, textEscaped)) + default: + panic(fmt.Sprintf("Unsupported format %s", format)) + } +} + +func timeSinceTo(then any, now time.Time) template.HTML { + thenTime, isZero := anyToTime(then) + if isZero { + return "-" + } + + friendlyText := thenTime.Format("2006-01-02 15:04:05 -07:00") + + // document: https://github.com/github/relative-time-element + attrs := `tense="past"` + isFuture := now.Before(thenTime) + if isFuture { + attrs = `tense="future"` + } + + // declare data-tooltip-content attribute to switch from "title" tooltip to "tippy" tooltip + htm := fmt.Sprintf(`%s`, + attrs, thenTime.Format(time.RFC3339), friendlyText) + return template.HTML(htm) +} + +// TimeSince renders relative time HTML given a time +func TimeSince(then any) template.HTML { + if setting.UI.PreferredTimestampTense == "absolute" { + return dateTimeFormat("full", then) + } + return timeSinceTo(then, time.Now()) +} diff --git a/modules/templates/util_date_test.go b/modules/templates/util_date_test.go new file mode 100644 index 0000000000..37caf0d422 --- /dev/null +++ b/modules/templates/util_date_test.go @@ -0,0 +1,82 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package templates + +import ( + "html/template" + "testing" + "time" + + "forgejo.org/modules/setting" + "forgejo.org/modules/test" + "forgejo.org/modules/timeutil" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDateTime(t *testing.T) { + testTz, err := time.LoadLocation("America/New_York") + require.NoError(t, err) + defer test.MockVariableValue(&setting.DefaultUILocation, testTz)() + defer test.MockVariableValue(&setting.IsInTesting, false)() + + du := NewDateUtils() + + refTimeStr := "2018-01-01T00:00:00Z" + refDateStr := "2018-01-01" + refTime, _ := time.Parse(time.RFC3339, refTimeStr) + refTimeStamp := timeutil.TimeStamp(refTime.Unix()) + + for _, val := range []any{nil, 0, time.Time{}, timeutil.TimeStamp(0)} { + for _, fun := range []func(val any) template.HTML{du.AbsoluteLong, du.AbsoluteShort, du.FullTime} { + assert.EqualValues(t, "-", fun(val)) + } + } + + actual := dateTimeLegacy("short", "invalid") + assert.EqualValues(t, `-`, actual) + + actual = dateTimeLegacy("short", refTimeStr) + assert.EqualValues(t, `2018-01-01`, actual) + + actual = du.AbsoluteShort(refTime) + assert.EqualValues(t, `2018-01-01`, actual) + + actual = du.AbsoluteLong(refTime) + assert.EqualValues(t, `2018-01-01`, actual) + + actual = dateTimeLegacy("short", refDateStr) + assert.EqualValues(t, `2018-01-01`, actual) + + actual = du.AbsoluteShort(refTimeStamp) + assert.EqualValues(t, `2017-12-31`, actual) + + actual = du.AbsoluteLong(refTimeStamp) + assert.EqualValues(t, `2017-12-31`, actual) + + actual = du.FullTime(refTimeStamp) + assert.EqualValues(t, `2017-12-31 19:00:00 -05:00`, actual) +} + +func TestTimeSince(t *testing.T) { + testTz, _ := time.LoadLocation("America/New_York") + defer test.MockVariableValue(&setting.DefaultUILocation, testTz)() + defer test.MockVariableValue(&setting.IsInTesting, false)() + + du := NewDateUtils() + assert.EqualValues(t, "-", du.TimeSince(nil)) + + refTimeStr := "2018-01-01T00:00:00Z" + refTime, _ := time.Parse(time.RFC3339, refTimeStr) + + actual := du.TimeSince(refTime) + assert.EqualValues(t, `2018-01-01 00:00:00 +00:00`, actual) + + actual = timeSinceTo(&refTime, time.Time{}) + assert.EqualValues(t, `2018-01-01 00:00:00 +00:00`, actual) + + actual = timeSinceLegacy(timeutil.TimeStampNano(refTime.UnixNano()), nil) + assert.EqualValues(t, `2017-12-31 19:00:00 -05:00`, actual) +} diff --git a/modules/templates/util_dict.go b/modules/templates/util_dict.go index 8d6376b522..16f722e61b 100644 --- a/modules/templates/util_dict.go +++ b/modules/templates/util_dict.go @@ -4,14 +4,15 @@ package templates import ( + "errors" "fmt" "html" "html/template" "reflect" - "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/container" + "forgejo.org/modules/json" + "forgejo.org/modules/setting" ) func dictMerge(base map[string]any, arg any) bool { @@ -33,7 +34,7 @@ func dictMerge(base map[string]any, arg any) bool { // The dot syntax is highly discouraged because it might cause unclear key conflicts. It's always good to use explicit keys. func dict(args ...any) (map[string]any, error) { if len(args)%2 != 0 { - return nil, fmt.Errorf("invalid dict constructor syntax: must have key-value pairs") + return nil, errors.New("invalid dict constructor syntax: must have key-value pairs") } m := make(map[string]any, len(args)/2) for i := 0; i < len(args); i += 2 { diff --git a/modules/templates/util_json.go b/modules/templates/util_json.go index 71a4e23d36..3bc80e8f21 100644 --- a/modules/templates/util_json.go +++ b/modules/templates/util_json.go @@ -6,7 +6,7 @@ package templates import ( "bytes" - "code.gitea.io/gitea/modules/json" + "forgejo.org/modules/json" ) type JsonUtils struct{} //nolint:revive diff --git a/modules/templates/util_misc.go b/modules/templates/util_misc.go index 774385483b..60f918be47 100644 --- a/modules/templates/util_misc.go +++ b/modules/templates/util_misc.go @@ -1,4 +1,5 @@ // Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package templates @@ -12,14 +13,16 @@ import ( "strings" "time" - activities_model "code.gitea.io/gitea/models/activities" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/modules/git" - giturl "code.gitea.io/gitea/modules/git/url" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/repository" - "code.gitea.io/gitea/modules/svg" + activities_model "forgejo.org/models/activities" + asymkey_model "forgejo.org/models/asymkey" + repo_model "forgejo.org/models/repo" + user_model "forgejo.org/models/user" + "forgejo.org/modules/git" + giturl "forgejo.org/modules/git/url" + "forgejo.org/modules/json" + "forgejo.org/modules/log" + "forgejo.org/modules/repository" + "forgejo.org/modules/svg" "github.com/editorconfig/editorconfig-core-go/v2" ) @@ -38,10 +41,11 @@ func SortArrow(normSort, revSort, urlSort string, isDefault bool) template.HTML } else { // if sort arg is in url test if it correlates with column header sort arguments // the direction of the arrow should indicate the "current sort order", up means ASC(normal), down means DESC(rev) - if urlSort == normSort { + switch urlSort { + case normSort: // the table is sorted with this header normal return svg.RenderHTML("octicon-triangle-up", 16) - } else if urlSort == revSort { + case revSort: // the table is sorted with this header reverse return svg.RenderHTML("octicon-triangle-down", 16) } @@ -59,6 +63,7 @@ func IsMultilineCommitMessage(msg string) bool { type Actioner interface { GetOpType() activities_model.ActionType GetActUserName(ctx context.Context) string + GetRepo(ctx context.Context) *repo_model.Repository GetRepoUserName(ctx context.Context) string GetRepoName(ctx context.Context) string GetRepoPath(ctx context.Context) string @@ -108,7 +113,7 @@ func ActionIcon(opType activities_model.ActionType) string { } // ActionContent2Commits converts action content to push commits -func ActionContent2Commits(act Actioner) *repository.PushCommits { +func ActionContent2Commits(ctx context.Context, act Actioner) *repository.PushCommits { push := repository.NewPushCommits() if act == nil || act.GetContent() == "" { @@ -122,6 +127,23 @@ func ActionContent2Commits(act Actioner) *repository.PushCommits { if push.Len == 0 { push.Len = len(push.Commits) } + repo := act.GetRepo(ctx) + for _, commit := range push.Commits { + gitCommit, err := repository.PushCommitToCommit(commit) + if err != nil { + // Only happens if the commit has an invalid sha + commit.Verification = &asymkey_model.ObjectVerification{ + Verified: false, + Reason: "git.error.invalid_commit_id", + } + continue + } + verification := asymkey_model.ParseCommitWithSignature(ctx, gitCommit) + _ = asymkey_model.CalculateTrustStatus(verification, repo.GetTrustModel(), func(user *user_model.User) (bool, error) { + return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID) + }, nil) + commit.Verification = verification + } return push } diff --git a/modules/templates/util_misc_test.go b/modules/templates/util_misc_test.go new file mode 100644 index 0000000000..3d59f29bd1 --- /dev/null +++ b/modules/templates/util_misc_test.go @@ -0,0 +1,124 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package templates + +import ( + "testing" + + activities_model "forgejo.org/models/activities" + asymkey_model "forgejo.org/models/asymkey" + repo_model "forgejo.org/models/repo" + "forgejo.org/models/unittest" + user_model "forgejo.org/models/user" + "forgejo.org/modules/git" + "forgejo.org/modules/json" + "forgejo.org/modules/repository" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func pushCommits() *repository.PushCommits { + pushCommits := repository.NewPushCommits() + pushCommits.Commits = []*repository.PushCommit{ + { + Sha1: "x", + CommitterEmail: "user2@example.com", + CommitterName: "User2", + AuthorEmail: "user2@example.com", + AuthorName: "User2", + Message: "invalid sha1", + }, + { + Sha1: "2c54faec6c45d31c1abfaecdab471eac6633738a", + CommitterEmail: "user2@example.com", + CommitterName: "User2", + AuthorEmail: "user2@example.com", + AuthorName: "User2", + Message: "not signed commit", + }, + { + Sha1: "2d491b2985a7ff848d5c02748e7ea9f9f7619f9f", + CommitterEmail: "non-existent", + CommitterName: "user2", + AuthorEmail: "non-existent", + AuthorName: "user2", + Message: "Using email that isn't known to Forgejo", + Signature: &git.ObjectSignature{ + Payload: `tree 2d491b2985a7ff848d5c02748e7ea9f9f7619f9f +parent 45b03601635a1f463b81963a4022c7f87ce96ef9 +author user2 1699710556 +0100 +committer user2 1699710556 +0100 + +Using email that isn't known to Forgejo +`, + Signature: `-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgoGSe9Zy7Ez9bSJcaTNjh/Y7p95 +f5DujjqkpzFRtw6CEAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQIMufOuSjZeDUujrkVK4sl7ICa0WwEftas8UAYxx0Thdkiw2qWjR1U1PKfTLm16/w8 +/bS1LX1lZNuzm2LR2qEgw= +-----END SSH SIGNATURE----- +`, + }, + }, + { + Sha1: "853694aae8816094a0d875fee7ea26278dbf5d0f", + CommitterEmail: "user2@example.com", + CommitterName: "user2", + AuthorEmail: "user2@example.com", + AuthorName: "user2", + Message: "Add content", + Signature: &git.ObjectSignature{ + Payload: `tree 853694aae8816094a0d875fee7ea26278dbf5d0f +parent c2780d5c313da2a947eae22efd7dacf4213f4e7f +author user2 1699707877 +0100 +committer user2 1699707877 +0100 + +Add content +`, + Signature: `-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgoGSe9Zy7Ez9bSJcaTNjh/Y7p95 +f5DujjqkpzFRtw6CEAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQBe2Fwk/FKY3SBCnG6jSYcO6ucyahp2SpQ/0P+otslzIHpWNW8cQ0fGLdhhaFynJXQ +fs9cMpZVM9BfIKNUSO8QY= +-----END SSH SIGNATURE----- +`, + }, + }, + } + return pushCommits +} + +func TestActionContent2Commits_VerificationState(t *testing.T) { + defer unittest.OverrideFixtures("models/fixtures/TestParseCommitWithSSHSignature/")() + require.NoError(t, unittest.PrepareTestDatabase()) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2, OwnerID: user2.ID}) + commits, err := json.Marshal(pushCommits()) + require.NoError(t, err) + + act := &activities_model.Action{ + OpType: activities_model.ActionCommitRepo, + Repo: repo, + Content: string(commits), + } + push := ActionContent2Commits(t.Context(), act) + + assert.Equal(t, 4, push.Len) + assert.False(t, push.Commits[0].Verification.Verified) + assert.Empty(t, push.Commits[0].Verification.TrustStatus) + assert.Equal(t, "git.error.invalid_commit_id", push.Commits[0].Verification.Reason) + + assert.False(t, push.Commits[1].Verification.Verified) + assert.Empty(t, push.Commits[1].Verification.TrustStatus) + assert.Equal(t, asymkey_model.NotSigned, push.Commits[1].Verification.Reason) + + assert.False(t, push.Commits[2].Verification.Verified) + assert.Empty(t, push.Commits[2].Verification.TrustStatus) + assert.Equal(t, asymkey_model.NoKeyFound, push.Commits[2].Verification.Reason) + + assert.True(t, push.Commits[3].Verification.Verified) + assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", push.Commits[3].Verification.Reason) + assert.Equal(t, "trusted", push.Commits[3].Verification.TrustStatus) +} diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go index c53bdd876f..a4d7a82eea 100644 --- a/modules/templates/util_render.go +++ b/modules/templates/util_render.go @@ -14,14 +14,14 @@ import ( "strings" "unicode" - issues_model "code.gitea.io/gitea/models/issues" - "code.gitea.io/gitea/modules/emoji" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/markup/markdown" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/translation" - "code.gitea.io/gitea/modules/util" + issues_model "forgejo.org/models/issues" + "forgejo.org/modules/emoji" + "forgejo.org/modules/log" + "forgejo.org/modules/markup" + "forgejo.org/modules/markup/markdown" + "forgejo.org/modules/setting" + "forgejo.org/modules/translation" + "forgejo.org/modules/util" ) // RenderCommitMessage renders commit message with XSS-safe and special links. @@ -262,3 +262,15 @@ func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issu htmlCode += "" return template.HTML(htmlCode) } + +func RenderReviewRequest(users []issues_model.RequestReviewTarget) template.HTML { + usernames := make([]string, 0, len(users)) + for _, user := range users { + usernames = append(usernames, template.HTMLEscapeString(user.Name())) + } + + htmlCode := `` + htmlCode += strings.Join(usernames, ", ") + htmlCode += "" + return template.HTML(htmlCode) +} diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go index da74298ef7..b75b061218 100644 --- a/modules/templates/util_render_test.go +++ b/modules/templates/util_render_test.go @@ -8,10 +8,10 @@ import ( "html/template" "testing" - "code.gitea.io/gitea/models/db" - issues_model "code.gitea.io/gitea/models/issues" - "code.gitea.io/gitea/models/unittest" - "code.gitea.io/gitea/modules/translation" + "forgejo.org/models/db" + issues_model "forgejo.org/models/issues" + "forgejo.org/models/unittest" + "forgejo.org/modules/translation" "github.com/stretchr/testify/assert" ) @@ -46,13 +46,13 @@ var testMetas = map[string]string{ } func TestApostrophesInMentions(t *testing.T) { - rendered := RenderMarkdownToHtml(context.Background(), "@mention-user's comment") - assert.EqualValues(t, template.HTML("

    @mention-user's comment

    \n"), rendered) + rendered := RenderMarkdownToHtml(t.Context(), "@mention-user's comment") + assert.Equal(t, template.HTML("

    @mention-user's comment

    \n"), rendered) } func TestNonExistantUserMention(t *testing.T) { - rendered := RenderMarkdownToHtml(context.Background(), "@ThisUserDoesNotExist @mention-user") - assert.EqualValues(t, template.HTML("

    @ThisUserDoesNotExist @mention-user

    \n"), rendered) + rendered := RenderMarkdownToHtml(t.Context(), "@ThisUserDoesNotExist @mention-user") + assert.Equal(t, template.HTML("

    @ThisUserDoesNotExist @mention-user

    \n"), rendered) } func TestRenderCommitBody(t *testing.T) { @@ -69,7 +69,7 @@ func TestRenderCommitBody(t *testing.T) { { name: "multiple lines", args: args{ - ctx: context.Background(), + ctx: t.Context(), msg: "first line\nsecond line", }, want: "second line", @@ -77,7 +77,7 @@ func TestRenderCommitBody(t *testing.T) { { name: "multiple lines with leading newlines", args: args{ - ctx: context.Background(), + ctx: t.Context(), msg: "\n\n\n\nfirst line\nsecond line", }, want: "second line", @@ -85,7 +85,7 @@ func TestRenderCommitBody(t *testing.T) { { name: "multiple lines with trailing newlines", args: args{ - ctx: context.Background(), + ctx: t.Context(), msg: "first line\nsecond line\n\n\n", }, want: "second line", @@ -111,25 +111,25 @@ func TestRenderCommitBody(t *testing.T) { com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare 88fc37a3c0 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit -👠+👠mail@domain.com @mention-user test #123 space -` + "`code 👠#123 code`" - assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas)) +` + "`code 👠#123 code`" + assert.EqualValues(t, expected, RenderCommitBody(t.Context(), testInput, testMetas)) } func TestRenderCommitMessage(t *testing.T) { expected := `space @mention-user ` - assert.EqualValues(t, expected, RenderCommitMessage(context.Background(), testInput, testMetas)) + assert.EqualValues(t, expected, RenderCommitMessage(t.Context(), testInput, testMetas)) } func TestRenderCommitMessageLinkSubject(t *testing.T) { expected := `space @mention-user` - assert.EqualValues(t, expected, RenderCommitMessageLinkSubject(context.Background(), testInput, "https://example.com/link", testMetas)) + assert.EqualValues(t, expected, RenderCommitMessageLinkSubject(t.Context(), testInput, "https://example.com/link", testMetas)) } func TestRenderIssueTitle(t *testing.T) { @@ -148,14 +148,14 @@ https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb.. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit -👠+👠mail@domain.com @mention-user test #123 space code :+1: #123 code ` - assert.EqualValues(t, expected, RenderIssueTitle(context.Background(), testInput, testMetas)) + assert.EqualValues(t, expected, RenderIssueTitle(t.Context(), testInput, testMetas)) } func TestRenderRefIssueTitle(t *testing.T) { @@ -174,18 +174,18 @@ https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb.. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit -👠+👠mail@domain.com @mention-user test #123 space code :+1: #123 code ` - assert.EqualValues(t, expected, RenderRefIssueTitle(context.Background(), testInput)) + assert.EqualValues(t, expected, RenderRefIssueTitle(t.Context(), testInput)) } func TestRenderMarkdownToHtml(t *testing.T) { - expected := `

    space @mention-user
    + expected := `

    space @mention-user
    /just/a/path.bin https://example.com/file.bin local link @@ -194,20 +194,20 @@ func TestRenderMarkdownToHtml(t *testing.T) { remote link local image remote image -local image -remote link + + 88fc37a3c0...12fc37a3c0 (hash) com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare 88fc37a3c0 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit -👠+👠mail@domain.com -@mention-user test +@mention-user test #123 space code :+1: #123 code

    ` - assert.EqualValues(t, expected, RenderMarkdownToHtml(context.Background(), testInput)) + assert.EqualValues(t, expected, RenderMarkdownToHtml(t.Context(), testInput)) } func TestRenderLabels(t *testing.T) { diff --git a/modules/templates/util_string.go b/modules/templates/util_string.go index f23b74786a..2d255e54a7 100644 --- a/modules/templates/util_string.go +++ b/modules/templates/util_string.go @@ -8,7 +8,7 @@ import ( "html/template" "strings" - "code.gitea.io/gitea/modules/base" + "forgejo.org/modules/base" ) type StringUtils struct{} @@ -19,6 +19,10 @@ func NewStringUtils() *StringUtils { return &stringUtils } +func (su *StringUtils) Make(arr ...string) []string { + return arr +} + func (su *StringUtils) HasPrefix(s any, prefix string) bool { switch v := s.(type) { case string: diff --git a/modules/templates/util_test.go b/modules/templates/util_test.go index 79aaba4a0e..e28da8090b 100644 --- a/modules/templates/util_test.go +++ b/modules/templates/util_test.go @@ -29,7 +29,7 @@ func TestDict(t *testing.T) { for _, c := range cases { got, err := dict(c.args...) require.NoError(t, err) - assert.EqualValues(t, c.want, got) + assert.Equal(t, c.want, got) } bads := []struct { diff --git a/modules/templates/vars/vars_test.go b/modules/templates/vars/vars_test.go index c54342204d..a0c3490c3a 100644 --- a/modules/templates/vars/vars_test.go +++ b/modules/templates/vars/vars_test.go @@ -61,7 +61,7 @@ func TestExpandVars(t *testing.T) { for _, kase := range kases { t.Run(kase.tmpl, func(t *testing.T) { res, err := Expand(kase.tmpl, kase.data) - assert.EqualValues(t, kase.out, res) + assert.Equal(t, kase.out, res) if kase.error { require.Error(t, err) } else { diff --git a/modules/test/distant_federation_server_mock.go b/modules/test/distant_federation_server_mock.go new file mode 100644 index 0000000000..9bd908e2b9 --- /dev/null +++ b/modules/test/distant_federation_server_mock.go @@ -0,0 +1,117 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package test + +import ( + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +type FederationServerMockPerson struct { + ID int64 + Name string + PubKey string +} +type FederationServerMockRepository struct { + ID int64 +} +type FederationServerMock struct { + Persons []FederationServerMockPerson + Repositories []FederationServerMockRepository + LastPost string +} + +func NewFederationServerMockPerson(id int64, name string) FederationServerMockPerson { + return FederationServerMockPerson{ + ID: id, + Name: name, + PubKey: `"-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA18H5s7N6ItZUAh9tneII\nIuZdTTa3cZlLa/9ejWAHTkcp3WLW+/zbsumlMrWYfBy2/yTm56qasWt38iY4D6ul\n` + + `CPiwhAqX3REvVq8tM79a2CEqZn9ka6vuXoDgBg/sBf/BUWqf7orkjUXwk/U0Egjf\nk5jcurF4vqf1u+rlAHH37dvSBaDjNj6Qnj4OP12bjfaY/yvs7+jue/eNXFHjzN4E\n` + + `T2H4B/yeKTJ4UuAwTlLaNbZJul2baLlHelJPAsxiYaziVuV5P+IGWckY6RSerRaZ\nAkc4mmGGtjAyfN9aewe+lNVfwS7ElFx546PlLgdQgjmeSwLX8FWxbPE5A/PmaXCs\n` + + `nx+nou+3dD7NluULLtdd7K+2x02trObKXCAzmi5/Dc+yKTzpFqEz+hLNCz7TImP/\ncK//NV9Q+X67J9O27baH9R9ZF4zMw8rv2Pg0WLSw1z7lLXwlgIsDapeMCsrxkVO4\n` + + `LXX5AQ1xQNtlssnVoUBqBrvZsX2jUUKUocvZqMGuE4hfAgMBAAE=\n-----END PUBLIC KEY-----\n"`, + } +} + +func NewFederationServerMockRepository(id int64) FederationServerMockRepository { + return FederationServerMockRepository{ + ID: id, + } +} + +func (p FederationServerMockPerson) marshal(host string) string { + return fmt.Sprintf(`{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1"],`+ + `"id":"http://%[1]v/api/activitypub/user-id/%[2]v",`+ + `"type":"Person",`+ + `"icon":{"type":"Image","mediaType":"image/png","url":"http://%[1]v/avatars/1bb05d9a5f6675ed0272af9ea193063c"},`+ + `"url":"http://%[1]v/%[2]v",`+ + `"inbox":"http://%[1]v/api/activitypub/user-id/%[2]v/inbox",`+ + `"outbox":"http://%[1]v/api/activitypub/user-id/%[2]v/outbox",`+ + `"preferredUsername":"%[3]v",`+ + `"publicKey":{"id":"http://%[1]v/api/activitypub/user-id/%[2]v#main-key",`+ + `"owner":"http://%[1]v/api/activitypub/user-id/%[2]v",`+ + `"publicKeyPem":%[4]v}}`, host, p.ID, p.Name, p.PubKey) +} + +func NewFederationServerMock() *FederationServerMock { + return &FederationServerMock{ + Persons: []FederationServerMockPerson{ + NewFederationServerMockPerson(15, "stargoose1"), + NewFederationServerMockPerson(30, "stargoose2"), + }, + Repositories: []FederationServerMockRepository{ + NewFederationServerMockRepository(1), + }, + LastPost: "", + } +} + +func (mock *FederationServerMock) DistantServer(t *testing.T) *httptest.Server { + federatedRoutes := http.NewServeMux() + federatedRoutes.HandleFunc("/.well-known/nodeinfo", + func(res http.ResponseWriter, req *http.Request) { + // curl -H "Accept: application/json" https://federated-repo.prod.meissa.de/.well-known/nodeinfo + // TODO: as soon as content-type will become important: content-type: application/json;charset=utf-8 + fmt.Fprintf(res, `{"links":[{"href":"http://%s/api/v1/nodeinfo","rel":"http://nodeinfo.diaspora.software/ns/schema/2.1"}]}`, req.Host) + }) + federatedRoutes.HandleFunc("/api/v1/nodeinfo", + func(res http.ResponseWriter, req *http.Request) { + // curl -H "Accept: application/json" https://federated-repo.prod.meissa.de/api/v1/nodeinfo + fmt.Fprint(res, `{"version":"2.1","software":{"name":"forgejo","version":"1.20.0+dev-3183-g976d79044",`+ + `"repository":"https://codeberg.org/forgejo/forgejo.git","homepage":"https://forgejo.org/"},`+ + `"protocols":["activitypub"],"services":{"inbound":[],"outbound":["rss2.0"]},`+ + `"openRegistrations":true,"usage":{"users":{"total":14,"activeHalfyear":2}},"metadata":{}}`) + }) + for _, person := range mock.Persons { + federatedRoutes.HandleFunc(fmt.Sprintf("/api/v1/activitypub/user-id/%v", person.ID), + func(res http.ResponseWriter, req *http.Request) { + // curl -H "Accept: application/json" https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/2 + fmt.Fprint(res, person.marshal(req.Host)) + }) + } + for _, repository := range mock.Repositories { + federatedRoutes.HandleFunc(fmt.Sprintf("/api/v1/activitypub/repository-id/%v/inbox", repository.ID), + func(res http.ResponseWriter, req *http.Request) { + if req.Method != "POST" { + t.Errorf("POST expected at: %q", req.URL.EscapedPath()) + } + buf := new(strings.Builder) + _, err := io.Copy(buf, req.Body) + if err != nil { + t.Errorf("Error reading body: %q", err) + } + mock.LastPost = buf.String() + }) + } + federatedRoutes.HandleFunc("/", + func(res http.ResponseWriter, req *http.Request) { + t.Errorf("Unhandled request: %q", req.URL.EscapedPath()) + }) + federatedSrv := httptest.NewServer(federatedRoutes) + return federatedSrv +} diff --git a/modules/test/logchecker.go b/modules/test/logchecker.go index 0f12257f3e..8e8fc32216 100644 --- a/modules/test/logchecker.go +++ b/modules/test/logchecker.go @@ -11,7 +11,7 @@ import ( "sync/atomic" "time" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) type LogChecker struct { diff --git a/modules/test/logchecker_test.go b/modules/test/logchecker_test.go index 0f410fed12..d81142bf1c 100644 --- a/modules/test/logchecker_test.go +++ b/modules/test/logchecker_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" "github.com/stretchr/testify/assert" ) diff --git a/modules/test/utils.go b/modules/test/utils.go index 3d884b6cbe..db131f19d0 100644 --- a/modules/test/utils.go +++ b/modules/test/utils.go @@ -7,8 +7,9 @@ import ( "net/http" "net/http/httptest" "strings" + "time" - "code.gitea.io/gitea/modules/json" + "forgejo.org/modules/json" ) // RedirectURL returns the redirect URL of a http response. @@ -46,3 +47,8 @@ func MockProtect[T any](p *T) (reset func()) { old := *p return func() { *p = old } } + +// When this is called, sleep until the unix time was increased by one. +func SleepTillNextSecond() { + time.Sleep(time.Second - time.Since(time.Now().Truncate(time.Second))) +} diff --git a/modules/testlogger/testlogger.go b/modules/testlogger/testlogger.go index 95cbb86591..772ae47e71 100644 --- a/modules/testlogger/testlogger.go +++ b/modules/testlogger/testlogger.go @@ -16,8 +16,9 @@ import ( "testing" "time" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/queue" + "forgejo.org/modules/log" + "forgejo.org/modules/queue" + "forgejo.org/modules/util" ) var ( @@ -131,6 +132,8 @@ var ignoredErrorMessage = []string{ `:SSHLog() [E] ssh: Not allowed to push to protected branch protected. HookPreReceive(last) failed: internal API error response, status=403`, // TestGit/HTTP/BranchProtectMerge `:SSHLog() [E] ssh: branch protected is protected from force push. HookPreReceive(last) failed: internal API error response, status=403`, + // TestGit/HTTP/BranchProtect + `:SSHLog() [E] ssh: branch before-create-2 is protected from changing file protected-file-data-`, // TestGit/HTTP/MergeFork/CreatePRAndMerge `:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1099 name: user2:master]`, // sqlite "s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 10000 name: user2:master]", // mysql @@ -360,6 +363,12 @@ var ignoredErrorMessage = []string{ // TestDatabaseCollation `[E] [Error SQL Query] INSERT INTO test_collation_tbl (txt) VALUES ('main') []`, + + // Test_CmdForgejo_Actions + `DB: No dedicated replica host defined; falling back to primary DSN for replica connections`, + + // TestDevtestErrorpages + `ErrorPage() [E] Example error: Example error`, } func (w *testLoggerWriterCloser) recordError(msg string) { @@ -443,10 +452,7 @@ func (w *testLoggerWriterCloser) Reset() error { func PrintCurrentTest(t testing.TB, skip ...int) func() { t.Helper() start := time.Now() - actualSkip := 1 - if len(skip) > 0 { - actualSkip = skip[0] + 1 - } + actualSkip := util.OptionalArg(skip) + 1 _, filename, line, _ := runtime.Caller(actualSkip) if log.CanColorStdout { @@ -471,7 +477,7 @@ func PrintCurrentTest(t testing.TB, skip ...int) func() { _, _ = fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", t.Name(), SlowFlush) } }) - if err := queue.GetManager().FlushAll(context.Background(), time.Minute); err != nil { + if err := queue.GetManager().FlushAll(t.Context(), time.Minute); err != nil { t.Errorf("Flushing queues failed with error %v", err) } timer.Stop() @@ -486,7 +492,7 @@ func PrintCurrentTest(t testing.TB, skip ...int) func() { if err := WriterCloser.popT(); err != nil { // disable test failure for now (too flacky) - _, _ = fmt.Fprintf(os.Stdout, "testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err) + _, _ = fmt.Fprintf(os.Stdout, "testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v\n", err) // t.Errorf("testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err) } } diff --git a/modules/timeutil/datetime.go b/modules/timeutil/datetime.go deleted file mode 100644 index c089173560..0000000000 --- a/modules/timeutil/datetime.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package timeutil - -import ( - "fmt" - "html" - "html/template" - "strings" - "time" -) - -// DateTime renders an absolute time HTML element by datetime. -func DateTime(format string, datetime any, extraAttrs ...string) template.HTML { - // TODO: remove the extraAttrs argument, it's not used in any call to DateTime - - if p, ok := datetime.(*time.Time); ok { - datetime = *p - } - if p, ok := datetime.(*TimeStamp); ok { - datetime = *p - } - switch v := datetime.(type) { - case TimeStamp: - datetime = v.AsTime() - case int: - datetime = TimeStamp(v).AsTime() - case int64: - datetime = TimeStamp(v).AsTime() - } - - var datetimeEscaped, textEscaped string - switch v := datetime.(type) { - case nil: - return "-" - case string: - datetimeEscaped = html.EscapeString(v) - textEscaped = datetimeEscaped - case time.Time: - if v.IsZero() || v.Unix() == 0 { - return "-" - } - datetimeEscaped = html.EscapeString(v.Format(time.RFC3339)) - if format == "full" { - textEscaped = html.EscapeString(v.Format("2006-01-02 15:04:05 -07:00")) - } else { - textEscaped = html.EscapeString(v.Format("2006-01-02")) - } - default: - panic(fmt.Sprintf("Unsupported time type %T", datetime)) - } - - attrs := make([]string, 0, 10+len(extraAttrs)) - attrs = append(attrs, extraAttrs...) - attrs = append(attrs, `weekday=""`, `year="numeric"`) - - switch format { - case "short", "long": // date only - attrs = append(attrs, `month="`+format+`"`, `day="numeric"`) - return template.HTML(fmt.Sprintf(`%s`, strings.Join(attrs, " "), datetimeEscaped, textEscaped)) - case "full": // full date including time - attrs = append(attrs, `format="datetime"`, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`, `data-tooltip-content`, `data-tooltip-interactive="true"`) - return template.HTML(fmt.Sprintf(`%s`, strings.Join(attrs, " "), datetimeEscaped, textEscaped)) - default: - panic(fmt.Sprintf("Unsupported format %s", format)) - } -} diff --git a/modules/timeutil/datetime_test.go b/modules/timeutil/datetime_test.go deleted file mode 100644 index ac2ce35ba2..0000000000 --- a/modules/timeutil/datetime_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package timeutil - -import ( - "testing" - "time" - - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/test" - - "github.com/stretchr/testify/assert" -) - -func TestDateTime(t *testing.T) { - testTz, _ := time.LoadLocation("America/New_York") - defer test.MockVariableValue(&setting.DefaultUILocation, testTz)() - - refTimeStr := "2018-01-01T00:00:00Z" - refDateStr := "2018-01-01" - refTime, _ := time.Parse(time.RFC3339, refTimeStr) - refTimeStamp := TimeStamp(refTime.Unix()) - - assert.EqualValues(t, "-", DateTime("short", nil)) - assert.EqualValues(t, "-", DateTime("short", 0)) - assert.EqualValues(t, "-", DateTime("short", time.Time{})) - assert.EqualValues(t, "-", DateTime("short", TimeStamp(0))) - - actual := DateTime("short", "invalid") - assert.EqualValues(t, `invalid`, actual) - - actual = DateTime("short", refTimeStr) - assert.EqualValues(t, `2018-01-01T00:00:00Z`, actual) - - actual = DateTime("short", refTime) - assert.EqualValues(t, `2018-01-01`, actual) - - actual = DateTime("short", refDateStr) - assert.EqualValues(t, `2018-01-01`, actual) - - actual = DateTime("short", refTimeStamp) - assert.EqualValues(t, `2017-12-31`, actual) - - actual = DateTime("full", refTimeStamp) - assert.EqualValues(t, `2017-12-31 19:00:00 -05:00`, actual) -} diff --git a/modules/timeutil/executable.go b/modules/timeutil/executable.go index 57ae8b2a9d..7b30176df0 100644 --- a/modules/timeutil/executable.go +++ b/modules/timeutil/executable.go @@ -9,7 +9,7 @@ import ( "sync" "time" - "code.gitea.io/gitea/modules/log" + "forgejo.org/modules/log" ) var ( diff --git a/modules/timeutil/since.go b/modules/timeutil/since.go index dba42c793a..f296a2dc86 100644 --- a/modules/timeutil/since.go +++ b/modules/timeutil/since.go @@ -4,13 +4,10 @@ package timeutil import ( - "fmt" - "html/template" "strings" "time" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/translation" + "forgejo.org/modules/translation" ) // Seconds-based time units @@ -81,16 +78,11 @@ func computeTimeDiffFloor(diff int64, lang translation.Locale) (int64, string) { return diff, diffStr } -// MinutesToFriendly returns a user friendly string with number of minutes +// MinutesToFriendly returns a user-friendly string with number of minutes // converted to hours and minutes. func MinutesToFriendly(minutes int, lang translation.Locale) string { duration := time.Duration(minutes) * time.Minute - return TimeSincePro(time.Now().Add(-duration), lang) -} - -// TimeSincePro calculates the time interval and generate full user-friendly string. -func TimeSincePro(then time.Time, lang translation.Locale) string { - return timeSincePro(then, time.Now(), lang) + return timeSincePro(time.Now().Add(-duration), time.Now(), lang) } func timeSincePro(then, now time.Time, lang translation.Locale) string { @@ -104,42 +96,9 @@ func timeSincePro(then, now time.Time, lang translation.Locale) string { } var timeStr, diffStr string - for { - if diff == 0 { - break - } - + for diff != 0 { diff, diffStr = computeTimeDiffFloor(diff, lang) timeStr += ", " + diffStr } return strings.TrimPrefix(timeStr, ", ") } - -func timeSinceUnix(then, now time.Time, _ translation.Locale) template.HTML { - friendlyText := then.Format("2006-01-02 15:04:05 -07:00") - - // document: https://github.com/github/relative-time-element - attrs := `tense="past"` - isFuture := now.Before(then) - if isFuture { - attrs = `tense="future"` - } - - // declare data-tooltip-content attribute to switch from "title" tooltip to "tippy" tooltip - htm := fmt.Sprintf(`%s`, - attrs, then.Format(time.RFC3339), friendlyText) - return template.HTML(htm) -} - -// TimeSince renders relative time HTML given a time.Time -func TimeSince(then time.Time, lang translation.Locale) template.HTML { - if setting.UI.PreferredTimestampTense == "absolute" { - return DateTime("full", then) - } - return timeSinceUnix(then, time.Now(), lang) -} - -// TimeSinceUnix renders relative time HTML given a TimeStamp -func TimeSinceUnix(then TimeStamp, lang translation.Locale) template.HTML { - return TimeSince(then.AsLocalTime(), lang) -} diff --git a/modules/timeutil/since_test.go b/modules/timeutil/since_test.go index 40fefe8700..b47b2c76dd 100644 --- a/modules/timeutil/since_test.go +++ b/modules/timeutil/since_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/translation" + "forgejo.org/modules/setting" + "forgejo.org/modules/translation" "github.com/stretchr/testify/assert" ) diff --git a/modules/timeutil/timestamp.go b/modules/timeutil/timestamp.go index 27a80b6682..783ccba30b 100644 --- a/modules/timeutil/timestamp.go +++ b/modules/timeutil/timestamp.go @@ -6,7 +6,7 @@ package timeutil import ( "time" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" ) // TimeStamp defines a timestamp diff --git a/modules/timeutil/timestampnano.go b/modules/timeutil/timestampnano.go index 4a9f7955b9..e2e86b863f 100644 --- a/modules/timeutil/timestampnano.go +++ b/modules/timeutil/timestampnano.go @@ -6,7 +6,7 @@ package timeutil import ( "time" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" ) // TimeStampNano is for nano time in database, do not use it unless there is a real requirement. diff --git a/modules/translation/i18n/dummy.go b/modules/translation/i18n/dummy.go new file mode 100644 index 0000000000..9f1e682f11 --- /dev/null +++ b/modules/translation/i18n/dummy.go @@ -0,0 +1,67 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package i18n + +import ( + "fmt" + "html/template" + "reflect" + "slices" + "strings" +) + +type KeyLocale struct{} + +var _ Locale = (*KeyLocale)(nil) + +func (k *KeyLocale) Language() string { + return "dummy" +} + +// HasKey implements Locale. +func (k *KeyLocale) HasKey(trKey string) bool { + return true +} + +// TrHTML implements Locale. +func (k *KeyLocale) TrHTML(trKey string, trArgs ...any) template.HTML { + return template.HTML(k.TrString(trKey, PrepareArgsForHTML(trArgs...)...)) +} + +// TrString implements Locale. +func (k *KeyLocale) TrString(trKey string, trArgs ...any) string { + return FormatDummy(trKey, trArgs...) +} + +// TrPluralString implements Locale. +func (k *KeyLocale) TrPluralString(count any, trKey string, trArgs ...any) template.HTML { + return template.HTML(FormatDummy(trKey, PrepareArgsForHTML(trArgs...)...)) +} + +// TrPluralStringAllForms implements Locale. +func (k *KeyLocale) TrPluralStringAllForms(trKey string) ([]string, []string) { + return []string{trKey}, nil +} + +func FormatDummy(trKey string, args ...any) string { + if len(args) == 0 { + return fmt.Sprintf("(%s)", trKey) + } + + fmtArgs := make([]any, 0, len(args)+1) + fmtArgs = append(fmtArgs, trKey) + for _, arg := range args { + val := reflect.ValueOf(arg) + if val.Kind() == reflect.Slice { + for i := 0; i < val.Len(); i++ { + fmtArgs = append(fmtArgs, val.Index(i).Interface()) + } + } else { + fmtArgs = append(fmtArgs, arg) + } + } + + template := fmt.Sprintf("(%%s: %s)", strings.Join(slices.Repeat([]string{"%v"}, len(fmtArgs)-1), ", ")) + return fmt.Sprintf(template, fmtArgs...) +} diff --git a/modules/translation/i18n/dummy_test.go b/modules/translation/i18n/dummy_test.go new file mode 100644 index 0000000000..1df3d0c348 --- /dev/null +++ b/modules/translation/i18n/dummy_test.go @@ -0,0 +1,19 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package i18n_test + +import ( + "testing" + + "forgejo.org/modules/translation/i18n" + + "github.com/stretchr/testify/assert" +) + +func TestFormatDummy(t *testing.T) { + assert.Equal(t, "(admin.config.git_max_diff_lines)", i18n.FormatDummy("admin.config.git_max_diff_lines")) + assert.Equal(t, "(dashboard)", i18n.FormatDummy("dashboard")) + assert.Equal(t, "(branch.create_branch: main)", i18n.FormatDummy("branch.create_branch", "main")) + assert.Equal(t, "(test.test: a, 1, true)", i18n.FormatDummy("test.test", "a", 1, true)) +} diff --git a/modules/translation/i18n/errors.go b/modules/translation/i18n/errors.go index 7f64ccf908..63a5f48dfa 100644 --- a/modules/translation/i18n/errors.go +++ b/modules/translation/i18n/errors.go @@ -4,10 +4,12 @@ package i18n import ( - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/util" ) var ( - ErrLocaleAlreadyExist = util.SilentWrap{Message: "lang already exists", Err: util.ErrAlreadyExist} - ErrUncertainArguments = util.SilentWrap{Message: "arguments to i18n should not contain uncertain slices", Err: util.ErrInvalidArgument} + ErrLocaleAlreadyExist = util.SilentWrap{Message: "lang already exists", Err: util.ErrAlreadyExist} + ErrLocaleDoesNotExist = util.SilentWrap{Message: "lang does not exist", Err: util.ErrNotExist} + ErrTranslationDoesNotExist = util.SilentWrap{Message: "translation does not exist", Err: util.ErrNotExist} + ErrUncertainArguments = util.SilentWrap{Message: "arguments to i18n should not contain uncertain slices", Err: util.ErrInvalidArgument} ) diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go index 1555cd961e..10ed8ac199 100644 --- a/modules/translation/i18n/i18n.go +++ b/modules/translation/i18n/i18n.go @@ -8,15 +8,36 @@ import ( "io" ) +type ( + PluralFormIndex uint8 + PluralFormRule func(int64) PluralFormIndex +) + +const ( + PluralFormZero PluralFormIndex = iota + PluralFormOne + PluralFormTwo + PluralFormFew + PluralFormMany + PluralFormOther +) + var DefaultLocales = NewLocaleStore() type Locale interface { + Language() string // TrString translates a given key and arguments for a language TrString(trKey string, trArgs ...any) string + // TrPluralString translates a given pluralized key and arguments for a language. + // This function returns an error if new-style support for the given key is not available. + TrPluralString(count any, trKey string, trArgs ...any) template.HTML // TrHTML translates a given key and arguments for a language, string arguments are escaped to HTML TrHTML(trKey string, trArgs ...any) template.HTML // HasKey reports if a locale has a translation for a given key HasKey(trKey string) bool + // TrPluralStringAllForms returns all plural form variants for a given string, and also + // the fallbacks for the default language if the translation is incomplete. + TrPluralStringAllForms(trKey string) ([]string, []string) } // LocaleStore provides the functions common to all locale stores @@ -25,14 +46,18 @@ type LocaleStore interface { // SetDefaultLang sets the default language to fall back to SetDefaultLang(lang string) + // GetDefaultLang returns the name of the default language to fall back to + GetDefaultLang() string // ListLangNameDesc provides paired slices of language names to descriptors ListLangNameDesc() (names, desc []string) // Locale return the locale for the provided language or the default language if not found Locale(langName string) (Locale, bool) // HasLang returns whether a given language is present in the store HasLang(langName string) bool - // AddLocaleByIni adds a new language to the store - AddLocaleByIni(langName, langDesc string, source, moreSource []byte) error + // AddLocaleByIni adds a new old-style language to the store + AddLocaleByIni(langName, langDesc string, pluralRule PluralFormRule, usedPluralForms []PluralFormIndex, source, moreSource []byte) error + // AddLocaleByJSON adds new-style content to an existing language to the store + AddToLocaleFromJSON(langName string, source []byte) error } // ResetDefaultLocales resets the current default locales diff --git a/modules/translation/i18n/i18n_test.go b/modules/translation/i18n/i18n_test.go index 244f6ffbb3..ac086d75d9 100644 --- a/modules/translation/i18n/i18n_test.go +++ b/modules/translation/i18n/i18n_test.go @@ -12,6 +12,31 @@ import ( "github.com/stretchr/testify/require" ) +var MockPluralRule PluralFormRule = func(n int64) PluralFormIndex { + if n == 0 { + return PluralFormZero + } + if n == 1 { + return PluralFormOne + } + if n >= 2 && n <= 4 { + return PluralFormFew + } + return PluralFormOther +} + +var MockPluralRuleEnglish PluralFormRule = func(n int64) PluralFormIndex { + if n == 1 { + return PluralFormOne + } + return PluralFormOther +} + +var ( + UsedPluralFormsEnglish = []PluralFormIndex{PluralFormOne, PluralFormOther} + UsedPluralFormsMock = []PluralFormIndex{PluralFormZero, PluralFormOne, PluralFormFew, PluralFormOther} +) + func TestLocaleStore(t *testing.T) { testData1 := []byte(` .dot.name = Dot Name @@ -27,11 +52,48 @@ fmt = %[2]s %[1]s [section] sub = Changed Sub String +commits = fallback value for commits +`) + + testDataJSON2 := []byte(` +{ + "section.json": "the JSON is %s", + "section.commits": { + "one": "one %d commit", + "few": "some %d commits", + "other": "lots of %d commits" + }, + "section.incomplete": { + "few": "some %d objects (translated)" + }, + "nested": { + "outer": { + "inner": { + "json": "Hello World", + "issue": { + "one": "one %d issue", + "few": "some %d issues", + "other": "lots of %d issues" + } + } + } + } +} +`) + testDataJSON1 := []byte(` +{ + "section.incomplete": { + "one": "[untranslated] some %d object", + "other": "[untranslated] some %d objects" + } +} `) ls := NewLocaleStore() - require.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1, nil)) - require.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", testData2, nil)) + require.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", MockPluralRuleEnglish, UsedPluralFormsEnglish, testData1, nil)) + require.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", MockPluralRule, UsedPluralFormsMock, testData2, nil)) + require.NoError(t, ls.AddToLocaleFromJSON("lang1", testDataJSON1)) + require.NoError(t, ls.AddToLocaleFromJSON("lang2", testDataJSON2)) ls.SetDefaultLang("lang1") lang1, _ := ls.Locale("lang1") @@ -56,12 +118,60 @@ sub = Changed Sub String result2 := lang2.TrHTML("section.mixed", "a&b") assert.EqualValues(t, `test value; a&b`, result2) + result = lang2.TrString("section.json", "valid") + assert.Equal(t, "the JSON is valid", result) + + result = lang2.TrString("nested.outer.inner.json") + assert.Equal(t, "Hello World", result) + + result = lang2.TrString("section.commits") + assert.Equal(t, "lots of %d commits", result) + + result2 = lang2.TrPluralString(1, "section.commits", 1) + assert.EqualValues(t, "one 1 commit", result2) + + result2 = lang2.TrPluralString(3, "section.commits", 3) + assert.EqualValues(t, "some 3 commits", result2) + + result2 = lang2.TrPluralString(8, "section.commits", 8) + assert.EqualValues(t, "lots of 8 commits", result2) + + result2 = lang2.TrPluralString(0, "section.commits") + assert.EqualValues(t, "section.commits", result2) + + result2 = lang2.TrPluralString(1, "nested.outer.inner.issue", 1) + assert.EqualValues(t, "one 1 issue", result2) + + result2 = lang2.TrPluralString(3, "nested.outer.inner.issue", 3) + assert.EqualValues(t, "some 3 issues", result2) + + result2 = lang2.TrPluralString(9, "nested.outer.inner.issue", 9) + assert.EqualValues(t, "lots of 9 issues", result2) + + result2 = lang2.TrPluralString(3, "section.incomplete", 3) + assert.EqualValues(t, "some 3 objects (translated)", result2) + + result2 = lang2.TrPluralString(1, "section.incomplete", 1) + assert.EqualValues(t, "[untranslated] some 1 object", result2) + + result2 = lang2.TrPluralString(7, "section.incomplete", 7) + assert.EqualValues(t, "[untranslated] some 7 objects", result2) + langs, descs := ls.ListLangNameDesc() assert.ElementsMatch(t, []string{"lang1", "lang2"}, langs) assert.ElementsMatch(t, []string{"Lang1", "Lang2"}, descs) - found := lang1.HasKey("no-such") + // Test HasKey for JSON + found := lang2.HasKey("section.json") + assert.True(t, found) + + // Test HasKey for INI + found = lang2.HasKey("section.sub") + assert.True(t, found) + + found = lang1.HasKey("no-such") assert.False(t, found) + assert.Equal(t, "no-such", lang1.TrString("no-such")) require.NoError(t, ls.Close()) } @@ -77,7 +187,7 @@ c=22 `) ls := NewLocaleStore() - require.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1, testData2)) + require.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", MockPluralRule, UsedPluralFormsMock, testData1, testData2)) lang1, _ := ls.Locale("lang1") assert.Equal(t, "11", lang1.TrString("a")) assert.Equal(t, "21", lang1.TrString("b")) @@ -118,7 +228,7 @@ func (e *errorPointerReceiver) Error() string { func TestLocaleWithTemplate(t *testing.T) { ls := NewLocaleStore() - require.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", []byte(`key=%s`), nil)) + require.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", MockPluralRule, UsedPluralFormsMock, []byte(`key=%s`), nil)) lang1, _ := ls.Locale("lang1") tmpl := template.New("test").Funcs(template.FuncMap{"tr": lang1.TrHTML}) @@ -181,7 +291,7 @@ func TestLocaleStoreQuirks(t *testing.T) { for _, testData := range testDataList { ls := NewLocaleStore() - err := ls.AddLocaleByIni("lang1", "Lang1", []byte("a="+testData.in), nil) + err := ls.AddLocaleByIni("lang1", "Lang1", nil, nil, []byte("a="+testData.in), nil) lang1, _ := ls.Locale("lang1") require.NoError(t, err, testData.hint) assert.Equal(t, testData.out, lang1.TrString("a"), testData.hint) diff --git a/modules/translation/i18n/localestore.go b/modules/translation/i18n/localestore.go index 0e6ddab401..fc27c75d13 100644 --- a/modules/translation/i18n/localestore.go +++ b/modules/translation/i18n/localestore.go @@ -1,4 +1,5 @@ // Copyright 2022 The Gitea Authors. All rights reserved. +// Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package i18n @@ -8,8 +9,10 @@ import ( "html/template" "slices" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" + "forgejo.org/modules/translation/localeiter" + "forgejo.org/modules/util" ) // This file implements the static LocaleStore that will not watch for changes @@ -18,6 +21,10 @@ type locale struct { store *localeStore langName string idxToMsgMap map[int]string // the map idx is generated by store's trKeyToIdxMap + + newStyleMessages map[string]string + pluralRule PluralFormRule + usedPluralForms []PluralFormIndex } var _ Locale = (*locale)(nil) @@ -38,8 +45,19 @@ func NewLocaleStore() LocaleStore { return &localeStore{localeMap: make(map[string]*locale), trKeyToIdxMap: make(map[string]int)} } +const ( + PluralFormSeparator string = "\036" +) + +// A note about pluralization rules. +// go-i18n supports plural rules in theory. +// In practice, it relies on another library that hardcodes a list of common languages +// and their plural rules, and does not support languages not hardcoded there. +// So we pretend that all languages are English and use our own function to extract +// the correct plural form for a given count and language. + // AddLocaleByIni adds locale by ini into the store -func (store *localeStore) AddLocaleByIni(langName, langDesc string, source, moreSource []byte) error { +func (store *localeStore) AddLocaleByIni(langName, langDesc string, pluralRule PluralFormRule, usedPluralForms []PluralFormIndex, source, moreSource []byte) error { if _, ok := store.localeMap[langName]; ok { return ErrLocaleAlreadyExist } @@ -47,7 +65,7 @@ func (store *localeStore) AddLocaleByIni(langName, langDesc string, source, more store.langNames = append(store.langNames, langName) store.langDescs = append(store.langDescs, langDesc) - l := &locale{store: store, langName: langName, idxToMsgMap: make(map[int]string)} + l := &locale{store: store, langName: langName, idxToMsgMap: make(map[int]string), pluralRule: pluralRule, usedPluralForms: usedPluralForms, newStyleMessages: make(map[string]string)} store.localeMap[l.langName] = l iniFile, err := setting.NewConfigProviderForLocale(source, moreSource) @@ -78,6 +96,68 @@ func (store *localeStore) AddLocaleByIni(langName, langDesc string, source, more return nil } +func (store *localeStore) AddToLocaleFromJSON(langName string, source []byte) error { + locale, ok := store.localeMap[langName] + if !ok { + return ErrLocaleDoesNotExist + } + + return localeiter.IterateMessagesNextContent(source, func(key, pluralForm, value string) error { + msgKey := key + if pluralForm != "" { + msgKey = key + PluralFormSeparator + pluralForm + } + locale.newStyleMessages[msgKey] = value + return nil + }) +} + +func (l *locale) LookupNewStyleMessage(trKey string) string { + if msg, ok := l.newStyleMessages[trKey]; ok { + return msg + } + return "" +} + +func (l *locale) LookupPluralByCount(trKey string, count any) string { + n, err := util.ToInt64(count) + if err != nil { + log.Error("Invalid plural count '%s'", count) + return "" + } + + pluralForm := l.pluralRule(n) + return l.LookupPluralByForm(trKey, pluralForm) +} + +func (l *locale) LookupPluralByForm(trKey string, pluralForm PluralFormIndex) string { + suffix := "" + switch pluralForm { + case PluralFormZero: + suffix = PluralFormSeparator + "zero" + case PluralFormOne: + suffix = PluralFormSeparator + "one" + case PluralFormTwo: + suffix = PluralFormSeparator + "two" + case PluralFormFew: + suffix = PluralFormSeparator + "few" + case PluralFormMany: + suffix = PluralFormSeparator + "many" + case PluralFormOther: + // No suffix for the "other" string. + default: + log.Error("Invalid plural form index %d", pluralForm) + return "" + } + + if result, ok := l.newStyleMessages[trKey+suffix]; ok { + return result + } + + log.Error("Missing translation for plural form %s", suffix) + return "" +} + func (store *localeStore) HasLang(langName string) bool { _, ok := store.localeMap[langName] return ok @@ -92,6 +172,10 @@ func (store *localeStore) SetDefaultLang(lang string) { store.defaultLang = lang } +func (store *localeStore) GetDefaultLang() string { + return store.defaultLang +} + // Locale returns the locale for the lang or the default language func (store *localeStore) Locale(lang string) (Locale, bool) { l, found := store.localeMap[lang] @@ -110,25 +194,45 @@ func (store *localeStore) Close() error { return nil } +func (l *locale) Language() string { + return l.langName +} + func (l *locale) TrString(trKey string, trArgs ...any) string { format := trKey - idx, ok := l.store.trKeyToIdxMap[trKey] - found := false - if ok { - if msg, ok := l.idxToMsgMap[idx]; ok { - format = msg // use the found translation - found = true - } else if def, ok := l.store.localeMap[l.store.defaultLang]; ok { - // try to use default locale's translation - if msg, ok := def.idxToMsgMap[idx]; ok { - format = msg + if msg := l.LookupNewStyleMessage(trKey); msg != "" { + format = msg + } else { + // First fallback: old-style translation + idx, foundIndex := l.store.trKeyToIdxMap[trKey] + found := false + if foundIndex { + if msg, ok := l.idxToMsgMap[idx]; ok { + format = msg // use the found translation found = true } } - } - if !found { - log.Error("Missing translation %q", trKey) + + if !found { + // Second fallback: new-style default language + if defaultLang, ok := l.store.localeMap[l.store.defaultLang]; ok { + if msg := defaultLang.LookupNewStyleMessage(trKey); msg != "" { + format = msg + found = true + } else if foundIndex { + // Third fallback: old-style default language + if msg, ok := defaultLang.idxToMsgMap[idx]; ok { + format = msg + found = true + } + } + } + + if !found { + log.Error("Missing translation %q", trKey) + } + } } msg, err := Format(format, trArgs...) @@ -138,7 +242,7 @@ func (l *locale) TrString(trKey string, trArgs ...any) string { return msg } -func (l *locale) TrHTML(trKey string, trArgs ...any) template.HTML { +func PrepareArgsForHTML(trArgs ...any) []any { args := slices.Clone(trArgs) for i, v := range args { switch v := v.(type) { @@ -152,11 +256,68 @@ func (l *locale) TrHTML(trKey string, trArgs ...any) template.HTML { args[i] = template.HTMLEscapeString(fmt.Sprint(v)) } } - return template.HTML(l.TrString(trKey, args...)) + return args +} + +func (l *locale) TrHTML(trKey string, trArgs ...any) template.HTML { + return template.HTML(l.TrString(trKey, PrepareArgsForHTML(trArgs...)...)) +} + +func (l *locale) TrPluralString(count any, trKey string, trArgs ...any) template.HTML { + message := l.LookupPluralByCount(trKey, count) + + if message == "" { + if defaultLang, ok := l.store.localeMap[l.store.defaultLang]; ok { + message = defaultLang.LookupPluralByCount(trKey, count) + } + if message == "" { + message = trKey + } + } + + message, err := Format(message, PrepareArgsForHTML(trArgs...)...) + if err != nil { + log.Error("Error whilst formatting %q in %s: %v", trKey, l.langName, err) + } + return template.HTML(message) +} + +func (l *locale) TrPluralStringAllForms(trKey string) ([]string, []string) { + defaultLang, hasDefaultLang := l.store.localeMap[l.store.defaultLang] + + var fallback []string + fallback = nil + + result := make([]string, len(l.usedPluralForms)) + allPresent := true + + for i, form := range l.usedPluralForms { + result[i] = l.LookupPluralByForm(trKey, form) + if result[i] == "" { + allPresent = false + } + } + + if !allPresent { + if hasDefaultLang { + fallback = make([]string, len(defaultLang.usedPluralForms)) + for i, form := range defaultLang.usedPluralForms { + fallback[i] = defaultLang.LookupPluralByForm(trKey, form) + } + } else { + log.Error("Plural set for '%s' is incomplete and no fallback language is set.", trKey) + } + } + + return result, fallback } // HasKey returns whether a key is present in this locale or not func (l *locale) HasKey(trKey string) bool { + _, ok := l.newStyleMessages[trKey] + if ok { + return true + } idx, ok := l.store.trKeyToIdxMap[trKey] if !ok { return false diff --git a/modules/translation/localeiter/utils.go b/modules/translation/localeiter/utils.go new file mode 100644 index 0000000000..de398258e2 --- /dev/null +++ b/modules/translation/localeiter/utils.go @@ -0,0 +1,89 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +// extracted from `/build/lint-locale.go`, `/build/lint-locale-usage.go` + +package localeiter + +import ( + "encoding/json" //nolint:depguard + "fmt" + + "forgejo.org/modules/setting" +) + +func IterateMessagesContent(localeContent []byte, onMsgid func(string, string) error) error { + cfg, err := setting.NewConfigProviderForLocale(localeContent) + if err != nil { + return err + } + + for _, section := range cfg.Sections() { + for _, key := range section.Keys() { + var trKey string + // see https://codeberg.org/forgejo/discussions/issues/104 + // https://github.com/WeblateOrg/weblate/issues/10831 + // for an explanation of why "common" is an alternative + if section.Name() == "" || section.Name() == "DEFAULT" || section.Name() == "common" { + trKey = key.Name() + } else { + trKey = section.Name() + "." + key.Name() + } + if err := onMsgid(trKey, key.Value()); err != nil { + return err + } + } + } + + return nil +} + +func iterateMessagesNextInner(onMsgid func(string, string, string) error, data map[string]any, trKey string) error { + for key, value := range data { + fullKey := key + if trKey != "" { + fullKey = trKey + "." + key + } + switch value := value.(type) { + case string: + // Check whether we are adding a plural form to the parent object, or a new nested JSON object. + realKey := trKey + pluralSuffix := "" + + switch key { + case "zero", "one", "two", "few", "many": + pluralSuffix = key + case "other": + // do nothing + default: + realKey = fullKey + } + + if err := onMsgid(realKey, pluralSuffix, value); err != nil { + return err + } + + case map[string]any: + if err := iterateMessagesNextInner(onMsgid, value, fullKey); err != nil { + return err + } + + case nil: + // do nothing + + default: + return fmt.Errorf("Unexpected JSON type: %s - %T", fullKey, value) + } + } + + return nil +} + +func IterateMessagesNextContent(localeContent []byte, onMsgid func(string, string, string) error) error { + var localeData map[string]any + if err := json.Unmarshal(localeContent, &localeData); err != nil { + return err + } + return iterateMessagesNextInner(onMsgid, localeData, "") +} diff --git a/modules/translation/mock.go b/modules/translation/mock.go index fe3a1502ea..fc1c6a83fd 100644 --- a/modules/translation/mock.go +++ b/modules/translation/mock.go @@ -6,11 +6,15 @@ package translation import ( "fmt" "html/template" + + "forgejo.org/modules/translation/i18n" ) -// MockLocale provides a mocked locale without any translations +// MockLocale provides a mocked locale without any translations, other than those inserted into MockTranslations by a testcase type MockLocale struct { Lang, LangName string // these fields are used directly in templates: ctx.Locale.Lang + + MockTranslations map[string]string } var _ Locale = (*MockLocale)(nil) @@ -20,21 +24,37 @@ func (l MockLocale) Language() string { } func (l MockLocale) TrString(s string, _ ...any) string { + if val, ok := l.MockTranslations[s]; ok { + return val + } return s } func (l MockLocale) Tr(s string, a ...any) template.HTML { - return template.HTML(s) + return template.HTML(l.TrString(s)) } func (l MockLocale) TrN(cnt any, key1, keyN string, args ...any) template.HTML { return template.HTML(key1) } +func (l MockLocale) TrPluralString(count any, trKey string, trArgs ...any) template.HTML { + return template.HTML(trKey) +} + +// TrPluralStringAllForms implements Locale. +func (l MockLocale) TrPluralStringAllForms(trKey string) ([]string, []string) { + return []string{l.TrString(trKey + i18n.PluralFormSeparator + "one"), l.TrString(trKey + i18n.PluralFormSeparator + "other")}, nil +} + func (l MockLocale) TrSize(s int64) ReadableSize { return ReadableSize{fmt.Sprint(s), ""} } +func (l MockLocale) HasKey(key string) bool { + return true +} + func (l MockLocale) PrettyNumber(v any) string { return fmt.Sprint(v) } diff --git a/modules/translation/plural_rules.go b/modules/translation/plural_rules.go new file mode 100644 index 0000000000..59665da255 --- /dev/null +++ b/modules/translation/plural_rules.go @@ -0,0 +1,284 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +// Some useful links: +// https://www.unicode.org/cldr/charts/46/supplemental/language_plural_rules.html +// https://translate.codeberg.org/languages/$LANGUAGE_CODE/#information +// https://github.com/WeblateOrg/language-data/blob/main/languages.csv +// Note that in some cases there is ambiguity about the correct form for a given language. In this case, ask the locale's translators. + +package translation + +import ( + "strings" + + "forgejo.org/modules/log" + "forgejo.org/modules/translation/i18n" +) + +// The constants refer to indices below in `PluralRules` and also in i18n.js, keep them in sync! +const ( + PluralRuleDefault = 0 + PluralRuleBengali = 1 + PluralRuleIcelandic = 2 + PluralRuleFilipino = 3 + PluralRuleOneForm = 4 + PluralRuleCzech = 5 + PluralRuleRussian = 6 + PluralRulePolish = 7 + PluralRuleLatvian = 8 + PluralRuleLithuanian = 9 + PluralRuleFrench = 10 + PluralRuleCatalan = 11 + PluralRuleSlovenian = 12 + PluralRuleArabic = 13 +) + +func GetPluralRuleImpl(langName string) int { + // First, check for languages with country-specific plural rules. + switch langName { + case "pt-BR": + return PluralRuleFrench + + case "pt-PT": + return PluralRuleCatalan + + default: + break + } + + // Remove the country portion of the locale name. + langName = strings.Split(strings.Split(langName, "_")[0], "-")[0] + + // When adding a new language not in the list, add its plural rule definition here. + switch langName { + case "en", "aa", "ab", "abr", "ada", "ae", "aeb", "af", "afh", "aii", "ain", "akk", "ale", "aln", "alt", "ami", "an", "ang", "anp", "apc", "arc", "arp", "arq", "arw", "arz", "asa", "ast", "av", "avk", "awa", "ayc", "az", "azb", "ba", "bal", "ban", "bar", "bas", "bbc", "bci", "bej", "bem", "ber", "bew", "bez", "bg", "bgc", "bgn", "bhb", "bhi", "bi", "bik", "bin", "bjj", "bjn", "bla", "bnt", "bqi", "bra", "brb", "brh", "brx", "bua", "bug", "bum", "byn", "cad", "cak", "car", "ce", "cgg", "ch", "chb", "chg", "chk", "chm", "chn", "cho", "chp", "chr", "chy", "ckb", "co", "cop", "cpe", "cpf", "cr", "crp", "cu", "cv", "da", "dak", "dar", "dcc", "de", "del", "den", "dgr", "din", "dje", "dnj", "dnk", "dru", "dry", "dua", "dum", "dv", "dyu", "ee", "efi", "egl", "egy", "eka", "el", "elx", "enm", "eo", "et", "eu", "ewo", "ext", "fan", "fat", "fbl", "ffm", "fi", "fj", "fo", "fon", "frk", "frm", "fro", "frr", "frs", "fuq", "fur", "fuv", "fvr", "fy", "gaa", "gay", "gba", "gbm", "gez", "gil", "gl", "glk", "gmh", "gn", "goh", "gom", "gon", "gor", "got", "grb", "gsw", "guc", "gum", "gur", "guz", "gwi", "ha", "hai", "haw", "haz", "hil", "hit", "hmn", "hnd", "hne", "hno", "ho", "hoc", "hoj", "hrx", "ht", "hu", "hup", "hus", "hz", "ia", "iba", "ibb", "ie", "ik", "ilo", "inh", "io", "jam", "jgo", "jmc", "jpr", "jrb", "ka", "kaa", "kac", "kaj", "kam", "kaw", "kbd", "kcg", "kfr", "kfy", "kg", "kha", "khn", "kho", "ki", "kj", "kk", "kkj", "kl", "kln", "kmb", "kmr", "kok", "kpe", "kr", "krc", "kri", "krl", "kru", "ks", "ksb", "ku", "kum", "kut", "kv", "kxm", "ky", "la", "lad", "laj", "lam", "lb", "lez", "lfn", "lg", "li", "lij", "ljp", "lki", "lmn", "lmo", "lol", "loz", "lrc", "lu", "lua", "lui", "lun", "luo", "lus", "luy", "luz", "mad", "mag", "mai", "mak", "man", "mas", "mdf", "mdh", "mdr", "men", "mer", "mfa", "mga", "mgh", "mgo", "mh", "mhr", "mic", "min", "mjw", "ml", "mn", "mnc", "mni", "mnw", "moe", "moh", "mos", "mr", "mrh", "mtr", "mus", "mwk", "mwl", "mwr", "mxc", "myv", "myx", "mzn", "na", "nah", "nap", "nb", "nd", "ndc", "nds", "ne", "new", "ng", "ngl", "nia", "nij", "niu", "nl", "nn", "nnh", "nod", "noe", "nog", "non", "nr", "nuk", "nv", "nwc", "ny", "nym", "nyn", "nyo", "nzi", "oj", "om", "or", "os", "ota", "otk", "ovd", "pag", "pal", "pam", "pap", "pau", "pbb", "pdt", "peo", "phn", "pi", "pms", "pon", "pro", "ps", "pwn", "qu", "quc", "qug", "qya", "raj", "rap", "rar", "rcf", "rej", "rhg", "rif", "rkt", "rm", "rmt", "rn", "rng", "rof", "rom", "rue", "rup", "rw", "rwk", "sad", "sai", "sam", "saq", "sas", "sc", "sck", "sco", "sd", "sdh", "sef", "seh", "sel", "sga", "sgn", "sgs", "shn", "sid", "sjd", "skr", "sm", "sml", "sn", "snk", "so", "sog", "sou", "sq", "srn", "srr", "ss", "ssy", "st", "suk", "sus", "sux", "sv", "sw", "swg", "swv", "sxu", "syc", "syl", "syr", "szy", "ta", "tay", "tcy", "te", "tem", "teo", "ter", "tet", "tig", "tiv", "tk", "tkl", "tli", "tly", "tmh", "tn", "tog", "tr", "trv", "ts", "tsg", "tsi", "tsj", "tts", "tum", "tvl", "tw", "ty", "tyv", "tzj", "tzl", "udm", "ug", "uga", "umb", "und", "unr", "ur", "uz", "vai", "ve", "vls", "vmf", "vmw", "vo", "vot", "vro", "vun", "wae", "wal", "war", "was", "wbq", "wbr", "wep", "wtm", "xal", "xh", "xnr", "xog", "yao", "yap", "yi", "yua", "za", "zap", "zbl", "zen", "zgh", "zun", "zza": + return PluralRuleDefault + + case "ach", "ady", "ak", "am", "arn", "as", "bh", "bho", "bn", "csw", "doi", "fa", "ff", "frc", "frp", "gu", "gug", "gun", "guw", "hi", "hy", "kab", "kn", "ln", "mfe", "mg", "mi", "mia", "nso", "oc", "pa", "pcm", "pt", "qdt", "qtp", "si", "tg", "ti", "wa", "zu": + return PluralRuleBengali + + case "is": + return PluralRuleIcelandic + + case "fil": + return PluralRuleFilipino + + case "ace", "ay", "bm", "bo", "cdo", "cpx", "crh", "dz", "gan", "hak", "hnj", "hsn", "id", "ig", "ii", "ja", "jbo", "jv", "kde", "kea", "km", "ko", "kos", "lkt", "lo", "lzh", "ms", "my", "nan", "nqo", "osa", "sah", "ses", "sg", "son", "su", "th", "tlh", "to", "tok", "tpi", "tt", "vi", "wo", "wuu", "yo", "yue", "zh": + return PluralRuleOneForm + + case "cpp", "cs", "sk": + return PluralRuleCzech + + case "be", "bs", "cnr", "hr", "ru", "sr", "uk", "wen": + return PluralRuleRussian + + case "csb", "pl", "szl": + return PluralRulePolish + + case "lv", "prg": + return PluralRuleLatvian + + case "lt": + return PluralRuleLithuanian + + case "fr": + return PluralRuleFrench + + case "ca", "es", "it": + return PluralRuleCatalan + + case "sl": + return PluralRuleSlovenian + + case "ar": + return PluralRuleArabic + + default: + break + } + + log.Error("No plural rule defined for language %s", langName) + return PluralRuleDefault +} + +var PluralRules = []i18n.PluralFormRule{ + // [ 0] Common 2-form, e.g. English, German + func(n int64) i18n.PluralFormIndex { + if n != 1 { + return i18n.PluralFormOther + } + return i18n.PluralFormOne + }, + + // [ 1] Bengali + func(n int64) i18n.PluralFormIndex { + if n > 1 { + return i18n.PluralFormOther + } + return i18n.PluralFormOne + }, + + // [ 2] Icelandic + func(n int64) i18n.PluralFormIndex { + if n%10 != 1 || n%100 == 11 { + return i18n.PluralFormOther + } + return i18n.PluralFormOne + }, + + // [ 3] Filipino + func(n int64) i18n.PluralFormIndex { + if n != 1 && n != 2 && n != 3 && (n%10 == 4 || n%10 == 6 || n%10 == 9) { + return i18n.PluralFormOther + } + return i18n.PluralFormOne + }, + + // [ 4] OneForm + func(n int64) i18n.PluralFormIndex { + return i18n.PluralFormOther + }, + + // [ 5] Czech + func(n int64) i18n.PluralFormIndex { + if n == 1 { + return i18n.PluralFormOne + } + if n >= 2 && n <= 4 { + return i18n.PluralFormFew + } + return i18n.PluralFormOther + }, + + // [ 6] Russian + func(n int64) i18n.PluralFormIndex { + if n%10 == 1 && n%100 != 11 { + return i18n.PluralFormOne + } + if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) { + return i18n.PluralFormFew + } + return i18n.PluralFormMany + }, + + // [ 7] Polish + func(n int64) i18n.PluralFormIndex { + if n == 1 { + return i18n.PluralFormOne + } + if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) { + return i18n.PluralFormFew + } + return i18n.PluralFormMany + }, + + // [ 8] Latvian + func(n int64) i18n.PluralFormIndex { + if n%10 == 0 || n%100 >= 11 && n%100 <= 19 { + return i18n.PluralFormZero + } + if n%10 == 1 && n%100 != 11 { + return i18n.PluralFormOne + } + return i18n.PluralFormOther + }, + + // [ 9] Lithuanian + func(n int64) i18n.PluralFormIndex { + if n%10 == 1 && (n%100 < 11 || n%100 > 19) { + return i18n.PluralFormOne + } + if n%10 >= 2 && n%10 <= 9 && (n%100 < 11 || n%100 > 19) { + return i18n.PluralFormFew + } + return i18n.PluralFormMany + }, + + // [10] French + func(n int64) i18n.PluralFormIndex { + if n == 0 || n == 1 { + return i18n.PluralFormOne + } + if n != 0 && n%1000000 == 0 { + return i18n.PluralFormMany + } + return i18n.PluralFormOther + }, + + // [11] Catalan + func(n int64) i18n.PluralFormIndex { + if n == 1 { + return i18n.PluralFormOne + } + if n != 0 && n%1000000 == 0 { + return i18n.PluralFormMany + } + return i18n.PluralFormOther + }, + + // [12] Slovenian + func(n int64) i18n.PluralFormIndex { + if n%100 == 1 { + return i18n.PluralFormOne + } + if n%100 == 2 { + return i18n.PluralFormTwo + } + if n%100 == 3 || n%100 == 4 { + return i18n.PluralFormFew + } + return i18n.PluralFormOther + }, + + // [13] Arabic + func(n int64) i18n.PluralFormIndex { + if n == 0 { + return i18n.PluralFormZero + } + if n == 1 { + return i18n.PluralFormOne + } + if n == 2 { + return i18n.PluralFormTwo + } + if n%100 >= 3 && n%100 <= 10 { + return i18n.PluralFormFew + } + if n%100 >= 11 { + return i18n.PluralFormMany + } + return i18n.PluralFormOther + }, +} + +var UsedPluralForms = [][]i18n.PluralFormIndex{ + // [ 0] Common 2-form, e.g. English, German + {i18n.PluralFormOne, i18n.PluralFormOther}, + // [ 1] Bengali + {i18n.PluralFormOne, i18n.PluralFormOther}, + // [ 2] Icelandic + {i18n.PluralFormOne, i18n.PluralFormOther}, + // [ 3] Filipino + {i18n.PluralFormOne, i18n.PluralFormOther}, + // [ 4] OneForm + {i18n.PluralFormOther}, + // [ 5] Czech + {i18n.PluralFormOne, i18n.PluralFormFew, i18n.PluralFormOther}, + // [ 6] Russian + {i18n.PluralFormOne, i18n.PluralFormFew, i18n.PluralFormMany}, + // [ 7] Polish + {i18n.PluralFormOne, i18n.PluralFormFew, i18n.PluralFormOther}, + // [ 8] Latvian + {i18n.PluralFormZero, i18n.PluralFormOne, i18n.PluralFormOther}, + // [ 9] Lithuanian + {i18n.PluralFormOne, i18n.PluralFormFew, i18n.PluralFormMany}, + // [10] French + {i18n.PluralFormOne, i18n.PluralFormMany, i18n.PluralFormOther}, + // [11] Catalan + {i18n.PluralFormOne, i18n.PluralFormMany, i18n.PluralFormOther}, + // [12] Slovenian + {i18n.PluralFormOne, i18n.PluralFormTwo, i18n.PluralFormFew, i18n.PluralFormOther}, + // [13] Arabic + {i18n.PluralFormZero, i18n.PluralFormOne, i18n.PluralFormTwo, i18n.PluralFormFew, i18n.PluralFormMany, i18n.PluralFormOther}, +} diff --git a/modules/translation/translation.go b/modules/translation/translation.go index 16eb55e28e..17c7cc068b 100644 --- a/modules/translation/translation.go +++ b/modules/translation/translation.go @@ -10,11 +10,11 @@ import ( "strings" "sync" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/options" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/translation/i18n" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/log" + "forgejo.org/modules/options" + "forgejo.org/modules/setting" + "forgejo.org/modules/translation/i18n" + "forgejo.org/modules/util" "github.com/dustin/go-humanize" "golang.org/x/text/language" @@ -27,16 +27,27 @@ type contextKey struct{} var ContextKey any = &contextKey{} // Locale represents an interface to translation +// +// If this gets modified, remember to also adjust +// build/lint-locale-usage/lint-locale-usage.go's InitLocaleTrFunctions(), +// which requires to know in what argument positions `trKey`'s are given. type Locale interface { Language() string TrString(string, ...any) string Tr(key string, args ...any) template.HTML + // New-style pluralized strings + TrPluralString(count any, trKey string, trArgs ...any) template.HTML + // Old-style pseudo-pluralized strings, deprecated TrN(cnt any, key1, keyN string, args ...any) template.HTML TrSize(size int64) ReadableSize + HasKey(trKey string) bool + PrettyNumber(v any) string + + TrPluralStringAllForms(trKey string) ([]string, []string) } // LangType represents a lang type @@ -99,9 +110,19 @@ func InitLocales(ctx context.Context) { } } + pluralRuleIndex := GetPluralRuleImpl(setting.Langs[i]) key := "locale_" + setting.Langs[i] + ".ini" - if err = i18n.DefaultLocales.AddLocaleByIni(setting.Langs[i], setting.Names[i], localeDataBase, localeData[key]); err != nil { - log.Error("Failed to set messages to %s: %v", setting.Langs[i], err) + if err = i18n.DefaultLocales.AddLocaleByIni(setting.Langs[i], setting.Names[i], PluralRules[pluralRuleIndex], UsedPluralForms[pluralRuleIndex], localeDataBase, localeData[key]); err != nil { + log.Error("Failed to set old-style messages to %s: %v", setting.Langs[i], err) + } + + key = "locale_next/locale_" + setting.Langs[i] + ".json" + if bytes, err := options.AssetFS().ReadFile(key); err == nil { + if err = i18n.DefaultLocales.AddToLocaleFromJSON(setting.Langs[i], bytes); err != nil { + log.Error("Failed to add new-style messages to %s: %v", setting.Langs[i], err) + } + } else { + log.Error("Failed to open new-style messages for %s: %v", setting.Langs[i], err) } } if len(setting.Langs) != 0 { @@ -160,6 +181,16 @@ func NewLocale(lang string) Locale { defer lock.RUnlock() } + if lang == "dummy" { + l := &locale{ + Locale: &i18n.KeyLocale{}, + Lang: lang, + LangName: lang, + msgPrinter: message.NewPrinter(language.English), + } + return l + } + langName := "unknown" if l, ok := allLangMap[lang]; ok { langName = l.Name @@ -296,6 +327,14 @@ func (l *locale) PrettyNumber(v any) string { return l.msgPrinter.Sprintf("%v", number.Decimal(v)) } +func GetPluralRule(l Locale) int { + return GetPluralRuleImpl(l.Language()) +} + +func GetDefaultPluralRule() int { + return GetPluralRuleImpl(i18n.DefaultLocales.GetDefaultLang()) +} + func init() { // prepare a default matcher, especially for tests supportedTags = []language.Tag{language.English} diff --git a/modules/translation/translation_test.go b/modules/translation/translation_test.go index bffbb155ca..7584490941 100644 --- a/modules/translation/translation_test.go +++ b/modules/translation/translation_test.go @@ -8,7 +8,7 @@ package translation import ( "testing" - "code.gitea.io/gitea/modules/translation/i18n" + "forgejo.org/modules/translation/i18n" "github.com/stretchr/testify/assert" ) @@ -16,19 +16,19 @@ import ( func TestTrSize(t *testing.T) { l := NewLocale("") size := int64(1) - assert.EqualValues(t, "1 munits.data.b", l.TrSize(size).String()) + assert.Equal(t, "1 munits.data.b", l.TrSize(size).String()) size *= 2048 - assert.EqualValues(t, "2 munits.data.kib", l.TrSize(size).String()) + assert.Equal(t, "2 munits.data.kib", l.TrSize(size).String()) size *= 2048 - assert.EqualValues(t, "4 munits.data.mib", l.TrSize(size).String()) + assert.Equal(t, "4 munits.data.mib", l.TrSize(size).String()) size *= 2048 - assert.EqualValues(t, "8 munits.data.gib", l.TrSize(size).String()) + assert.Equal(t, "8 munits.data.gib", l.TrSize(size).String()) size *= 2048 - assert.EqualValues(t, "16 munits.data.tib", l.TrSize(size).String()) + assert.Equal(t, "16 munits.data.tib", l.TrSize(size).String()) size *= 2048 - assert.EqualValues(t, "32 munits.data.pib", l.TrSize(size).String()) + assert.Equal(t, "32 munits.data.pib", l.TrSize(size).String()) size *= 128 - assert.EqualValues(t, "4 munits.data.eib", l.TrSize(size).String()) + assert.Equal(t, "4 munits.data.eib", l.TrSize(size).String()) } func TestPrettyNumber(t *testing.T) { @@ -38,13 +38,121 @@ func TestPrettyNumber(t *testing.T) { allLangMap["id-ID"] = &LangType{Lang: "id-ID", Name: "Bahasa Indonesia"} l := NewLocale("id-ID") - assert.EqualValues(t, "1.000.000", l.PrettyNumber(1000000)) - assert.EqualValues(t, "1.000.000,1", l.PrettyNumber(1000000.1)) - assert.EqualValues(t, "1.000.000", l.PrettyNumber("1000000")) - assert.EqualValues(t, "1.000.000", l.PrettyNumber("1000000.0")) - assert.EqualValues(t, "1.000.000,1", l.PrettyNumber("1000000.1")) + assert.Equal(t, "1.000.000", l.PrettyNumber(1000000)) + assert.Equal(t, "1.000.000,1", l.PrettyNumber(1000000.1)) + assert.Equal(t, "1.000.000", l.PrettyNumber("1000000")) + assert.Equal(t, "1.000.000", l.PrettyNumber("1000000.0")) + assert.Equal(t, "1.000.000,1", l.PrettyNumber("1000000.1")) l = NewLocale("nosuch") - assert.EqualValues(t, "1,000,000", l.PrettyNumber(1000000)) - assert.EqualValues(t, "1,000,000.1", l.PrettyNumber(1000000.1)) + assert.Equal(t, "1,000,000", l.PrettyNumber(1000000)) + assert.Equal(t, "1,000,000.1", l.PrettyNumber(1000000.1)) +} + +func TestGetPluralRule(t *testing.T) { + assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("en")) + assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("en-US")) + assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("en_UK")) + assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("nds")) + assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("de-DE")) + + assert.Equal(t, PluralRuleOneForm, GetPluralRuleImpl("zh")) + assert.Equal(t, PluralRuleOneForm, GetPluralRuleImpl("ja")) + + assert.Equal(t, PluralRuleBengali, GetPluralRuleImpl("bn")) + + assert.Equal(t, PluralRuleIcelandic, GetPluralRuleImpl("is")) + + assert.Equal(t, PluralRuleFilipino, GetPluralRuleImpl("fil")) + + assert.Equal(t, PluralRuleCzech, GetPluralRuleImpl("cs")) + + assert.Equal(t, PluralRuleRussian, GetPluralRuleImpl("ru")) + + assert.Equal(t, PluralRulePolish, GetPluralRuleImpl("pl")) + + assert.Equal(t, PluralRuleLatvian, GetPluralRuleImpl("lv")) + + assert.Equal(t, PluralRuleLithuanian, GetPluralRuleImpl("lt")) + + assert.Equal(t, PluralRuleFrench, GetPluralRuleImpl("fr")) + + assert.Equal(t, PluralRuleCatalan, GetPluralRuleImpl("ca")) + + assert.Equal(t, PluralRuleSlovenian, GetPluralRuleImpl("sl")) + + assert.Equal(t, PluralRuleArabic, GetPluralRuleImpl("ar")) + + assert.Equal(t, PluralRuleCatalan, GetPluralRuleImpl("pt-PT")) + assert.Equal(t, PluralRuleFrench, GetPluralRuleImpl("pt-BR")) + + assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("invalid")) +} + +func TestApplyPluralRule(t *testing.T) { + testCases := []struct { + expect i18n.PluralFormIndex + pluralRule int + values []int64 + }{ + {i18n.PluralFormOne, PluralRuleDefault, []int64{1}}, + {i18n.PluralFormOther, PluralRuleDefault, []int64{0, 2, 10, 256}}, + + {i18n.PluralFormOther, PluralRuleOneForm, []int64{0, 1, 2}}, + + {i18n.PluralFormOne, PluralRuleBengali, []int64{0, 1}}, + {i18n.PluralFormOther, PluralRuleBengali, []int64{2, 10, 256}}, + + {i18n.PluralFormOne, PluralRuleIcelandic, []int64{1, 21, 31}}, + {i18n.PluralFormOther, PluralRuleIcelandic, []int64{0, 2, 11, 15, 256}}, + + {i18n.PluralFormOne, PluralRuleFilipino, []int64{0, 1, 2, 3, 5, 7, 8, 10, 11, 12, 257}}, + {i18n.PluralFormOther, PluralRuleFilipino, []int64{4, 6, 9, 14, 16, 19, 256}}, + + {i18n.PluralFormOne, PluralRuleCzech, []int64{1}}, + {i18n.PluralFormFew, PluralRuleCzech, []int64{2, 3, 4}}, + {i18n.PluralFormOther, PluralRuleCzech, []int64{5, 0, 12, 78, 254}}, + + {i18n.PluralFormOne, PluralRuleRussian, []int64{1, 21, 31}}, + {i18n.PluralFormFew, PluralRuleRussian, []int64{2, 23, 34}}, + {i18n.PluralFormMany, PluralRuleRussian, []int64{0, 5, 11, 37, 111, 256}}, + + {i18n.PluralFormOne, PluralRulePolish, []int64{1}}, + {i18n.PluralFormFew, PluralRulePolish, []int64{2, 23, 34}}, + {i18n.PluralFormMany, PluralRulePolish, []int64{0, 5, 11, 21, 37, 256}}, + + {i18n.PluralFormZero, PluralRuleLatvian, []int64{0, 10, 11, 17}}, + {i18n.PluralFormOne, PluralRuleLatvian, []int64{1, 21, 71}}, + {i18n.PluralFormOther, PluralRuleLatvian, []int64{2, 7, 22, 23, 256}}, + + {i18n.PluralFormOne, PluralRuleLithuanian, []int64{1, 21, 31}}, + {i18n.PluralFormFew, PluralRuleLithuanian, []int64{2, 5, 9, 23, 34, 256}}, + {i18n.PluralFormMany, PluralRuleLithuanian, []int64{0, 10, 11, 18}}, + + {i18n.PluralFormOne, PluralRuleFrench, []int64{0, 1}}, + {i18n.PluralFormMany, PluralRuleFrench, []int64{1000000, 2000000}}, + {i18n.PluralFormOther, PluralRuleFrench, []int64{2, 4, 10, 256}}, + + {i18n.PluralFormOne, PluralRuleCatalan, []int64{1}}, + {i18n.PluralFormMany, PluralRuleCatalan, []int64{1000000, 2000000}}, + {i18n.PluralFormOther, PluralRuleCatalan, []int64{0, 2, 4, 10, 256}}, + + {i18n.PluralFormOne, PluralRuleSlovenian, []int64{1, 101, 201, 501}}, + {i18n.PluralFormTwo, PluralRuleSlovenian, []int64{2, 102, 202, 502}}, + {i18n.PluralFormFew, PluralRuleSlovenian, []int64{3, 103, 203, 503, 4, 104, 204, 504}}, + {i18n.PluralFormOther, PluralRuleSlovenian, []int64{0, 5, 11, 12, 20, 256}}, + + {i18n.PluralFormZero, PluralRuleArabic, []int64{0}}, + {i18n.PluralFormOne, PluralRuleArabic, []int64{1}}, + {i18n.PluralFormTwo, PluralRuleArabic, []int64{2}}, + {i18n.PluralFormFew, PluralRuleArabic, []int64{3, 4, 9, 10, 103, 104}}, + {i18n.PluralFormMany, PluralRuleArabic, []int64{11, 12, 13, 14, 17, 111, 256}}, + {i18n.PluralFormOther, PluralRuleArabic, []int64{100, 101, 102}}, + } + + for _, tc := range testCases { + for _, n := range tc.values { + assert.Equal(t, tc.expect, PluralRules[tc.pluralRule](n), "Testcase for plural rule %d, value %d", tc.pluralRule, n) + } + } } diff --git a/modules/turnstile/turnstile.go b/modules/turnstile/turnstile.go index 38d0233446..31ba256195 100644 --- a/modules/turnstile/turnstile.go +++ b/modules/turnstile/turnstile.go @@ -11,8 +11,8 @@ import ( "net/url" "strings" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/json" + "forgejo.org/modules/setting" ) // Response is the structure of JSON returned from API diff --git a/modules/typesniffer/typesniffer.go b/modules/typesniffer/typesniffer.go index 6aec5c285e..a8fc70e54c 100644 --- a/modules/typesniffer/typesniffer.go +++ b/modules/typesniffer/typesniffer.go @@ -11,7 +11,7 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/util" ) // Use at most this many bytes to determine Content Type. @@ -20,6 +20,8 @@ const sniffLen = 1024 const ( // SvgMimeType MIME type of SVG images. SvgMimeType = "image/svg+xml" + // AvifMimeType MIME type of AVIF images + AvifMimeType = "image/avif" // ApplicationOctetStream MIME type of binary files. ApplicationOctetStream = "application/octet-stream" ) @@ -106,6 +108,12 @@ func DetectContentType(data []byte) SniffedType { } } + // AVIF is unsupported by http.DetectContentType + // Signature taken from https://stackoverflow.com/a/68322450 + if bytes.Index(data, []byte("ftypavif")) == 4 { + ct = AvifMimeType + } + if strings.HasPrefix(ct, "audio/") && bytes.HasPrefix(data, []byte("ID3")) { // The MP3 detection is quite inaccurate, any content with "ID3" prefix will result in "audio/mpeg". // So remove the "ID3" prefix and detect again, if result is text, then it must be text content. diff --git a/modules/typesniffer/typesniffer_test.go b/modules/typesniffer/typesniffer_test.go index f6fa07ee7f..8d80b4ddb4 100644 --- a/modules/typesniffer/typesniffer_test.go +++ b/modules/typesniffer/typesniffer_test.go @@ -135,3 +135,13 @@ func TestDetectContentTypeOgg(t *testing.T) { require.NoError(t, err) assert.True(t, st.IsVideo()) } + +func TestDetectContentTypeAvif(t *testing.T) { + avifImage, err := hex.DecodeString("000000206674797061766966") + require.NoError(t, err) + + st, err := DetectContentTypeFromReader(bytes.NewReader(avifImage)) + require.NoError(t, err) + + assert.True(t, st.IsImage()) +} diff --git a/modules/updatechecker/update_checker.go b/modules/updatechecker/update_checker.go index 0c93f08d21..b0932ba663 100644 --- a/modules/updatechecker/update_checker.go +++ b/modules/updatechecker/update_checker.go @@ -11,10 +11,10 @@ import ( "net/http" "strings" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/proxy" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/system" + "forgejo.org/modules/json" + "forgejo.org/modules/proxy" + "forgejo.org/modules/setting" + "forgejo.org/modules/system" "github.com/hashicorp/go-version" ) diff --git a/modules/user/user.go b/modules/user/user.go index eee401a23f..d153413c70 100644 --- a/modules/user/user.go +++ b/modules/user/user.go @@ -6,8 +6,6 @@ package user import ( "os" "os/user" - "runtime" - "strings" ) // CurrentUsername return current login OS user name @@ -16,12 +14,7 @@ func CurrentUsername() string { if err != nil { return fallbackCurrentUsername() } - username := userinfo.Username - if runtime.GOOS == "windows" { - parts := strings.Split(username, "\\") - username = parts[len(parts)-1] - } - return username + return userinfo.Username } // Old method, used if new method doesn't work on your OS for some reason diff --git a/modules/user/user_test.go b/modules/user/user_test.go index 372a675d34..c7eff85c90 100644 --- a/modules/user/user_test.go +++ b/modules/user/user_test.go @@ -5,7 +5,6 @@ package user import ( "os/exec" - "runtime" "strings" "testing" ) @@ -23,10 +22,6 @@ func TestCurrentUsername(t *testing.T) { if len(user) == 0 { t.Errorf("expected non-empty user, got: %s", user) } - // Windows whoami is weird, so just skip remaining tests - if runtime.GOOS == "windows" { - t.Skip("skipped test because of weird whoami on Windows") - } whoami, err := getWhoamiOutput() if err != nil { t.Errorf("failed to run whoami to test current user: %f", err) diff --git a/modules/util/file_unix.go b/modules/util/file_unix.go index 79a29c8b3b..b722eee97d 100644 --- a/modules/util/file_unix.go +++ b/modules/util/file_unix.go @@ -1,8 +1,6 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !windows - package util import ( diff --git a/modules/util/file_unix_test.go b/modules/util/file_unix_test.go index d60082a034..228c64f980 100644 --- a/modules/util/file_unix_test.go +++ b/modules/util/file_unix_test.go @@ -1,8 +1,6 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !windows - package util import ( diff --git a/modules/util/file_windows.go b/modules/util/file_windows.go deleted file mode 100644 index 77a33d3c49..0000000000 --- a/modules/util/file_windows.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build windows - -package util - -import ( - "os" -) - -func ApplyUmask(f string, newMode os.FileMode) error { - // do nothing for Windows, because Windows doesn't use umask - return nil -} diff --git a/modules/util/keypair_test.go b/modules/util/keypair_test.go index ec9bca7efa..6c05db779a 100644 --- a/modules/util/keypair_test.go +++ b/modules/util/keypair_test.go @@ -10,7 +10,6 @@ import ( "crypto/sha256" "crypto/x509" "encoding/pem" - "regexp" "testing" "github.com/stretchr/testify/assert" @@ -24,8 +23,8 @@ func TestKeygen(t *testing.T) { assert.NotEmpty(t, priv) assert.NotEmpty(t, pub) - assert.Regexp(t, regexp.MustCompile("^-----BEGIN RSA PRIVATE KEY-----.*"), priv) - assert.Regexp(t, regexp.MustCompile("^-----BEGIN PUBLIC KEY-----.*"), pub) + assert.Regexp(t, "^-----BEGIN RSA PRIVATE KEY-----.*", priv) + assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----.*", pub) } func TestSignUsingKeys(t *testing.T) { diff --git a/modules/util/paginate_test.go b/modules/util/paginate_test.go index 6e69dd19cc..3dc5095071 100644 --- a/modules/util/paginate_test.go +++ b/modules/util/paginate_test.go @@ -13,23 +13,23 @@ func TestPaginateSlice(t *testing.T) { stringSlice := []string{"a", "b", "c", "d", "e"} result, ok := PaginateSlice(stringSlice, 1, 2).([]string) assert.True(t, ok) - assert.EqualValues(t, []string{"a", "b"}, result) + assert.Equal(t, []string{"a", "b"}, result) result, ok = PaginateSlice(stringSlice, 100, 2).([]string) assert.True(t, ok) - assert.EqualValues(t, []string{}, result) + assert.Equal(t, []string{}, result) result, ok = PaginateSlice(stringSlice, 3, 2).([]string) assert.True(t, ok) - assert.EqualValues(t, []string{"e"}, result) + assert.Equal(t, []string{"e"}, result) result, ok = PaginateSlice(stringSlice, 1, 0).([]string) assert.True(t, ok) - assert.EqualValues(t, []string{"a", "b", "c", "d", "e"}, result) + assert.Equal(t, []string{"a", "b", "c", "d", "e"}, result) result, ok = PaginateSlice(stringSlice, 1, -1).([]string) assert.True(t, ok) - assert.EqualValues(t, []string{"a", "b", "c", "d", "e"}, result) + assert.Equal(t, []string{"a", "b", "c", "d", "e"}, result) type Test struct { Val int @@ -38,9 +38,9 @@ func TestPaginateSlice(t *testing.T) { testVar := []*Test{{Val: 2}, {Val: 3}, {Val: 4}} testVar, ok = PaginateSlice(testVar, 1, 50).([]*Test) assert.True(t, ok) - assert.EqualValues(t, []*Test{{Val: 2}, {Val: 3}, {Val: 4}}, testVar) + assert.Equal(t, []*Test{{Val: 2}, {Val: 3}, {Val: 4}}, testVar) testVar, ok = PaginateSlice(testVar, 2, 2).([]*Test) assert.True(t, ok) - assert.EqualValues(t, []*Test{{Val: 4}}, testVar) + assert.Equal(t, []*Test{{Val: 4}}, testVar) } diff --git a/modules/util/path.go b/modules/util/path.go index 185e7cf882..3ef3925c49 100644 --- a/modules/util/path.go +++ b/modules/util/path.go @@ -10,8 +10,6 @@ import ( "os" "path" "path/filepath" - "regexp" - "runtime" "strings" ) @@ -36,9 +34,10 @@ func PathJoinRel(elem ...string) string { elems[i] = path.Clean("/" + e) } p := path.Join(elems...) - if p == "" { + switch p { + case "": return "" - } else if p == "/" { + case "/": return "." } return p[1:] @@ -78,11 +77,7 @@ func FilePathJoinAbs(base string, sub ...string) string { // POSIX filesystem can have `\` in file names. Windows: `\` and `/` are both used for path separators // to keep the behavior consistent, we do not allow `\` in file names, replace all `\` with `/` - if isOSWindows() { - elems[0] = filepath.Clean(base) - } else { - elems[0] = filepath.Clean(strings.ReplaceAll(base, "\\", pathSeparator)) - } + elems[0] = filepath.Clean(strings.ReplaceAll(base, "\\", pathSeparator)) if !filepath.IsAbs(elems[0]) { // This shouldn't happen. If there is really necessary to pass in relative path, return the full path with filepath.Abs() instead panic(fmt.Sprintf("FilePathJoinAbs: %q (for path %v) is not absolute, do not guess a relative path based on current working directory", elems[0], elems)) @@ -91,11 +86,7 @@ func FilePathJoinAbs(base string, sub ...string) string { if s == "" { continue } - if isOSWindows() { - elems = append(elems, filepath.Clean(pathSeparator+s)) - } else { - elems = append(elems, filepath.Clean(pathSeparator+strings.ReplaceAll(s, "\\", pathSeparator))) - } + elems = append(elems, filepath.Clean(pathSeparator+strings.ReplaceAll(s, "\\", pathSeparator))) } // the elems[0] must be an absolute path, just join them together return filepath.Join(elems...) @@ -217,12 +208,6 @@ func StatDir(rootPath string, includeDir ...bool) ([]string, error) { return statDir(rootPath, "", isIncludeDir, false, false) } -func isOSWindows() bool { - return runtime.GOOS == "windows" -} - -var driveLetterRegexp = regexp.MustCompile("/[A-Za-z]:/") - // FileURLToPath extracts the path information from a file://... url. // It returns an error only if the URL is not a file URL. func FileURLToPath(u *url.URL) (string, error) { @@ -230,17 +215,7 @@ func FileURLToPath(u *url.URL) (string, error) { return "", errors.New("URL scheme is not 'file': " + u.String()) } - path := u.Path - - if !isOSWindows() { - return path, nil - } - - // If it looks like there's a Windows drive letter at the beginning, strip off the leading slash. - if driveLetterRegexp.MatchString(path) { - return path[1:], nil - } - return path, nil + return u.Path, nil } // HomeDir returns path of '~'(in Linux) on Windows, @@ -249,14 +224,7 @@ func HomeDir() (home string, err error) { // TODO: some users run Gitea with mismatched uid and "HOME=xxx" (they set HOME=xxx by environment manually) // TODO: when running gitea as a sub command inside git, the HOME directory is not the user's home directory // so at the moment we can not use `user.Current().HomeDir` - if isOSWindows() { - home = os.Getenv("USERPROFILE") - if home == "" { - home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") - } - } else { - home = os.Getenv("HOME") - } + home = os.Getenv("HOME") if home == "" { return "", errors.New("cannot get home directory") diff --git a/modules/util/path_test.go b/modules/util/path_test.go index 3699f052d1..b912b76f6e 100644 --- a/modules/util/path_test.go +++ b/modules/util/path_test.go @@ -5,7 +5,6 @@ package util import ( "net/url" - "runtime" "testing" "github.com/stretchr/testify/assert" @@ -17,7 +16,6 @@ func TestFileURLToPath(t *testing.T) { url string expected string haserror bool - windows bool }{ // case 0 { @@ -34,18 +32,9 @@ func TestFileURLToPath(t *testing.T) { url: "file:///path", expected: "/path", }, - // case 3 - { - url: "file:///C:/path", - expected: "C:/path", - windows: true, - }, } for n, c := range cases { - if c.windows && runtime.GOOS != "windows" { - continue - } u, _ := url.Parse(c.url) p, err := FileURLToPath(u) if c.haserror { @@ -177,35 +166,18 @@ func TestCleanPath(t *testing.T) { assert.Equal(t, c.expected, PathJoinRelX(c.elems...), "case: %v", c.elems) } - // for POSIX only, but the result is similar on Windows, because the first element must be an absolute path - if isOSWindows() { - cases = []struct { - elems []string - expected string - }{ - {[]string{`C:\..`}, `C:\`}, - {[]string{`C:\a`}, `C:\a`}, - {[]string{`C:\a/`}, `C:\a`}, - {[]string{`C:\..\a\`, `../b`, `c\..`, `d`}, `C:\a\b\d`}, - {[]string{`C:\a/..\b`}, `C:\b`}, - {[]string{`C:\a`, ``, `b`}, `C:\a\b`}, - {[]string{`C:\a`, `..`, `b`}, `C:\a\b`}, - {[]string{`C:\lfs`, `repo/..`, `user/../path`}, `C:\lfs\path`}, - } - } else { - cases = []struct { - elems []string - expected string - }{ - {[]string{`/..`}, `/`}, - {[]string{`/a`}, `/a`}, - {[]string{`/a/`}, `/a`}, - {[]string{`/../a/`, `../b`, `c/..`, `d`}, `/a/b/d`}, - {[]string{`/a\..\b`}, `/b`}, - {[]string{`/a`, ``, `b`}, `/a/b`}, - {[]string{`/a`, `..`, `b`}, `/a/b`}, - {[]string{`/lfs`, `repo/..`, `user/../path`}, `/lfs/path`}, - } + cases = []struct { + elems []string + expected string + }{ + {[]string{`/..`}, `/`}, + {[]string{`/a`}, `/a`}, + {[]string{`/a/`}, `/a`}, + {[]string{`/../a/`, `../b`, `c/..`, `d`}, `/a/b/d`}, + {[]string{`/a\..\b`}, `/b`}, + {[]string{`/a`, ``, `b`}, `/a/b`}, + {[]string{`/a`, `..`, `b`}, `/a/b`}, + {[]string{`/lfs`, `repo/..`, `user/../path`}, `/lfs/path`}, } for _, c := range cases { assert.Equal(t, c.expected, FilePathJoinAbs(c.elems[0], c.elems[1:]...), "case: %v", c.elems) diff --git a/modules/util/remove.go b/modules/util/remove.go index d1e38faf5f..2a65a6b0aa 100644 --- a/modules/util/remove.go +++ b/modules/util/remove.go @@ -5,13 +5,10 @@ package util import ( "os" - "runtime" "syscall" "time" ) -const windowsSharingViolationError syscall.Errno = 32 - // Remove removes the named file or (empty) directory with at most 5 attempts. func Remove(name string) error { var err error @@ -27,12 +24,6 @@ func Remove(name string) error { continue } - if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" { - // try again - <-time.After(100 * time.Millisecond) - continue - } - if unwrapped == syscall.ENOENT { // it's already gone return nil @@ -56,12 +47,6 @@ func RemoveAll(name string) error { continue } - if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" { - // try again - <-time.After(100 * time.Millisecond) - continue - } - if unwrapped == syscall.ENOENT { // it's already gone return nil @@ -85,12 +70,6 @@ func Rename(oldpath, newpath string) error { continue } - if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" { - // try again - <-time.After(100 * time.Millisecond) - continue - } - if i == 0 && os.IsNotExist(err) { return err } diff --git a/modules/util/rotatingfilewriter/writer.go b/modules/util/rotatingfilewriter/writer.go index c595f49c49..ff234eea93 100644 --- a/modules/util/rotatingfilewriter/writer.go +++ b/modules/util/rotatingfilewriter/writer.go @@ -14,8 +14,8 @@ import ( "sync" "time" - "code.gitea.io/gitea/modules/graceful/releasereopen" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/graceful/releasereopen" + "forgejo.org/modules/util" ) type Options struct { diff --git a/modules/util/slice.go b/modules/util/slice.go index 9c878c24be..80c8e62f6f 100644 --- a/modules/util/slice.go +++ b/modules/util/slice.go @@ -4,7 +4,6 @@ package util import ( - "cmp" "slices" "strings" ) @@ -47,13 +46,6 @@ func SliceRemoveAll[T comparable](slice []T, target T) []T { return slices.DeleteFunc(slice, func(t T) bool { return t == target }) } -// Sorted returns the sorted slice -// Note: The parameter is sorted inline. -func Sorted[S ~[]E, E cmp.Ordered](values S) S { - slices.Sort(values) - return values -} - // TODO: Replace with "maps.Values" once available, current it only in golang.org/x/exp/maps but not in standard library func ValuesOfMap[K comparable, V any](m map[K]V) []V { values := make([]V, 0, len(m)) diff --git a/modules/util/truncate.go b/modules/util/truncate.go index 77b116eeff..f2edbdc673 100644 --- a/modules/util/truncate.go +++ b/modules/util/truncate.go @@ -41,6 +41,8 @@ func SplitStringAtByteN(input string, n int) (left, right string) { // SplitTrimSpace splits the string at given separator and trims leading and trailing space func SplitTrimSpace(input, sep string) []string { + // Trim initial leading & trailing space + input = strings.TrimSpace(input) // replace CRLF with LF input = strings.ReplaceAll(input, "\r\n", "\n") diff --git a/modules/util/util.go b/modules/util/util.go index 0444680228..548fd1e90b 100644 --- a/modules/util/util.go +++ b/modules/util/util.go @@ -14,22 +14,11 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/modules/optional" - "golang.org/x/crypto/ssh" "golang.org/x/text/cases" "golang.org/x/text/language" ) -// OptionalBoolParse get the corresponding optional.Option[bool] of a string using strconv.ParseBool -func OptionalBoolParse(s string) optional.Option[bool] { - v, e := strconv.ParseBool(s) - if e != nil { - return optional.None[bool]() - } - return optional.Some(v) -} - // IsEmptyString checks if the provided string is empty func IsEmptyString(s string) bool { return len(strings.TrimSpace(s)) == 0 @@ -99,10 +88,16 @@ func CryptoRandomString(length int64) (string, error) { // CryptoRandomBytes generates `length` crypto bytes // This differs from CryptoRandomString, as each byte in CryptoRandomString is generated by [0,61] range // This function generates totally random bytes, each byte is generated by [0,255] range -func CryptoRandomBytes(length int64) ([]byte, error) { +func CryptoRandomBytes(length int64) []byte { + // crypto/rand.Read is documented to never return a error. + // https://go.dev/issue/66821 buf := make([]byte, length) - _, err := rand.Read(buf) - return buf, err + n, err := rand.Read(buf) + if err != nil || n != int(length) { + panic(err) + } + + return buf } // ToUpperASCII returns s with all ASCII letters mapped to their upper case. @@ -225,6 +220,34 @@ func Iif[T any](condition bool, trueVal, falseVal T) T { return falseVal } +// IfZero returns "def" if "v" is a zero value, otherwise "v" +func IfZero[T comparable](v, def T) T { + var zero T + if v == zero { + return def + } + return v +} + +// OptionalArg helps the "optional argument" in Golang: +// +// func foo(optArg ...int) { return OptionalArg(optArg) } +// calling `foo()` gets zero value 0, calling `foo(100)` gets 100 +// func bar(optArg ...int) { return OptionalArg(optArg, 42) } +// calling `bar()` gets default value 42, calling `bar(100)` gets 100 +// +// Passing more than 1 item to `optArg` or `defaultValue` is undefined behavior. +// At the moment only the first item is used. +func OptionalArg[T any](optArg []T, defaultValue ...T) (ret T) { + if len(optArg) >= 1 { + return optArg[0] + } + if len(defaultValue) >= 1 { + return defaultValue[0] + } + return ret +} + func ReserveLineBreakForTextarea(input string) string { // Since the content is from a form which is a textarea, the line endings are \r\n. // It's a standard behavior of HTML. diff --git a/modules/util/util_test.go b/modules/util/util_test.go index 549b53f5a7..21988fd0f8 100644 --- a/modules/util/util_test.go +++ b/modules/util/util_test.go @@ -11,9 +11,8 @@ import ( "strings" "testing" - "code.gitea.io/gitea/modules/optional" - "code.gitea.io/gitea/modules/test" - "code.gitea.io/gitea/modules/util" + "forgejo.org/modules/test" + "forgejo.org/modules/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -164,36 +163,21 @@ func Test_RandomString(t *testing.T) { } func Test_RandomBytes(t *testing.T) { - bytes1, err := util.CryptoRandomBytes(32) - require.NoError(t, err) - - bytes2, err := util.CryptoRandomBytes(32) - require.NoError(t, err) + bytes1 := util.CryptoRandomBytes(32) + bytes2 := util.CryptoRandomBytes(32) + assert.Len(t, bytes1, 32) + assert.Len(t, bytes2, 32) assert.NotEqual(t, bytes1, bytes2) - bytes3, err := util.CryptoRandomBytes(256) - require.NoError(t, err) - - bytes4, err := util.CryptoRandomBytes(256) - require.NoError(t, err) + bytes3 := util.CryptoRandomBytes(256) + bytes4 := util.CryptoRandomBytes(256) + assert.Len(t, bytes3, 256) + assert.Len(t, bytes4, 256) assert.NotEqual(t, bytes3, bytes4) } -func TestOptionalBoolParse(t *testing.T) { - assert.Equal(t, optional.None[bool](), util.OptionalBoolParse("")) - assert.Equal(t, optional.None[bool](), util.OptionalBoolParse("x")) - - assert.Equal(t, optional.Some(false), util.OptionalBoolParse("0")) - assert.Equal(t, optional.Some(false), util.OptionalBoolParse("f")) - assert.Equal(t, optional.Some(false), util.OptionalBoolParse("False")) - - assert.Equal(t, optional.Some(true), util.OptionalBoolParse("1")) - assert.Equal(t, optional.Some(true), util.OptionalBoolParse("t")) - assert.Equal(t, optional.Some(true), util.OptionalBoolParse("True")) -} - // Test case for any function which accepts and returns a single string. type StringTest struct { in, out string @@ -272,6 +256,19 @@ func TestGeneratingEd25519Keypair(t *testing.T) { publicKey, privateKey, err := util.GenerateSSHKeypair() require.NoError(t, err) - assert.EqualValues(t, testPublicKey, string(publicKey)) - assert.EqualValues(t, testPrivateKey, string(privateKey)) + assert.Equal(t, testPublicKey, string(publicKey)) + assert.Equal(t, testPrivateKey, string(privateKey)) +} + +func TestOptionalArg(t *testing.T) { + foo := func(other any, optArg ...int) int { + return util.OptionalArg(optArg) + } + bar := func(other any, optArg ...int) int { + return util.OptionalArg(optArg, 42) + } + assert.Equal(t, 0, foo(nil)) + assert.Equal(t, 100, foo(nil, 100)) + assert.Equal(t, 42, bar(nil)) + assert.Equal(t, 100, bar(nil, 100)) } diff --git a/modules/validation/binding.go b/modules/validation/binding.go index cb0a5063e5..f4f82278bd 100644 --- a/modules/validation/binding.go +++ b/modules/validation/binding.go @@ -8,10 +8,11 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/auth" - "code.gitea.io/gitea/modules/git" + "forgejo.org/modules/auth" + "forgejo.org/modules/git" + "forgejo.org/modules/util" - "gitea.com/go-chi/binding" + "code.forgejo.org/go-chi/binding" "github.com/gobwas/glob" ) @@ -26,11 +27,14 @@ const ( ErrUsername = "UsernameError" // ErrInvalidGroupTeamMap is returned when a group team mapping is invalid ErrInvalidGroupTeamMap = "InvalidGroupTeamMap" + // ErrEmail is returned when an email address is invalid + ErrEmail = "Email" ) // AddBindingRules adds additional binding rules func AddBindingRules() { addGitRefNameBindingRule() + addValidURLListBindingRule() addValidURLBindingRule() addValidSiteURLBindingRule() addGlobPatternRule() @@ -38,13 +42,14 @@ func AddBindingRules() { addGlobOrRegexPatternRule() addUsernamePatternRule() addValidGroupTeamMapRule() + addEmailBindingRules() } func addGitRefNameBindingRule() { // Git refname validation rule binding.AddRule(&binding.Rule{ IsMatch: func(rule string) bool { - return strings.HasPrefix(rule, "GitRefName") + return rule == "GitRefName" }, IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) { str := fmt.Sprintf("%v", val) @@ -58,11 +63,38 @@ func addGitRefNameBindingRule() { }) } +func addValidURLListBindingRule() { + // URL validation rule + binding.AddRule(&binding.Rule{ + IsMatch: func(rule string) bool { + return rule == "ValidUrlList" + }, + IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) { + str := fmt.Sprintf("%v", val) + if len(str) == 0 { + errs.Add([]string{name}, binding.ERR_URL, "Url") + return false, errs + } + + ok := true + urls := util.SplitTrimSpace(str, "\n") + for _, u := range urls { + if !IsValidURL(u) { + ok = false + errs.Add([]string{name}, binding.ERR_URL, u) + } + } + + return ok, errs + }, + }) +} + func addValidURLBindingRule() { // URL validation rule binding.AddRule(&binding.Rule{ IsMatch: func(rule string) bool { - return strings.HasPrefix(rule, "ValidUrl") + return rule == "ValidUrl" }, IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) { str := fmt.Sprintf("%v", val) @@ -80,7 +112,7 @@ func addValidSiteURLBindingRule() { // URL validation rule binding.AddRule(&binding.Rule{ IsMatch: func(rule string) bool { - return strings.HasPrefix(rule, "ValidSiteUrl") + return rule == "ValidSiteUrl" }, IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) { str := fmt.Sprintf("%v", val) @@ -171,7 +203,7 @@ func addUsernamePatternRule() { func addValidGroupTeamMapRule() { binding.AddRule(&binding.Rule{ IsMatch: func(rule string) bool { - return strings.HasPrefix(rule, "ValidGroupTeamMap") + return rule == "ValidGroupTeamMap" }, IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) { _, err := auth.UnmarshalGroupTeamMapping(fmt.Sprintf("%v", val)) @@ -185,6 +217,34 @@ func addValidGroupTeamMapRule() { }) } +func addEmailBindingRules() { + binding.AddRule(&binding.Rule{ + IsMatch: func(rule string) bool { + return strings.HasPrefix(rule, "EmailWithAllowedDomain") + }, + IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) { + if err := ValidateEmail(fmt.Sprintf("%v", val)); err != nil { + errs.Add([]string{name}, ErrEmail, err.Error()) + return false, errs + } + return true, errs + }, + }) + + binding.AddRule(&binding.Rule{ + IsMatch: func(rule string) bool { + return strings.HasPrefix(rule, "EmailForAdmin") + }, + IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) { + if err := ValidateEmailForAdmin(fmt.Sprintf("%v", val)); err != nil { + errs.Add([]string{name}, ErrEmail, err.Error()) + return false, errs + } + return true, errs + }, + }) +} + func portOnly(hostport string) string { colon := strings.IndexByte(hostport, ':') if colon == -1 { diff --git a/modules/validation/binding_test.go b/modules/validation/binding_test.go index 01ff4e3435..5adcdf0289 100644 --- a/modules/validation/binding_test.go +++ b/modules/validation/binding_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "gitea.com/go-chi/binding" + "code.forgejo.org/go-chi/binding" chi "github.com/go-chi/chi/v5" "github.com/stretchr/testify/assert" ) @@ -27,6 +27,7 @@ type ( TestForm struct { BranchName string `form:"BranchName" binding:"GitRefName"` URL string `form:"ValidUrl" binding:"ValidUrl"` + URLs string `form:"ValidUrls" binding:"ValidUrlList"` GlobPattern string `form:"GlobPattern" binding:"GlobPattern"` RegexPattern string `form:"RegexPattern" binding:"RegexPattern"` } diff --git a/modules/validation/email.go b/modules/validation/email.go new file mode 100644 index 0000000000..8e1ffc203d --- /dev/null +++ b/modules/validation/email.go @@ -0,0 +1,125 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2020 The Gitea Authors. All rights reserved. +// Copyright 2024 The Forgejo Authors. All rights reserved +// SPDX-License-Identifier: MIT + +package validation + +import ( + "fmt" + "net/mail" + "strings" + + "forgejo.org/modules/setting" + "forgejo.org/modules/util" + + "github.com/gobwas/glob" +) + +// ErrEmailNotActivated e-mail address has not been activated error +var ErrEmailNotActivated = util.NewInvalidArgumentErrorf("e-mail address has not been activated") + +// ErrEmailInvalid represents an error where the email address does not comply with RFC 5322 +// or has a leading '-' character +type ErrEmailInvalid struct { + Email string +} + +// IsErrEmailInvalid checks if an error is an ErrEmailInvalid +func IsErrEmailInvalid(err error) bool { + _, ok := err.(ErrEmailInvalid) + return ok +} + +func (err ErrEmailInvalid) Error() string { + return fmt.Sprintf("e-mail invalid [email: %s]", err.Email) +} + +func (err ErrEmailInvalid) Unwrap() error { + return util.ErrInvalidArgument +} + +// check if email is a valid address with allowed domain +func ValidateEmail(email string) error { + if err := validateEmailBasic(email); err != nil { + return err + } + return validateEmailDomain(email) +} + +// check if email is a valid address when admins manually add or edit users +func ValidateEmailForAdmin(email string) error { + return validateEmailBasic(email) + // In this case we do not need to check the email domain +} + +// validateEmailBasic checks whether the email complies with the rules +func validateEmailBasic(email string) error { + if len(email) == 0 { + return ErrEmailInvalid{email} + } + + parsedAddress, err := mail.ParseAddress(email) + if err != nil { + return ErrEmailInvalid{email} + } + + if parsedAddress.Name != "" { + return ErrEmailInvalid{email} + } + + return nil +} + +func validateEmailDomain(email string) error { + if !IsEmailDomainAllowed(email) { + return ErrEmailInvalid{email} + } + + return nil +} + +func IsEmailDomainAllowed(email string) bool { + return isEmailDomainAllowedInternal( + email, + setting.Service.EmailDomainAllowList, + setting.Service.EmailDomainBlockList) +} + +func isEmailDomainAllowedInternal( + email string, + emailDomainAllowList []glob.Glob, + emailDomainBlockList []glob.Glob, +) bool { + var result bool + + if len(emailDomainAllowList) == 0 { + result = !isEmailDomainListed(emailDomainBlockList, email) + } else { + result = isEmailDomainListed(emailDomainAllowList, email) + } + return result +} + +// isEmailDomainListed checks whether the domain of an email address +// matches a list of domains +func isEmailDomainListed(globs []glob.Glob, email string) bool { + if len(globs) == 0 { + return false + } + + n := strings.LastIndex(email, "@") + if n <= 0 { + return false + } + + domain := strings.ToLower(email[n+1:]) + + for _, g := range globs { + if g.Match(domain) { + return true + } + } + + return false +} diff --git a/modules/validation/email_test.go b/modules/validation/email_test.go new file mode 100644 index 0000000000..b7ee766ddb --- /dev/null +++ b/modules/validation/email_test.go @@ -0,0 +1,74 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Copyright 2024 The Forgejo Authors. All rights reserved +// SPDX-License-Identifier: MIT + +package validation + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEmailAddressValidate(t *testing.T) { + kases := map[string]error{ + "abc@gmail.com": nil, + "132@hotmail.com": nil, + "1-3-2@test.org": nil, + "1.3.2@test.org": nil, + "a_123@test.org.cn": nil, + `first.last@iana.org`: nil, + `first!last@iana.org`: nil, + `first#last@iana.org`: nil, + `first$last@iana.org`: nil, + `first%last@iana.org`: nil, + `first&last@iana.org`: nil, + `first'last@iana.org`: nil, + `first*last@iana.org`: nil, + `first+last@iana.org`: nil, + `first/last@iana.org`: nil, + `first=last@iana.org`: nil, + `first?last@iana.org`: nil, + `first^last@iana.org`: nil, + "first`last@iana.org": nil, + `first{last@iana.org`: nil, + `first|last@iana.org`: nil, + `first}last@iana.org`: nil, + `first~last@iana.org`: nil, + `first;last@iana.org`: ErrEmailInvalid{`first;last@iana.org`}, + ".233@qq.com": ErrEmailInvalid{".233@qq.com"}, + "!233@qq.com": nil, + "#233@qq.com": nil, + "$233@qq.com": nil, + "%233@qq.com": nil, + "&233@qq.com": nil, + "'233@qq.com": nil, + "*233@qq.com": nil, + "+233@qq.com": nil, + "-233@qq.com": nil, + "/233@qq.com": nil, + "=233@qq.com": nil, + "?233@qq.com": nil, + "^233@qq.com": nil, + "_233@qq.com": nil, + "`233@qq.com": nil, + "{233@qq.com": nil, + "|233@qq.com": nil, + "}233@qq.com": nil, + "~233@qq.com": nil, + "\"~@ \"@famfo.xyz": nil, + "Foo ": ErrEmailInvalid{"Foo "}, + ";233@qq.com": ErrEmailInvalid{";233@qq.com"}, + string([]byte{0xE2, 0x84, 0xAA}): ErrEmailInvalid{string([]byte{0xE2, 0x84, 0xAA})}, + } + for kase, err := range kases { + t.Run(kase, func(t *testing.T) { + assert.Equal(t, err, ValidateEmail(kase)) + }) + } +} + +func TestEmailDomainAllowList(t *testing.T) { + res := IsEmailDomainAllowed("someuser@localhost.localdomain") + assert.True(t, res) +} diff --git a/modules/validation/glob_pattern_test.go b/modules/validation/glob_pattern_test.go index 1bf622e61d..42d86754e1 100644 --- a/modules/validation/glob_pattern_test.go +++ b/modules/validation/glob_pattern_test.go @@ -6,7 +6,7 @@ package validation import ( "testing" - "gitea.com/go-chi/binding" + "code.forgejo.org/go-chi/binding" "github.com/gobwas/glob" ) diff --git a/modules/validation/helpers.go b/modules/validation/helpers.go index 567ad867fe..1f573564e6 100644 --- a/modules/validation/helpers.go +++ b/modules/validation/helpers.go @@ -9,9 +9,7 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/setting" - - "github.com/gobwas/glob" + "forgejo.org/modules/setting" ) var externalTrackerRegex = regexp.MustCompile(`({?)(?:user|repo|index)+?(}?)`) @@ -50,29 +48,6 @@ func IsValidSiteURL(uri string) bool { return false } -// IsEmailDomainListed checks whether the domain of an email address -// matches a list of domains -func IsEmailDomainListed(globs []glob.Glob, email string) bool { - if len(globs) == 0 { - return false - } - - n := strings.LastIndex(email, "@") - if n <= 0 { - return false - } - - domain := strings.ToLower(email[n+1:]) - - for _, g := range globs { - if g.Match(domain) { - return true - } - } - - return false -} - // IsAPIURL checks if URL is current Gitea instance API URL func IsAPIURL(uri string) bool { return strings.HasPrefix(strings.ToLower(uri), strings.ToLower(setting.AppURL+"api")) diff --git a/modules/validation/helpers_test.go b/modules/validation/helpers_test.go index a1bdf2a29c..7e32184691 100644 --- a/modules/validation/helpers_test.go +++ b/modules/validation/helpers_test.go @@ -6,7 +6,8 @@ package validation import ( "testing" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" + "forgejo.org/modules/test" "github.com/stretchr/testify/assert" ) @@ -47,7 +48,7 @@ func Test_IsValidURL(t *testing.T) { } func Test_IsValidExternalURL(t *testing.T) { - setting.AppURL = "https://try.gitea.io/" + defer test.MockVariableValue(&setting.AppURL, "https://code.forgejo.org/")() cases := []struct { description string @@ -56,7 +57,7 @@ func Test_IsValidExternalURL(t *testing.T) { }{ { description: "Current instance URL", - url: "https://try.gitea.io/test", + url: "https://code.forgejo.org/test", valid: true, }, { @@ -66,7 +67,7 @@ func Test_IsValidExternalURL(t *testing.T) { }, { description: "Current instance API URL", - url: "https://try.gitea.io/api/v1/user/follow", + url: "https://code.forgejo.org/api/v1/user/follow", valid: false, }, { @@ -89,7 +90,7 @@ func Test_IsValidExternalURL(t *testing.T) { } func Test_IsValidExternalTrackerURLFormat(t *testing.T) { - setting.AppURL = "https://try.gitea.io/" + defer test.MockVariableValue(&setting.AppURL, "https://code.forgejo.org/")() cases := []struct { description string @@ -156,7 +157,8 @@ func Test_IsValidExternalTrackerURLFormat(t *testing.T) { } func TestIsValidUsernameAllowDots(t *testing.T) { - setting.Service.AllowDotsInUsernames = true + defer test.MockVariableValue(&setting.Service.AllowDotsInUsernames, true)() + tests := []struct { arg string want bool @@ -188,10 +190,7 @@ func TestIsValidUsernameAllowDots(t *testing.T) { } func TestIsValidUsernameBanDots(t *testing.T) { - setting.Service.AllowDotsInUsernames = false - defer func() { - setting.Service.AllowDotsInUsernames = true - }() + defer test.MockVariableValue(&setting.Service.AllowDotsInUsernames, false)() tests := []struct { arg string diff --git a/modules/validation/refname_test.go b/modules/validation/refname_test.go index 3af7387c47..bb64cab51e 100644 --- a/modules/validation/refname_test.go +++ b/modules/validation/refname_test.go @@ -6,7 +6,7 @@ package validation import ( "testing" - "gitea.com/go-chi/binding" + "code.forgejo.org/go-chi/binding" ) var gitRefNameValidationTestCases = []validationTestCase{ diff --git a/modules/validation/regex_pattern_test.go b/modules/validation/regex_pattern_test.go index efcb276734..90bd969c4f 100644 --- a/modules/validation/regex_pattern_test.go +++ b/modules/validation/regex_pattern_test.go @@ -7,7 +7,7 @@ import ( "regexp" "testing" - "gitea.com/go-chi/binding" + "code.forgejo.org/go-chi/binding" ) func getRegexPatternErrorString(pattern string) string { diff --git a/modules/validation/validatable.go b/modules/validation/validatable.go index 94b5cc135c..4500f6e53d 100644 --- a/modules/validation/validatable.go +++ b/modules/validation/validatable.go @@ -1,5 +1,4 @@ -// Copyright 2024 The Forgejo Authors. All rights reserved. -// Copyright 2023 The Forgejo Authors. All rights reserved. +// Copyright 2023, 2024, 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package validation @@ -10,7 +9,9 @@ import ( "strings" "unicode/utf8" - "code.gitea.io/gitea/modules/timeutil" + "forgejo.org/modules/timeutil" + + ap "github.com/go-ap/activitypub" ) // ErrNotValid represents an validation error @@ -33,15 +34,22 @@ type Validateable interface { } func IsValid(v Validateable) (bool, error) { - if err := v.Validate(); len(err) > 0 { + if valdationErrors := v.Validate(); len(valdationErrors) > 0 { typeof := reflect.TypeOf(v) - errString := strings.Join(err, "\n") + errString := strings.Join(valdationErrors, "\n") return false, ErrNotValid{fmt.Sprint(typeof, ": ", errString)} } return true, nil } +func ValidateIDExists(value ap.Item, name string) []string { + if value == nil { + return []string{fmt.Sprintf("%v should not be nil", name)} + } + return ValidateNotEmpty(value.GetID().String(), name) +} + func ValidateNotEmpty(value any, name string) []string { isValid := true switch v := value.(type) { @@ -53,6 +61,10 @@ func ValidateNotEmpty(value any, name string) []string { if v.IsZero() { isValid = false } + case uint16: + if v == 0 { + isValid = false + } case int64: if v == 0 { isValid = false @@ -80,5 +92,5 @@ func ValidateOneOf(value any, allowed []any, name string) []string { return []string{} } } - return []string{fmt.Sprintf("Value %v is not contained in allowed values %v", value, allowed)} + return []string{fmt.Sprintf("Field %s contains the value %v, which is not in allowed subset %v", name, value, allowed)} } diff --git a/modules/validation/validatable_test.go b/modules/validation/validatable_test.go index 919f5a3183..1fe407b343 100644 --- a/modules/validation/validatable_test.go +++ b/modules/validation/validatable_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 The Forgejo Authors. All rights reserved. +// Copyright 2024, 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package validation @@ -6,7 +6,10 @@ package validation import ( "testing" - "code.gitea.io/gitea/modules/timeutil" + "forgejo.org/modules/timeutil" + + ap "github.com/go-ap/activitypub" + "github.com/stretchr/testify/assert" ) type Sut struct { @@ -37,33 +40,50 @@ func Test_IsValid(t *testing.T) { func Test_ValidateNotEmpty_ForString(t *testing.T) { sut := "" - if len(ValidateNotEmpty(sut, "dummyField")) == 0 { - t.Errorf("sut should be invalid") - } + res := ValidateNotEmpty(sut, "dummyField") + assert.Len(t, res, 1) + sut = "not empty" - if res := ValidateNotEmpty(sut, "dummyField"); len(res) > 0 { - t.Errorf("sut should be valid but was %q", res) - } + res = ValidateNotEmpty(sut, "dummyField") + assert.Empty(t, res, 0) } func Test_ValidateNotEmpty_ForTimestamp(t *testing.T) { sut := timeutil.TimeStamp(0) - if res := ValidateNotEmpty(sut, "dummyField"); len(res) == 0 { - t.Errorf("sut should be invalid") - } + res := ValidateNotEmpty(sut, "dummyField") + assert.Len(t, res, 1) + sut = timeutil.TimeStampNow() - if res := ValidateNotEmpty(sut, "dummyField"); len(res) > 0 { - t.Errorf("sut should be valid but was %q", res) + res = ValidateNotEmpty(sut, "dummyField") + assert.Empty(t, res, 0) +} + +func Test_ValidateIDExists_ForItem(t *testing.T) { + sut := ap.Activity{ + Object: nil, } + res := ValidateIDExists(sut.Object, "dummyField") + assert.Len(t, res, 1) + + sut = ap.Activity{ + Object: ap.IRI(""), + } + res = ValidateIDExists(sut.Object, "dummyField") + assert.Len(t, res, 1) + + sut = ap.Activity{ + Object: ap.IRI("https://dummy.link/id"), + } + res = ValidateIDExists(sut.Object, "dummyField") + assert.Empty(t, res, 0) } func Test_ValidateMaxLen(t *testing.T) { sut := "0123456789" - if len(ValidateMaxLen(sut, 9, "dummyField")) == 0 { - t.Errorf("sut should be invalid") - } + res := ValidateMaxLen(sut, 9, "dummyField") + assert.Len(t, res, 1) + sut = "0123456789" - if res := ValidateMaxLen(sut, 11, "dummyField"); len(res) > 0 { - t.Errorf("sut should be valid but was %q", res) - } + res = ValidateMaxLen(sut, 11, "dummyField") + assert.Empty(t, res, 0) } diff --git a/modules/validation/validurl_test.go b/modules/validation/validurl_test.go index 39f7fa5d65..77fa7aa097 100644 --- a/modules/validation/validurl_test.go +++ b/modules/validation/validurl_test.go @@ -6,7 +6,7 @@ package validation import ( "testing" - "gitea.com/go-chi/binding" + "code.forgejo.org/go-chi/binding" ) var urlValidationTestCases = []validationTestCase{ diff --git a/modules/validation/validurllist_test.go b/modules/validation/validurllist_test.go new file mode 100644 index 0000000000..506f96da69 --- /dev/null +++ b/modules/validation/validurllist_test.go @@ -0,0 +1,157 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package validation + +import ( + "testing" + + "code.forgejo.org/go-chi/binding" +) + +// This is a copy of all the URL tests cases, plus additional ones to +// account for multiple URLs +var urlListValidationTestCases = []validationTestCase{ + { + description: "Empty URL", + data: TestForm{ + URLs: "", + }, + expectedErrors: binding.Errors{}, + }, + { + description: "URL without port", + data: TestForm{ + URLs: "http://test.lan/", + }, + expectedErrors: binding.Errors{}, + }, + { + description: "URL with port", + data: TestForm{ + URLs: "http://test.lan:3000/", + }, + expectedErrors: binding.Errors{}, + }, + { + description: "URL with IPv6 address without port", + data: TestForm{ + URLs: "http://[::1]/", + }, + expectedErrors: binding.Errors{}, + }, + { + description: "URL with IPv6 address with port", + data: TestForm{ + URLs: "http://[::1]:3000/", + }, + expectedErrors: binding.Errors{}, + }, + { + description: "Invalid URL", + data: TestForm{ + URLs: "http//test.lan/", + }, + expectedErrors: binding.Errors{ + binding.Error{ + FieldNames: []string{"URLs"}, + Classification: binding.ERR_URL, + Message: "http//test.lan/", + }, + }, + }, + { + description: "Invalid schema", + data: TestForm{ + URLs: "ftp://test.lan/", + }, + expectedErrors: binding.Errors{ + binding.Error{ + FieldNames: []string{"URLs"}, + Classification: binding.ERR_URL, + Message: "ftp://test.lan/", + }, + }, + }, + { + description: "Invalid port", + data: TestForm{ + URLs: "http://test.lan:3x4/", + }, + expectedErrors: binding.Errors{ + binding.Error{ + FieldNames: []string{"URLs"}, + Classification: binding.ERR_URL, + Message: "http://test.lan:3x4/", + }, + }, + }, + { + description: "Invalid port with IPv6 address", + data: TestForm{ + URLs: "http://[::1]:3x4/", + }, + expectedErrors: binding.Errors{ + binding.Error{ + FieldNames: []string{"URLs"}, + Classification: binding.ERR_URL, + Message: "http://[::1]:3x4/", + }, + }, + }, + { + description: "Multi URLs", + data: TestForm{ + URLs: "http://test.lan:3000/\nhttp://test.local/", + }, + expectedErrors: binding.Errors{}, + }, + { + description: "Multi URLs with newline", + data: TestForm{ + URLs: "http://test.lan:3000/\nhttp://test.local/\n", + }, + expectedErrors: binding.Errors{}, + }, + { + description: "List with invalid entry", + data: TestForm{ + URLs: "http://test.lan:3000/\nhttp://[::1]:3x4/", + }, + expectedErrors: binding.Errors{ + binding.Error{ + FieldNames: []string{"URLs"}, + Classification: binding.ERR_URL, + Message: "http://[::1]:3x4/", + }, + }, + }, + { + description: "List with two invalid entries", + data: TestForm{ + URLs: "ftp://test.lan:3000/\nhttp://[::1]:3x4/\n", + }, + expectedErrors: binding.Errors{ + binding.Error{ + FieldNames: []string{"URLs"}, + Classification: binding.ERR_URL, + Message: "ftp://test.lan:3000/", + }, + binding.Error{ + FieldNames: []string{"URLs"}, + Classification: binding.ERR_URL, + Message: "http://[::1]:3x4/", + }, + }, + }, +} + +func Test_ValidURLListValidation(t *testing.T) { + AddBindingRules() + + for _, testCase := range urlListValidationTestCases { + t.Run(testCase.description, func(t *testing.T) { + performValidationTest(t, testCase) + }) + } +} diff --git a/modules/web/handler.go b/modules/web/handler.go index 728cc5a160..4a7f28b1fa 100644 --- a/modules/web/handler.go +++ b/modules/web/handler.go @@ -9,9 +9,9 @@ import ( "net/http" "reflect" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/web/routing" - "code.gitea.io/gitea/modules/web/types" + "forgejo.org/modules/log" + "forgejo.org/modules/web/routing" + "forgejo.org/modules/web/types" ) var responseStatusProviders = map[reflect.Type]func(req *http.Request) types.ResponseStatusProvider{} diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index 8fa71a81bd..123eb29015 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -8,12 +8,12 @@ import ( "reflect" "strings" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/translation" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/validation" + "forgejo.org/modules/setting" + "forgejo.org/modules/translation" + "forgejo.org/modules/util" + "forgejo.org/modules/validation" - "gitea.com/go-chi/binding" + "code.forgejo.org/go-chi/binding" ) // Form form binding interface @@ -79,6 +79,11 @@ func GetInclude(field reflect.StructField) string { return getRuleBody(field, "Include(") } +func GetRange(field reflect.StructField) (string, string) { + min, max, _ := strings.Cut(getRuleBody(field, "Range("), ",") + return min, max +} + // Validate populates the data with validation error (if any). func Validate(errs binding.Errors, data map[string]any, f any, l translation.Locale) binding.Errors { if errs.Len() == 0 { @@ -131,6 +136,9 @@ func Validate(errs binding.Errors, data map[string]any, f any, l translation.Loc data["ErrorMsg"] = trName + l.TrString("form.url_error", errs[0].Message) case binding.ERR_INCLUDE: data["ErrorMsg"] = trName + l.TrString("form.include_error", GetInclude(field)) + case binding.ERR_RANGE: + min, max := GetRange(field) + data["ErrorMsg"] = trName + l.TrString("alert.range_error", l.PrettyNumber(min), l.PrettyNumber(max)) case validation.ErrGlobPattern: data["ErrorMsg"] = trName + l.TrString("form.glob_pattern_error", errs[0].Message) case validation.ErrRegexPattern: @@ -143,6 +151,8 @@ func Validate(errs binding.Errors, data map[string]any, f any, l translation.Loc } case validation.ErrInvalidGroupTeamMap: data["ErrorMsg"] = trName + l.TrString("form.invalid_group_team_map_error", errs[0].Message) + case validation.ErrEmail: + data["ErrorMsg"] = trName + l.TrString("form.email_error") default: msg := errs[0].Classification if msg != "" && errs[0].Message != "" { diff --git a/modules/web/middleware/cookie.go b/modules/web/middleware/cookie.go index f2d25f5b1c..3bfaeabe69 100644 --- a/modules/web/middleware/cookie.go +++ b/modules/web/middleware/cookie.go @@ -9,8 +9,8 @@ import ( "net/url" "strings" - "code.gitea.io/gitea/modules/session" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/session" + "forgejo.org/modules/setting" ) // SetRedirectToCookie convenience function to set the RedirectTo cookie consistently diff --git a/modules/web/middleware/data.go b/modules/web/middleware/data.go index 08d83f94be..4603e64052 100644 --- a/modules/web/middleware/data.go +++ b/modules/web/middleware/data.go @@ -7,7 +7,7 @@ import ( "context" "time" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" ) // ContextDataStore represents a data store diff --git a/modules/web/middleware/locale.go b/modules/web/middleware/locale.go index 34a16f04e7..565fb2f502 100644 --- a/modules/web/middleware/locale.go +++ b/modules/web/middleware/locale.go @@ -6,8 +6,8 @@ package middleware import ( "net/http" - "code.gitea.io/gitea/modules/translation" - "code.gitea.io/gitea/modules/translation/i18n" + "forgejo.org/modules/translation" + "forgejo.org/modules/translation/i18n" "golang.org/x/text/language" ) @@ -26,8 +26,10 @@ func Locale(resp http.ResponseWriter, req *http.Request) translation.Locale { } } - // Check again in case someone changes the supported language list. - if lang != "" && !i18n.DefaultLocales.HasLang(lang) { + if lang == "dummy" { + changeLang = false + } else if lang != "" && !i18n.DefaultLocales.HasLang(lang) { + // Check again in case someone changes the supported language list. lang = "" changeLang = false } @@ -51,9 +53,3 @@ func Locale(resp http.ResponseWriter, req *http.Request) translation.Locale { func SetLocaleCookie(resp http.ResponseWriter, lang string, maxAge int) { SetSiteCookie(resp, "lang", lang, maxAge) } - -// DeleteLocaleCookie convenience function to delete the locale cookie consistently -// Setting the lang cookie will trigger the middleware to reset the language to previous state. -func DeleteLocaleCookie(resp http.ResponseWriter) { - SetSiteCookie(resp, "lang", "", -1) -} diff --git a/modules/web/route.go b/modules/web/route.go index 805fcb4411..046c9f4ba7 100644 --- a/modules/web/route.go +++ b/modules/web/route.go @@ -7,9 +7,9 @@ import ( "net/http" "strings" - "code.gitea.io/gitea/modules/web/middleware" + "forgejo.org/modules/web/middleware" - "gitea.com/go-chi/binding" + "code.forgejo.org/go-chi/binding" "github.com/go-chi/chi/v5" ) diff --git a/modules/web/route_test.go b/modules/web/route_test.go index d8015d6e0d..44626ec141 100644 --- a/modules/web/route_test.go +++ b/modules/web/route_test.go @@ -23,17 +23,17 @@ func TestRoute1(t *testing.T) { r := NewRoute() r.Get("/{username}/{reponame}/{type:issues|pulls}", func(resp http.ResponseWriter, req *http.Request) { username := chi.URLParam(req, "username") - assert.EqualValues(t, "gitea", username) + assert.Equal(t, "gitea", username) reponame := chi.URLParam(req, "reponame") - assert.EqualValues(t, "gitea", reponame) + assert.Equal(t, "gitea", reponame) tp := chi.URLParam(req, "type") - assert.EqualValues(t, "issues", tp) + assert.Equal(t, "issues", tp) }) req, err := http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues", nil) require.NoError(t, err) r.ServeHTTP(recorder, req) - assert.EqualValues(t, http.StatusOK, recorder.Code) + assert.Equal(t, http.StatusOK, recorder.Code) } func TestRoute2(t *testing.T) { @@ -48,23 +48,23 @@ func TestRoute2(t *testing.T) { r.Group("", func() { r.Get("/{type:issues|pulls}", func(resp http.ResponseWriter, req *http.Request) { username := chi.URLParam(req, "username") - assert.EqualValues(t, "gitea", username) + assert.Equal(t, "gitea", username) reponame := chi.URLParam(req, "reponame") - assert.EqualValues(t, "gitea", reponame) + assert.Equal(t, "gitea", reponame) tp := chi.URLParam(req, "type") - assert.EqualValues(t, "issues", tp) + assert.Equal(t, "issues", tp) hit = 0 }) r.Get("/{type:issues|pulls}/{index}", func(resp http.ResponseWriter, req *http.Request) { username := chi.URLParam(req, "username") - assert.EqualValues(t, "gitea", username) + assert.Equal(t, "gitea", username) reponame := chi.URLParam(req, "reponame") - assert.EqualValues(t, "gitea", reponame) + assert.Equal(t, "gitea", reponame) tp := chi.URLParam(req, "type") - assert.EqualValues(t, "issues", tp) + assert.Equal(t, "issues", tp) index := chi.URLParam(req, "index") - assert.EqualValues(t, "1", index) + assert.Equal(t, "1", index) hit = 1 }) }, func(resp http.ResponseWriter, req *http.Request) { @@ -77,11 +77,11 @@ func TestRoute2(t *testing.T) { r.Group("/issues/{index}", func() { r.Get("/view", func(resp http.ResponseWriter, req *http.Request) { username := chi.URLParam(req, "username") - assert.EqualValues(t, "gitea", username) + assert.Equal(t, "gitea", username) reponame := chi.URLParam(req, "reponame") - assert.EqualValues(t, "gitea", reponame) + assert.Equal(t, "gitea", reponame) index := chi.URLParam(req, "index") - assert.EqualValues(t, "1", index) + assert.Equal(t, "1", index) hit = 2 }) }) @@ -90,26 +90,26 @@ func TestRoute2(t *testing.T) { req, err := http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues", nil) require.NoError(t, err) r.ServeHTTP(recorder, req) - assert.EqualValues(t, http.StatusOK, recorder.Code) - assert.EqualValues(t, 0, hit) + assert.Equal(t, http.StatusOK, recorder.Code) + assert.Equal(t, 0, hit) req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1", nil) require.NoError(t, err) r.ServeHTTP(recorder, req) - assert.EqualValues(t, http.StatusOK, recorder.Code) - assert.EqualValues(t, 1, hit) + assert.Equal(t, http.StatusOK, recorder.Code) + assert.Equal(t, 1, hit) req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1?stop=100", nil) require.NoError(t, err) r.ServeHTTP(recorder, req) - assert.EqualValues(t, http.StatusOK, recorder.Code) - assert.EqualValues(t, 100, hit) + assert.Equal(t, http.StatusOK, recorder.Code) + assert.Equal(t, 100, hit) req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1/view", nil) require.NoError(t, err) r.ServeHTTP(recorder, req) - assert.EqualValues(t, http.StatusOK, recorder.Code) - assert.EqualValues(t, 2, hit) + assert.Equal(t, http.StatusOK, recorder.Code) + assert.Equal(t, 2, hit) } func TestRoute3(t *testing.T) { @@ -150,30 +150,30 @@ func TestRoute3(t *testing.T) { req, err := http.NewRequest("GET", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections", nil) require.NoError(t, err) r.ServeHTTP(recorder, req) - assert.EqualValues(t, http.StatusOK, recorder.Code) - assert.EqualValues(t, 0, hit) + assert.Equal(t, http.StatusOK, recorder.Code) + assert.Equal(t, 0, hit) req, err = http.NewRequest("POST", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections", nil) require.NoError(t, err) r.ServeHTTP(recorder, req) - assert.EqualValues(t, http.StatusOK, recorder.Code, http.StatusOK) - assert.EqualValues(t, 1, hit) + assert.Equal(t, http.StatusOK, recorder.Code, http.StatusOK) + assert.Equal(t, 1, hit) req, err = http.NewRequest("GET", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil) require.NoError(t, err) r.ServeHTTP(recorder, req) - assert.EqualValues(t, http.StatusOK, recorder.Code) - assert.EqualValues(t, 2, hit) + assert.Equal(t, http.StatusOK, recorder.Code) + assert.Equal(t, 2, hit) req, err = http.NewRequest("PATCH", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil) require.NoError(t, err) r.ServeHTTP(recorder, req) - assert.EqualValues(t, http.StatusOK, recorder.Code) - assert.EqualValues(t, 3, hit) + assert.Equal(t, http.StatusOK, recorder.Code) + assert.Equal(t, 3, hit) req, err = http.NewRequest("DELETE", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil) require.NoError(t, err) r.ServeHTTP(recorder, req) - assert.EqualValues(t, http.StatusOK, recorder.Code) - assert.EqualValues(t, 4, hit) + assert.Equal(t, http.StatusOK, recorder.Code) + assert.Equal(t, 4, hit) } diff --git a/modules/web/routemock.go b/modules/web/routemock.go index cb41f63b91..33d2ad06eb 100644 --- a/modules/web/routemock.go +++ b/modules/web/routemock.go @@ -6,7 +6,7 @@ package web import ( "net/http" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" ) // MockAfterMiddlewares is a general mock point, it's between middlewares and the handler diff --git a/modules/web/routemock_test.go b/modules/web/routemock_test.go index cd99b99323..46f0296046 100644 --- a/modules/web/routemock_test.go +++ b/modules/web/routemock_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "code.gitea.io/gitea/modules/setting" + "forgejo.org/modules/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -35,9 +35,9 @@ func TestRouteMock(t *testing.T) { require.NoError(t, err) r.ServeHTTP(recorder, req) assert.Len(t, recorder.Header(), 3) - assert.EqualValues(t, "m1", recorder.Header().Get("X-Test-Middleware1")) - assert.EqualValues(t, "m2", recorder.Header().Get("X-Test-Middleware2")) - assert.EqualValues(t, "h", recorder.Header().Get("X-Test-Handler")) + assert.Equal(t, "m1", recorder.Header().Get("X-Test-Middleware1")) + assert.Equal(t, "m2", recorder.Header().Get("X-Test-Middleware2")) + assert.Equal(t, "h", recorder.Header().Get("X-Test-Handler")) RouteMockReset() // mock at "mock-point" @@ -50,8 +50,8 @@ func TestRouteMock(t *testing.T) { require.NoError(t, err) r.ServeHTTP(recorder, req) assert.Len(t, recorder.Header(), 2) - assert.EqualValues(t, "m1", recorder.Header().Get("X-Test-Middleware1")) - assert.EqualValues(t, "a", recorder.Header().Get("X-Test-MockPoint")) + assert.Equal(t, "m1", recorder.Header().Get("X-Test-Middleware1")) + assert.Equal(t, "a", recorder.Header().Get("X-Test-MockPoint")) RouteMockReset() // mock at MockAfterMiddlewares @@ -64,8 +64,8 @@ func TestRouteMock(t *testing.T) { require.NoError(t, err) r.ServeHTTP(recorder, req) assert.Len(t, recorder.Header(), 3) - assert.EqualValues(t, "m1", recorder.Header().Get("X-Test-Middleware1")) - assert.EqualValues(t, "m2", recorder.Header().Get("X-Test-Middleware2")) - assert.EqualValues(t, "b", recorder.Header().Get("X-Test-MockPoint")) + assert.Equal(t, "m1", recorder.Header().Get("X-Test-Middleware1")) + assert.Equal(t, "m2", recorder.Header().Get("X-Test-Middleware2")) + assert.Equal(t, "b", recorder.Header().Get("X-Test-MockPoint")) RouteMockReset() } diff --git a/modules/web/routing/logger.go b/modules/web/routing/logger.go index 5f3a7592af..8fd24c9733 100644 --- a/modules/web/routing/logger.go +++ b/modules/web/routing/logger.go @@ -8,8 +8,8 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/web/types" + "forgejo.org/modules/log" + "forgejo.org/modules/web/types" ) // NewLoggerHandler is a handler that will log routing to the router log taking account of @@ -90,7 +90,7 @@ func logPrinter(logger log.Logger) func(trigger Event, record *requestRecord) { status = v.WrittenStatus() } logf := logger.Info - if strings.HasPrefix(req.RequestURI, "/assets/") { + if strings.HasPrefix(req.RequestURI, "/assets/") || req.RequestURI == "/api/actions/runner.v1.RunnerService/FetchTask" || req.RequestURI == "/api/actions/runner.v1.RunnerService/UpdateLog" { logf = logger.Trace } message := completedMessage diff --git a/modules/web/routing/logger_manager.go b/modules/web/routing/logger_manager.go index aa25ec3a27..4b12419b44 100644 --- a/modules/web/routing/logger_manager.go +++ b/modules/web/routing/logger_manager.go @@ -9,8 +9,8 @@ import ( "sync" "time" - "code.gitea.io/gitea/modules/graceful" - "code.gitea.io/gitea/modules/process" + "forgejo.org/modules/graceful" + "forgejo.org/modules/process" ) // Event indicates when the printer is triggered diff --git a/options/gitignore/Flutter b/options/gitignore/Flutter new file mode 100644 index 0000000000..39b8814aec --- /dev/null +++ b/options/gitignore/Flutter @@ -0,0 +1,119 @@ +# Miscellaneous +*.class +*.lock +*.log +*.pyc +*.swp +.buildlog/ +.history + + + +# Flutter repo-specific +/bin/cache/ +/bin/internal/bootstrap.bat +/bin/internal/bootstrap.sh +/bin/mingit/ +/dev/benchmarks/mega_gallery/ +/dev/bots/.recipe_deps +/dev/bots/android_tools/ +/dev/devicelab/ABresults*.json +/dev/docs/doc/ +/dev/docs/flutter.docs.zip +/dev/docs/lib/ +/dev/docs/pubspec.yaml +/dev/integration_tests/**/xcuserdata +/dev/integration_tests/**/Pods +/packages/flutter/coverage/ +version +analysis_benchmark.json + +# packages file containing multi-root paths +.packages.generated + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +**/generated_plugin_registrant.dart +.packages +.pub-preload-cache/ +.pub/ +build/ +flutter_*.png +linked_*.ds +unlinked.ds +unlinked_spec.ds + +# Android related +**/android/**/gradle-wrapper.jar +.gradle/ +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +*.jks + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/ephemeral +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# macOS +**/Flutter/ephemeral/ +**/Pods/ +**/macos/Flutter/GeneratedPluginRegistrant.swift +**/macos/Flutter/ephemeral +**/xcuserdata/ + +# Windows +**/windows/flutter/generated_plugin_registrant.cc +**/windows/flutter/generated_plugin_registrant.h +**/windows/flutter/generated_plugins.cmake + +# Linux +**/linux/flutter/generated_plugin_registrant.cc +**/linux/flutter/generated_plugin_registrant.h +**/linux/flutter/generated_plugins.cmake + +# Coverage +coverage/ + +# Symbols +app.*.symbols + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock \ No newline at end of file diff --git a/options/gitignore/Nix b/options/gitignore/Nix index 1fd04ef1f6..912e6700f4 100644 --- a/options/gitignore/Nix +++ b/options/gitignore/Nix @@ -1,3 +1,6 @@ # Ignore build outputs from performing a nix-build or `nix build` command result result-* + +# Ignore automatically generated direnv output +.direnv diff --git a/options/gitignore/NotesAndCoreConfiguration b/options/gitignore/NotesAndCoreConfiguration new file mode 100644 index 0000000000..4eff01dae1 --- /dev/null +++ b/options/gitignore/NotesAndCoreConfiguration @@ -0,0 +1,16 @@ +# Excludes Obsidian workspace cache and plugins. All notes and core obsidian +# configuration files are tracked by Git. + +# The current application UI state (DOM layout, recently-opened files, etc.) is +# stored in these files (separate for desktop and mobile) so you can resume +# your session seamlessly after a restart. If you want to track UI state, use +# the Workspaces core plugin instead of relying on these files. +.obsidian/workspace.json +.obsidian/workspace-mobile.json + +# Obsidian plugins are stored under .obsidian/plugins/$plugin_name. They +# contain metadata (manifest.json), application code (main.js), stylesheets +# (styles.css), and user-configuration data (data.json). +# We want to exclude all plugin-related files, so we can exclude everything +# under this directory. +.obsidian/plugins/**/* diff --git a/options/gitignore/NotesAndExtendedConfiguration b/options/gitignore/NotesAndExtendedConfiguration new file mode 100644 index 0000000000..3e0804f299 --- /dev/null +++ b/options/gitignore/NotesAndExtendedConfiguration @@ -0,0 +1,38 @@ +# Excludes Obsidian workspace cache and plugin code, but retains plugin +# configuration. All notes and user-controlled configuration files are tracked +# by Git. +# +# !!! WARNING !!! +# +# Community plugins may store sensitive secrets in their data.json files. By +# including these files, those secrets may be tracked in your Git repository. +# +# To ignore configurations for specific plugins, add a line like this after the +# contents of this file (order is important): +# .obsidian/plugins/{{plugin_name}}/data.json +# +# Alternatively, ensure that you are treating your entire Git repository as +# sensitive data, since it may contain secrets, or may have contained them in +# past commits. Understand your threat profile, and make the decision +# appropriate for yourself. If in doubt, err on the side of not including +# plugin configuration. Use one of the alternative gitignore files instead: +# * NotesOnly.gitignore +# * NotesAndCoreConfiguration.gitignore + +# The current application UI state (DOM layout, recently-opened files, etc.) is +# stored in these files (separate for desktop and mobile) so you can resume +# your session seamlessly after a restart. If you want to track UI state, use +# the Workspaces core plugin instead of relying on these files. +.obsidian/workspace.json +.obsidian/workspace-mobile.json + +# Obsidian plugins are stored under .obsidian/plugins/$plugin_name. They +# contain metadata (manifest.json), application code (main.js), stylesheets +# (styles.css), and user-configuration data (data.json). +# We only want to track data.json, so we: +# 1. exclude everything under the plugins directory recursively, +# 2. unignore the plugin directories themselves, which then allows us to +# 3. unignore the data.json files +.obsidian/plugins/**/* +!.obsidian/plugins/*/ +!.obsidian/plugins/*/data.json diff --git a/options/gitignore/NotesOnly b/options/gitignore/NotesOnly new file mode 100644 index 0000000000..2b3b76ee0e --- /dev/null +++ b/options/gitignore/NotesOnly @@ -0,0 +1,4 @@ +# Excludes all Obsidian-related configuration. All notes are tracked by Git. + +# All Obsidian configuration and runtime state is stored here +.obsidian/**/* diff --git a/options/gitignore/Zig b/options/gitignore/Zig index 236ae6be8c..748837a058 100644 --- a/options/gitignore/Zig +++ b/options/gitignore/Zig @@ -1,4 +1,4 @@ -zig-cache/ +.zig-cache/ zig-out/ build/ build-*/ diff --git a/options/locale/locale_ar.ini b/options/locale/locale_ar.ini index 00d7b9486e..f4ac1a0e3d 100644 --- a/options/locale/locale_ar.ini +++ b/options/locale/locale_ar.ini @@ -1,6 +1,3 @@ - - - [common] language = لغة passcode = رمز المرور @@ -13,7 +10,7 @@ preview = عاين disabled = معطّل go_back = Ø¹ÙØ¯ للوراء licenses = التراخيص -sign_in = سجل الدخول +sign_in = تسجيل الدخول activities = الأنشطة copy_content = انسخ المحتوى collaborative = مشترك @@ -142,6 +139,11 @@ filter.not_fork = ليست اشتقاقات filter.not_archived = ليس مؤرش٠filter.public = علني filter.private = خاص +new_repo.title = مستودع جديد +new_migrate.title = انتقال جديد +new_org.title = منظمة جديدة +new_repo.link = مستودع جديد +new_migrate.link = انتقال جديد [install] db_name = اسم قاعدة البيانات @@ -955,7 +957,7 @@ settings.recent_deliveries = التوصيل الأخيرة projects.new = مشروع جديد file_history = تاريخ editor.directory_is_a_file = اسم المجلد "%s" مستخدم ÙØ¹Ù„ا لاسم مل٠ÙÙŠ هذا المستودع. -editor.commit_directly_to_this_branch = أودع مباشرةً إلى ÙØ±Ø¹ %s. +editor.commit_directly_to_this_branch = أودع مباشرةً إلى ÙØ±Ø¹ %[1]s. editor.unable_to_upload_files = تعذر Ø±ÙØ¹ Ø§Ù„Ù…Ù„ÙØ§Øª إلى "%s" برسالة الخطأ: %v settings.webhook.payload = المحتوى invisible_runes_header = `يحتوي هذا المل٠على محار٠يونيكود غير مرئية` @@ -1015,7 +1017,7 @@ commit.revert-header = إرجاع: %s editor.file_already_exists = يوجد ÙØ¹Ù„ا ÙÙŠ هذا المستودع مل٠باسم "%s". settings.web_hook_name_matrix = متركس editor.filename_cannot_be_empty = لا يمكن ترك اسم Ø§Ù„Ù…Ù„Ù ÙØ§Ø±ØºØ§. -editor.add_tmpl = أض٠'' +editor.add_tmpl = أض٠'<%s>' editor.new_branch_name_desc = اسم Ø§Ù„ÙØ±Ø¹ الجديد… release = إصدار editor.delete_this_file = احذ٠المل٠@@ -1100,7 +1102,7 @@ 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_branch = `إلى %s Ùˆ"` activity.git_stats_push_to_all_branches = إلى كل Ø§Ù„ÙØ±ÙˆØ¹. activity.git_stats_on_default_branch = ÙÙŠ %sØŒ activity.git_stats_file_1 = %d مل٠@@ -1110,7 +1112,7 @@ 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_and_deletions = `Ùˆ"` activity.git_stats_deletion_1 = %d إزالة activity.git_stats_deletion_n = %d إزالة settings.mirror_settings.direction = الاتجاه @@ -1982,4 +1984,10 @@ match_tooltip = قم بتضمين النتائج التي تطابق مصطلح repo_kind = بحث ÙÙŠ المستودعات... user_kind = بحث عن المستخدمين... team_kind = بحث عن Ø§Ù„ÙØ±Ù‚ ... -code_kind = بحث ÙÙŠ الكود... \ No newline at end of file +code_kind = بحث ÙÙŠ الكود... +project_kind = البحث ضمن المشاريع... +branch_kind = البحث ضمن Ø§Ù„ÙØ±ÙˆØ¹... +no_results = لا توجد نتائج مطابقة. +issue_kind = البحث ضمن الأعطال... +pull_kind = البحث ضمن طلبات السحب... +keyword_search_unavailable = البحث من خلال الكلمات Ø§Ù„Ù…ÙØªØ§Ø­ÙŠØ© ليس Ù…ØªÙˆÙØ± حالياً. رجاءاً تواصل مع مشر٠الموقع. diff --git a/options/locale/locale_be.ini b/options/locale/locale_be.ini index f9d8e738c3..fe04dadc3e 100644 --- a/options/locale/locale_be.ini +++ b/options/locale/locale_be.ini @@ -1,6 +1,3 @@ - - - [common] dashboard = ПанÑль ÐºÑ–Ñ€Ð°Ð²Ð°Ð½Ð½Ñ explore = ÐглÑд diff --git a/options/locale/locale_bg.ini b/options/locale/locale_bg.ini index 01fb84c3c6..6fc4b55eae 100644 --- a/options/locale/locale_bg.ini +++ b/options/locale/locale_bg.ini @@ -1,194 +1,3 @@ - - - -[settings] -ui = Тема -delete_key = Премахване -applications = ÐŸÑ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ -visibility = ВидимоÑÑ‚ на Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ -location = МеÑтоположение -password = Парола -appearance = Облик -new_password = Ðова парола -oauth2_application_edit = Редактиране -repos = Хранилища -can_write_info = ПиÑане -delete = Изтриване на акаунта -social = Социални акаунти -twofa = Двуфакторно удоÑтоверÑване (TOTP) -update_theme = ПромÑна на темата -can_read_info = Четене -access_token_deletion_confirm_action = Изтриване -website = УебÑайт -cancel = Отказ -delete_token = Изтриване -uid = UID -language = Език -save_application = Запазване -privacy = ПоверителноÑÑ‚ -avatar = Профилна Ñнимка -add_key = ДобавÑне на ключ -account_link = Свързани акаунти -delete_email = Премахване -update_language = ПромÑна на езика -organization = Организации -link_account = Свързване на акаунт -add_new_gpg_key = ДобавÑне на GPG ключ -manage_gpg_keys = Управление на GPG ключовете -manage_ssh_keys = Управление на SSH ключовете -old_password = Текуща парола -public_profile = Публичен профил -full_name = Пълно име -security = СигурноÑÑ‚ -add_new_key = ДобавÑне на SSH ключ -account = Ðкаунт -update_avatar = ОбновÑване на профилната Ñнимка -ssh_gpg_keys = SSH / GPG ключове -comment_type_group_milestone = Етап -manage_emails = Управление на адреÑите на ел. поща -permission_read = Четене -update_password = ОбновÑване на паролата -biography_placeholder = Разкажете ни малко за Ñебе Ñи! (Можете да използвате Markdown) -orgs = Организации -continue = Продължаване -blocked_users = Блокирани потребители -emails = ÐдреÑи на ел. поща -update_profile = ОбновÑване на профила -profile = Профил -change_password = ПромÑна на паролата -retype_new_password = Потвърдете новата парола -choose_new_avatar = Изберете нова профилна Ñнимка -delete_current_avatar = Изтриване на текущата профилна Ñнимка -gpg_key_deletion_success = GPG ключът е премахнат. -permission_no_access = Без доÑтъп -ssh_key_deletion_success = SSH ключът е премахнат. -comment_type_group_project = Проект -update_language_success = Езикът е обновен. -add_key_success = SSH ключът „%s“ е добавен. -add_gpg_key_success = GPG ключът „%s“ е добавен. -user_unblock_success = ПотребителÑÑ‚ е отблокиран уÑпешно. -user_block_success = ПотребителÑÑ‚ е блокиран уÑпешно. -update_profile_success = Профилът ви е обновен. -update_user_avatar_success = Профилната Ñнимка на Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ Ðµ обновена. -remove_oauth2_application_success = Приложението е изтрито. -email_deletion_success = ÐдреÑÑŠÑ‚ на ел. поща е премахнат. -update_avatar_success = Профилната ви Ñнимка е обновена. -change_username = ПотребителÑкото ви име е променено. -comment_type_group_assignee = Изпълнител -enable_custom_avatar = Използване на перÑонализирана профилна Ñнимка -requires_activation = ИзиÑква активиране -activated = Ðктивиран -primary = ОÑновен -email_deletion = Премахване на адреÑа на ел. поща -add_new_email = ДобавÑне на нов Ð°Ð´Ñ€ÐµÑ Ð½Ð° ел. поща -add_email = ДобавÑне на Ð°Ð´Ñ€ÐµÑ Ð½Ð° ел. поща -key_content_gpg_placeholder = Започва Ñ â€ž-----BEGIN PGP PUBLIC KEY BLOCK-----“ -comment_type_group_title = Заглавие -comment_type_group_label = Етикет -change_username_prompt = Забележка: ПромÑната на потребителÑкото ви име Ð¿Ñ€Ð¾Ð¼ÐµÐ½Ñ Ñъщо URL на Ð²Ð°ÑˆÐ¸Ñ Ð°ÐºÐ°ÑƒÐ½Ñ‚. -update_language_not_found = Езикът „%s“ не е наличен. -keep_activity_private_popup = Вашата дейноÑÑ‚ ще бъде видима Ñамо за Ð²Ð°Ñ Ð¸ админиÑтраторите на Ñайта -uploaded_avatar_not_a_image = КачениÑÑ‚ файл не е изображение. -uploaded_avatar_is_too_big = Размерът на ÐºÐ°Ñ‡ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð» (%d KiB) надвишава макÑÐ¸Ð¼Ð°Ð»Ð½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€ (%d KiB). -change_password_success = Паролата ви е обновена. Влизайте Ñ Ð½Ð¾Ð²Ð°Ñ‚Ð° Ñи парола от Ñега нататък. -manage_themes = Тема по подразбиране -manage_openid = OpenID адреÑи -primary_email = Да е оÑновен -keep_email_private = Скриване на адреÑа на ел. поща -theme_update_error = Избраната тема не ÑъщеÑтвува. -theme_update_success = Темата ви е обновена. -key_content_ssh_placeholder = Започва Ñ â€žssh-ed25519“, „ssh-rsa“, „ecdsa-sha2-nistp256“, „ecdsa-sha2-nistp384“, „ecdsa-sha2-nistp521“, „sk-ecdsa-sha2-nistp256@openssh.com“, или „sk-ssh-ed25519@openssh.com“ -hide_openid = Скриване от профила -key_content = Съдържание -ssh_key_deletion = Премахване на SSH ключ -gpg_key_deletion = Премахване на GPG ключ -key_name = Име на ключа -key_id = ID на ключа -show_openid = Показване в профила -visibility.public = Публична -visibility.limited = Ограничена -visibility.private = ЧаÑтна -location_placeholder = Споделете приблизителното Ñи меÑтоположение Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ‚Ðµ -key_signature_gpg_placeholder = Започва Ñ â€ž-----BEGIN PGP SIGNATURE-----“ -key_signature_ssh_placeholder = Започва Ñ â€ž-----BEGIN SSH SIGNATURE-----“ -saved_successfully = ÐаÑтройките бÑха запазени уÑпешно. -no_activity = ÐÑма Ñкорошна дейноÑÑ‚ -theme_desc = Това ще бъде вашата тема по подразбиране в Ñ†ÐµÐ»Ð¸Ñ Ñайт. -keep_activity_private = Скриване на дейноÑтта от профилната Ñтраница -lookup_avatar_by_mail = ТърÑене на профилна Ñнимка по адреÑа на ел. поща -password_incorrect = Текущата парола е неправилна. -change_username_redirect_prompt = Старото потребителÑко име ще Ñе пренаÑочва, докато нÑкой не го вземе. -principal_content = Съдържание -manage_ssh_principals = Управление на SSH Certificate Principals -twofa_disabled = Двуфакторното удоÑтоверÑване е изключено. -orgs_none = Ðе Ñте учаÑтник в никакви организации. -repos_none = Ðе притежавате никакви хранилища. -blocked_users_none = ÐÑма блокирани потребители. -profile_desc = Контролирайте как вашиÑÑ‚ профил Ñе показва на другите потребители. ВашиÑÑ‚ оÑновен Ð°Ð´Ñ€ÐµÑ Ð½Ð° ел. поща ще Ñе използва за извеÑтиÑ, възÑтановÑване на паролата и уеб базирани Git операции. -permission_write = Четене и пиÑане -twofa_disable = Изключване на двуфакторното удоÑтоверÑване -twofa_enroll = Включване на двуфакторно удоÑтоверÑване -ssh_key_name_used = Вече ÑъщеÑтвува SSH ключ ÑÑŠÑ Ñъщото име във Ð²Ð°ÑˆÐ¸Ñ Ð°ÐºÐ°ÑƒÐ½Ñ‚. -email_notifications.enable = Включване на извеÑтиÑта по ел. поща -delete_prompt = Тази Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ñ‰Ðµ изтрие перманентно потребителÑÐºÐ¸Ñ Ð²Ð¸ акаунт. Това ÐЕ МОЖЕ да бъде отменено. -email_notifications.disable = Изключване на извеÑтиÑта по ел. поща -delete_account = Изтриване на акаунта ви -confirm_delete_account = Потвърждаване на изтриването -email_notifications.onmention = Ел. поща Ñамо при Ñпоменаване -pronouns_unspecified = ÐепоÑочени -pronouns = МеÑÑ‚Ð¾Ð¸Ð¼ÐµÐ½Ð¸Ñ -gpg_token_code = echo "%s" | gpg -a --default-key %s --detach-sig -language.title = Език по подразбиране -language.localization_project = Помогнете ни да преведем Forgejo на Ð²Ð°ÑˆÐ¸Ñ ÐµÐ·Ð¸Ðº! Ðаучете повече. -language.description = Този език ще бъде запазен във Ð²Ð°ÑˆÐ¸Ñ Ð°ÐºÐ°ÑƒÐ½Ñ‚ и ще Ñе използва като език по подразбиране, Ñлед като влезете. - -[packages] -container.labels.value = СтойноÑÑ‚ -alpine.repository.repositories = Хранилища -dependency.version = ВерÑÐ¸Ñ -title = Пакети -empty = Ð’Ñе още нÑма пакети. -empty.documentation = За повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно региÑтъра на пакетите вижте документациÑта. -container.labels.key = Ключ -requirements = ИзиÑÐºÐ²Ð°Ð½Ð¸Ñ -details = ПодробноÑти -details.license = Лиценз -container.labels = Етикети -versions = ВерÑии -empty.repo = Качихте ли пакет, но той не Ñе показва тук? Отидете в наÑтройките за пакети и го Ñвържете към това хранилище. -keywords = Ключови думи -details.author = Ðвтор -about = ОтноÑно този пакет -settings.delete.success = Пакетът е изтрит. -settings.delete = Изтриване на пакета -container.details.platform = Платформа -settings.delete.error = ÐеуÑпешно изтриване на пакет. -installation = ИнÑÑ‚Ð°Ð»Ð°Ñ†Ð¸Ñ -versions.view_all = Вижте вÑички -dependencies = ЗавиÑимоÑти -published_by_in = Публикуван %[1]s от %[3]s в %[5]s -published_by = Публикуван %[1]s от %[3]s - -[tool] -hours = %d чаÑа -now = Ñега -raw_seconds = Ñекунди -1m = 1 минута -1s = 1 Ñекунда -months = %d меÑеца -weeks = %d Ñедмици -1w = 1 Ñедмица -years = %d години -seconds = %d Ñекунди -days = %d дни -1d = 1 ден -minutes = %d минути -1mon = 1 меÑец -1h = 1 Ñ‡Ð°Ñ -1y = 1 година -future = бъдеще -raw_minutes = минути - [common] language = Език cancel = Отказ @@ -302,6 +111,202 @@ new_migrate.link = Ðова Ð¼Ð¸Ð³Ñ€Ð°Ñ†Ð¸Ñ new_org.link = Ðова Ð¾Ñ€Ð³Ð°Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ copy_generic = Копиране в клипборда copy_error = ÐеуÑпешно копиране +copy_path = Копиране на Ð¿ÑŠÑ‚Ñ + +[settings] +ui = Тема +delete_key = Премахване +applications = ÐŸÑ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ +visibility = ВидимоÑÑ‚ на Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ +location = МеÑтоположение +password = Парола +appearance = Облик +new_password = Ðова парола +oauth2_application_edit = Редактиране +repos = Хранилища +can_write_info = ПиÑане +delete = Изтриване на акаунта +social = Социални акаунти +twofa = Двуфакторно удоÑтоверÑване (TOTP) +update_theme = ПромÑна на темата +can_read_info = Четене +access_token_deletion_confirm_action = Изтриване +website = УебÑайт +cancel = Отказ +delete_token = Изтриване +uid = UID +language = Език +save_application = Запазване +privacy = ПоверителноÑÑ‚ +avatar = Профилна Ñнимка +add_key = ДобавÑне на ключ +account_link = Свързани акаунти +delete_email = Премахване +update_language = ПромÑна на езика +organization = Организации +link_account = Свързване на акаунт +add_new_gpg_key = ДобавÑне на GPG ключ +manage_gpg_keys = Управление на GPG ключовете +manage_ssh_keys = Управление на SSH ключовете +old_password = Текуща парола +public_profile = Публичен профил +full_name = Пълно име +security = СигурноÑÑ‚ +add_new_key = ДобавÑне на SSH ключ +account = Ðкаунт +update_avatar = ОбновÑване на профилната Ñнимка +ssh_gpg_keys = SSH / GPG ключове +comment_type_group_milestone = Етап +manage_emails = Управление на адреÑите на ел. поща +permission_read = Четене +update_password = ОбновÑване на паролата +biography_placeholder = Разкажете на другите малко за Ñебе Ñи! (Можете да използвате Маркдаун) +orgs = Организации +continue = Продължаване +blocked_users = Блокирани потребители +emails = ÐдреÑи на ел. поща +update_profile = ОбновÑване на профила +profile = Профил +change_password = ПромÑна на паролата +retype_new_password = Потвърдете новата парола +choose_new_avatar = Изберете нова профилна Ñнимка +delete_current_avatar = Изтриване на текущата профилна Ñнимка +gpg_key_deletion_success = GPG ключът е премахнат. +permission_no_access = Без доÑтъп +ssh_key_deletion_success = SSH ключът е премахнат. +comment_type_group_project = Проект +update_language_success = Езикът е обновен. +add_key_success = SSH ключът „%s“ е добавен. +add_gpg_key_success = GPG ключът „%s“ е добавен. +user_unblock_success = ПотребителÑÑ‚ е отблокиран уÑпешно. +user_block_success = ПотребителÑÑ‚ е блокиран уÑпешно. +update_profile_success = Профилът ви е обновен. +update_user_avatar_success = Профилната Ñнимка на Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ Ðµ обновена. +remove_oauth2_application_success = Приложението е изтрито. +email_deletion_success = ÐдреÑÑŠÑ‚ на ел. поща е премахнат. +update_avatar_success = Профилната ви Ñнимка е обновена. +change_username = ПотребителÑкото ви име е променено. +comment_type_group_assignee = Изпълнител +enable_custom_avatar = Използване на перÑонализирана профилна Ñнимка +requires_activation = ИзиÑква активиране +activated = Ðктивиран +primary = ОÑновен +email_deletion = Премахване на адреÑа на ел. поща +add_new_email = ДобавÑне на нов Ð°Ð´Ñ€ÐµÑ Ð½Ð° ел. поща +add_email = ДобавÑне на Ð°Ð´Ñ€ÐµÑ Ð½Ð° ел. поща +key_content_gpg_placeholder = Започва Ñ â€ž-----BEGIN PGP PUBLIC KEY BLOCK-----“ +comment_type_group_title = Заглавие +comment_type_group_label = Етикет +change_username_prompt = Забележка: ПромÑната на потребителÑкото ви име Ð¿Ñ€Ð¾Ð¼ÐµÐ½Ñ Ñъщо URL на Ð²Ð°ÑˆÐ¸Ñ Ð°ÐºÐ°ÑƒÐ½Ñ‚. +update_language_not_found = Езикът „%s“ не е наличен. +keep_activity_private_popup = Вашата дейноÑÑ‚ ще бъде видима Ñамо за Ð²Ð°Ñ Ð¸ админиÑтраторите на Ñайта +uploaded_avatar_not_a_image = КачениÑÑ‚ файл не е изображение. +uploaded_avatar_is_too_big = Размерът на ÐºÐ°Ñ‡ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð» (%d KiB) надвишава макÑÐ¸Ð¼Ð°Ð»Ð½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€ (%d KiB). +change_password_success = Паролата ви е обновена. Влизайте Ñ Ð½Ð¾Ð²Ð°Ñ‚Ð° Ñи парола от Ñега нататък. +manage_themes = Тема по подразбиране +manage_openid = OpenID адреÑи +primary_email = Да е оÑновен +keep_email_private = Скриване на адреÑа на ел. поща +theme_update_error = Избраната тема не ÑъщеÑтвува. +theme_update_success = Темата ви е обновена. +key_content_ssh_placeholder = Започва Ñ â€žssh-ed25519“, „ssh-rsa“, „ecdsa-sha2-nistp256“, „ecdsa-sha2-nistp384“, „ecdsa-sha2-nistp521“, „sk-ecdsa-sha2-nistp256@openssh.com“, или „sk-ssh-ed25519@openssh.com“ +hide_openid = Скриване от профила +key_content = Съдържание +ssh_key_deletion = Премахване на SSH ключ +gpg_key_deletion = Премахване на GPG ключ +key_name = Име на ключа +key_id = ID на ключа +show_openid = Показване в профила +visibility.public = Публична +visibility.limited = Ограничена +visibility.private = ЧаÑтна +location_placeholder = Споделете приблизителното Ñи меÑтоположение Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ‚Ðµ +key_signature_gpg_placeholder = Започва Ñ â€ž-----BEGIN PGP SIGNATURE-----“ +key_signature_ssh_placeholder = Започва Ñ â€ž-----BEGIN SSH SIGNATURE-----“ +saved_successfully = ÐаÑтройките бÑха запазени уÑпешно. +no_activity = ÐÑма Ñкорошна дейноÑÑ‚ +theme_desc = Тази тема ще Ñе използва за уеб интерфейÑа, когато Ñте влезли. +keep_activity_private = Скриване на дейноÑтта от профилната Ñтраница +lookup_avatar_by_mail = ТърÑене на профилна Ñнимка по адреÑа на ел. поща +password_incorrect = Текущата парола е неправилна. +change_username_redirect_prompt = Старото потребителÑко име ще Ñе пренаÑочва, докато нÑкой не го вземе. +principal_content = Съдържание +manage_ssh_principals = Управление на SSH Certificate Principals +twofa_disabled = Двуфакторното удоÑтоверÑване е изключено. +orgs_none = Ðе Ñте учаÑтник в никакви организации. +repos_none = Ðе притежавате никакви хранилища. +blocked_users_none = ÐÑма блокирани потребители. +profile_desc = ВашиÑÑ‚ профил +permission_write = Четене и пиÑане +twofa_disable = Изключване на двуфакторното удоÑтоверÑване +twofa_enroll = Включване на двуфакторно удоÑтоверÑване +ssh_key_name_used = Вече ÑъщеÑтвува SSH ключ ÑÑŠÑ Ñъщото име във Ð²Ð°ÑˆÐ¸Ñ Ð°ÐºÐ°ÑƒÐ½Ñ‚. +email_notifications.enable = Включване на извеÑтиÑта по ел. поща +delete_prompt = Тази Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ñ‰Ðµ изтрие перманентно потребителÑÐºÐ¸Ñ Ð²Ð¸ акаунт. Това ÐЕ МОЖЕ да бъде отменено. +email_notifications.disable = Изключване на извеÑтиÑта по ел. поща +delete_account = Изтриване на акаунта ви +confirm_delete_account = Потвърждаване на изтриването +email_notifications.onmention = Ел. поща Ñамо при Ñпоменаване +pronouns_unspecified = ÐепоÑочени +pronouns = МеÑÑ‚Ð¾Ð¸Ð¼ÐµÐ½Ð¸Ñ +gpg_token_code = echo "%s" | gpg -a --default-key %s --detach-sig +language.title = Език по подразбиране +language.localization_project = Помогнете ни да преведем Forgejo на Ð²Ð°ÑˆÐ¸Ñ ÐµÐ·Ð¸Ðº! Ðаучете повече. +language.description = Този език ще бъде запазен във Ð²Ð°ÑˆÐ¸Ñ Ð°ÐºÐ°ÑƒÐ½Ñ‚ и ще Ñе използва като език по подразбиране, Ñлед като влезете. +pronouns_custom = ПерÑонализирани +visibility.limited_tooltip = Видимо Ñамо за влезли потребители +pronouns_custom_label = ПерÑонализирани меÑÑ‚Ð¾Ð¸Ð¼ÐµÐ½Ð¸Ñ + +[packages] +container.labels.value = СтойноÑÑ‚ +alpine.repository.repositories = Хранилища +dependency.version = ВерÑÐ¸Ñ +title = Пакети +empty = Ð’Ñе още нÑма пакети. +empty.documentation = За повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно региÑтъра на пакетите вижте документациÑта. +container.labels.key = Ключ +requirements = ИзиÑÐºÐ²Ð°Ð½Ð¸Ñ +details = ПодробноÑти +details.license = Лиценз +container.labels = Етикети +versions = ВерÑии +empty.repo = Качихте ли пакет, но той не Ñе показва тук? Отидете в наÑтройките за пакети и го Ñвържете към това хранилище. +keywords = Ключови думи +details.author = Ðвтор +about = ОтноÑно този пакет +settings.delete.success = Пакетът е изтрит. +settings.delete = Изтриване на пакета +container.details.platform = Платформа +settings.delete.error = ÐеуÑпешно изтриване на пакет. +installation = ИнÑÑ‚Ð°Ð»Ð°Ñ†Ð¸Ñ +versions.view_all = Вижте вÑички +dependencies = ЗавиÑимоÑти +published_by_in = Публикуван %[1]s от %[3]s в %[5]s +published_by = Публикуван %[1]s от %[3]s +generic.download = Изтеглете пакета от ÐºÐ¾Ð¼Ð°Ð½Ð´Ð½Ð¸Ñ Ñ€ÐµÐ´: +container.details.type = Тип образ +alpine.repository = За хранилището +container.images.title = Образи + +[tool] +hours = %d чаÑа +now = Ñега +raw_seconds = Ñекунди +1m = 1 минута +1s = 1 Ñекунда +months = %d меÑеца +weeks = %d Ñедмици +1w = 1 Ñедмица +years = %d години +seconds = %d Ñекунди +days = %d дни +1d = 1 ден +minutes = %d минути +1mon = 1 меÑец +1h = 1 Ñ‡Ð°Ñ +1y = 1 година +future = бъдеще +raw_minutes = минути [repo] issues.context.edit = Редактиране @@ -399,7 +404,7 @@ issues.keyword_search_unavailable = Ð’ момента търÑенето по к repo_desc_helper = Въведете кратко опиÑание (опционално) mirror_address = Клониране от URL owner_helper = ÐÑкои организации може да не Ñе показват в падащото меню поради ограничение за макÑимален брой хранилища. -new_repo_helper = Хранилището Ñъдържа вÑички файлове на проекта, включително хронологиÑта на ревизиите. Вече хоÑтвате хранилище другаде? Мигрирайте хранилище. +new_repo_helper = Хранилището Ñъдържа вÑички файлове на проекта, включително хронологиÑта на ревизиите. Вече хоÑтвате хранилище другаде? Мигрирайте хранилище. repo_name_helper = Добрите имена на хранилища използват кратки, запомнÑщи Ñе и уникални ключови думи. migrated_from = Мигрирано от %[2]s visibility_description = Само притежателÑÑ‚ или учаÑтниците в организациÑта, ако имат права, ще могат да го видÑÑ‚. @@ -426,14 +431,14 @@ settings.add_webhook = ДобавÑне на уеб-кука template.webhooks = Уеб-куки issues.label_templates.info = Ð’Ñе още нÑма етикети. Създайте етикет Ñ â€žÐов етикет“ или използвайте предварително зададен набор от етикети: labels = Етикети -license_helper_desc = Лицензът Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»Ñ ÐºÐ°ÐºÐ²Ð¾ могат и какво не могат да правÑÑ‚ другите Ñ Ð²Ð°ÑˆÐ¸Ñ ÐºÐ¾Ð´. Ðе Ñте Ñигурни кой е подходÑщ за Ð²Ð°ÑˆÐ¸Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚? Вижте Избиране на лиценз. +license_helper_desc = Лицензът Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»Ñ ÐºÐ°ÐºÐ²Ð¾ могат и какво не могат да правÑÑ‚ другите Ñ Ð²Ð°ÑˆÐ¸Ñ ÐºÐ¾Ð´. Ðе Ñте Ñигурни кой е подходÑщ за Ð²Ð°ÑˆÐ¸Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚? Вижте Избиране на лиценз. issues.choose.blank = По подразбиране settings.hooks = Уеб-куки issue_labels = Етикети issue_labels_helper = Изберете набор от етикети readme_helper_desc = Това е мÑÑтото, където можете да напишете пълно опиÑание на Ð²Ð°ÑˆÐ¸Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚. repo_gitignore_helper = Изберете .gitignore шаблони -auto_init = Да Ñе инициализира хранилище (Ð”Ð¾Ð±Ð°Ð²Ñ .gitignore, License и README) +auto_init = Да Ñе инициализира хранилище template.issue_labels = Етикети за задачите migrate_items_labels = Етикети issues.label_templates.title = Зареждане на предв. зададен набор от етикети @@ -632,9 +637,9 @@ editor.file_delete_success = Файлът „%s“ е изтрит. projects.type.uncategorized = Ðекатегоризирано projects.column.set_default = Задаване по подразбиране projects.column.assigned_to = Възложено на -issues.reopen_comment_issue = Коментиране и отварÑне +issues.reopen_comment_issue = ОтварÑне наново Ñ ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð°Ñ€ issues.reopen_issue = ОтварÑне наново -issues.close_comment_issue = Коментиране и ЗатварÑне +issues.close_comment_issue = ЗатварÑне Ñ ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð°Ñ€ milestones.filter_sort.latest_due_date = Ðай-далечен краен Ñрок diff.view_file = Преглед на файла release.deletion_success = Изданието е изтрито. @@ -817,7 +822,7 @@ diff.browse_source = Разглеждане на Ð¸Ð·Ñ…Ð¾Ð´Ð½Ð¸Ñ ÐºÐ¾Ð´ file_view_rendered = Преглед на Ð²Ð¸Ð·ÑƒÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ issues.lock_with_reason = заключи като %s и ограничи обÑъждането до Ñътрудници %s milestones.new_subheader = Етапите ви помагат да управлÑвате задачите и да проÑледÑвате напредъка им. -release.edit = редактиране +release.edit = Редактиране activity.published_release_label = Издание activity.navbar.contributors = ДопринеÑли pulls.recently_pushed_new_branches = ИзтлаÑкахте в клона %[1]s %[2]s @@ -833,7 +838,8 @@ file_too_large = Файлът е твърде голÑм, за да бъде п commits = ÐŸÐ¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ commit = Подаване editor.commit_changes = Подаване на промените -editor.add_tmpl = ДобавÑне на "<име на файла>" +editor.add_tmpl = ДобавÑне на "<%s>" +editor.add_tmpl.filename = име на файла editor.add = ДобавÑне на %s editor.delete = Изтриване на %s editor.update = ОбновÑване на %s @@ -845,7 +851,7 @@ editor.propose_file_change = Предлагане на промÑна на фа editor.create_new_branch = Създаване на нов клон за това подаване и започване на заÑвка за Ñливане. editor.create_new_branch_np = Създаване на нов клон за това подаване. editor.filename_is_invalid = Името на файла е невалидно: „%s“. -editor.commit_directly_to_this_branch = Подаване директно към клона %s. +editor.commit_directly_to_this_branch = Подаване директно към клона %[1]s. editor.branch_already_exists = Клонът „%s“ вече ÑъщеÑтвува в това хранилище. editor.file_already_exists = Файл Ñ Ð¸Ð¼Ðµ „%s“ вече ÑъщеÑтвува в това хранилище. editor.commit_empty_file_header = Подаване на празен файл @@ -940,7 +946,7 @@ pulls.approve_count_1 = %d одобрение pulls.can_auto_merge_desc = Тази заÑвка за Ñливане може да бъде ÑлÑта автоматично. pulls.num_conflicting_files_1 = %d конфликтен файл activity.git_stats_commit_n = %d Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ -settings.event_issues = Задачи +settings.event_issues = Изменение branch.delete_head = Изтриване branch.delete = Изтриване на клона „%s“ branch.delete_html = Изтриване на клона @@ -996,7 +1002,7 @@ pulls.cmd_instruction_hint = Вижте инÑтрукциите за коман pulls.showing_only_single_commit = Показани Ñа Ñамо промените в подаване %[1]s issues.lock_no_reason = заключи и ограничи обÑъждането до Ñътрудници %s pulls.expand_files = Разгъване на вÑички файлове -pulls.title_desc_few = иÑка да Ñлее %[1]d Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ Ð¾Ñ‚ %[2]s в %[3]s +pulls.title_desc_few = иÑка да Ñлее %[1]d Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ Ð¾Ñ‚ %[2]s в %[3]s issues.content_history.deleted = изтрито activity.git_stats_exclude_merges = С изключение на ÑливаниÑта, activity.navbar.pulse = ПоÑледна дейноÑÑ‚ @@ -1016,11 +1022,11 @@ pulls.collapse_files = Свиване на вÑички файлове pulls.show_all_commits = Показване на вÑички Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ diff.whitespace_button = Празни знаци issues.content_history.edited = редактирано -pulls.title_desc_one = иÑка да Ñлее %[1]d подаване от %[2]s в %[3]s +pulls.title_desc_one = иÑка да Ñлее %[1]d подаване от %[2]s в %[3]s pulls.showing_specified_commit_range = Показани Ñа Ñамо промените между %[1]s..%[2]s pulls.merged_title_desc_one = ÑÐ»Ñ %[1]d подаване от %[2]s в %[3]s %[4]s pulls.no_merge_access = Ðе Ñте упълномощени за Ñливане на тази заÑвка за Ñливане. -activity.navbar.code_frequency = ЧеÑтота на кода +activity.navbar.code_frequency = ЧеÑтота на промените activity.git_stats_pushed_1 = е изтлаÑкал activity.git_stats_push_to_branch = към %s и contributors.contribution_type.commits = ÐŸÐ¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ @@ -1081,11 +1087,11 @@ pulls.commit_ref_at = `Ñпомена тази заÑвка за Ñливане issues.change_ref_at = `промени препратката от %s на %s %s` diff.review.reject = ПоиÑкване на промени diff.bin_not_shown = ДвоичниÑÑ‚ файл не е показан. -settings.units.units = Елементи на хранилището +settings.units.units = Елементи settings.delete_notices_fork_1 = - РазклонениÑта на това хранилище ще Ñтанат незавиÑими Ñлед изтриване. settings.actions_desc = Включване на интегрираните CI/CD pipelines Ñ Forgejo Actions settings.packages_desc = Включване на региÑтъра на пакетите за хранилището -settings.units.add_more = ДобавÑне... +settings.units.add_more = Включване на повече settings.use_external_issue_tracker = Използване на външен тракер за задачи settings.releases_desc = Включване на изданиÑта за хранилището settings.projects_desc = Включване на проектите за хранилището @@ -1114,7 +1120,7 @@ pulls.reject_count_1 = %d поиÑкана промÑна issues.review.show_resolved = Показване на решено issues.review.hide_resolved = Скриване на решено issues.review.resolve_conversation = Решаване на обÑъждането -diff.comment.markdown_info = Поддържа Ñе Ñтилизиране Ñ markdown. +diff.comment.markdown_info = Поддържа Ñе Ñтилизиране Ñ ÐœÐ°Ñ€ÐºÐ´Ð°ÑƒÐ½. diff.file_suppressed = Разликите не Ñа показани, защото Ñа твърде много pulls.reject_count_n = %d поиÑкани промени settings.pulls.default_allow_edits_from_maintainers = ПозволÑване на редакции от поддържащите по подразбиране @@ -1183,6 +1189,107 @@ diff.hide_file_tree = Скриване на файловото дърво tag.ahead.target = в %s Ñлед този маркер diff.file_image_width = Широчина activity.unresolved_conv_label = Отворено +invisible_runes_line = `Този ред Ñъдържа невидими Уникод знаци` +code.desc = ДоÑтъп до Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð½Ð¸Ñ ÐºÐ¾Ð´, файловете, подаваниÑта и клоновете. +settings.branches.update_default_branch = ОбновÑване на ÑÑ‚Ð°Ð½Ð´Ð°Ñ€Ñ‚Ð½Ð¸Ñ ÐºÐ»Ð¾Ð½ +settings.default_branch_desc = Изберете Ñтандартен клон за хранилището, за заÑвки за Ñливане и Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ Ð½Ð° код: +settings.transfer.button = ПрехвърлÑне на притежанието +settings.transfer.modal.title = ПрехвърлÑне на притежанието +ambiguous_runes_line = `Този ред Ñъдържа двуÑмиÑлени Уникод знаци` +ambiguous_character = `%[1]c [U+%04[1]X] може да бъде объркан Ñ %[2]c [U+%04[2]X]` +invisible_runes_header = `Този файл Ñъдържа невидими Уникод знаци` +issues.all_title = Общо +issues.new.assign_to_me = Възлагане на мен +ext_wiki = Външно уики +ext_issues = Външни задачи +readme_helper = Изберете шаблон за файл README +settings.event_pull_request_review_desc = ЗаÑвка за Ñливане е одобрена, отхвърлена или Ñа добавени рецензионни коментари. +settings.event_pull_request_review = Рецензии +issues.filter_sort.relevance = СъответÑтвие +settings.confirm_wiki_branch_rename = Преименуване на клона на уикито +settings.webhook.request = ЗаÑвка +settings.webhook.response = Отговор +settings.event_create = Създаване +settings.event_push_only = Ð¡ÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð¿Ñ€Ð¸ изтлаÑкване +settings.event_delete = Изтриване +settings.event_header_repository = Ð¡ÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð·Ð° хранилището +settings.event_fork_desc = Хранилище е разклонено. +settings.event_fork = РазклонÑване +settings.event_wiki_desc = Уики Ñтраница е Ñъздадена, преименувана, редактирана или изтрита. +settings.event_issue_milestone = Етапи +settings.event_pull_request_milestone_desc = Етап е добавен, премахнат или изменен. +settings.event_pull_request_label_desc = Етикети на заÑвка за Ñливане Ñа добавени или премахнати. +settings.event_pull_request_merge = Сливане на заÑвка за Ñливане +settings.archive.tagsettings_unavailable = ÐаÑтройките за маркери не Ñа налични в архивирани хранилища. +settings.event_desc = ЗадейÑтване при: +settings.event_create_desc = Клон или маркер е Ñъздаден. +generate_from = Генериране от +settings.event_push_desc = Git изтлаÑкване към хранилище. +settings.event_package = Пакет +settings.event_pull_request_label = Етикети +settings.event_pull_request_assign_desc = ЗаÑвка за Ñливане е възложена или отвъзложена. +settings.event_choose = ПерÑонализирани ÑъбитиÑ… +settings.event_header_issue = Ð¡ÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð¿Ñ€Ð¸ задачи +fork_no_valid_owners = Това хранилище не може да бъде разклонено, защото нÑма валидни притежатели. +settings.unarchive.text = Разархивирането на хранилище ще възÑтанови ÑпоÑобноÑтта му да получава Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ Ð¸ изтлаÑкваниÑ, както и нови задачи и заÑвки за Ñливане. +settings.archive.branchsettings_unavailable = ÐаÑтройките за клонове не Ñа налични в архивирани хранилища. +settings.event_send_everything = Ð’Ñички ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ +settings.event_pull_request_approvals = ÐžÐ´Ð¾Ð±Ñ€ÐµÐ½Ð¸Ñ Ð½Ð° заÑвка за Ñливане +release.invalid_external_url = Ðевалиден външен URL адреÑ: "%s" +settings.event_delete_desc = Клон или маркер е изтрит. +settings.discord_icon_url = URL Ð°Ð´Ñ€ÐµÑ Ð½Ð° иконка +settings.discord_icon_url.exceeds_max_length = URL адреÑÑŠÑ‚ на иконката трÑбва да е по-малък или равен на 2048 знака +settings.event_push = ИзтлаÑкване +settings.event_repository_desc = Хранилище е Ñъздадено или изтрито. +settings.slack_icon_url = URL Ð°Ð´Ñ€ÐµÑ Ð½Ð° иконка +settings.event_issue_comment = Коментари +settings.event_pull_request_desc = ЗаÑвка за Ñливане е отворена, затворена, отворена наново или редактирана. +settings.event_issue_comment_desc = Коментар на задача е Ñъздаден, редактиран или изтрит. +settings.event_release_desc = Издание е публикувано, обновено или изтрито в хранилище. +settings.event_pull_request_review_request = ИÑÐºÐ°Ð½Ð¸Ñ Ð·Ð° Ñ€ÐµÑ†ÐµÐ½Ð·Ð¸Ñ +settings.event_pull_request_enforcement = Принудително изпълнение +diff.git-notes.remove-header = Премахване на бележката +diff.git-notes.add = ДобавÑне на бележка +settings.event_pull_request_assign = Възлагане +new_advanced_expand = Щракнете за разгъване +new_advanced = Разширени наÑтройки +new_from_template = Използване на шаблон +new_from_template_description = Можете да изберете ÑъщеÑтвуващо шаблонно хранилище в тази инÑÑ‚Ð°Ð½Ñ†Ð¸Ñ Ð¸ да приложите неговите наÑтройки. +settings.event_pull_request_comment = Коментари +repo_gitignore_helper_desc = Изберете кои файлове да не Ñе проÑледÑват от ÑпиÑък Ñ ÑˆÐ°Ð±Ð»Ð¾Ð½Ð¸ за обичайните езици. Типичните артефакти, генерирани от инÑтрументите за изграждане, Ñа включени в .gitignore по подразбиране. +object_format_helper = Формат на обектите на хранилището. Ðе може да Ñе Ð¿Ñ€Ð¾Ð¼ÐµÐ½Ñ Ð¿Ð¾-къÑно. SHA1 е най-ÑъвмеÑтим. +issues.num_reviews_one = %d Ñ€ÐµÑ†ÐµÐ½Ð·Ð¸Ñ +settings.event_pull_request = Изменение +settings.event_issue_label = Етикети +settings.event_issue_assign = Възлагане +settings.event_header_pull_request = Ð¡ÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð¿Ñ€Ð¸ заÑвка за Ñливане +settings.event_issue_milestone_desc = Етап е добавен, премахнат или изменен. +settings.event_issue_label_desc = Етикети на задача Ñа добавени или премахнати. +settings.event_issues_desc = Задача е отворена, затворена, отворена наново или редактирана. +settings.webhook.headers = Заглавки +settings.webhook.body = ТÑло +settings.event_pull_request_sync = Синхронизирано +settings.event_pull_request_sync_desc = Клонът е обновен автоматично Ñ Ñ†ÐµÐ»ÐµÐ²Ð¸Ñ ÐºÐ»Ð¾Ð½. +settings.event_package_desc = Пакет е Ñъздаден или изтрит в хранилище. +template_description = Шаблонните хранилища позволÑват на потребителите да генерират нови хранилища ÑÑŠÑ Ñъщата Ñтруктура на директориите, файлове и опционални наÑтройки. +auto_init_description = ПоÑтавете началото на Git иÑториÑта Ñ README и по избор добавете файлове License и .gitignore. +pulls.sign_in_require = Влезте, за да Ñъздадете нова заÑвка за Ñливане. +issues.num_reviews_few = %d рецензии +diff.git-notes.remove-body = Тази бележка ще бъде премахната. +issues.review.add_remove_review_requests = поиÑка рецензии от %[1]s и премахна заÑвки за Ñ€ÐµÑ†ÐµÐ½Ð·Ð¸Ñ Ð·Ð° %[2]s %[3]s +form.name_pattern_not_allowed = Шаблонът "%s" не е разрешен в име на хранилище. +settings.wiki_rename_branch_main_notices_2 = Това ще преименува перманентно Ð²ÑŠÑ‚Ñ€ÐµÑˆÐ½Ð¸Ñ ÐºÐ»Ð¾Ð½ на уикито на хранилището %s. СъщеÑтвуващите изтеглÑÐ½Ð¸Ñ Ñ‰Ðµ трÑбва да бъдат обновени. +settings.event_pull_request_milestone = Етапи +settings.event_pull_request_comment_desc = ЗаÑвка за Ñливане е Ñъздадена, редактирана или изтрита. +settings.event_issue_assign_desc = Задача е възложена или отвъзложена. +settings.event_pull_request_review_request_desc = Ð ÐµÑ†ÐµÐ½Ð·Ð¸Ñ Ð½Ð° заÑвка за Ñливане е поиÑкана или е премахната. +generate_repo = Генериране на хранилище +default_branch_helper = СтандартниÑÑ‚ клон е оÑÐ½Ð¾Ð²Ð½Ð¸Ñ ÐºÐ»Ð¾Ð½ за заÑвки за Ñливане и Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ Ð½Ð° код. +issues.reaction.add = ДобавÑне на Ñ€ÐµÐ°ÐºÑ†Ð¸Ñ +issues.reaction.alt_few = %[1]s реагира Ñ %[2]s. +issues.reaction.alt_many = %[1]s и още %[2]d реагираха Ñ %[3]s. +issues.reaction.alt_add = ДобавÑне на Ñ€ÐµÐ°ÐºÑ†Ð¸Ñ %[1]s към коментара. +issues.reaction.alt_remove = Премахване на Ñ€ÐµÐ°ÐºÑ†Ð¸Ñ %[1]s от коментара. [modal] confirm = Потвърждаване @@ -1206,6 +1313,12 @@ buttons.italic.tooltip = ДобавÑне на курÑив текÑÑ‚ buttons.link.tooltip = ДобавÑне на връзка buttons.disable_monospace_font = Изключване на Ñ€Ð°Ð²Ð½Ð¾ÑˆÐ¸Ñ€Ð¾ÐºÐ¸Ñ ÑˆÑ€Ð¸Ñ„Ñ‚ buttons.ref.tooltip = Препратка към задача или заÑвка за Ñливане +table_modal.label.columns = Колони +table_modal.label.rows = Редове +table_modal.placeholder.content = Съдържание +table_modal.placeholder.header = Заглавка +buttons.new_table.tooltip = ДобавÑне на таблица +table_modal.header = ДобавÑне на таблица [org] teams.write_access = ПиÑане @@ -1237,7 +1350,7 @@ settings.visibility.public = Публична settings.visibility.limited_shortname = Ограничена settings.visibility.private_shortname = ЧаÑтна settings.permission = Ð Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ -settings.visibility.limited = Ограничена (видима Ñамо за удоÑтоверени потребители) +settings.visibility.limited = Ограничена (видима Ñамо за влезли потребители) settings.visibility.private = ЧаÑтна (видима Ñамо за учаÑтниците в организациÑта) org_name_helper = Имената на организациите е добре да Ñа кратки и запомнÑщи Ñе. org_full_name_holder = Пълно име на организациÑта @@ -1279,6 +1392,7 @@ members.member = УчаÑтник members.private_helper = Да е видим teams.no_desc = Този екип нÑма опиÑание settings.delete_org_desc = Тази Ð¾Ñ€Ð³Ð°Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ñ‰Ðµ бъде изтрита перманентно. Продължаване? +open_dashboard = ОтварÑне на таблото [install] admin_password = Парола @@ -1317,6 +1431,9 @@ err_empty_admin_email = ÐдминиÑтраторÑкиÑÑ‚ Ð°Ð´Ñ€ÐµÑ Ð½Ð° е password_algorithm = Ðлгоритъм за хеш. на паролите default_keep_email_private = Скриване на адреÑите на ел. поща по подразбиране invalid_password_algorithm = Ðевалиден алгоритъм за хеш. на паролите +err_admin_name_is_reserved = ПотребителÑкото име на админиÑтратора е невалидно, потребителÑкото име е резервирано +err_admin_name_pattern_not_allowed = ПотребителÑкото име на админиÑтратора е невалидно, потребителÑкото име ÑъответÑтва Ñ Ñ€ÐµÐ·ÐµÑ€Ð²Ð¸Ñ€Ð°Ð½ шаблон +err_admin_name_is_invalid = ПотребителÑкото име на админиÑтратора е невалидно [filter] string.asc = Ð - Я @@ -1348,6 +1465,14 @@ activate_account.text_1 = Здравейте, %[1]s, благодарим activate_email.text = МолÑ, щракнете върху Ñледната връзка, за да потвърдите ÑÐ²Ð¾Ñ Ð°Ð´Ñ€ÐµÑ Ð½Ð° ел. поща в рамките на %s: activate_email = Потвърдете ÑÐ²Ð¾Ñ Ð°Ð´Ñ€ÐµÑ Ð½Ð° ел. поща activate_account.text_2 = МолÑ, щракнете върху Ñледната връзка, за да активирате ÑÐ²Ð¾Ñ Ð°ÐºÐ°ÑƒÐ½Ñ‚ в рамките на %s: +issue_assigned.issue = @%[1]s ви възложи задача %[2]s в хранилище %[3]s. +issue.action.push_n = @%[1]s изтлаÑка %[3]d Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ ÐºÑŠÐ¼ %[2]s +issue.action.push_1 = @%[1]s изтлаÑка %[3]d подаване към %[2]s +repo.transfer.subject_to_you = %s иÑка да прехвърли хранилище "%s" към Ð²Ð°Ñ +issue.action.merge = @%[1]s ÑÐ»Ñ #%[2]d в %[3]s. +issue_assigned.pull = @%[1]s ви възложи заÑвката за Ñливане %[2]s в хранилище %[3]s. +issue.action.ready_for_review = @%[1]s отбелÑза тази заÑвка за Ñливане като готова за рецензиране. +repo.transfer.subject_to = %s иÑка да прехвърли хранилище "%s" към %s [user] joined_on = ПриÑъединени на %s @@ -1378,6 +1503,10 @@ followers.title.few = ПоÑледователи followers.title.one = ПоÑледовател following.title.one = Следван following.title.few = Следвани +public_activity.visibility_hint.self_public = Вашата дейноÑÑ‚ е видима за вÑички, Ñ Ð¸Ð·ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ðµ на взаимодейÑтвиÑта в чаÑтни проÑтранÑтва. Конфигуриране. +form.name_pattern_not_allowed = Шаблонът "%s" не е разрешен в потребителÑко име. +form.name_reserved = ПотребителÑкото име "%s" е резервирано. +public_activity.visibility_hint.self_private_profile = Вашата дейноÑÑ‚ е видима Ñамо за Ð²Ð°Ñ Ð¸ админиÑтраторите на инÑтанциÑта, тъй като вашиÑÑ‚ профил е чаÑтен. Конфигуриране. [home] filter = Други филтри @@ -1544,6 +1673,8 @@ push_tag = изтлаÑка маркер %[3]s към %[3]s#%[2]s` reject_pull_request = `предложи промени за %[3]s#%[2]s` compare_branch = СравнÑване +compare_commits_general = СравнÑване на Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ +compare_commits = Сравнете %d Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ [auth] tab_openid = OpenID @@ -1572,6 +1703,12 @@ tab_signin = Влизане tab_signup = РегиÑтриране password_pwned = Паролата, коÑто Ñте избрали, е в ÑпиÑък Ñ Ð¾Ñ‚ÐºÑ€Ð°Ð´Ð½Ð°Ñ‚Ð¸ пароли, разкрити преди това при публични пробиви на данни. МолÑ, опитайте отново Ñ Ñ€Ð°Ð·Ð»Ð¸Ñ‡Ð½Ð° парола. confirmation_mail_sent_prompt = Ðово ел. пиÑмо за потвърждение е изпратено до %s. За да завършите процеÑа на региÑтрациÑ, молÑ, проверете входÑщата Ñи ÐºÑƒÑ‚Ð¸Ñ Ð¸ поÑледвайте предоÑтавената връзка в рамките на Ñледващите %s. Ðко адреÑÑŠÑ‚ за ел. поща е неправилен, можете да влезете и да поиÑкате друго ел. пиÑмо за потвърждение да бъде изпратено на различен адреÑ. +hint_login = Вече имате акаунт? Влезте! +hint_register = Ðуждаете Ñе от акаунт? РегиÑтрирайте Ñе. +sign_up_button = РегиÑтрирайте Ñе. +back_to_sign_in = Ðазад към Вход +sign_in_openid = Продължаване Ñ OpenID +send_reset_mail = Изпращане на ел. пиÑмо за възÑтановÑване [aria] footer.software = ОтноÑно този Ñофтуер @@ -1582,7 +1719,7 @@ footer = Долен колонтитул install = ЛеÑен за инÑталиране lightweight = Лек license = Отворен код -install_desc = ПроÑто Ñтартирайте Ð´Ð²Ð¾Ð¸Ñ‡Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð» за вашата платформа, използвайте Docker, или го получете пакетирано. +install_desc = ПроÑто Ñтартирайте Ð´Ð²Ð¾Ð¸Ñ‡Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð» за вашата платформа, използвайте Docker, или го получете пакетиран. app_desc = Безпроблемна Git уÑлуга ÑÑŠÑ ÑамоÑтоÑтелен хоÑтинг platform = Междуплатформен lightweight_desc = Forgejo има ниÑки минимални изиÑÐºÐ²Ð°Ð½Ð¸Ñ Ð¸ може да работи на икономичен Raspberry Pi. СпеÑтете енергиÑта на вашата машина! @@ -1639,6 +1776,10 @@ variables.creation = ДобавÑне на променлива variables.deletion.failed = ÐеуÑпешно премахване на променлива. runners.task_list.repository = Хранилище runners.description = ОпиÑание +runs.no_workflows.help_no_write_access = За да научите повече за Forgejo Actions, вижте документациÑта. +variables.management = Управление на променливи +variables.not_found = Променливата не е открита. +variables.id_not_exist = Променлива Ñ Ð¸Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ‚Ð¾Ñ€ %d не ÑъщеÑтвува. [heatmap] less = По-малко @@ -1670,9 +1811,11 @@ contributors.what = приноÑи recent_commits.what = Ñкорошни Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ component_loading = Зареждане на %s... component_loading_info = Това може да отнеме извеÑтно време… +code_frequency.what = чеÑтота на промените [projects] type-1.display_name = Индивидуален проект +deleted.display_name = Изтрит проект [search] @@ -1707,4 +1850,4 @@ eib = ЕиБ [translation_meta] -test = окей \ No newline at end of file +test = окей diff --git a/options/locale/locale_bn.ini b/options/locale/locale_bn.ini index 8741fee98c..2155f9073c 100644 --- a/options/locale/locale_bn.ini +++ b/options/locale/locale_bn.ini @@ -1,6 +1,8 @@ - - - [common] help = সাহাযà§à¦¯ -dashboard = ডà§à¦¯à¦¾à¦¶à¦¬à§‹à¦°à§à¦¡ \ No newline at end of file +dashboard = ডà§à¦¯à¦¾à¦¶à¦¬à§‹à¦°à§à¦¡ +home = বাড়ি +explore = দেখোণ +logo = লোগো +sign_in = সাইণ ইণ +sign_in_or = বা \ No newline at end of file diff --git a/options/locale/locale_bs.ini b/options/locale/locale_bs.ini index bec7a65005..78eb7daa33 100644 --- a/options/locale/locale_bs.ini +++ b/options/locale/locale_bs.ini @@ -1,6 +1,3 @@ - - - [common] tracked_time_summary = Sažetak praćenog vremena bazirano na filterima liste problema language = Jezik diff --git a/options/locale/locale_ca.ini b/options/locale/locale_ca.ini index 8b2d944253..9cb7d5e50c 100644 --- a/options/locale/locale_ca.ini +++ b/options/locale/locale_ca.ini @@ -1,13 +1,10 @@ - - - [common] -home = inici +home = Inici dashboard = Panell de control explore = Explorar help = Ajuda logo = Logo -sign_in = Entrar +sign_in = Iniciar sessió sign_in_with_provider = Entra amb %s sign_in_or = o sign_out = Sortir @@ -18,7 +15,7 @@ page = Pàgina template = Plantilla language = Idioma notifications = Notificacions -active_stopwatch = Registre de Temps Actiu +active_stopwatch = Registre de temps actiu create_new = Crear… user_profile_and_more = Perfil i Configuració… signed_in_as = Entrat com @@ -118,7 +115,7 @@ write = Escriure preview = Previsualitzar loading = Carregant… error = Error -error404 = La pàgina a la que estàs intentant arribar no existeix o no estàs autoritzat a veure-la. +error404 = La pàgina a la qual estàs intentant arribar no existeix, ha sigut eliminada o no estàs autoritzat a veure-la. go_back = Tornar Enrere invalid_data = Dades invalides: %v unknown = Desconegut @@ -142,6 +139,13 @@ filter.is_archived = Arxivats filter.not_archived = No arxivats filter.not_fork = No és fork filter.is_fork = Són forks +copy_path = Copiar ruta +new_repo.title = Nou repositori +new_migrate.title = Nova migració +new_org.title = Nova organització +new_repo.link = Nou repositori +new_migrate.link = Nova migració +new_org.link = Nova organització [search] milestone_kind = Cerca fites... @@ -169,6 +173,8 @@ pull_kind = Cerca "pulls"... exact = Exacte exact_tooltip = Inclou només resultats que són exactament el terme de cerca issue_kind = Cerca problemes... +regexp = RegExp +regexp_tooltip = Interpreta el terme de cerca com una expressió regular [heatmap] number_of_contributions_in_the_last_12_months = %s contribucions en els últims 12 mesos @@ -207,7 +213,7 @@ reinstall_error = Estas intentant instaÅ€lar sobre una base de dades existent de reinstall_confirm_message = ReinstaÅ€lar amb una base de dades existent de Forgejo pot causar diferents problemes. En la majoria de casos, s'hauria d'utilitzar l'"app.ini" existent per executar Forgejo. Si saps el que estàs fent, confirma el seguent: no_admin_and_disable_registration = No pot deshabilitar l'autoregistre d'usuaris sense crear un compte d'administrador. err_admin_name_is_reserved = El nom d'usuari "Administrador" no es vàlid: està reservat -smtp_addr = Hoste SMPT +smtp_addr = Hoste SMTP smtp_port = Port SMPT smtp_from = Enviar correu com a mailer_user = Nom d'usuari SMTP @@ -339,6 +345,74 @@ disable_register_mail = Registre amb confirmació per correu deshabilitat. manual_activation_only = Contacti amb l'administrador de lloc per a completar l'activació. remember_me = Recordar aquest dispositiu create_new_account = Registrar compte +reset_password = Recuperació del compte +reset_password_wrong_user = Heu iniciat sessió com a %s, però l'enllaç de recuperació pertany a %s +allow_password_change = Requereix a l'usuari canviar la contrasenya (recomanat) +invalid_code_forgot_password = El codi de confirmació és invàlid o ha caducat. Feu click aquí per a iniciar una sessió nova. +twofa_scratch_used = Ja heu utilitzat el vostre codi de recuperació. Se us ha redirigit a la pàgina de configuració de l'autenticació de doble factor per tal d'eliminar el dispositiu o generar un codi de recuperació nou. +login_userpass = Entra +oauth.signin.error.temporarily_unavailable = Ha fallat l'autorització perquè el servidor d'autenticació no està disponible temporalment. Intenteu-ho de nou més tard. +authorization_failed_desc = Ha fallat l'autorització perquè s'ha detectat una sol·licitud invàlida. Si us plau, contacteu amb el responsable de l'aplicació que heu intentat autoritzar. +authorization_failed = Ha fallat l'autorització +last_admin = No podeu eliminar l'últim usuari administrador. Com a mínim n'hi ha d'haver un. +password_pwned_err = No s'ha pogut completar la sol·licitud a HaveIBeenPwned +forgot_password = Contrasenya oblidada? +reset_password_mail_sent_prompt = S'ha enviat un correu electrònic de confirmació a %s. Per tal de completar el procés de recuperació del compte, reviseu la safata d'entrada i seguiu l'enllaç que se us ha enviat en els següents %s. +prohibit_login = El compte està en suspensió +resent_limit_prompt = Fa poc que heu sol·licitat un correu electrònic d'activació. Si us plau, espereu 3 minuts i torneu a intentar-ho. +has_unconfirmed_mail = Hola %s, la vostra adreça de correu no s'ha confirmat (%s). Si no heu rebut un correu de confirmació o necessiteu que l'enviem de nou, feu clic al botó següent. +change_unconfirmed_email_summary = Canvieu l'adreça de correu on s'envia el correu d'activació. +invalid_code = El codi de confirmació no és vàlid o ha caducat. +invalid_password = La contrasenya no coincideix amb la que es va utilitzar per a crear el compte. +reset_password_helper = Recuperar compte +verify = Verificar +unauthorized_credentials = Les credencials són incorrectes o han caducat. Torneu a executar l'ordre o visiteu %s per a més informació +scratch_code = Codi de recuperació +use_scratch_code = Utilitzar un codi de recuperació +twofa_scratch_token_incorrect = El codi de recuperació és incorrecte. +oauth_signup_title = Completar compte nou +oauth_signup_submit = Completar compte +oauth.signin.error.access_denied = S'ha denegat la sol·licitud d'autorització. +openid_connect_submit = Connectar +openid_connect_title = Entreu a un compte existent +openid_register_title = Crear un compte nou +authorize_application = Autoritzar aplicació +authorize_redirect_notice = Sereu redirigits a %s si autoritzeu aquesta aplicació. +authorize_application_description = Si li concediu l'accés podrà accedir i escriure a tota la informació del vostre compte, inclòs repositoris privats i organitzacions. +authorize_title = Autoritzeu "%s" a accedir al vostre compte? +active_your_account = Activeu el compte +sign_up_successful = S'ha creat el compte correctament. Benvingut! +account_activated = El compte s'ha activat +send_reset_mail = Enviar correu electrònic de recuperació del compte +password_too_short = La longitud de la contrasenya no pot ser inferior a %d caràcters. +oauth_signin_title = Entreu per a autoritzar el compte vinculat +oauth_signin_submit = Vincular compte +disable_forgot_password_mail = La recuperació de comptes està deshabilitada perquè no hi ha configuració de correu electrònic. Si us plau, contacteu amb l'administrador del lloc. +email_domain_blacklisted = No podeu registrar-vos amb el correu electrònic. +hint_login = Ja tens compte? Entra ara! +hint_register = Necessites un compte? Registra't ara. +sign_up_button = Registra't ara. +must_change_password = Actualitza la contrasenya +change_unconfirmed_email_error = No s'ha pogut canviar l'adreça de correu: %v +oauth_signup_tab = Registrar compte nou +back_to_sign_in = Torneu a entrar +openid_signin_desc = Introduïu la URI OpenID. Per exemple: alice.openid.example.org o https://openid.example.org/alice. +authorize_application_created_by = Aquesta aplicació l'ha creat %s. +password_pwned = La contrasenya que heu introduït es troba en una llista de contrasenyes robades exposades en dades filtrades públicament. Si us plau, intenteu-ho de nou amb una contrasenya diferent i considereu modificar aquesta contrasenya a tot arreu on la utilitzeu. +use_onetime_code = Utilitzar un codi d'un sol ús +forgot_password_title = Contrasenya oblidada +confirmation_mail_sent_prompt = S'ha enviat un correu electrònic de confirmació a %s. Per tal de completar el registre, reviseu la safata d'entrada i seguiu l'enllaç que se us ha enviat en els següents %s. Si l'adreça de correu és incorrecta, podreu accedir al compte i demanar d'enviar un altre correu de confirmació a una altra adreça. +prohibit_login_desc = S'ha suspès la interacció del vostre compte amb la instància. Contacteu amb l'administrador per a recuperar-ne l'accés. +change_unconfirmed_email = Si heu proporcionat una direcció de correu incorrecta durant el registre, la podeu canviar aquí baix i se us enviarà una confirmació a l'adreça nova. +resend_mail = Feu clic aquí per tornar a enviar el correu electrònic d'activació +twofa_passcode_incorrect = El codi d'accés és incorrecte. Si heu perdut el dispositiu, useu el codi de recuperació per a entrar. +oauth_signin_tab = Vincular a un compte existent +oauth.signin.error = Hi ha hagut un error processant la sol·licitud d'autorització. Si persisteix, poseu-vos en contacte amb l'administrador del lloc. +disable_forgot_password_mail_admin = La recuperació de comptes només està disponible quan s'ha configurat el correu electrònic. Si us plau, configureu el correu electrònic per a habilitar la recuperació de comptes. +non_local_account = Els usuaris no locals no poden actualitzar la seva contrasenya mitjançant l'interfície web de Forgejo +openid_register_desc = No s'ha reconegut la URI OpenID. Vinculeu-la amb un compte nou aquí. +openid_connect_desc = No s'ha reconegut la URI OpenID. Vinculeu-la amb un compte nou aquí. +sign_in_openid = Accediu amb OpenID [editor] buttons.indent.tooltip = Aniua els elements un nivell @@ -357,6 +431,16 @@ buttons.list.unordered.tooltip = Afegir un llista de punts buttons.list.ordered.tooltip = Afegir una llista enumerada buttons.list.task.tooltip = Afegir una llista de tasques buttons.mention.tooltip = Mencionar un usuari o equip +buttons.new_table.tooltip = Afegir taula +table_modal.header = Afegir taula +table_modal.placeholder.header = Capçalera +table_modal.placeholder.content = Contingut +table_modal.label.rows = Files +table_modal.label.columns = Columnes +link_modal.header = Afegeix un enllaç +link_modal.url = URL +link_modal.description = Descripció +link_modal.paste_reminder = Pista: Amb un enllaç en el teu porta-retalls, pots enganxar-la directament a l'editor per a crear un enllaç. [home] my_orgs = Organitzacions @@ -384,4 +468,19 @@ filter = Altres filtres footer.software = Sobre aquest software footer.links = Enllaços navbar = Barra de navegació -footer = Peu de pàgina \ No newline at end of file +footer = Peu de pàgina + +[mail] +hi_user_x = Hola %s, +view_it_on = Veure a %s +link_not_working_do_paste = No funciona l'enllaç? Proveu a copiar-lo i enganxar-lo al navegador web. +activate_account = Si us plau, activeu el compte +reply = o responeu directament a aquest correu +activate_account.text_1 = Hola %[1]s, gràcies per registrar-te a %[2]s! +register_notify = Benvinguts a %s +admin.new_user.text = Si us plau, cliqueu aui per administrar aquest usuari des del panell d'administració. +admin.new_user.user_info = Informació d'usuari +admin.new_user.subject = Nou usuari %s s'acaba d'enregistrar +activate_email.text = Si us plau, cliqueu el següent enllaç per verificar la vostra adreça de correu electrònic en %s +activate_email = Verifica la teva adreça de correu electrònic +activate_account.text_2 = Si us plau, cliqueu l'enllaç següent per activar el vostre compte en %s: \ No newline at end of file diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 9ca8c6b387..27baf20117 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -4,8 +4,8 @@ dashboard=PÅ™ehled explore=Procházet help=NápovÄ›da logo=Logo -sign_in=PÅ™ihlášení -sign_in_with_provider=PÅ™ihlásit se pomocí %s +sign_in=PÅ™ihlásit se +sign_in_with_provider = PÅ™ihlásit se pÅ™es %s sign_in_or=nebo sign_out=Odhlásit se sign_up=Registrace @@ -23,7 +23,7 @@ create_new=VytvoÅ™it… user_profile_and_more=Profil a nastavení… signed_in_as=PÅ™ihlášen/a jako enable_javascript=Tato stránka vyžaduje JavaScript. -toc=Obsah +toc=Tabulka obsahu licenses=Licence return_to_forgejo=Vrátit se do Forgejo @@ -33,7 +33,7 @@ password=Heslo access_token=Přístupový token re_type=Potvrzení hesla captcha=CAPTCHA -twofa=Dvoufaktorové ověřování +twofa=Dvoufázové ověření twofa_scratch=Dvoufaktorový kód passcode=Přístupový kód @@ -112,20 +112,19 @@ preview=Náhled loading=NaÄítání… error=Chyba -error404=Stránka, kterou se snažíte zobrazit, buÄ neexistuje, nebo nemáte oprávnÄ›ní ji zobrazit. +error404=Stránka, kterou se snažíte zobrazit, buÄ neexistuje, byla odstranÄ›na nebo nemáte oprávnÄ›ní ji zobrazit. go_back=ZpÄ›t never=Nikdy unknown=Neznámý -rss_feed=RSS kanál +rss_feed=Kanál RSS pin=PÅ™ipnout unpin=Odepnout artifacts=Artefakty -confirm_delete_artifact=Jste si jisti, že chcete odstranit artefakt „%s“? - +confirm_delete_artifact = Opravdu chcete odstranit artefakt „%s“? archived=Archivováno concept_system_global=Globální @@ -142,8 +141,6 @@ confirm_delete_selected=Potvrdit odstranÄ›ní vÅ¡ech vybraných položek? name=Název value=Hodnota -sign_in_with_provider = PÅ™ihlásit se pÅ™es %s -confirm_delete_artifact = Opravdu chcete odstranit artefakt „%s“? toggle_menu = PÅ™epnout nabídku filter = Filtr filter.is_fork = Forky @@ -168,6 +165,7 @@ new_org.title = Nová organizace new_repo.link = Nový repozitář new_migrate.link = Nová migrace new_org.link = Nová organizace +copy_path = Kopírovat cestu [aria] navbar=NavigaÄní liÅ¡ta @@ -201,6 +199,16 @@ buttons.enable_monospace_font=Zapnout neproporcionální písmo buttons.disable_monospace_font=Vypnout neproporcionální písmo buttons.unindent.tooltip = ZruÅ¡it vnoÅ™ení položek pod jednu úroveň buttons.indent.tooltip = VnoÅ™it položky pod jednu úroveň +buttons.new_table.tooltip = PÅ™idat tabulku +table_modal.header = PÅ™idat tabulku +table_modal.placeholder.header = Záhlaví +table_modal.placeholder.content = Obsah +table_modal.label.rows = Řádky +table_modal.label.columns = Sloupce +link_modal.header = PÅ™idat odkaz +link_modal.url = URL +link_modal.description = Popis +link_modal.paste_reminder = Tip: pokud máte adresu zkopírovanou ve schránce, můžete vytvoÅ™it odkaz jejím vložením přímo do editoru. [filter] string.asc=A – Z @@ -216,7 +224,7 @@ network_error=Chyba sítÄ› server_internal = Interní chyba serveru [startpage] -app_desc=Bezproblémová samostatnÄ› hostovatelná služba Git +app_desc=Jednoduchá, samostatnÄ› hostovatelná služba Git install=Jednoduché na instalaci install_desc=JednoduÅ¡e spusÅ¥te binární soubor pro vaÅ¡i platformu, nasaÄte jej pomocí Dockeru nebo si jej stáhnÄ›te jako balíÄek. platform=Multiplatformní @@ -251,7 +259,7 @@ err_empty_db_path=Cesta k databázi SQLite3 nemůže být prázdná. no_admin_and_disable_registration=Nelze vypnout registraci úÄtů bez vytvoÅ™ení úÄtu administrátora. err_empty_admin_password=Heslo administrátora nemůže být prázdné. err_empty_admin_email=E-mail administrátora nemůže být prázdný. -err_admin_name_is_reserved=Uživatelské jméno administrátora není platné, uživatelské jméno je rezervované +err_admin_name_is_reserved=Uživatelské jméno administrátora není platné, jméno je rezervované err_admin_name_pattern_not_allowed=Uživatelské jméno administrátora je neplatné, uživatelské jméno odpovídá vyhrazenému vzoru err_admin_name_is_invalid=Uživatelské jméno administrátora není platné @@ -434,7 +442,7 @@ non_local_account=ExternÄ› ověřovaní uživatelé nemohou zmÄ›nit své heslo p verify=Ověřit scratch_code=Záložní kód use_scratch_code=Použít záložní kód -twofa_scratch_used=Použili jste váš záložní kód. Byli jste pÅ™esmÄ›rování na stránku s nastavením dvoufaktorového ověřování, kde můžete odstranit registraci vaÅ¡eho zařízení nebo vygenerovat nový záložní kód. +twofa_scratch_used=Použili jste svůj záložní kód. Byli jste pÅ™esmÄ›rování na stránku s nastavením dvoufázového ověření, kde můžete odstranit registraci vaÅ¡eho zařízení nebo vygenerovat nový záložní kód. twofa_passcode_incorrect=VaÅ¡e heslo je neplatné. Pokud jste ztratili vaÅ¡e zařízení, použijte záložní kód k pÅ™ihlášení. twofa_scratch_token_incorrect=Váš záložní kód není správný. login_userpass=PÅ™ihlásit se @@ -478,6 +486,8 @@ hint_register = Nemáte úÄet? Zaregistrujte se nyní. sign_up_button = Zaregistrujte se nyní. back_to_sign_in = ZpÄ›t na pÅ™ihlášení sign_in_openid = PokraÄovat s OpenID +unauthorized_credentials = Údaje jsou nesprávné nebo vyprÅ¡ely. Opakujte svůj příkaz nebo se podívejte na %s pro více informací +use_onetime_code = Použít jednorázový kód [mail] view_it_on=Zobrazit na %s @@ -488,11 +498,11 @@ hi_user_x=Ahoj %s, activate_account=Prosíme, aktivujte si váš úÄet activate_account.title=%s, prosím aktivujte si váš úÄet activate_account.text_1=Ahoj %[1]s, dÄ›kujeme za registraci na %[2]s! -activate_account.text_2=Pro aktivaci vaÅ¡eho úÄtu do %s kliknÄ›te na následující odkaz: +activate_account.text_2=Pro aktivaci vaÅ¡eho úÄtu kliknÄ›te %s na následující odkaz : activate_email=Ověřte vaÅ¡i e-mailovou adresu activate_email.title=%s, prosím ověřte vaÅ¡i e-mailovou adresu -activate_email.text=Pro aktivaci vaÅ¡eho úÄtu do %s kliknÄ›te na následující odkaz: +activate_email.text=Pro ověření vaší e-mailové adresy kliknÄ›te do %s na následující odkaz: register_notify=Vítejte v %s register_notify.title=%[1]s vítejte v %[2]s @@ -511,8 +521,8 @@ issue_assigned.issue=@%[1]s vás pÅ™iÅ™adil/a k problému %[2]s v repozitáři % issue.x_mentioned_you=@%s vás zmínil/a: issue.action.force_push=%[1]s vynutil/a nahrání %[2]s z %[3]s do %[4]s. -issue.action.push_1=@%[1]s nahrál/a %[3]d commit do %[2]s -issue.action.push_n=@%[1]s nahrál/a %[3]d commity do %[2]s +issue.action.push_1=Uživatel @%[1]s nahrál %[3]d revizi do %[2]s +issue.action.push_n=Uživatel @%[1]s nahrál %[3]d revizí do %[2]s issue.action.close=@%[1]s uzavÅ™el/a #%[2]d. issue.action.reopen=@%[1]s znovu otevÅ™el/a #%[2]d. issue.action.merge=@%[1]s slouÄil/a #%[2]d do %[3]s. @@ -584,9 +594,9 @@ AuthName=Název ověření AdminEmail=E-mailová adresa správce NewBranchName=Název nové vÄ›tve -CommitSummary=Shrnutí commity -CommitMessage=Zpráva commitu -CommitChoice=VýbÄ›r commitu +CommitSummary=Shrnutí revize +CommitMessage=Zpráva revize +CommitChoice=VýbÄ›r revize TreeName=Cesta k souboru Content=Obsah @@ -622,7 +632,7 @@ repository_files_already_exist.adopt=Soubory pro tento repozitář již existuj 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í. +2fa_auth_required=Vzdálený přístup vyžaduje dvoufázové ověření. org_name_been_taken=Název organizace je již použit. team_name_been_taken=Název týmu je již použit. team_no_units_error=Povolit přístup alespoň do jedné sekce repozitáře. @@ -646,8 +656,8 @@ cannot_add_org_to_team=Organizace nemůže být pÅ™idána jako Älen týmu. duplicate_invite_to_team=Uživatel byl již pozván jako Älen týmu. organization_leave_success=ÚspěšnÄ› jste opustili organizaci %s. -invalid_ssh_key=Nelze ověřit váš SSH klíÄ: %s -invalid_gpg_key=Nelze ověřit váš GPG klíÄ: %s +invalid_ssh_key=NepodaÅ™ilo se ověřit váš klÃ­Ä SSH: %s +invalid_gpg_key=NepodaÅ™ilo se ověřit váš klÃ­Ä GPG: %s invalid_ssh_principal=Neplatný SSH Principal certifikát: %s must_use_public_key=Zadaný klÃ­Ä je soukromý klíÄ. Nenahrávejte svůj soukromý klÃ­Ä nikde. Místo toho použijte váš veÅ™ejný klíÄ. unable_verify_ssh_key=NepodaÅ™ilo se ověřit klÃ­Ä SSH, zkontrolujte, zda neobsahuje chyby. @@ -660,10 +670,9 @@ org_still_own_repo=Organizace stále vlastní jeden nebo více repozitářů. Ne org_still_own_packages=Organizace stále vlastní jeden nebo více balíÄků. Nejdříve je odstraňte. target_branch_not_exist=Cílová vÄ›tev neexistuje. -admin_cannot_delete_self = Nemůžete odstranit sami sebe, když jste administrátorem. Nejprve prosím odeberte svá práva administrátora. +admin_cannot_delete_self=Nemůžete se smazat, dokud jste správce. Nejdříve prosím odeberte svá administrátorská oprávnÄ›ní. username_error_no_dots = ` může obsahovat pouze alfanumerické znaky („0-9“, „a-z“, „A-Z“), pomlÄky („-“) a podtržítka („_“). Nemůže zaÄínat nebo konÄit nealfanumerickými znaky. Jsou také zakázány po sobÄ› jdoucí nealfanumerické znaky.` -admin_cannot_delete_self=Nemůžete se smazat, dokud jste správce. Nejdříve prosím odeberte svá administrátorská oprávnÄ›ní. unset_password = Tento uživatel nemá nastavené heslo. unsupported_login_type = U tohoto typu úÄtu není funkce odstranÄ›ní úÄtu podporována. required_prefix = Vstup musí zaÄínat textem „%s“ @@ -675,6 +684,8 @@ Location = UmístÄ›ní To = Název vÄ›tve Biography = Životopis AccessToken = Přístupový token +username_claiming_cooldown = Uživatelské jméno nelze získat, protože jeÅ¡tÄ› neskonÄila doba jeho platnosti. Půjde jej získat %[1]s. +email_domain_is_not_allowed = Doména uživatelské e-mailové adresy %s je v rozporu se seznamem EMAIL_DOMAIN_ALLOWLIST nebo EMAIL_DOMAIN_BLOCKLIST. UjistÄ›te se, že je vaÅ¡e adresa správnÄ› nastavena. [user] change_avatar=ZmÄ›nit váš avatar… @@ -687,7 +698,7 @@ watched=Sledované repozitáře code=Kód projects=Projekty overview=PÅ™ehled -following_few=%d sledovaní +following_few=%d sledovaných follow=Sledovat unfollow=PÅ™estat sledovat user_bio=Životopis @@ -709,7 +720,7 @@ follow_blocked_user = Tohoto uživatele nemůžete sledovat, protože jste si je block = Zablokovat unblock = Odblokovat followers_one = %d sledující -following_one = %d následuje +following_one = %d sledovaný followers.title.one = Sledující followers.title.few = Sledující following.title.one = Sleduje @@ -718,6 +729,7 @@ public_activity.visibility_hint.self_private = VaÅ¡e aktivita je viditelná pouz public_activity.visibility_hint.admin_private = Tato aktivita je pro vás viditelná, protože jste administrátor, ale uživatel chce, aby zůstala soukromá. public_activity.visibility_hint.self_public = VaÅ¡e aktivita je viditelná vÅ¡em, mimo interakcí v soukromých prostorech. Nastavení. public_activity.visibility_hint.admin_public = Tato aktivita je viditelná vÅ¡em, ale jako administrátor také můžete vidÄ›t interakce v soukromých prostorech. +public_activity.visibility_hint.self_private_profile = VaÅ¡e aktivita je viditelná pouze vám a správcům instance, protože váš profil je soukromý. Nastavit. [settings] profile=Profil @@ -732,16 +744,16 @@ applications=Aplikace orgs=Organizace repos=Repozitáře delete=Smazat úÄet -twofa=Dvoufaktorové ověřování (TOTP) +twofa=Dvoufázové ověření (TOTP) account_link=Propojené úÄty organization=Organizace uid=UID -webauthn=Dvoufaktorové ověřování (bezpeÄnostní klíÄe) +webauthn=Dvoufázové ověření (bezpeÄnostní klíÄe) public_profile=VeÅ™ejný profil -biography_placeholder=ŘeknÄ›te nám nÄ›co o sobÄ›! (Můžete použít Markdown) +biography_placeholder=ŘeknÄ›te ostatním nÄ›co o sobÄ›! (Je podporován Markdown) location_placeholder=Sdílejte svou pÅ™ibližnou polohu s ostatními -profile_desc=Nastavte, jak bude váš profil zobrazen ostatním uživatelům. VaÅ¡e hlavní e-mailová adresa bude použita pro oznámení, obnovení hesla a operace Git. +profile_desc=O vás 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 website=Web @@ -761,7 +773,7 @@ language=Jazyk ui=Motiv vzhledu hidden_comment_types=Skryté typy komentářů hidden_comment_types_description=Zde zkontrolované typy komentářů nebudou zobrazeny na stránkách problémů. ZaÅ¡krtnutí „Štítek“ například odstraní vÅ¡echny komentáře „ pÅ™idal/odstranil
    - - + + + {{else}} + {{end}}
    images/icon-install.png Installation
    images/icon-usage.png Usage
    {{.Name}} {{.TypeName}} {{if .IsActive}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}{{DateTime "short" .UpdatedUnix}}{{DateTime "short" .CreatedUnix}}{{DateUtils.AbsoluteShort .UpdatedUnix}}{{DateUtils.AbsoluteShort .CreatedUnix}} {{svg "octicon-pencil"}}
    {{ctx.Locale.Tr "repo.pulls.no_results"}}
    diff --git a/templates/admin/auth/new.tmpl b/templates/admin/auth/new.tmpl index 47fa82825c..12d3798278 100644 --- a/templates/admin/auth/new.tmpl +++ b/templates/admin/auth/new.tmpl @@ -50,16 +50,13 @@ {{template "admin/auth/source/oauth" .}} - - {{template "admin/auth/source/sspi" .}} -
    -
    +
    diff --git a/templates/admin/auth/source/oauth.tmpl b/templates/admin/auth/source/oauth.tmpl index 0560cc8256..7d0a64d269 100644 --- a/templates/admin/auth/source/oauth.tmpl +++ b/templates/admin/auth/source/oauth.tmpl @@ -63,19 +63,27 @@
    - {{range .OAuth2Providers}}{{if .CustomURLSettings}} - - - - - - - {{end}}{{end}} - + {{range .OAuth2Providers}} + {{if .CustomURLSettings}} + + + + + + + {{end}} + {{if .CanProvideSSHKeys}} + + {{end}} + {{end}}
    +
    + + +
    diff --git a/templates/admin/auth/source/sspi.tmpl b/templates/admin/auth/source/sspi.tmpl deleted file mode 100644 index 6a3f00f9a8..0000000000 --- a/templates/admin/auth/source/sspi.tmpl +++ /dev/null @@ -1,43 +0,0 @@ -
    -
    -
    - - -

    {{ctx.Locale.Tr "admin.auths.sspi_auto_create_users_helper"}}

    -
    -
    -
    -
    - - -

    {{ctx.Locale.Tr "admin.auths.sspi_auto_activate_users_helper"}}

    -
    -
    -
    -
    - - -

    {{ctx.Locale.Tr "admin.auths.sspi_strip_domain_names_helper"}}

    -
    -
    -
    - - -

    {{ctx.Locale.Tr "admin.auths.sspi_separator_replacement_helper"}}

    -
    -
    - - -

    {{ctx.Locale.Tr "admin.auths.sspi_default_language_helper"}}

    -
    -
    diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 8f2b1c12e3..12504b8824 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -247,6 +247,16 @@
    +

    + {{ctx.Locale.Tr "admin.config.moderation_config"}} +

    +
    +
    +
    {{ctx.Locale.Tr "enabled"}}
    +
    {{if .Moderation.Enabled}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}
    +
    +
    +

    {{ctx.Locale.Tr "admin.config.cache_config"}}

    diff --git a/templates/admin/cron.tmpl b/templates/admin/cron.tmpl index 3cb641488c..ee37f6ca75 100644 --- a/templates/admin/cron.tmpl +++ b/templates/admin/cron.tmpl @@ -23,8 +23,8 @@ {{ctx.Locale.Tr (printf "admin.dashboard.%s" .Name)}} {{.Spec}} - {{DateTime "full" .Next}} - {{if gt .Prev.Year 1}}{{DateTime "full" .Prev}}{{else}}-{{end}} + {{DateUtils.FullTime .Next}} + {{if gt .Prev.Year 1}}{{DateUtils.FullTime .Prev}}{{else}}-{{end}} {{.ExecTimes}} {{if eq .Status ""}}—{{else if eq .Status "finished"}}{{svg "octicon-check" 16}}{{else}}{{svg "octicon-x" 16}}{{end}} diff --git a/templates/admin/emails/list.tmpl b/templates/admin/emails/list.tmpl index b07c6fcc01..5c30df87af 100644 --- a/templates/admin/emails/list.tmpl +++ b/templates/admin/emails/list.tmpl @@ -66,6 +66,8 @@
    + {{else}} + {{ctx.Locale.Tr "repo.pulls.no_results"}} {{end}} diff --git a/templates/admin/notice.tmpl b/templates/admin/notice.tmpl index 33d8a2f963..08f0a4f204 100644 --- a/templates/admin/notice.tmpl +++ b/templates/admin/notice.tmpl @@ -21,9 +21,11 @@ {{.ID}} {{ctx.Locale.Tr .TrStr}} {{.Description}} - {{DateTime "short" .CreatedUnix}} + {{DateUtils.AbsoluteShort .CreatedUnix}} {{svg "octicon-note" 16}} + {{else}} + {{ctx.Locale.Tr "repo.pulls.no_results"}} {{end}} {{if .Notices}} diff --git a/templates/admin/org/list.tmpl b/templates/admin/org/list.tmpl index 987ceab1e0..8c9c198897 100644 --- a/templates/admin/org/list.tmpl +++ b/templates/admin/org/list.tmpl @@ -63,9 +63,11 @@ {{.NumTeams}} {{.NumMembers}} {{.NumRepos}} - {{DateTime "short" .CreatedUnix}} + {{DateUtils.AbsoluteShort .CreatedUnix}} {{svg "octicon-pencil"}} + {{else}} + {{ctx.Locale.Tr "repo.pulls.no_results"}} {{end}} diff --git a/templates/admin/packages/list.tmpl b/templates/admin/packages/list.tmpl index 4ff49b8c43..5f9965e34c 100644 --- a/templates/admin/packages/list.tmpl +++ b/templates/admin/packages/list.tmpl @@ -71,9 +71,11 @@ {{end}} {{ctx.Locale.TrSize .CalculateBlobSize}} - {{DateTime "short" .Version.CreatedUnix}} + {{DateUtils.AbsoluteShort .Version.CreatedUnix}} {{svg "octicon-trash"}} + {{else}} + {{ctx.Locale.Tr "repo.pulls.no_results"}} {{end}} diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index 1ea6183d80..7a75ceded7 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -82,10 +82,12 @@ {{.NumIssues}} {{ctx.Locale.TrSize .GitSize}} {{ctx.Locale.TrSize .LFSSize}} - {{DateTime "short" .UpdatedUnix}} - {{DateTime "short" .CreatedUnix}} + {{DateUtils.AbsoluteShort .UpdatedUnix}} + {{DateUtils.AbsoluteShort .CreatedUnix}} {{svg "octicon-trash"}} + {{else}} + {{ctx.Locale.Tr "repo.pulls.no_results"}} {{end}} diff --git a/templates/admin/repo/unadopted.tmpl b/templates/admin/repo/unadopted.tmpl index a33cb43a2f..a95f6b5120 100644 --- a/templates/admin/repo/unadopted.tmpl +++ b/templates/admin/repo/unadopted.tmpl @@ -54,7 +54,7 @@ - {{template "base/modal_actions_confirm" (dict "ModalButtonColors" "primary")}} + {{template "base/modal_actions_confirm"}}
    diff --git a/templates/admin/stacktrace-row.tmpl b/templates/admin/stacktrace-row.tmpl index 694bf56d96..048056cf4e 100644 --- a/templates/admin/stacktrace-row.tmpl +++ b/templates/admin/stacktrace-row.tmpl @@ -7,13 +7,15 @@ {{svg "octicon-cpu" 16}} {{else if eq .Process.Type "normal"}} {{svg "octicon-terminal" 16}} + {{else if eq .Process.Type "git"}} + {{svg "octicon-git-branch" 16}} {{else}} {{svg "octicon-code" 16}} {{end}}
    {{.Process.Description}}
    -
    {{if ne .Process.Type "none"}}{{TimeSince .Process.Start ctx.Locale}}{{end}}
    +
    {{if ne .Process.Type "none"}}{{DateUtils.TimeSince .Process.Start}}{{end}}
    {{if or (eq .Process.Type "request") (eq .Process.Type "normal")}} diff --git a/templates/admin/stacktrace.tmpl b/templates/admin/stacktrace.tmpl index e324570c96..afe8e6942a 100644 --- a/templates/admin/stacktrace.tmpl +++ b/templates/admin/stacktrace.tmpl @@ -8,11 +8,12 @@ {{ctx.Locale.Tr "admin.monitor.stacktrace"}}
    -
    -
    - - {{ctx.Locale.Tr "tool.raw_seconds"}} -
    + + +
    diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index f5c85e9290..7d004dd903 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -9,7 +9,7 @@ {{.CsrfTokenHtml}}
    - +
    diff --git a/templates/admin/user/list.tmpl b/templates/admin/user/list.tmpl index e5d429952f..368e113d24 100644 --- a/templates/admin/user/list.tmpl +++ b/templates/admin/user/list.tmpl @@ -96,9 +96,9 @@ {{if .IsActive}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}} {{if .IsRestricted}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}} {{if index $.UsersTwoFaStatus .ID}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}} - {{DateTime "short" .CreatedUnix}} + {{DateUtils.AbsoluteShort .CreatedUnix}} {{if .LastLoginUnix}} - {{DateTime "short" .LastLoginUnix}} + {{DateUtils.AbsoluteShort .LastLoginUnix}} {{else}} {{ctx.Locale.Tr "admin.users.never_login"}} {{end}} @@ -109,6 +109,8 @@
    + {{else}} + {{ctx.Locale.Tr "repo.pulls.no_results"}} {{end}} diff --git a/templates/admin/user/new.tmpl b/templates/admin/user/new.tmpl index b04ebc4b60..0f7f930ec0 100644 --- a/templates/admin/user/new.tmpl +++ b/templates/admin/user/new.tmpl @@ -53,7 +53,7 @@
    - +
    diff --git a/templates/admin/user/view_details.tmpl b/templates/admin/user/view_details.tmpl index be2f32b5ec..c394b4bd3d 100644 --- a/templates/admin/user/view_details.tmpl +++ b/templates/admin/user/view_details.tmpl @@ -26,6 +26,14 @@ {{svg "octicon-x"}} {{end}}
    +
    + {{ctx.Locale.Tr "admin.users.prohibit_login"}}: + {{if .User.ProhibitLogin}} + {{svg "octicon-check"}} + {{else}} + {{svg "octicon-x"}} + {{end}} +
    {{ctx.Locale.Tr "admin.users.restricted"}}: {{if .User.IsRestricted}} diff --git a/templates/admin/user/view_emails.tmpl b/templates/admin/user/view_emails.tmpl index 22ce305a88..7e77206f1c 100644 --- a/templates/admin/user/view_emails.tmpl +++ b/templates/admin/user/view_emails.tmpl @@ -3,7 +3,7 @@
    - {{.Email}} + {{.Email}} {{if .IsPrimary}}
    {{ctx.Locale.Tr "settings.primary"}}
    {{end}} diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl index fed426a469..3c2def228a 100644 --- a/templates/base/footer.tmpl +++ b/templates/base/footer.tmpl @@ -13,7 +13,7 @@ {{template "base/footer_content" .}} - + {{template "custom/footer" .}} diff --git a/templates/base/footer_content.tmpl b/templates/base/footer_content.tmpl index 5db7464480..133ebac33a 100644 --- a/templates/base/footer_content.tmpl +++ b/templates/base/footer_content.tmpl @@ -8,7 +8,7 @@ {{if .IsAdmin}} {{AppVer}} {{else}} - {{AppVer}} + {{AppVerNoMetadata}} {{end}} {{end}} {{if and .TemplateLoadTimes ShowFooterTemplateLoadTime}} diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 7753f49243..7ec2ac87b3 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -20,12 +20,7 @@ {{template "base/head_script" .}} - + {{template "shared/user/mention_highlight" .}} {{template "base/head_opengraph" .}} {{template "base/head_style" .}} {{template "custom/header" .}} diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index ba17222f9b..0c13f9e844 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -194,11 +194,13 @@ {{else}} {{if .ShowRegistrationButton}} - {{svg "octicon-person"}} {{ctx.Locale.Tr "register"}} + {{svg "octicon-person" 16 "tw-mr-1"}} + {{ctx.Locale.Tr "register"}} {{end}} - {{svg "octicon-sign-in"}} {{ctx.Locale.Tr "sign_in"}} + {{svg "octicon-sign-in" 16 "tw-mr-1"}} + {{ctx.Locale.Tr "sign_in"}} {{end}}
    diff --git a/templates/base/head_opengraph.tmpl b/templates/base/head_opengraph.tmpl index 292c3bdd92..7f6eae3f49 100644 --- a/templates/base/head_opengraph.tmpl +++ b/templates/base/head_opengraph.tmpl @@ -1,53 +1,37 @@ -{{- /* og:description - a one to two sentence description of your object, maybe it only needs at most 300 bytes */ -}} -{{if .PageIsUserProfile}} - - - - - {{if .ContextUser.Description}} - - {{end}} -{{else if .Repository}} - {{if .Issue}} - - - {{if .Issue.Content}} - - {{end}} - {{else if or .PageIsDiff .IsViewFile}} - - - {{if and .PageIsDiff .Commit}} - {{- $commitMessageParts := StringUtils.Cut .Commit.Message "\n" -}} - {{- $commitMessageBody := index $commitMessageParts 1 -}} - {{- if $commitMessageBody -}} - - {{- end -}} - {{end}} - {{else if .Pages}} - - - {{if .Repository.Description}} - - {{end}} - {{else}} - - - {{if .Repository.Description}} - - {{end}} - {{end}} - - {{if (.Repository.AvatarLink ctx)}} - - {{else}} - - {{end}} +{{- /* See https://ogp.me for specification */ -}} +{{if .OpenGraphTitle}} + +{{else if .Title}} + {{else}} +{{end}} +{{- /* og:description - a one to two sentence description of your object, maybe it only needs at most 300 bytes */ -}} +{{if and .OpenGraphDescription (not .OpenGraphNoDescription)}} + +{{end}} +{{if .OpenGraphURL}} + +{{else}} + +{{end}} +{{if .OpenGraphType}} + +{{else}} - - - +{{end}} +{{if .OpenGraphImageURL}} + + {{if .OpenGraphImageWidth}} + + {{end}} + {{if .OpenGraphImageHeight}} + + {{end}} + {{if .OpenGraphImageAltText}} + + {{end}} +{{else}} + {{end}} diff --git a/templates/base/head_script.tmpl b/templates/base/head_script.tmpl index 22e08e9c8f..d2774010b6 100644 --- a/templates/base/head_script.tmpl +++ b/templates/base/head_script.tmpl @@ -42,6 +42,7 @@ If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly. modal_confirm: {{ctx.Locale.Tr "modal.confirm"}}, modal_cancel: {{ctx.Locale.Tr "modal.cancel"}}, more_items: {{ctx.Locale.Tr "more_items"}}, + incorrect_root_url: {{ctx.Locale.Tr "incorrect_root_url" AppUrl}}, }, }; {{/* in case some pages don't render the pageData, we make sure it is an object to prevent null access */}} diff --git a/templates/base/modal_actions_confirm.tmpl b/templates/base/modal_actions_confirm.tmpl index c44320deff..8c4e346088 100644 --- a/templates/base/modal_actions_confirm.tmpl +++ b/templates/base/modal_actions_confirm.tmpl @@ -1,7 +1,6 @@ {{/* Two buttons (negative, positive): * ModalButtonTypes: "yes" (default) or "confirm" -* ModalButtonColors: "primary" (default) / "blue" / "yellow" * ModalButtonCancelText * ModalButtonOkText @@ -23,13 +22,7 @@ The ".ok.button" and ".cancel.button" selectors are also used by Fomantic Modal {{if .ModalButtonCancelText}}{{$textNegitive = .ModalButtonCancelText}}{{end}} {{if .ModalButtonOkText}}{{$textPositive = .ModalButtonOkText}}{{end}} - {{$stylePositive := "primary"}} - {{if eq .ModalButtonColors "blue"}} - {{$stylePositive = "blue"}} - {{else if eq .ModalButtonColors "yellow"}} - {{$stylePositive = "yellow"}} - {{end}} - + {{end}}
    diff --git a/templates/base/paginate.tmpl b/templates/base/paginate.tmpl index 2ca72f6a34..253892c009 100644 --- a/templates/base/paginate.tmpl +++ b/templates/base/paginate.tmpl @@ -17,7 +17,7 @@ {{if eq .Num -1}} ... {{else}} - {{.Num}} + {{.Num}} {{end}} {{end}} diff --git a/templates/devtest/fomantic-modal.tmpl b/templates/devtest/fomantic-modal.tmpl index 5cd36721a7..5b94afc4f1 100644 --- a/templates/devtest/fomantic-modal.tmpl +++ b/templates/devtest/fomantic-modal.tmpl @@ -54,18 +54,6 @@ {{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
    - - - -
{{end}} - + {{ctx.Locale.Tr "settings.blocked_users"}} + {{if .EnableQuota}} + + {{ctx.Locale.Tr "settings.storage_overview"}} + + {{end}} {{ctx.Locale.Tr "org.settings.delete"}} diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl index 62debfc0ae..2ef7031aef 100644 --- a/templates/org/settings/options.tmpl +++ b/templates/org/settings/options.tmpl @@ -6,14 +6,18 @@
{{.CsrfTokenHtml}} -
- +
+ + {{ctx.Locale.Tr "org.settings.change_orgname_prompt"}} + {{if gt .CooldownPeriod 0}} + {{ctx.Locale.TrN .CooldownPeriod "org.settings.change_orgname_redirect_prompt.with_cooldown.one" "org.settings.change_orgname_redirect_prompt.with_cooldown.few" .CooldownPeriod}} + {{else}} + {{ctx.Locale.Tr "org.settings.change_orgname_redirect_prompt"}} + {{end}} + +
diff --git a/templates/org/settings/storage_overview.tmpl b/templates/org/settings/storage_overview.tmpl new file mode 100644 index 0000000000..e3f8c53152 --- /dev/null +++ b/templates/org/settings/storage_overview.tmpl @@ -0,0 +1,5 @@ +{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings size-overview")}} +
+ {{template "shared/quota_overview" .}} +
+{{template "org/settings/layout_footer" .}} diff --git a/templates/package/content/alt.tmpl b/templates/package/content/alt.tmpl new file mode 100644 index 0000000000..9a5e9c7656 --- /dev/null +++ b/templates/package/content/alt.tmpl @@ -0,0 +1,49 @@ +{{if eq .PackageDescriptor.Package.Type "alt"}} +

{{ctx.Locale.Tr "packages.installation"}}

+
+
+
+ +
{{- if gt (len .Groups) 1 -}}
+# {{ctx.Locale.Tr "packages.alt.repository.multiple_groups"}}
+
+{{end -}}
+# {{ctx.Locale.Tr "packages.alt.setup"}}
+{{- range $group := .Groups}}
+	{{- if $group}}{{$group = print "/" $group}}{{end}}
+apt-repo add rpm  _arch_ classic
+
+{{- end}}
+
+
+ +
+
# {{ctx.Locale.Tr "packages.alt.registry.install"}}
+apt-get update
+apt-get install {{$.PackageDescriptor.Package.Name}}
+
+
+
+ +
+
+
+ +

{{ctx.Locale.Tr "packages.alt.repository"}}

+
+ + + + + + + +
{{ctx.Locale.Tr "packages.alt.repository.architectures"}}
{{StringUtils.Join .Architectures ", "}}
+
+ + {{if or .PackageDescriptor.Metadata.Summary .PackageDescriptor.Metadata.Description}} +

{{ctx.Locale.Tr "packages.about"}}

+ {{if .PackageDescriptor.Metadata.Summary}}
{{.PackageDescriptor.Metadata.Summary}}
{{end}} + {{if .PackageDescriptor.Metadata.Description}}
{{.PackageDescriptor.Metadata.Description}}
{{end}} + {{end}} +{{end}} diff --git a/templates/package/content/arch.tmpl b/templates/package/content/arch.tmpl index bcc24b585b..6a041d323b 100644 --- a/templates/package/content/arch.tmpl +++ b/templates/package/content/arch.tmpl @@ -16,16 +16,16 @@ pacman-key --lsign-key '{{$.SignMail}}'

 {{- if gt (len $.Groups) 1 -}}
-# {{ctx.Locale.Tr "packages.arch.pacman.repo.multi"  $.PackageDescriptor.Package.LowerName}}
+# {{ctx.Locale.Tr "packages.arch.pacman.repo.multi" $.PackageDescriptor.Package.LowerName}}
 
 {{end -}}
 {{- $GroupSize := (len .Groups) -}}
-{{-  range $i,$v :=  .Groups -}}
+{{-  range $i,$v := .Groups -}}
 {{- if gt $i 0}}
 {{end -}}{{- if gt $GroupSize 1 -}}
 # {{ctx.Locale.Tr "packages.arch.pacman.repo.multi.item" .}}
 {{end -}}
-[{{$.PackageDescriptor.Owner.LowerName}}.{{$.RegistryHost}}]
+[{{$.PackageDescriptor.Owner.LowerName}}.{{$.PackageRegistryHost}}]
 SigLevel = Required
 Server = 
 {{end -}}
diff --git a/templates/package/content/container.tmpl b/templates/package/content/container.tmpl
index b5fdcfeb1b..dd1c24269b 100644
--- a/templates/package/content/container.tmpl
+++ b/templates/package/content/container.tmpl
@@ -5,13 +5,13 @@
 			
{{if eq .PackageDescriptor.Metadata.Type "helm"}} -
helm pull oci://{{.RegistryHost}}/{{.PackageDescriptor.Owner.LowerName}}/{{.PackageDescriptor.Package.LowerName}} --version {{.PackageDescriptor.Version.LowerVersion}}
+
helm pull oci://{{.PackageRegistryHost}}/{{.PackageDescriptor.Owner.LowerName}}/{{.PackageDescriptor.Package.LowerName}} --version {{.PackageDescriptor.Version.LowerVersion}}
{{else}} {{$separator := ":"}} {{if not .PackageDescriptor.Metadata.IsTagged}} {{$separator = "@"}} {{end}} -
docker pull {{.RegistryHost}}/{{.PackageDescriptor.Owner.LowerName}}/{{.PackageDescriptor.Package.LowerName}}{{$separator}}{{.PackageDescriptor.Version.LowerVersion}}
+
docker pull {{.PackageRegistryHost}}/{{.PackageDescriptor.Owner.LowerName}}/{{.PackageDescriptor.Package.LowerName}}{{$separator}}{{.PackageDescriptor.Version.LowerVersion}}
{{end}}
@@ -24,7 +24,7 @@
{{if .PackageDescriptor.Metadata.Manifests}} -

{{ctx.Locale.Tr "packages.container.multi_arch"}}

+

{{ctx.Locale.Tr "packages.container.images.title"}}

@@ -36,11 +36,13 @@ {{range .PackageDescriptor.Metadata.Manifests}} - + {{if ne .Platform "unknown/unknown"}} + - + + {{end}} {{end}}
{{.Digest}} {{.Platform}} {{ctx.Locale.TrSize .Size}}
diff --git a/templates/package/content/nuget.tmpl b/templates/package/content/nuget.tmpl index ea665c7bbc..c8568845f1 100644 --- a/templates/package/content/nuget.tmpl +++ b/templates/package/content/nuget.tmpl @@ -35,11 +35,12 @@ + {{$tooltipSearchInNuget := ctx.Locale.Tr "packages.search_in_external_registry" "nuget.org"}} {{range $framework, $dependencies := .PackageDescriptor.Metadata.Dependencies}} {{range $dependencies}} - {{.ID}} - {{.Version}} + {{.ID}} {{svg "octicon-link-external"}} + {{.Version}} {{svg "octicon-link-external"}} {{$framework}} {{end}} diff --git a/templates/package/metadata/alt.tmpl b/templates/package/metadata/alt.tmpl new file mode 100644 index 0000000000..16fb52e9b1 --- /dev/null +++ b/templates/package/metadata/alt.tmpl @@ -0,0 +1,4 @@ +{{if eq .PackageDescriptor.Package.Type "alt"}} + {{if .PackageDescriptor.Metadata.ProjectURL}}
{{svg "octicon-link-external" 16 "tw-mr-2"}} {{ctx.Locale.Tr "packages.details.project_site"}}
{{end}} + {{if .PackageDescriptor.Metadata.License}}
{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}
{{end}} +{{end}} diff --git a/templates/package/metadata/arch.tmpl b/templates/package/metadata/arch.tmpl index 822973eb7d..89001b979c 100644 --- a/templates/package/metadata/arch.tmpl +++ b/templates/package/metadata/arch.tmpl @@ -1,4 +1,4 @@ {{if eq .PackageDescriptor.Package.Type "arch"}} - {{range .PackageDescriptor.Metadata.License}}
{{svg "octicon-law" 16 "gt-mr-3"}} {{.}}
{{end}} - {{if .PackageDescriptor.Metadata.ProjectURL}}
{{svg "octicon-link-external" 16 "mr-3"}} {{ctx.Locale.Tr "packages.details.project_site"}}
{{end}} + {{if .PackageDescriptor.Metadata.ProjectURL}}
{{svg "octicon-link-external" 16 "tw-mr-2"}} {{ctx.Locale.Tr "packages.details.project_site"}}
{{end}} + {{range .PackageDescriptor.Metadata.License}}
{{svg "octicon-law" 16 "tw-mr-2"}} {{.}}
{{end}} {{end}} diff --git a/templates/package/shared/cargo.tmpl b/templates/package/shared/cargo.tmpl index 5b0f63965d..8e158d60d8 100644 --- a/templates/package/shared/cargo.tmpl +++ b/templates/package/shared/cargo.tmpl @@ -3,25 +3,25 @@
- {{if .CargoIndexExists}}
- +
+ {{if .CargoIndexExists}} {{.CsrfTokenHtml}} - {{else}}
- +
+ {{else}}
{{.CsrfTokenHtml}}
- {{end}}
- +
+ {{end}}
diff --git a/templates/package/shared/cleanup_rules/preview.tmpl b/templates/package/shared/cleanup_rules/preview.tmpl index 0d9c4b0d46..da034fec7a 100644 --- a/templates/package/shared/cleanup_rules/preview.tmpl +++ b/templates/package/shared/cleanup_rules/preview.tmpl @@ -22,7 +22,7 @@ {{.Version.Version}} {{.Creator.Name}} {{ctx.Locale.TrSize .CalculateBlobSize}} - {{DateTime "short" .Version.CreatedUnix}} + {{DateUtils.AbsoluteShort .Version.CreatedUnix}} {{else}} diff --git a/templates/package/shared/list.tmpl b/templates/package/shared/list.tmpl index 36f8bc1522..19b41d0bc8 100644 --- a/templates/package/shared/list.tmpl +++ b/templates/package/shared/list.tmpl @@ -24,7 +24,7 @@ {{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}}
- {{$timeStr := TimeSinceUnix .Version.CreatedUnix ctx.Locale}} + {{$timeStr := DateUtils.TimeSince .Version.CreatedUnix}} {{$hasRepositoryAccess := false}} {{if .Repository}} {{$hasRepositoryAccess = index $.RepositoryAccessMap .Repository.ID}} diff --git a/templates/package/shared/versionlist.tmpl b/templates/package/shared/versionlist.tmpl index e5c568e059..7a1059e262 100644 --- a/templates/package/shared/versionlist.tmpl +++ b/templates/package/shared/versionlist.tmpl @@ -25,7 +25,7 @@
{{.Version.LowerVersion}}
- {{ctx.Locale.Tr "packages.published_by" (TimeSinceUnix .Version.CreatedUnix ctx.Locale) .Creator.HomeLink .Creator.GetDisplayName}} + {{ctx.Locale.Tr "packages.published_by" (DateUtils.TimeSince .Version.CreatedUnix) .Creator.HomeLink .Creator.GetDisplayName}}
diff --git a/templates/package/view.tmpl b/templates/package/view.tmpl index fe88e54317..18220e904b 100644 --- a/templates/package/view.tmpl +++ b/templates/package/view.tmpl @@ -8,7 +8,7 @@

{{.PackageDescriptor.Package.Name}} ({{.PackageDescriptor.Version.Version}})

- {{$timeStr := TimeSinceUnix .PackageDescriptor.Version.CreatedUnix ctx.Locale}} + {{$timeStr := DateUtils.TimeSince .PackageDescriptor.Version.CreatedUnix}} {{if .HasRepositoryAccess}} {{ctx.Locale.Tr "packages.published_by_in" $timeStr .PackageDescriptor.Creator.HomeLink .PackageDescriptor.Creator.GetDisplayName .PackageDescriptor.Repository.Link .PackageDescriptor.Repository.FullName}} {{else}} @@ -37,6 +37,7 @@ {{template "package/content/pub" .}} {{template "package/content/pypi" .}} {{template "package/content/rpm" .}} + {{template "package/content/alt" .}} {{template "package/content/rubygems" .}} {{template "package/content/swift" .}} {{template "package/content/vagrant" .}} @@ -48,7 +49,7 @@ {{if .HasRepositoryAccess}}
{{svg "octicon-repo" 16 "tw-mr-2"}} {{.PackageDescriptor.Repository.FullName}}
{{end}} -
{{svg "octicon-calendar" 16 "tw-mr-2"}} {{TimeSinceUnix .PackageDescriptor.Version.CreatedUnix ctx.Locale}}
+
{{svg "octicon-calendar" 16 "tw-mr-2"}} {{DateUtils.TimeSince .PackageDescriptor.Version.CreatedUnix}}
{{svg "octicon-download" 16 "tw-mr-2"}} {{.PackageDescriptor.Version.DownloadCount}}
{{template "package/metadata/alpine" .}} {{template "package/metadata/arch" .}} @@ -68,6 +69,7 @@ {{template "package/metadata/pub" .}} {{template "package/metadata/pypi" .}} {{template "package/metadata/rpm" .}} + {{template "package/metadata/alt" .}} {{template "package/metadata/rubygems" .}} {{template "package/metadata/swift" .}} {{template "package/metadata/vagrant" .}} @@ -94,7 +96,7 @@ {{range .LatestVersions}}
{{.Version}} - {{DateTime "short" .CreatedUnix}} + {{DateUtils.AbsoluteShort .CreatedUnix}}
{{end}}
diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl index b892cff996..8c9b7c6e6f 100644 --- a/templates/projects/list.tmpl +++ b/templates/projects/list.tmpl @@ -1,12 +1,12 @@ {{if and $.CanWriteProjects (not $.Repository.IsArchived)}}
- {{end}} @@ -152,11 +157,12 @@ {{ctx.AvatarUtils.AvatarByEmail .Commit.Author.Email .Commit.Author.Email 28 "tw-mr-2"}} {{.Commit.Author.Name}} {{end}} - {{TimeSince .Commit.Author.When ctx.Locale}} + {{DateUtils.TimeSince .Commit.Author.When}} {{if or (ne .Commit.Committer.Name .Commit.Author.Name) (ne .Commit.Committer.Email .Commit.Author.Email)}} + • {{ctx.Locale.Tr "repo.diff.committed_by"}} {{if ne .Verification.CommittingUser.ID 0}} - {{ctx.AvatarUtils.Avatar .Verification.CommittingUser 28 "tw-mx-2"}} + {{ctx.AvatarUtils.Avatar .Verification.CommittingUser 28 "tw-mr-2"}} {{.Commit.Committer.Name}} {{else}} {{ctx.AvatarUtils.AvatarByEmail .Commit.Committer.Email .Commit.Committer.Name 28 "tw-mr-2"}} @@ -170,16 +176,22 @@ {{ctx.Locale.Tr "repo.diff.parent"}} {{range .Parents}} {{if $.PageIsWiki}} - {{ShortSha .}} + + {{ShortSha .}} + {{else}} - {{ShortSha .}} + + {{ShortSha .}} + {{end}} {{end}}
{{end}}
{{ctx.Locale.Tr "repo.diff.commit"}} - {{ShortSha .CommitID}} + + {{ShortSha .CommitID}} +
@@ -211,8 +223,8 @@
{{if .Verification.Verified}} + {{svg "octicon-verified" 16 "tw-mr-2"}} {{if ne .Verification.SigningUser.ID 0}} - {{svg "octicon-verified" 16 "tw-mr-2"}} {{if .Verification.SigningSSHKey}} {{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}: {{.Verification.SigningSSHKey.Fingerprint}} @@ -221,7 +233,6 @@ {{.Verification.SigningKey.PaddedKeyID}} {{end}} {{else}} - {{svg "octicon-unverified" 16 "tw-mr-2"}} {{if .Verification.SigningSSHKey}} {{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}: {{.Verification.SigningSSHKey.Fingerprint}} @@ -259,7 +270,7 @@
{{end}} {{if .NoteRendered}} -
+
{{svg "octicon-note" 16 "tw-mr-2"}} {{ctx.Locale.Tr "repo.diff.git-notes"}}: {{if .NoteAuthor}} @@ -273,11 +284,61 @@ {{else}} {{.NoteCommit.Author.Name}} {{end}} - {{TimeSince .NoteCommit.Author.When ctx.Locale}} + {{DateUtils.TimeSince .NoteCommit.Author.When}} + {{if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}} +
+ + +
+ + {{end}}
-
+
{{.NoteRendered | SanitizeHTML}}
+ {{if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}} +
+
+ {{.CsrfTokenHtml}} + +
+ +
+ +
+ +
+
+
+ {{end}} + {{else if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}} +
+
+ {{.CsrfTokenHtml}} + +
+ +
+ +
+ +
+
+
{{end}} {{template "repo/diff/box" .}}
diff --git a/templates/repo/commits_list.tmpl b/templates/repo/commits_list.tmpl index c8c695e332..0e64a1ab1f 100644 --- a/templates/repo/commits_list.tmpl +++ b/templates/repo/commits_list.tmpl @@ -28,21 +28,6 @@
- {{$class := "ui sha label"}} - {{if .Signature}} - {{$class = (print $class " isSigned")}} - {{if .Verification.Verified}} - {{if eq .Verification.TrustStatus "trusted"}} - {{$class = (print $class " isVerified")}} - {{else if eq .Verification.TrustStatus "untrusted"}} - {{$class = (print $class " isVerifiedUntrusted")}} - {{else}} - {{$class = (print $class " isVerifiedUnmatched")}} - {{end}} - {{else if .Verification.Warning}} - {{$class = (print $class " isWarning")}} - {{end}} - {{end}} {{$commitShaLink := ""}} {{if $.PageIsWiki}} {{$commitShaLink = (printf "%s/wiki/commit/%s" $commitRepoLink (PathEscape .ID.String))}} @@ -51,10 +36,12 @@ {{else if $.Reponame}} {{$commitShaLink = (printf "%s/commit/%s" $commitRepoLink (PathEscape .ID.String))}} {{end}} - - {{ShortSha .ID.String}} - {{if .Signature}}{{template "repo/shabox_badge" dict "root" $ "verification" .Verification}}{{end}} - + {{template "repo/shabox" (dict + "sha1" .ID.String + "commitLink" $commitShaLink + "signature" .Signature + "verification" .Verification + )}} @@ -74,13 +61,21 @@ {{end}} {{if .Committer}} - {{TimeSince .Committer.When ctx.Locale}} + {{DateUtils.TimeSince .Committer.When}} {{else}} - {{TimeSince .Author.When ctx.Locale}} + {{DateUtils.TimeSince .Author.When}} {{end}} {{if not $.PageIsWiki}} + {{if $.FileName}} + + {{svg "octicon-file-diff"}} + + {{end}} ... {{end}} - + {{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}} - {{$class := "ui sha label"}} - {{if .Signature}} - {{$class = (print $class " isSigned")}} - {{if .Verification.Verified}} - {{if eq .Verification.TrustStatus "trusted"}} - {{$class = (print $class " isVerified")}} - {{else if eq .Verification.TrustStatus "untrusted"}} - {{$class = (print $class " isVerifiedUntrusted")}} - {{else}} - {{$class = (print $class " isVerifiedUnmatched")}} - {{end}} - {{else if .Verification.Warning}} - {{$class = (print $class " isWarning")}} - {{end}} - {{end}} - - {{ShortSha .ID.String}} - {{if .Signature}} - {{template "repo/shabox_badge" dict "root" $.root "verification" .Verification}} - {{end}} - + {{template "repo/shabox" (dict + "sha1" .ID.String + "commitLink" $commitLink + "signature" .Signature + "verification" .Verification + )}} {{if IsMultilineCommitMessage .Message}} diff --git a/templates/repo/commits_table.tmpl b/templates/repo/commits_table.tmpl index 7249becbab..621fc44bf5 100644 --- a/templates/repo/commits_table.tmpl +++ b/templates/repo/commits_table.tmpl @@ -10,9 +10,13 @@ {{if .IsDiffCompare}} {{end}} diff --git a/templates/repo/contributors.tmpl b/templates/repo/contributors.tmpl index f7f5d796f4..c71312fc6c 100644 --- a/templates/repo/contributors.tmpl +++ b/templates/repo/contributors.tmpl @@ -1,6 +1,7 @@ {{if .Permission.CanRead $.UnitTypeCode}}
- {{template "base/alert" .}} - {{template "repo/create_helper" .}} + {{if or .CanCreateRepo .Orgs}} + {{template "base/alert" .}} + {{template "repo/create_helper" .}} - {{if not .CanCreateRepo}} -
-

{{ctx.Locale.TrN .MaxCreationLimit "repo.form.reach_limit_of_creation_1" "repo.form.reach_limit_of_creation_n" .MaxCreationLimit}}

-
- {{end}} -
- - - -
- - - {{ctx.Locale.Tr "repo.repo_name_helper"}} -
-
- -
- - -
- {{if .IsForcedPrivate}} - {{ctx.Locale.Tr "repo.visibility_helper_forced"}} {{end}} - {{ctx.Locale.Tr "repo.visibility_description"}} -
-
- - -
-
- - -
+
+ {{template "repo/create_basic" .}} +
-
-
- -
- - -
-
- - -
-
-
- -
- - -
-
- - -
-
-
- -
- - -
-
- - -
-
-
- -
- - -
-
-
+
+ + {{ctx.Locale.Tr "repo.new_from_template"}} + {{ctx.Locale.Tr "repo.new_from_template_description"}} + + {{template "repo/create_from_template" .}} +
-
-
- - -
+
+
+ {{ctx.Locale.Tr "repo.auto_init"}} + {{template "repo/create_init" .}} +
-
- -
- - - {{ctx.Locale.Tr "repo.repo_gitignore_helper_desc"}} +
+ {{ctx.Locale.Tr "repo.new_advanced"}} +
{{ctx.Locale.Tr "repo.new_advanced_expand"}} + {{template "repo/create_advanced" .}} +
+
-
- - - {{ctx.Locale.Tr "repo.license_helper_desc" "https://choosealicense.com/"}} -
- -
- - - {{ctx.Locale.Tr "repo.readme_helper_desc"}} -
-
-
- - -
-
-
- - - {{ctx.Locale.Tr "repo.default_branch_helper"}} -
-
- - - {{ctx.Locale.Tr "repo.object_format_helper"}} -
-
- -
- - -
-
-
-
-
- - -
+ {{else}} +
+ {{ctx.Locale.Tr "repo.form.cannot_create"}} +
+ {{end}}
diff --git a/templates/repo/create_advanced.tmpl b/templates/repo/create_advanced.tmpl new file mode 100644 index 0000000000..c0274701f8 --- /dev/null +++ b/templates/repo/create_advanced.tmpl @@ -0,0 +1,45 @@ + + +{{$supportedFormatsLength := len .SupportedObjectFormats}} +{{/* Only offer object format selection if there is an actual choice */}} +{{if ge $supportedFormatsLength 2}} + +{{else}} + +{{end}} + + + + diff --git a/templates/repo/create_basic.tmpl b/templates/repo/create_basic.tmpl new file mode 100644 index 0000000000..90545c2769 --- /dev/null +++ b/templates/repo/create_basic.tmpl @@ -0,0 +1,57 @@ + + + + diff --git a/templates/repo/create_from_template.tmpl b/templates/repo/create_from_template.tmpl new file mode 100644 index 0000000000..47cda3df02 --- /dev/null +++ b/templates/repo/create_from_template.tmpl @@ -0,0 +1,49 @@ + +{{/* If the dropdown is inside the label, the focus works correctly and it is more accessible. + However, the Javascript takes the focus and opens the dropdown again immediately after closing. + When the user interacts (via mouse or keyboard), the dropdown closes again. + Due to the fieldset legend, this solutions is probably acceptable until the dropdown can be fixed properly. */}} + + +
+ {{ctx.Locale.Tr "repo.template.items"}} + + + + + + + + +
diff --git a/templates/repo/create_init.tmpl b/templates/repo/create_init.tmpl new file mode 100644 index 0000000000..729b44c8e6 --- /dev/null +++ b/templates/repo/create_init.tmpl @@ -0,0 +1,56 @@ + + +
+ + + + + {{$supportedReadmesLength := len .Readmes}} + {{/* Only offer README selection if there is an actual choice */}} + {{if ge $supportedReadmesLength 2}} + + {{else}} + + {{end}} +
diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 230e49752f..e24c880746 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -85,7 +85,6 @@ diffFileInfo.files.push(...diffDataFiles); window.config.pageData.diffFileInfo = diffFileInfo; -
{{end}}
{{if $showFileTree}} @@ -130,7 +129,7 @@
{{if $file.IsRenamed}}{{$file.OldName}} → {{end}}{{$file.Name}} {{if .IsLFSFile}} ({{ctx.Locale.Tr "repo.stored_lfs"}}){{end}} - + {{if $file.IsGenerated}} {{ctx.Locale.Tr "repo.diff.generated"}} {{end}} @@ -248,8 +247,8 @@
{{end}}
- - + +
diff --git a/templates/repo/diff/comment_form.tmpl b/templates/repo/diff/comment_form.tmpl index 856b3da01a..bb29583059 100644 --- a/templates/repo/diff/comment_form.tmpl +++ b/templates/repo/diff/comment_form.tmpl @@ -31,7 +31,6 @@ {{if $.reply}} - {{else}} {{if $.root.CurrentReview}} diff --git a/templates/repo/diff/comments.tmpl b/templates/repo/diff/comments.tmpl index 2e0c85d0a1..3128149c75 100644 --- a/templates/repo/diff/comments.tmpl +++ b/templates/repo/diff/comments.tmpl @@ -1,6 +1,6 @@ {{range .comments}} -{{$createdStr:= TimeSinceUnix .CreatedUnix ctx.Locale}} +{{$createdStr:= DateUtils.TimeSince .CreatedUnix}}
{{if .OriginalAuthor}} {{ctx.AvatarUtils.Avatar nil}} @@ -33,19 +33,15 @@
{{if .Invalidated}} {{$referenceUrl := printf "%s#%s" $.root.Issue.Link .HashTag}} - + {{ctx.Locale.Tr "repo.issues.review.outdated"}} {{end}} {{if and .Review}} {{if eq .Review.Type 0}} -
+
{{ctx.Locale.Tr "repo.issues.review.pending"}}
- {{else}} -
- {{ctx.Locale.Tr "repo.issues.review.review"}} -
{{end}} {{end}} {{template "repo/issue/view_content/add_reaction" dict "ctxData" $.root "ActionURL" (printf "%s/comments/%d/reactions" $.root.RepoLink .ID)}} @@ -53,7 +49,7 @@
-
+
{{if .RenderedContent}} {{.RenderedContent}} {{else}} diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl index 110f8ac60b..c1b00c5f9e 100644 --- a/templates/repo/diff/compare.tmpl +++ b/templates/repo/diff/compare.tmpl @@ -39,21 +39,13 @@ {{svg "octicon-filter" 16}}
-
- + -
- + + {{else}} +
+ {{ctx.Locale.Tr "repo.pulls.sign_in_require" .SignInLink}} +
{{end}} {{if $.IsSigned}}
diff --git a/templates/repo/diff/conversation.tmpl b/templates/repo/diff/conversation.tmpl index c80d999f47..9753bd80e1 100644 --- a/templates/repo/diff/conversation.tmpl +++ b/templates/repo/diff/conversation.tmpl @@ -14,7 +14,7 @@ We only handle the case $resolved=true and $invalid=true in this template because if the comment is not resolved it has the outdated label in the comments area (not the header above). The case $resolved=false and $invalid=true is handled in repo/diff/comments.tmpl --> - + {{ctx.Locale.Tr "repo.issues.review.outdated"}} {{end}} diff --git a/templates/repo/diff/image_diff.tmpl b/templates/repo/diff/image_diff.tmpl index 0612854609..a793f54da1 100644 --- a/templates/repo/diff/image_diff.tmpl +++ b/templates/repo/diff/image_diff.tmpl @@ -22,7 +22,7 @@ {{if .blobBase}}

{{ctx.Locale.Tr "repo.diff.file_before"}}

- +

{{ctx.Locale.Tr "repo.diff.file_image_width"}}: @@ -37,7 +37,7 @@ {{if .blobHead}}

{{ctx.Locale.Tr "repo.diff.file_after"}}

- +

{{ctx.Locale.Tr "repo.diff.file_image_width"}}: @@ -55,9 +55,9 @@

- + {{ctx.Locale.Tr - + {{ctx.Locale.Tr @@ -70,8 +70,8 @@
- - + {{ctx.Locale.Tr + {{ctx.Locale.Tr
diff --git a/templates/repo/diff/new_review.tmpl b/templates/repo/diff/new_review.tmpl index a2eae007a5..13d09babe1 100644 --- a/templates/repo/diff/new_review.tmpl +++ b/templates/repo/diff/new_review.tmpl @@ -1,9 +1,16 @@
- +
+ +
{{if $.IsShowingAllCommits}}
diff --git a/templates/repo/diff/options_dropdown.tmpl b/templates/repo/diff/options_dropdown.tmpl index 09b7b80e41..44b0743e09 100644 --- a/templates/repo/diff/options_dropdown.tmpl +++ b/templates/repo/diff/options_dropdown.tmpl @@ -1,7 +1,6 @@ diff --git a/templates/repo/issue/milestone_issues.tmpl b/templates/repo/issue/milestone_issues.tmpl index e5dc8cb605..8b8b194f46 100644 --- a/templates/repo/issue/milestone_issues.tmpl +++ b/templates/repo/issue/milestone_issues.tmpl @@ -30,7 +30,7 @@
- {{$closedDate:= TimeSinceUnix .Milestone.ClosedDateUnix ctx.Locale}} + {{$closedDate:= DateUtils.TimeSince .Milestone.ClosedDateUnix}} {{if .IsClosed}} {{svg "octicon-clock"}} {{ctx.Locale.Tr "repo.milestones.closed" $closedDate}} {{else}} @@ -38,7 +38,7 @@ {{if .Milestone.DeadlineString}} {{svg "octicon-calendar"}} - {{DateTime "short" .Milestone.DeadlineString}} + {{DateUtils.AbsoluteShort (.Milestone.DeadlineString|DateUtils.ParseLegacy)}} {{else}} {{svg "octicon-calendar"}} diff --git a/templates/repo/issue/milestone_new.tmpl b/templates/repo/issue/milestone_new.tmpl index 9f32df00e3..4a5f0a15ba 100644 --- a/templates/repo/issue/milestone_new.tmpl +++ b/templates/repo/issue/milestone_new.tmpl @@ -35,8 +35,15 @@
- - + {{template "shared/combomarkdowneditor" (dict + "MarkdownPreviewUrl" (print .Repository.Link "/markup") + "MarkdownPreviewContext" .RepoLink + "TextareaName" "content" + "TextareaPlaceholder" (ctx.Locale.Tr "repo.milestones.desc") + "TextareaAriaLabel" (ctx.Locale.Tr "repo.milestones.desc") + "TextareaContent" .content + "EasyMDE" true + )}}
diff --git a/templates/repo/issue/milestones.tmpl b/templates/repo/issue/milestones.tmpl index 63a6f6b26a..a070a02f72 100644 --- a/templates/repo/issue/milestones.tmpl +++ b/templates/repo/issue/milestones.tmpl @@ -21,7 +21,7 @@ {{range .Milestones}}
  • -

    +

    {{svg "octicon-milestone" 16}} {{.Name}}

    @@ -49,19 +49,19 @@ {{if .UpdatedUnix}}
    {{svg "octicon-clock"}} - {{ctx.Locale.Tr "repo.milestones.update_ago" (TimeSinceUnix .UpdatedUnix ctx.Locale)}} + {{ctx.Locale.Tr "repo.milestones.update_ago" (DateUtils.TimeSince .UpdatedUnix)}}
    {{end}}
    {{if .IsClosed}} - {{$closedDate:= TimeSinceUnix .ClosedDateUnix ctx.Locale}} + {{$closedDate:= DateUtils.TimeSince .ClosedDateUnix}} {{svg "octicon-clock" 14}} {{ctx.Locale.Tr "repo.milestones.closed" $closedDate}} {{else}} {{if .DeadlineString}} {{svg "octicon-calendar" 14}} - {{DateTime "short" .DeadlineString}} + {{DateUtils.AbsoluteShort (.DeadlineString|DateUtils.ParseLegacy)}} {{else}} {{svg "octicon-calendar" 14}} diff --git a/templates/repo/issue/navbar.tmpl b/templates/repo/issue/navbar.tmpl index 30e42c77cc..4d90ca3c0e 100644 --- a/templates/repo/issue/navbar.tmpl +++ b/templates/repo/issue/navbar.tmpl @@ -1,4 +1,4 @@ - +
    diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index fe10c1f9b9..2c7807206e 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -110,7 +110,7 @@
    {{range .OpenProjects}} - {{svg .IconName 18 "tw-mr-2"}}{{.Title}} + {{svg .IconName 16 "tw-mr-2"}}{{.Title}} {{end}} {{end}} @@ -121,7 +121,7 @@
  • {{range .ClosedProjects}} - {{svg .IconName 18 "tw-mr-2"}}{{.Title}} + {{svg .IconName 16 "tw-mr-2"}}{{.Title}} {{end}} {{end}} @@ -133,49 +133,14 @@
    {{end}}
    - - -
    - - {{ctx.Locale.Tr "repo.issues.new.no_assignees"}} - - -
    + {{template "repo/issue/view_content/sidebar/assignees" dict "isExistingIssue" false "." .}} {{if and .PageIsComparePull (not (eq .HeadRepo.FullName .BaseCompareRepo.FullName)) .CanWriteToHeadRepo}}
    diff --git a/templates/repo/issue/openclose.tmpl b/templates/repo/issue/openclose.tmpl index eb2d6e09ee..5d5cf4140e 100644 --- a/templates/repo/issue/openclose.tmpl +++ b/templates/repo/issue/openclose.tmpl @@ -1,16 +1,22 @@ - diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index 683dea8425..630cc40b86 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -6,7 +6,7 @@ - {{$createdStr:= TimeSinceUnix .Issue.CreatedUnix ctx.Locale}} + {{$createdStr:= DateUtils.TimeSince .Issue.CreatedUnix}}
    @@ -52,7 +52,7 @@
    -
    +
    {{if .Issue.RenderedContent}} {{.Issue.RenderedContent}} {{else}} @@ -79,8 +79,19 @@ {{template "repo/issue/view_content/pull".}} {{end}} - {{if .IsSigned}} - {{if and (or .IsRepoAdmin .HasIssuesOrPullsWritePermission (not .Issue.IsLocked)) (not .Repository.IsArchived)}} + {{if .Repository.IsArchived}} +
    + {{ctx.Locale.Tr "repo.archive.nocomment"}} +
    + {{else if .IsBlocked}} +
    + {{ctx.Locale.Tr "repo.comment.blocked_by_user"}} +
    + {{else if and .Issue.IsLocked (not (or .IsRepoAdmin .HasIssuesOrPullsWritePermission))}} +
    + {{ctx.Locale.Tr "discussion.locked"}} +
    + {{else if .IsSigned}} @@ -163,8 +155,8 @@
    - - + +
    diff --git a/templates/repo/issue/view_content/add_reaction.tmpl b/templates/repo/issue/view_content/add_reaction.tmpl index 37931f287e..5d4aa2298e 100644 --- a/templates/repo/issue/view_content/add_reaction.tmpl +++ b/templates/repo/issue/view_content/add_reaction.tmpl @@ -1,5 +1,5 @@ {{if .ctxData.IsSigned}} -