Compare commits

..

No commits in common. "forgejo" and "v10.0.0-dev" have entirely different histories.

3195 changed files with 49139 additions and 110149 deletions

View file

@ -1,7 +1,7 @@
forgejo.org/cmd code.gitea.io/gitea/cmd
NoMainListener NoMainListener
forgejo.org/cmd/forgejo code.gitea.io/gitea/cmd/forgejo
ContextSetNoInit ContextSetNoInit
ContextSetNoExit ContextSetNoExit
ContextSetStderr ContextSetStderr
@ -9,152 +9,204 @@ forgejo.org/cmd/forgejo
ContextSetStdout ContextSetStdout
ContextSetStdin ContextSetStdin
forgejo.org/models code.gitea.io/gitea/models
IsErrUpdateTaskNotExist
ErrUpdateTaskNotExist.Error
ErrUpdateTaskNotExist.Unwrap
IsErrSHANotFound IsErrSHANotFound
IsErrMergeDivergingFastForwardOnly IsErrMergeDivergingFastForwardOnly
GetYamlFixturesAccess
forgejo.org/models/auth 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
WebAuthnCredentials WebAuthnCredentials
forgejo.org/models/db code.gitea.io/gitea/models/db
TruncateBeans TruncateBeans
InTransaction InTransaction
DumpTables DumpTables
forgejo.org/models/dbfs code.gitea.io/gitea/models/dbfs
file.renameTo file.renameTo
Create Create
Rename Rename
forgejo.org/models/forgefed code.gitea.io/gitea/models/forgefed
GetFederationHost GetFederationHost
forgejo.org/models/forgejo/semver code.gitea.io/gitea/models/forgejo/semver
GetVersion GetVersion
SetVersionString SetVersionString
SetVersion SetVersion
forgejo.org/models/git code.gitea.io/gitea/models/git
RemoveDeletedBranchByID RemoveDeletedBranchByID
forgejo.org/models/issues code.gitea.io/gitea/models/issues
IsErrUnknownDependencyType IsErrUnknownDependencyType
ErrNewIssueInsert.Error
IsErrIssueWasClosed IsErrIssueWasClosed
ChangeMilestoneStatus
forgejo.org/models/organization code.gitea.io/gitea/models/organization
GetTeamNamesByID
UpdateTeamUnits
SearchMembersOptions.ToConds SearchMembersOptions.ToConds
UsersInTeamsCount
forgejo.org/models/perm/access code.gitea.io/gitea/models/perm/access
GetRepoWriters GetRepoWriters
forgejo.org/models/repo code.gitea.io/gitea/models/project
UpdateColumnSorting
ChangeProjectStatus
code.gitea.io/gitea/models/repo
DeleteAttachmentsByIssue
FindReposMapByIDs
IsErrTopicNotExist
ErrTopicNotExist.Error
ErrTopicNotExist.Unwrap
GetTopicByName
WatchRepoMode WatchRepoMode
forgejo.org/models/user code.gitea.io/gitea/models/user
ErrUserInactive.Error
ErrUserInactive.Unwrap
IsErrExternalLoginUserAlreadyExist IsErrExternalLoginUserAlreadyExist
IsErrExternalLoginUserNotExist IsErrExternalLoginUserNotExist
NewFederatedUser NewFederatedUser
IsErrUserSettingIsNotExist IsErrUserSettingIsNotExist
GetUserAllSettings GetUserAllSettings
DeleteUserSetting DeleteUserSetting
GetUserEmailsByNames
GetUserNamesByIDs
forgejo.org/modules/activitypub code.gitea.io/gitea/modules/activitypub
NewContext NewContext
Context.APClientFactory Context.APClientFactory
forgejo.org/modules/assetfs code.gitea.io/gitea/modules/assetfs
Bindata Bindata
forgejo.org/modules/auth/password/hash code.gitea.io/gitea/modules/auth/password/hash
DummyHasher.HashWithSaltBytes DummyHasher.HashWithSaltBytes
NewDummyHasher NewDummyHasher
forgejo.org/modules/auth/password/pwn code.gitea.io/gitea/modules/auth/password/pwn
WithHTTP WithHTTP
forgejo.org/modules/base code.gitea.io/gitea/modules/base
SetupGiteaRoot SetupGiteaRoot
forgejo.org/modules/cache code.gitea.io/gitea/modules/cache
GetInt GetInt
WithNoCacheContext WithNoCacheContext
RemoveContextData RemoveContextData
forgejo.org/modules/emoji code.gitea.io/gitea/modules/charset
BreakWriter.Write
code.gitea.io/gitea/modules/emoji
ReplaceCodes ReplaceCodes
forgejo.org/modules/eventsource code.gitea.io/gitea/modules/eventsource
Event.String Event.String
forgejo.org/modules/forgefed code.gitea.io/gitea/modules/forgefed
NewForgeUndoLike
ForgeUndoLike.UnmarshalJSON
ForgeUndoLike.Validate
GetItemByType GetItemByType
JSONUnmarshalerFn JSONUnmarshalerFn
NotEmpty NotEmpty
ToRepository ToRepository
OnRepository OnRepository
forgejo.org/modules/git code.gitea.io/gitea/modules/git
AllowLFSFiltersArgs AllowLFSFiltersArgs
AddChanges AddChanges
AddChangesWithArgs AddChangesWithArgs
CommitChanges CommitChanges
CommitChangesWithArgs CommitChangesWithArgs
IsErrExecTimeout
ErrExecTimeout.Error
ErrUnsupportedVersion.Error
SetUpdateHook SetUpdateHook
openRepositoryWithDefaultContext openRepositoryWithDefaultContext
IsTagExist
ToEntryMode ToEntryMode
LimitedReaderCloser.Read
LimitedReaderCloser.Close
forgejo.org/modules/gitrepo code.gitea.io/gitea/modules/gitgraph
Parser.Reset
code.gitea.io/gitea/modules/gitrepo
GetBranchCommitID GetBranchCommitID
GetWikiDefaultBranch GetWikiDefaultBranch
forgejo.org/modules/graceful code.gitea.io/gitea/modules/graceful
Manager.TerminateContext Manager.TerminateContext
Manager.Err Manager.Err
Manager.Value Manager.Value
Manager.Deadline Manager.Deadline
forgejo.org/modules/hcaptcha code.gitea.io/gitea/modules/hcaptcha
WithHTTP WithHTTP
forgejo.org/modules/hostmatcher code.gitea.io/gitea/modules/hostmatcher
HostMatchList.AppendPattern HostMatchList.AppendPattern
forgejo.org/modules/json code.gitea.io/gitea/modules/json
StdJSON.Marshal StdJSON.Marshal
StdJSON.Unmarshal StdJSON.Unmarshal
StdJSON.NewEncoder StdJSON.NewEncoder
StdJSON.NewDecoder StdJSON.NewDecoder
StdJSON.Indent StdJSON.Indent
forgejo.org/modules/log code.gitea.io/gitea/modules/markup
NewEventWriterBuffer
forgejo.org/modules/markup
GetRendererByType GetRendererByType
RenderString RenderString
IsMarkupFile IsMarkupFile
forgejo.org/modules/markup/console code.gitea.io/gitea/modules/markup/console
Render Render
RenderString RenderString
forgejo.org/modules/markup/markdown code.gitea.io/gitea/modules/markup/markdown
IsDetails
IsSummary
IsTaskCheckBoxListItem
IsIcon
RenderRawString RenderRawString
forgejo.org/modules/markup/mdstripper code.gitea.io/gitea/modules/markup/markdown/math
WithInlineDollarParser
WithBlockDollarParser
code.gitea.io/gitea/modules/markup/mdstripper
stripRenderer.AddOptions stripRenderer.AddOptions
StripMarkdown StripMarkdown
forgejo.org/modules/markup/orgmode code.gitea.io/gitea/modules/markup/orgmode
RenderString RenderString
forgejo.org/modules/process code.gitea.io/gitea/modules/private
ActionsRunnerRegister
code.gitea.io/gitea/modules/process
Manager.ExecTimeout Manager.ExecTimeout
forgejo.org/modules/queue code.gitea.io/gitea/modules/queue
newBaseChannelSimple newBaseChannelSimple
newBaseChannelUnique newBaseChannelUnique
newBaseRedisSimple newBaseRedisSimple
@ -163,73 +215,89 @@ forgejo.org/modules/queue
testStateRecorder.Reset testStateRecorder.Reset
newWorkerPoolQueueForTest newWorkerPoolQueueForTest
forgejo.org/modules/queue/lqinternal code.gitea.io/gitea/modules/queue/lqinternal
QueueItemIDBytes QueueItemIDBytes
QueueItemKeyBytes QueueItemKeyBytes
ListLevelQueueKeys ListLevelQueueKeys
forgejo.org/modules/setting code.gitea.io/gitea/modules/setting
NewConfigProviderFromData NewConfigProviderFromData
GitConfigType.GetOption GitConfigType.GetOption
InitLoggersForTest InitLoggersForTest
forgejo.org/modules/sync code.gitea.io/gitea/modules/storage
ErrInvalidConfiguration.Error
IsErrInvalidConfiguration
code.gitea.io/gitea/modules/structs
ParseCreateHook
ParsePushHook
code.gitea.io/gitea/modules/sync
StatusTable.Start StatusTable.Start
StatusTable.IsRunning StatusTable.IsRunning
forgejo.org/modules/timeutil code.gitea.io/gitea/modules/timeutil
GetExecutableModTime GetExecutableModTime
MockSet MockSet
MockUnset MockUnset
forgejo.org/modules/translation code.gitea.io/gitea/modules/translation
MockLocale.Language MockLocale.Language
MockLocale.TrString MockLocale.TrString
MockLocale.Tr MockLocale.Tr
MockLocale.TrN MockLocale.TrN
MockLocale.TrPluralString
MockLocale.TrPluralStringAllForms
MockLocale.TrSize MockLocale.TrSize
MockLocale.HasKey
MockLocale.PrettyNumber MockLocale.PrettyNumber
forgejo.org/modules/translation/localeiter code.gitea.io/gitea/modules/util/filebuffer
IterateMessagesContent
forgejo.org/modules/util
OptionalArg
forgejo.org/modules/util/filebuffer
CreateFromReader CreateFromReader
forgejo.org/modules/validation code.gitea.io/gitea/modules/validation
IsErrNotValid IsErrNotValid
ValidateIDExists
forgejo.org/modules/web code.gitea.io/gitea/modules/web
RouteMock RouteMock
RouteMockReset RouteMockReset
forgejo.org/modules/zstd code.gitea.io/gitea/modules/web/middleware
DeleteLocaleCookie
code.gitea.io/gitea/modules/zstd
NewWriter NewWriter
Writer.Write Writer.Write
Writer.Close Writer.Close
forgejo.org/routers/web/org code.gitea.io/gitea/routers/web
NotFound
code.gitea.io/gitea/routers/web/org
MustEnableProjects MustEnableProjects
forgejo.org/services/context code.gitea.io/gitea/services/context
GetPrivateContext GetPrivateContext
forgejo.org/services/repository 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
IsErrForkAlreadyExist IsErrForkAlreadyExist
forgejo.org/services/repository/files code.gitea.io/gitea/services/repository/archiver
ArchiveRepository
code.gitea.io/gitea/services/repository/files
ContentType.String ContentType.String
GetFileResponseFromCommit
TemporaryUploadRepository.GetLastCommit
TemporaryUploadRepository.GetLastCommitByRef
forgejo.org/services/repository/gitgraph code.gitea.io/gitea/services/webhook
Parser.Reset
forgejo.org/services/webhook
NewNotifier NewNotifier

View file

@ -1,12 +1,16 @@
{ {
"name": "Gitea DevContainer", "name": "Gitea DevContainer",
"image": "mcr.microsoft.com/devcontainers/go:1.24-bullseye", "image": "mcr.microsoft.com/devcontainers/go:1.23-bullseye",
"features": { "features": {
// installs nodejs into container // installs nodejs into container
"ghcr.io/devcontainers/features/node:1": { "ghcr.io/devcontainers/features/node:1": {
"version": "22" "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"
}, },
"ghcr.io/devcontainers/features/git-lfs:1.2.3": {},
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {} "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
}, },
"customizations": { "customizations": {

View file

@ -34,7 +34,6 @@ _testmain.go
*coverage.out *coverage.out
coverage.all coverage.all
coverage/
cpu.out cpu.out
/modules/migration/bindata.go /modules/migration/bindata.go

View file

@ -12,9 +12,6 @@ insert_final_newline = true
[{*.{go,tmpl,html},Makefile,go.mod}] [{*.{go,tmpl,html},Makefile,go.mod}]
indent_style = tab indent_style = tab
[go.*]
indent_style = tab
[templates/custom/*.tmpl] [templates/custom/*.tmpl]
insert_final_newline = false insert_final_newline = false
@ -29,8 +26,3 @@ insert_final_newline = false
[options/locale/locale_*.ini] [options/locale/locale_*.ini]
insert_final_newline = false insert_final_newline = false
# Weblate JSON output defaults to four spaces
[options/locale_next/locale_*.json]
indent_style = space
indent_size = 4

803
.eslintrc.yaml Normal file
View file

@ -0,0 +1,803 @@
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]

View file

@ -1,7 +1,7 @@
contact_links: contact_links:
- name: 🔓 Security Reports - name: 🔓 Security Reports
url: mailto:security@forgejo.org url: mailto:security@forgejo.org
about: "Please email <security@forgejo.org> (See https://forgejo.org/.well-known/security.txt)." about: "Please email <security@forgejo.org> (GPG: `A4676E79`) instead of opening a public issue."
- name: 💬 Matrix Chat Room - name: 💬 Matrix Chat Room
url: https://matrix.to/#/#forgejo-chat:matrix.org url: https://matrix.to/#/#forgejo-chat:matrix.org
about: Please ask questions and discuss configuration or deployment problems here. about: Please ask questions and discuss configuration or deployment problems here.

View file

@ -1,6 +1,6 @@
FROM data.forgejo.org/oci/alpine:3.21 FROM code.forgejo.org/oci/alpine:3.20
ARG RELEASE_VERSION=unkown ARG RELEASE_VERSION=unkown
LABEL maintainer="contact@forgejo.org" \ LABEL maintainer="contact@forgejo.org" \
org.opencontainers.image.version="${RELEASE_VERSION}" org.opencontainers.image.version="${RELEASE_VERSION}"
RUN mkdir -p /app/gitea RUN mkdir -p /app/gitea
RUN ( echo '#!/bin/sh' ; echo "echo forgejo v$RELEASE_VERSION" ) > /app/gitea/gitea ; chmod +x /app/gitea/gitea RUN ( echo '#!/bin/sh' ; echo "echo forgejo v$RELEASE_VERSION" ) > /app/gitea/forgejo-cli ; chmod +x /app/gitea/forgejo-cli

View file

@ -1,3 +1,3 @@
module forgejo.org module code.gitea.io/gitea
go 1.23.3 go 1.23.1

View file

@ -1,29 +0,0 @@
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}}

View file

@ -1,15 +0,0 @@
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

View file

@ -1,61 +0,0 @@
# 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 }}

View file

@ -1,25 +0,0 @@
# 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

View file

@ -22,8 +22,6 @@
# `backport/v1.21` label on a merged pull request that can be backported # `backport/v1.21` label on a merged pull request that can be backported
# without conflict. # without conflict.
# #
name: issue-labels
on: on:
pull_request_target: pull_request_target:
types: types:
@ -33,21 +31,21 @@ on:
jobs: jobs:
backporting: backporting:
if: > if: >
( vars.ROLE == 'forgejo-coding' ) && ( !startsWith(vars.ROLE, 'forgejo-') && (
github.event.pull_request.merged github.event.pull_request.merged
&& &&
contains(toJSON(github.event.pull_request.labels), 'backport/v') contains(toJSON(github.event.pull_request.labels), 'backport/v')
) )
runs-on: docker runs-on: docker
container: container:
image: 'data.forgejo.org/oci/node:22-bookworm' image: 'code.forgejo.org/oci/node:20-bookworm'
steps: steps:
- name: event info - name: event info
run: | run: |
cat <<'EOF' cat <<'EOF'
${{ toJSON(github) }} ${{ toJSON(github) }}
EOF EOF
- uses: https://data.forgejo.org/actions/git-backporting@v4.8.5 - uses: https://code.forgejo.org/actions/git-backporting@v4.8.0
with: with:
target-branch-pattern: "^backport/(?<target>(v.*))$" target-branch-pattern: "^backport/(?<target>(v.*))$"
strategy: ort strategy: ort

View file

@ -22,13 +22,13 @@ on:
jobs: jobs:
release-simulation: release-simulation:
if: vars.ROLE == 'forgejo-coding' if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: lxc-bookworm runs-on: self-hosted
steps: steps:
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: actions/checkout@v3
- id: forgejo - id: forgejo
uses: https://data.forgejo.org/actions/setup-forgejo@v2.0.4 uses: https://code.forgejo.org/actions/setup-forgejo@v1
with: with:
user: root user: root
password: admin1234 password: admin1234

View file

@ -14,12 +14,6 @@
# secrets.CASCADE_DESTINATION_TOKEN: <generated from code.forgejo.org/forgejo-ci> scope read:user, write:repository, write:issue # secrets.CASCADE_DESTINATION_TOKEN: <generated from code.forgejo.org/forgejo-ci> scope read:user, write:repository, write:issue
# vars.CASCADE_DESTINATION_DOER: forgejo-ci # 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: on:
push: push:
tags: 'v[0-9]+.[0-9]+.*' tags: 'v[0-9]+.[0-9]+.*'
@ -29,11 +23,11 @@ on:
jobs: jobs:
release: release:
runs-on: lxc-bookworm runs-on: self-hosted
# root is used for testing, allow it # root is used for testing, allow it
if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root' if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root'
steps: steps:
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
@ -43,11 +37,11 @@ jobs:
repository="${{ github.repository }}" repository="${{ github.repository }}"
echo "value=${repository##*/}" >> "$GITHUB_OUTPUT" echo "value=${repository##*/}" >> "$GITHUB_OUTPUT"
- uses: https://data.forgejo.org/actions/setup-node@v4 - uses: https://code.forgejo.org/actions/setup-node@v3
with: with:
node-version: 22 node-version: 20
- uses: https://data.forgejo.org/actions/setup-go@v5 - uses: https://code.forgejo.org/actions/setup-go@v4
with: with:
go-version-file: "go.mod" go-version-file: "go.mod"
@ -93,7 +87,7 @@ jobs:
- name: cache node_modules - name: cache node_modules
id: node id: node
uses: https://data.forgejo.org/actions/cache@v4 uses: https://code.forgejo.org/actions/cache@v3
with: with:
path: | path: |
node_modules node_modules
@ -164,7 +158,7 @@ jobs:
- name: build container & release - name: build container & release
if: ${{ secrets.TOKEN != '' }} if: ${{ secrets.TOKEN != '' }}
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.4 uses: https://code.forgejo.org/forgejo/forgejo-build-publish/build@v5.1.1
with: with:
forgejo: "${{ env.GITHUB_SERVER_URL }}" forgejo: "${{ env.GITHUB_SERVER_URL }}"
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}" owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
@ -176,14 +170,14 @@ jobs:
platforms: linux/amd64,linux/arm64,linux/arm/v6 platforms: linux/amd64,linux/arm64,linux/arm/v6
release-notes: "${{ steps.release-notes.outputs.value }}" release-notes: "${{ steps.release-notes.outputs.value }}"
binary-name: forgejo binary-name: forgejo
binary-path: /app/gitea/gitea binary-path: /app/gitea/forgejo-cli
override: "${{ steps.release-info.outputs.override }}" override: "${{ steps.release-info.outputs.override }}"
verify-labels: "maintainer=contact@forgejo.org,org.opencontainers.image.version=${{ steps.release-info.outputs.version }}" verify-labels: "maintainer=contact@forgejo.org,org.opencontainers.image.version=${{ steps.release-info.outputs.version }}"
verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }} verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }}
- name: build rootless container - name: build rootless container
if: ${{ secrets.TOKEN != '' }} if: ${{ secrets.TOKEN != '' }}
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.4 uses: https://code.forgejo.org/forgejo/forgejo-build-publish/build@v5.1.1
with: with:
forgejo: "${{ env.GITHUB_SERVER_URL }}" forgejo: "${{ env.GITHUB_SERVER_URL }}"
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}" owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
@ -200,8 +194,8 @@ jobs:
verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }} verbose: ${{ vars.VERBOSE || secrets.VERBOSE || 'false' }}
- name: end-to-end tests - name: end-to-end tests
if: ${{ secrets.TOKEN != '' && vars.ROLE == 'forgejo-integration' && vars.SKIP_END_TO_END != 'true' }} if: ${{ secrets.TOKEN != '' && vars.ROLE == 'forgejo-integration' }}
uses: https://data.forgejo.org/actions/cascading-pr@v2.2.0 uses: https://code.forgejo.org/actions/cascading-pr@v2
with: with:
origin-url: ${{ env.GITHUB_SERVER_URL }} origin-url: ${{ env.GITHUB_SERVER_URL }}
origin-repo: ${{ github.repository }} origin-repo: ${{ github.repository }}

View file

@ -12,10 +12,8 @@
# whatever is in the default branch instead # whatever is in the default branch instead
# #
# - after it is merged, double check it works by setting the # - after it is merged, double check it works by setting the
# run-end-to-end-test on a pull request (any pull request will do) # run-end-to-end-test on a pull request (any pull request will doe
# #
name: issue-labels
on: on:
push: push:
branches: branches:
@ -25,23 +23,42 @@ on:
- labeled - labeled
jobs: 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: cascade:
if: > if: >
vars.ROLE == 'forgejo-coding' && ( !startsWith(vars.ROLE, 'forgejo-') && (
github.event_name == 'push' || github.event_name == 'push' ||
( (
github.event.action == 'label_updated' && github.event.label.name == 'run-end-to-end-tests' github.event.action == 'label_updated' && contains(github.event.pull_request.labels.*.name, 'run-end-to-end-tests')
) )
) )
runs-on: docker runs-on: docker
container: container:
image: data.forgejo.org/oci/node:22-bookworm image: code.forgejo.org/oci/node:20-bookworm
steps: steps:
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: '0' fetch-depth: '0'
show-progress: 'false' show-progress: 'false'
- uses: https://data.forgejo.org/actions/cascading-pr@v2.2.0 - uses: actions/cascading-pr@v2
with: with:
origin-url: ${{ env.GITHUB_SERVER_URL }} origin-url: ${{ env.GITHUB_SERVER_URL }}
origin-repo: ${{ github.repository }} origin-repo: ${{ github.repository }}

View file

@ -0,0 +1,37 @@
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

View file

@ -9,7 +9,7 @@ jobs:
if: vars.ROLE == 'forgejo-integration' if: vars.ROLE == 'forgejo-integration'
runs-on: docker runs-on: docker
container: container:
image: 'data.forgejo.org/oci/node:22-bookworm' image: 'code.forgejo.org/oci/node:20-bookworm'
steps: steps:
- name: apt install curl jq - name: apt install curl jq

View file

@ -1,45 +0,0 @@
# 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

View file

@ -1,24 +0,0 @@
# 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 }}

View file

@ -11,7 +11,7 @@ jobs:
if: ${{ secrets.MIRROR_TOKEN != '' }} if: ${{ secrets.MIRROR_TOKEN != '' }}
runs-on: docker runs-on: docker
container: container:
image: 'data.forgejo.org/oci/node:22-bookworm' image: 'code.forgejo.org/oci/node:20-bookworm'
steps: steps:
- name: git push {v*/,}forgejo - name: git push {v*/,}forgejo
run: | run: |

View file

@ -2,8 +2,6 @@
# #
# See also https://forgejo.org/docs/next/contributor/release/#stable-release-process # 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 # https://codeberg.org/forgejo-experimental/forgejo
# #
# Copies a release from codeberg.org/forgejo-integration to codeberg.org/forgejo-experimental # Copies a release from codeberg.org/forgejo-integration to codeberg.org/forgejo-experimental
@ -16,7 +14,7 @@
# vars.DOER: forgejo-experimental-ci # vars.DOER: forgejo-experimental-ci
# secrets.TOKEN: <generated from codeberg.org/forgejo-experimental-ci> # secrets.TOKEN: <generated from codeberg.org/forgejo-experimental-ci>
# #
# http://invisible.forgejo.org/forgejo/forgejo # http://private.forgejo.org/forgejo/forgejo
# #
# Copies & sign a release from codeberg.org/forgejo-integration to codeberg.org/forgejo # Copies & sign a release from codeberg.org/forgejo-integration to codeberg.org/forgejo
# #
@ -38,20 +36,20 @@ on:
jobs: jobs:
publish: publish:
runs-on: lxc-bookworm runs-on: self-hosted
if: vars.DOER != '' && vars.FORGEJO != '' && vars.TO_OWNER != '' && vars.FROM_OWNER != '' && secrets.TOKEN != '' if: vars.DOER != '' && vars.FORGEJO != '' && vars.TO_OWNER != '' && vars.FROM_OWNER != '' && secrets.TOKEN != ''
steps: steps:
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: actions/checkout@v3
- name: copy & sign - name: copy & sign
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.3.4 uses: https://code.forgejo.org/forgejo/forgejo-build-publish/publish@v5
with: with:
from-forgejo: ${{ vars.FORGEJO }} from-forgejo: ${{ vars.FORGEJO }}
to-forgejo: ${{ vars.FORGEJO }} to-forgejo: ${{ vars.FORGEJO }}
from-owner: ${{ vars.FROM_OWNER }} from-owner: ${{ vars.FROM_OWNER }}
to-owner: ${{ vars.TO_OWNER }} to-owner: ${{ vars.TO_OWNER }}
repo: ${{ vars.REPO }} repo: ${{ vars.REPO }}
release-notes: "See https://codeberg.org/forgejo/forgejo/src/branch/forgejo/release-notes-published/{VERSION}.md" release-notes: "See https://codeberg.org/forgejo/forgejo/src/branch/forgejo/RELEASE-NOTES.md#{ANCHOR}"
ref-name: ${{ github.ref_name }} ref-name: ${{ github.ref_name }}
sha: ${{ github.sha }} sha: ${{ github.sha }}
from-token: ${{ secrets.TOKEN }} from-token: ${{ secrets.TOKEN }}
@ -61,28 +59,30 @@ jobs:
gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }} gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }}
verbose: ${{ vars.VERBOSE }} verbose: ${{ vars.VERBOSE }}
- 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: trigger the mirror
uses: https://data.forgejo.org/infrastructure/issue-action/set@v1.3.0
with:
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 - name: upgrade v*.next.forgejo.org
uses: https://data.forgejo.org/infrastructure/next-digest@v1.1.0 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: 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
with: with:
url: https://placeholder:${{ secrets.TOKEN_NEXT_DIGEST }}@invisible.forgejo.org/infrastructure/next-digest go-version-file: "go.mod"
ref_name: '${{ github.ref_name }}' - name: update the _release.experimental DNS record
image: 'codeberg.org/forgejo-experimental/forgejo' if: vars.ROLE == 'forgejo-experimental' && secrets.OVH_APP_KEY != ''
tag_suffix: '-rootless' uses: https://code.forgejo.org/actions/ovh-dns-update@v1
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 }}

View file

@ -4,19 +4,16 @@ on:
schedule: schedule:
- cron: '@daily' - cron: '@daily'
env:
RNA_VERSION: v1.2.5 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
jobs: jobs:
release-notes: release-notes:
if: vars.ROLE == 'forgejo-coding' if: ${{ !startsWith(vars.ROLE, 'forgejo-')
runs-on: docker runs-on: docker
container: container:
image: 'data.forgejo.org/oci/node:22-bookworm' image: 'code.forgejo.org/oci/node:20-bookworm'
steps: steps:
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://data.forgejo.org/actions/setup-go@v5 - uses: https://code.forgejo.org/actions/setup-go@v4
with: with:
go-version-file: "go.mod" go-version-file: "go.mod"
cache: false cache: false
@ -32,5 +29,5 @@ jobs:
set -x set -x
curl -sS $GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/milestones?state=open | jq -r '.[] | .title' | while read forgejo version ; do curl -sS $GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/milestones?state=open | jq -r '.[] | .title' | while read forgejo version ; do
milestone="$forgejo $version" milestone="$forgejo $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 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
done done

View file

@ -1,5 +1,3 @@
name: issue-labels
on: on:
pull_request_target: pull_request_target:
types: types:
@ -7,17 +5,14 @@ on:
- synchronize - synchronize
- labeled - labeled
env:
RNA_VERSION: v1.2.5 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
jobs: jobs:
release-notes: release-notes:
if: ( vars.ROLE == 'forgejo-coding' ) && contains(github.event.pull_request.labels.*.name, 'worth a release-note') if: ${{ !startsWith(vars.ROLE, 'forgejo-') && contains(github.event.pull_request.labels.*.name, 'worth a release-note') }}
runs-on: docker runs-on: docker
container: container:
image: 'data.forgejo.org/oci/node:22-bookworm' image: 'code.forgejo.org/oci/node:20-bookworm'
steps: steps:
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v3
- name: event - name: event
run: | run: |
@ -28,7 +23,7 @@ jobs:
${{ toJSON(github.event) }} ${{ toJSON(github.event) }}
EOF EOF
- uses: https://data.forgejo.org/actions/setup-go@v5 - uses: https://code.forgejo.org/actions/setup-go@v4
with: with:
go-version-file: "go.mod" go-version-file: "go.mod"
cache: false cache: false
@ -41,4 +36,4 @@ jobs:
- name: release-notes-assistant preview - name: release-notes-assistant preview
run: | run: |
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 }} 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 }}

View file

@ -8,9 +8,7 @@ name: renovate
on: on:
push: push:
branches: branches:
- renovate/** # self-test updates - 'renovate/**' # self-test updates
paths:
- .forgejo/workflows/renovate.yml
schedule: schedule:
- cron: '0 0/2 * * *' - cron: '0 0/2 * * *'
workflow_dispatch: workflow_dispatch:
@ -18,21 +16,18 @@ on:
env: env:
RENOVATE_DRY_RUN: ${{ (github.event_name != 'schedule' && github.ref_name != github.event.repository.default_branch) && 'full' || '' }} RENOVATE_DRY_RUN: ${{ (github.event_name != 'schedule' && github.ref_name != github.event.repository.default_branch) && 'full' || '' }}
RENOVATE_REPOSITORIES: ${{ github.repository }} 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: jobs:
renovate: renovate:
if: vars.ROLE == 'forgejo-coding' && secrets.RENOVATE_TOKEN != '' if: ${{ secrets.RENOVATE_TOKEN != '' }}
runs-on: docker runs-on: docker
container: container:
image: data.forgejo.org/renovate/renovate:40.31.0 image: code.forgejo.org/forgejo-contrib/renovate:38.93.2
steps: steps:
- name: Load renovate repo cache - name: Load renovate repo cache
uses: https://data.forgejo.org/actions/cache/restore@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 uses: https://code.forgejo.org/actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with: with:
path: | path: |
.tmp/cache/renovate/repository .tmp/cache/renovate/repository
@ -65,7 +60,7 @@ jobs:
- name: Save renovate repo cache - name: Save renovate repo cache
if: always() && env.RENOVATE_DRY_RUN != 'full' if: always() && env.RENOVATE_DRY_RUN != 'full'
uses: https://data.forgejo.org/actions/cache/save@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 uses: https://code.forgejo.org/actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with: with:
path: | path: |
.tmp/cache/renovate/repository .tmp/cache/renovate/repository

View file

@ -6,270 +6,319 @@ on:
branches: branches:
- 'forgejo*' - 'forgejo*'
- 'v*/forgejo*' - 'v*/forgejo*'
workflow_dispatch:
jobs: jobs:
backend-checks: backend-checks:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
container: container:
image: 'data.forgejo.org/oci/node:22-bookworm' image: 'code.forgejo.org/oci/node:20-bookworm'
options: --tmpfs /tmp:exec,noatime
steps: steps:
- name: event info - name: event info
run: | run: |
cat <<'EOF' cat <<'EOF'
${{ toJSON(github) }} ${{ toJSON(github) }}
EOF EOF
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v3
- uses: ./.forgejo/workflows-composite/setup-env - uses: https://code.forgejo.org/actions/setup-go@v4
- run: su forgejo -c 'make deps-backend deps-tools' with:
- 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 go-version-file: "go.mod"
- uses: ./.forgejo/workflows-composite/build-backend - 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 }}
frontend-checks: frontend-checks:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
container: container:
image: 'data.forgejo.org/oci/node:22-bookworm' image: 'code.forgejo.org/oci/node:20-bookworm'
options: --tmpfs /tmp:exec,noatime
steps: steps:
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v3
- run: make deps-frontend - run: make deps-frontend
- run: make lint-frontend - run: make lint-frontend
- run: make checks-frontend - run: make checks-frontend
- run: make test-frontend-coverage - run: make test-frontend-coverage
- run: make frontend - 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: test-unit:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
needs: [backend-checks, frontend-checks] needs: [backend-checks, frontend-checks]
container: container:
image: 'data.forgejo.org/oci/node:22-bookworm' image: 'code.forgejo.org/oci/node:20-bookworm'
options: --tmpfs /tmp:exec,noatime
services: services:
elasticsearch: elasticsearch:
image: data.forgejo.org/oci/bitnami/elasticsearch:7 image: docker.io/bitnami/elasticsearch:7
options: --tmpfs /bitnami/elasticsearch/data
env: env:
discovery.type: single-node discovery.type: single-node
ES_JAVA_OPTS: "-Xms512m -Xmx512m" ES_JAVA_OPTS: "-Xms512m -Xmx512m"
minio: minio:
image: data.forgejo.org/oci/bitnami/minio:2024.8.17 image: docker.io/bitnami/minio:2024.8.17
options: >- options: >-
--hostname gitea.minio --tmpfs /bitnami/minio/data:noatime --hostname gitea.minio
env: env:
MINIO_DOMAIN: minio MINIO_DOMAIN: minio
MINIO_ROOT_USER: 123456 MINIO_ROOT_USER: 123456
MINIO_ROOT_PASSWORD: 12345678 MINIO_ROOT_PASSWORD: 12345678
steps: steps:
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v3
- uses: ./.forgejo/workflows-composite/setup-env - uses: https://code.forgejo.org/actions/setup-go@v4
- name: install git >= 2.42
uses: ./.forgejo/workflows-composite/apt-install-from
with: with:
packages: git go-version-file: "go.mod"
- run: |
git config --add safe.directory '*'
adduser --quiet --comment forgejo --disabled-password forgejo
chown -R forgejo:forgejo .
- 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
- name: test release-notes-assistant.sh - name: test release-notes-assistant.sh
run: | run: |
apt-get -q install -qq -y jq apt-get -q install -qq -y jq
./release-notes-assistant.sh test_main ./release-notes-assistant.sh test_main
- uses: ./.forgejo/workflows-composite/build-backend - 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
- run: | - run: |
su forgejo -c 'make test-backend test-check' su forgejo -c 'make test-backend test-check'
timeout-minutes: 120 timeout-minutes: 50
env: env:
RACE_ENABLED: 'true' RACE_ENABLED: 'true'
TAGS: bindata TAGS: bindata
TEST_ELASTICSEARCH_URL: http://elasticsearch:9200 TEST_ELASTICSEARCH_URL: http://elasticsearch:9200
test-e2e: test-remote-cacher:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
needs: [backend-checks, frontend-checks] needs: [backend-checks, frontend-checks]
container: container:
image: 'data.forgejo.org/oci/playwright:latest' image: 'code.forgejo.org/oci/node:20-bookworm'
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: strategy:
matrix: matrix:
cacher: cacher:
- name: redis # redis
image: data.forgejo.org/oci/bitnami/redis:7.2 - image: docker.io/bitnami/redis:7.2
options: --tmpfs /bitnami/redis/data:noatime port: 6379
- name: redict # redict
image: registry.redict.io/redict:7.3.0-scratch - image: registry.redict.io/redict:7.3.0-scratch
options: --tmpfs /data:noatime port: 6379
- name: valkey # valkey
image: data.forgejo.org/oci/bitnami/valkey:7.2 - image: docker.io/bitnami/valkey:7.2
options: --tmpfs /bitnami/redis/data:noatime port: 6379
- name: garnet # garnet
image: ghcr.io/microsoft/garnet-alpine:1.0.14 - image: ghcr.io/microsoft/garnet-alpine:1.0.14
options: --tmpfs /data:noatime port: 6379
services: services:
cacher: cacher:
image: ${{ matrix.cacher.image }} image: ${{ matrix.cacher.image }}
options: ${{ matrix.cacher.options }} options: ${{ matrix.cacher.options }}
steps: steps:
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v3
- uses: ./.forgejo/workflows-composite/setup-env - uses: https://code.forgejo.org/actions/setup-go@v4
- name: install git >= 2.42
uses: ./.forgejo/workflows-composite/apt-install-from
with: with:
packages: git go-version-file: "go.mod"
- uses: ./.forgejo/workflows-composite/build-backend - run: |
git config --add safe.directory '*'
adduser --quiet --comment forgejo --disabled-password forgejo
chown -R forgejo:forgejo .
- 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
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
- run: | - run: |
su forgejo -c 'make test-remote-cacher test-check' su forgejo -c 'make test-remote-cacher test-check'
timeout-minutes: 120 timeout-minutes: 50
env: env:
RACE_ENABLED: 'true' RACE_ENABLED: 'true'
TAGS: bindata TAGS: bindata
TEST_REDIS_SERVER: cacher:${{ matrix.cacher.port }} TEST_REDIS_SERVER: cacher:${{ matrix.cacher.port }}
test-mysql: test-mysql:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
needs: [backend-checks, frontend-checks] needs: [backend-checks, frontend-checks]
container: container:
image: 'data.forgejo.org/oci/node:22-bookworm' image: 'code.forgejo.org/oci/node:20-bookworm'
options: --tmpfs /tmp:exec,noatime
services: services:
mysql: mysql:
image: 'data.forgejo.org/oci/bitnami/mysql:8.4' image: 'docker.io/bitnami/mysql:8.4'
env: env:
ALLOW_EMPTY_PASSWORD: yes ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: testgitea MYSQL_DATABASE: testgitea
# #
# See also https://codeberg.org/forgejo/forgejo/issues/976 # 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 --disable-log-bin 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
options: --tmpfs /bitnami/mysql/data:noatime
steps: steps:
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v3
- uses: ./.forgejo/workflows-composite/setup-env - uses: https://code.forgejo.org/actions/setup-go@v4
- name: install dependencies & git >= 2.42
uses: ./.forgejo/workflows-composite/apt-install-from
with: with:
packages: git git-lfs go-version-file: "go.mod"
- uses: ./.forgejo/workflows-composite/build-backend - 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
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
- run: | - run: |
su forgejo -c 'make test-mysql-migration test-mysql' su forgejo -c 'make test-mysql-migration test-mysql'
timeout-minutes: 120 timeout-minutes: 50
env: env:
USE_REPO_TEST_DIR: 1 USE_REPO_TEST_DIR: 1
test-pgsql: test-pgsql:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
needs: [backend-checks, frontend-checks] needs: [backend-checks, frontend-checks]
container: container:
image: 'data.forgejo.org/oci/node:22-bookworm' image: 'code.forgejo.org/oci/node:20-bookworm'
options: --tmpfs /tmp:exec,noatime
services: services:
minio: minio:
image: data.forgejo.org/oci/bitnami/minio:2024.8.17 image: docker.io/bitnami/minio:2024.8.17
env: env:
MINIO_ROOT_USER: 123456 MINIO_ROOT_USER: 123456
MINIO_ROOT_PASSWORD: 12345678 MINIO_ROOT_PASSWORD: 12345678
options: --tmpfs /bitnami/minio/data
ldap: ldap:
image: data.forgejo.org/oci/test-openldap:latest image: docker.io/gitea/test-openldap:latest
pgsql: pgsql:
image: data.forgejo.org/oci/bitnami/postgresql:16 image: 'code.forgejo.org/oci/postgres:15'
env: env:
POSTGRESQL_DATABASE: test POSTGRES_DB: test
POSTGRESQL_PASSWORD: postgres POSTGRES_PASSWORD: postgres
POSTGRESQL_FSYNC: off
POSTGRESQL_EXTRA_FLAGS: -c full_page_writes=off
options: --tmpfs /bitnami/postgresql
steps: steps:
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v3
- uses: ./.forgejo/workflows-composite/setup-env - uses: https://code.forgejo.org/actions/setup-go@v4
- name: install dependencies & git >= 2.42
uses: ./.forgejo/workflows-composite/apt-install-from
with: with:
packages: git git-lfs go-version-file: "go.mod"
- uses: ./.forgejo/workflows-composite/build-backend - 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
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
- run: | - run: |
su forgejo -c 'make test-pgsql-migration test-pgsql' su forgejo -c 'make test-pgsql-migration test-pgsql'
timeout-minutes: 120 timeout-minutes: 50
env: env:
RACE_ENABLED: true RACE_ENABLED: true
USE_REPO_TEST_DIR: 1 USE_REPO_TEST_DIR: 1
TEST_LDAP: 1 TEST_LDAP: 1
test-sqlite: test-sqlite:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
needs: [backend-checks, frontend-checks] needs: [backend-checks, frontend-checks]
container: container:
image: 'data.forgejo.org/oci/node:22-bookworm' image: 'code.forgejo.org/oci/node:20-bookworm'
options: --tmpfs /tmp:exec,noatime
steps: steps:
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v3
- uses: ./.forgejo/workflows-composite/setup-env - uses: https://code.forgejo.org/actions/setup-go@v4
- name: install dependencies & git >= 2.42
uses: ./.forgejo/workflows-composite/apt-install-from
with: with:
packages: git git-lfs go-version-file: "go.mod"
- uses: ./.forgejo/workflows-composite/build-backend - 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
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
- run: | - run: |
su forgejo -c 'make test-sqlite-migration test-sqlite' su forgejo -c 'make test-sqlite-migration test-sqlite'
timeout-minutes: 120 timeout-minutes: 50
env: env:
TAGS: sqlite sqlite_unlock_notify TAGS: sqlite sqlite_unlock_notify
RACE_ENABLED: true RACE_ENABLED: true
TEST_TAGS: sqlite sqlite_unlock_notify TEST_TAGS: sqlite sqlite_unlock_notify
USE_REPO_TEST_DIR: 1 USE_REPO_TEST_DIR: 1
security-check: security-check:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
needs: needs:
- test-sqlite - test-sqlite
@ -278,10 +327,11 @@ jobs:
- test-remote-cacher - test-remote-cacher
- test-unit - test-unit
container: container:
image: 'data.forgejo.org/oci/node:22-bookworm' image: 'code.forgejo.org/oci/node:20-bookworm'
options: --tmpfs /tmp:exec,noatime
steps: steps:
- uses: https://data.forgejo.org/actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v3
- uses: ./.forgejo/workflows-composite/setup-env - uses: https://code.forgejo.org/actions/setup-go@v4
- run: su forgejo -c 'make deps-backend deps-tools' with:
- run: su forgejo -c 'make security-check' go-version-file: "go.mod"
- run: make deps-backend deps-tools
- run: make security-check

6
.gitignore vendored
View file

@ -37,7 +37,6 @@ _testmain.go
*coverage.out *coverage.out
coverage.all coverage.all
coverage/
cpu.out cpu.out
/modules/migration/bindata.go /modules/migration/bindata.go
@ -57,7 +56,6 @@ cpu.out
/gitea-vet /gitea-vet
/debug /debug
/integrations.test /integrations.test
/forgejo
/bin /bin
/dist /dist
@ -74,7 +72,6 @@ cpu.out
/tests/e2e/reports /tests/e2e/reports
/tests/e2e/test-artifacts /tests/e2e/test-artifacts
/tests/e2e/test-snapshots /tests/e2e/test-snapshots
/tests/e2e/.auth
/tests/*.ini /tests/*.ini
/tests/**/*.git/**/*.sample /tests/**/*.git/**/*.sample
/node_modules /node_modules
@ -118,9 +115,6 @@ prime/
*_source.tar.bz2 *_source.tar.bz2
.DS_Store .DS_Store
# Direnv configuration
/.envrc
# nix-direnv generated files # nix-direnv generated files
.direnv/ .direnv/

View file

@ -1,9 +1,7 @@
version: "2"
output:
sort-order:
- file
linters: linters:
default: none enable-all: false
disable-all: true
fast: false
enable: enable:
- bidichk - bidichk
- depguard - depguard
@ -11,37 +9,37 @@ linters:
- errcheck - errcheck
- forbidigo - forbidigo
- gocritic - gocritic
- gofmt
- gofumpt
- gosimple
- govet - govet
- ineffassign - ineffassign
- nakedret - nakedret
- nolintlint - nolintlint
- revive - revive
- staticcheck - staticcheck
- stylecheck
- tenv
- testifylint - testifylint
- typecheck
- unconvert - unconvert
- unparam
- unused - unused
- usetesting - unparam
- wastedassign - wastedassign
settings:
depguard: run:
rules: timeout: 10m
main:
deny: output:
- pkg: encoding/json sort-results: true
desc: use gitea's modules/json instead of encoding/json sort-order: [file]
- pkg: github.com/unknwon/com show-stats: true
desc: use gitea's util and replacements
- pkg: io/ioutil linters-settings:
desc: use os or io instead stylecheck:
- pkg: golang.org/x/exp checks: ["all", "-ST1005", "-ST1003"]
desc: it's experimental and unreliable nakedret:
- pkg: forgejo.org/modules/git/internal max-func-lines: 0
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: gocritic:
disabled-checks: disabled-checks:
- ifElseChain - ifElseChain
@ -79,87 +77,72 @@ linters:
- name: unreachable-code - name: unreachable-code
- name: var-declaration - name: var-declaration
- name: var-naming - name: var-naming
- name: redefines-builtin-id gofumpt:
disabled: true extra-rules: true
staticcheck: depguard:
checks: rules:
- all 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: testifylint:
disable: disable:
- go-require - go-require
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- 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: issues:
max-issues-per-linter: 0 max-issues-per-linter: 0
max-same-issues: 0 max-same-issues: 0
formatters: exclude-dirs: [node_modules, public, web_src]
enable: exclude-case-sensitive: true
- gofmt exclude-rules:
- gofumpt - path: models/db/sql_postgres_with_schema.go
settings: linters:
gofumpt: - nolintlint
extra-rules: true - path: _test\.go
exclusions: linters:
generated: lax - gocyclo
paths: - errcheck
- node_modules - dupl
- public - gosec
- web_src - unparam
- third_party$ - staticcheck
- builtin$ - path: models/migrations/v
- examples$ 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

View file

@ -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"' 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: | render-header: |
## Release notes ## Draft release notes
comment: | comment: |
<details> <details>
<summary>Where does that come from?</summary> <summary>Where does that come from?</summary>

View file

@ -36,6 +36,10 @@ GARGS = "--no-print-directory"
JARG = -j$(.MAKE.JOBS) JARG = -j$(.MAKE.JOBS)
.endif .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 # The GNU convention is to use the lowercased `prefix` variable/macro to
# specify the installation directory. Humor them. # specify the installation directory. Humor them.
GPREFIX = GPREFIX =
@ -44,12 +48,11 @@ GPREFIX =
.endif .endif
.BEGIN: .SILENT .BEGIN: .SILENT
which $(GMAKE) >/dev/null || (printf "Error: GNU Make is required!\n\n" 1>&2 && false) which $(GMAKE) || (printf "Error: GNU Make is required!\n\n" 1>&2 && false)
.PHONY: EMPTY .PHONY: FRC
EMPTY: .SILENT $(.TARGETS): FRC
$(GMAKE) $(GPREFIX) $(GARGS) $(JARG) $(GMAKE) $(GPREFIX) $(GARGS) $(.TARGETS:S,.DONE,,) $(JARG)
.PHONY: $(.TARGETS) .DONE .DEFAULT: .SILENT
$(.TARGETS): .SILENT $(GMAKE) $(GPREFIX) $(GARGS) $(.TARGETS:S,.DONE,,) $(JARG)
$(GMAKE) $(GPREFIX) $(GARGS) $(JARG) $@

View file

@ -9,16 +9,13 @@
# Files related to frontend development. # Files related to frontend development.
# Javascript and CSS code. # Javascript and CSS code.
web_src/.* @beowulf @gusted web_src/.* @caesar @crystal @gusted
# HTML templates used by the backend. # HTML templates used by the backend.
templates/.* @beowulf @gusted templates/.* @caesar @crystal @gusted
## the issue sidebar was touched by fnetx ## the issue sidebar was touched by fnetx
templates/repo/issue/view_content/sidebar.* @fnetx templates/repo/issue/view_content/sidebar.* @fnetx
# Playwright tests
tests/e2e/.* @fnetx
# Files related to Go development. # Files related to Go development.
# The modules usually don't require much knowledge about Forgejo and could # The modules usually don't require much knowledge about Forgejo and could
@ -33,9 +30,8 @@ models/.* @gusted
# for code that lives in here. # for code that lives in here.
routers/.* @gusted routers/.* @gusted
# Let locale changes be checked by the translation team. # Let new strings be checked by the translation team.
options/locale/.* @0ko options/locale/locale_en-US.ini @0ko
options/locale_next/.* @0ko
# Personal interest # Personal interest
.*/webhook.* @oliverpool .*/webhook.* @oliverpool

View file

@ -1,9 +1,9 @@
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.21 AS build-env FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/golang:1.23-alpine3.20 as build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct} ENV GOPROXY=${GOPROXY:-direct}
ARG RELEASE_VERSION ARG RELEASE_VERSION
ARG TAGS="sqlite sqlite_unlock_notify" 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 RUN apk --no-cache add build-base git nodejs npm
COPY . ${GOPATH}/src/forgejo.org COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/forgejo.org WORKDIR ${GOPATH}/src/code.gitea.io/gitea
RUN make clean RUN make clean
RUN make frontend RUN make frontend
@ -47,11 +47,11 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/tmp/local/etc/s6/gitea/* \ /tmp/local/etc/s6/gitea/* \
/tmp/local/etc/s6/openssh/* \ /tmp/local/etc/s6/openssh/* \
/tmp/local/etc/s6/.s6-svscan/* \ /tmp/local/etc/s6/.s6-svscan/* \
/go/src/forgejo.org/gitea \ /go/src/code.gitea.io/gitea/gitea \
/go/src/forgejo.org/environment-to-ini /go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
FROM data.forgejo.org/oci/alpine:3.21 FROM code.forgejo.org/oci/golang:1.23-alpine3.20
ARG RELEASE_VERSION ARG RELEASE_VERSION
LABEL maintainer="contact@forgejo.org" \ LABEL maintainer="contact@forgejo.org" \
org.opencontainers.image.authors="Forgejo" \ org.opencontainers.image.authors="Forgejo" \
@ -98,11 +98,11 @@ ENV GITEA_CUSTOM=/data/gitea
VOLUME ["/data"] VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/entrypoint"] ENTRYPOINT ["/usr/bin/entrypoint"]
CMD ["/usr/bin/s6-svscan", "/etc/s6"] CMD ["/bin/s6-svscan", "/etc/s6"]
COPY --from=build-env /tmp/local / COPY --from=build-env /tmp/local /
RUN cd /usr/local/bin ; ln -s gitea forgejo RUN cd /usr/local/bin ; ln -s gitea forgejo
COPY --from=build-env /go/src/forgejo.org/gitea /app/gitea/gitea COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
RUN ln -s /app/gitea/gitea /app/gitea/forgejo-cli RUN ln /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/code.gitea.io/gitea/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 COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh

View file

@ -1,9 +1,9 @@
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.21 AS build-env FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/golang:1.23-alpine3.20 as build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct} ENV GOPROXY=${GOPROXY:-direct}
ARG RELEASE_VERSION ARG RELEASE_VERSION
ARG TAGS="sqlite sqlite_unlock_notify" 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 RUN apk --no-cache add build-base git nodejs npm
COPY . ${GOPATH}/src/forgejo.org COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/forgejo.org WORKDIR ${GOPATH}/src/code.gitea.io/gitea
RUN make clean RUN make clean
RUN make frontend RUN make frontend
@ -45,12 +45,11 @@ COPY docker/rootless /tmp/local
RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
/tmp/local/usr/local/bin/docker-setup.sh \ /tmp/local/usr/local/bin/docker-setup.sh \
/tmp/local/usr/local/bin/gitea \ /tmp/local/usr/local/bin/gitea \
/go/src/forgejo.org/gitea \ /go/src/code.gitea.io/gitea/gitea \
/go/src/forgejo.org/environment-to-ini /go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
FROM data.forgejo.org/oci/alpine:3.21 FROM code.forgejo.org/oci/golang:1.23-alpine3.20
ARG RELEASE_VERSION
LABEL maintainer="contact@forgejo.org" \ LABEL maintainer="contact@forgejo.org" \
org.opencontainers.image.authors="Forgejo" \ org.opencontainers.image.authors="Forgejo" \
org.opencontainers.image.url="https://forgejo.org" \ org.opencontainers.image.url="https://forgejo.org" \
@ -72,7 +71,6 @@ RUN apk --no-cache add \
git \ git \
curl \ curl \
gnupg \ gnupg \
openssh-client \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
RUN addgroup \ RUN addgroup \
@ -91,10 +89,10 @@ RUN chown git:git /var/lib/gitea /etc/gitea
COPY --from=build-env /tmp/local / COPY --from=build-env /tmp/local /
RUN cd /usr/local/bin ; ln -s gitea forgejo RUN cd /usr/local/bin ; ln -s gitea forgejo
COPY --from=build-env --chown=root:root /go/src/forgejo.org/gitea /app/gitea/gitea COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
RUN ln -s /app/gitea/gitea /app/gitea/forgejo-cli RUN ln /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 --chown=root:root /go/src/code.gitea.io/gitea/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 COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
#git:git #git:git
USER 1000:1000 USER 1000:1000

294
Makefile
View file

@ -16,51 +16,50 @@ else
DIST := dist DIST := dist
DIST_DIRS := $(DIST)/binaries $(DIST)/release DIST_DIRS := $(DIST)/binaries $(DIST)/release
IMPORT := forgejo.org IMPORT := code.gitea.io/gitea
GO ?= $(shell go env GOROOT)/bin/go GO ?= go
SHASUM ?= shasum -a 256 SHASUM ?= shasum -a 256
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes) HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
COMMA := , COMMA := ,
DIFF ?= diff --unified 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 XGO_VERSION := go-1.21.x
AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go 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.2.1 # 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.8.0 # renovate: datasource=go GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0 # renovate: datasource=go
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 # renovate: datasource=go GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.61.0 # renovate: datasource=go
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 # 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 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 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 # renovate: datasource=go
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go 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 GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.32.0 # 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.5.1 # renovate: datasource=go GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.18.1 # renovate: datasource=go GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.16.2 # renovate: datasource=go
RENOVATE_NPM_PACKAGE ?= renovate@40.31.0 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate RENOVATE_NPM_PACKAGE ?= renovate@38.93.2 # renovate: datasource=docker packageName=code.forgejo.org/forgejo-contrib/renovate
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
ifeq ($(HAS_GO), yes) ifeq ($(HAS_GO), yes)
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766 CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS) CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
endif endif
GOFLAGS := -v ifeq ($(GOOS),windows)
EXECUTABLE ?= gitea 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
ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu) ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu)
SED_INPLACE := sed -i SED_INPLACE := sed -i
@ -92,36 +91,38 @@ else
FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY} FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY}
else else
# drop the "g" prefix prepended by git describe to the commit hash # drop the "g" prefix prepended by git describe to the commit hash
FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always 2>/dev/null | sed 's/^v//' | sed 's/\-g/-/') FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY}
ifneq ($(FORGEJO_VERSION),)
ifeq ($(findstring $(GITEA_COMPATIBILITY),$(FORGEJO_VERSION)),)
FORGEJO_VERSION := $(FORGEJO_VERSION)+$(GITEA_COMPATIBILITY)
endif
endif
endif endif
endif endif
FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//') 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/') 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} RELEASE_VERSION ?= ${FORGEJO_VERSION}
VERSION ?= ${RELEASE_VERSION} VERSION ?= ${RELEASE_VERSION}
FORGEJO_VERSION_API ?= ${FORGEJO_VERSION} FORGEJO_VERSION_API ?= ${FORGEJO_VERSION}
# Strip binaries by default to reduce size, allow overriding for debugging show-version-api:
STRIP ?= 1 @echo ${FORGEJO_VERSION_API}
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)" 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 LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
ifeq ($(HAS_GO), yes) ifeq ($(HAS_GO), yes)
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 ./...)) 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 ./...))
endif endif
REMOTE_CACHER_MODULES ?= cache nosql session queue REMOTE_CACHER_MODULES ?= cache nosql session queue
GO_TEST_REMOTE_CACHER_PACKAGES ?= $(addprefix forgejo.org/modules/,$(REMOTE_CACHER_MODULES)) GO_TEST_REMOTE_CACHER_PACKAGES ?= $(addprefix code.gitea.io/gitea/modules/,$(REMOTE_CACHER_MODULES))
FOMANTIC_WORK_DIR := web_src/fomantic FOMANTIC_WORK_DIR := web_src/fomantic
@ -153,8 +154,9 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN
GO_DIRS := build cmd models modules routers services tests GO_DIRS := build cmd models modules routers services tests
WEB_DIRS := web_src/js web_src/css 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 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 *.ts *.vue *.md *.yml *.yaml) SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github $(wildcard *.go *.js *.md *.yml *.yaml *.toml)
GO_SOURCES := $(wildcard *.go) GO_SOURCES := $(wildcard *.go)
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go) GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go)
@ -162,7 +164,7 @@ GO_SOURCES += $(GENERATED_GO_DEST)
GO_SOURCES_NO_BINDATA := $(GO_SOURCES) GO_SOURCES_NO_BINDATA := $(GO_SOURCES)
ifeq ($(HAS_GO), yes) ifeq ($(HAS_GO), yes)
MIGRATION_PACKAGES := $(shell $(GO) list forgejo.org/models/migrations/... forgejo.org/models/forgejo_migrations/...) MIGRATION_PACKAGES := $(shell $(GO) list code.gitea.io/gitea/models/migrations/... code.gitea.io/gitea/models/forgejo_migrations/...)
endif endif
ifeq ($(filter $(TAGS_SPLIT),bindata),bindata) ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
@ -214,12 +216,16 @@ help:
@echo " - deps-frontend install frontend dependencies" @echo " - deps-frontend install frontend dependencies"
@echo " - deps-backend install backend dependencies" @echo " - deps-backend install backend dependencies"
@echo " - deps-tools install tool dependencies" @echo " - deps-tools install tool dependencies"
@echo " - deps-py install python dependencies"
@echo " - lint lint everything" @echo " - lint lint everything"
@echo " - lint-fix lint everything and fix issues" @echo " - lint-fix lint everything and fix issues"
@echo " - lint-frontend lint frontend files" @echo " - lint-frontend lint frontend files"
@echo " - lint-frontend-fix lint frontend files and fix issues" @echo " - lint-frontend-fix lint frontend files and fix issues"
@echo " - lint-backend lint backend files" @echo " - lint-backend lint backend files"
@echo " - lint-backend-fix lint backend files and fix issues" @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 lint go files"
@echo " - lint-go-fix lint go files and fix issues" @echo " - lint-go-fix lint go files and fix issues"
@echo " - lint-go-vet lint go files with vet" @echo " - lint-go-vet lint go files with vet"
@ -230,7 +236,11 @@ help:
@echo " - lint-css-fix lint css files and fix issues" @echo " - lint-css-fix lint css files and fix issues"
@echo " - lint-md lint markdown files" @echo " - lint-md lint markdown files"
@echo " - lint-swagger lint swagger files" @echo " - lint-swagger lint swagger files"
@echo " - lint-templates lint template files"
@echo " - lint-renovate lint renovate 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 run various consistency checks"
@echo " - checks-frontend check frontend files" @echo " - checks-frontend check frontend files"
@echo " - checks-backend check backend files" @echo " - checks-backend check backend files"
@ -261,30 +271,6 @@ help:
@echo " - test-sqlite[\#TestSpecificName] run integration test for sqlite" @echo " - test-sqlite[\#TestSpecificName] run integration test for sqlite"
@echo " - reproduce-build\#version build a reproducible binary for the specified release version" @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 # Check system and environment requirements
### ###
@ -410,10 +396,10 @@ checks-frontend: lockfile-check svg-check
checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check
.PHONY: lint .PHONY: lint
lint: lint-frontend lint-backend lint: lint-frontend lint-backend lint-spell
.PHONY: lint-fix .PHONY: lint-fix
lint-fix: lint-frontend-fix lint-backend-fix lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix
.PHONY: lint-frontend .PHONY: lint-frontend
lint-frontend: lint-js lint-css lint-frontend: lint-js lint-css
@ -422,18 +408,30 @@ lint-frontend: lint-js lint-css
lint-frontend-fix: lint-js-fix lint-css-fix lint-frontend-fix: lint-js-fix lint-css-fix
.PHONY: lint-backend .PHONY: lint-backend
lint-backend: lint-go lint-go-vet lint-editorconfig lint-renovate lint-locale lint-locale-usage lint-disposable-emails lint-backend: lint-go lint-go-vet lint-editorconfig lint-renovate
.PHONY: lint-backend-fix .PHONY: lint-backend-fix
lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig lint-disposable-emails-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
.PHONY: lint-js .PHONY: lint-js
lint-js: node_modules lint-js: node_modules
npx eslint --color --max-warnings=0 npx eslint --color --max-warnings=0 --ext js,vue $(ESLINT_FILES)
.PHONY: lint-js-fix .PHONY: lint-js-fix
lint-js-fix: node_modules lint-js-fix: node_modules
npx eslint --color --max-warnings=0 --fix npx eslint --color --max-warnings=0 --ext js,vue $(ESLINT_FILES) --fix
.PHONY: lint-css .PHONY: lint-css
lint-css: node_modules lint-css: node_modules
@ -449,23 +447,23 @@ lint-swagger: node_modules
.PHONY: lint-renovate .PHONY: lint-renovate
lint-renovate: node_modules lint-renovate: node_modules
npx --yes --package $(RENOVATE_NPM_PACKAGE) -- renovate-config-validator > .lint-renovate 2>&1 || true npx --yes --package $(RENOVATE_NPM_PACKAGE) -- renovate-config-validator --strict > .lint-renovate 2>&1 || true
@if grep --quiet --extended-regexp -e '^( ERROR:)' .lint-renovate ; then cat .lint-renovate ; rm .lint-renovate ; exit 1 ; fi @if grep --quiet --extended-regexp -e '^( WARN:|ERROR:)' .lint-renovate ; then cat .lint-renovate ; rm .lint-renovate ; exit 1 ; fi
@rm .lint-renovate @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 .PHONY: lint-md
lint-md: node_modules lint-md: node_modules
npx markdownlint docs *.md npx markdownlint docs *.md
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-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
.PHONY: lint-go .PHONY: lint-go
lint-go: lint-go:
@ -479,6 +477,13 @@ lint-go-fix:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) --fix $(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) --fix
$(RUN_DEADCODE) > .deadcode-out $(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 .PHONY: lint-go-vet
lint-go-vet: lint-go-vet:
@echo "Running go vet..." @echo "Running go vet..."
@ -493,17 +498,18 @@ lint-go-gopls:
lint-editorconfig: lint-editorconfig:
$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates .forgejo/workflows $(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates .forgejo/workflows
.PHONY: lint-disposable-emails .PHONY: lint-templates
lint-disposable-emails: lint-templates: .venv node_modules
$(GO) run build/generate-disposable-email.go -check -r $(DISPOSABLE_EMAILS_SHA) @node tools/lint-templates-svg.js
@poetry run djlint $(shell find templates -type f -iname '*.tmpl')
.PHONY: lint-disposable-emails-fix .PHONY: lint-yaml
lint-disposable-emails-fix: lint-yaml: .venv
$(GO) run build/generate-disposable-email.go -r $(DISPOSABLE_EMAILS_SHA) @poetry run yamllint .
.PHONY: security-check .PHONY: security-check
security-check: security-check:
go run $(GOVULNCHECK_PACKAGE) -show color ./... go run $(GOVULNCHECK_PACKAGE) ./...
### ###
# Development and testing targets # Development and testing targets
@ -528,12 +534,12 @@ test: test-frontend test-backend
.PHONY: test-backend .PHONY: test-backend
test-backend: test-backend:
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." @echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
@$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES) @$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
.PHONY: test-remote-cacher .PHONY: test-remote-cacher
test-remote-cacher: test-remote-cacher:
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." @echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
@$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_REMOTE_CACHER_PACKAGES) @$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_REMOTE_CACHER_PACKAGES)
.PHONY: test-frontend .PHONY: test-frontend
test-frontend: node_modules test-frontend: node_modules
@ -557,8 +563,8 @@ test-check:
.PHONY: test\#% .PHONY: test\#%
test\#%: test\#%:
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." @echo "Running go test with -tags '$(TEST_TAGS)'..."
@$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES) @$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
.PHONY: coverage .PHONY: coverage
coverage: coverage:
@ -569,7 +575,7 @@ coverage:
.PHONY: unit-test-coverage .PHONY: unit-test-coverage
unit-test-coverage: unit-test-coverage:
@echo "Running unit-test-coverage $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." @echo "Running unit-test-coverage $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
@$(GOTEST) $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1 @$(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
.PHONY: tidy .PHONY: tidy
tidy: tidy:
@ -590,7 +596,7 @@ tidy-check: tidy
go-licenses: $(GO_LICENSE_FILE) go-licenses: $(GO_LICENSE_FILE)
$(GO_LICENSE_FILE): go.mod go.sum $(GO_LICENSE_FILE): go.mod go.sum
-$(GO) run $(GO_LICENSES_PACKAGE) save . --force --ignore forgejo.org --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null -$(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 build/generate-go-licenses.go $(GO_LICENSE_TMP_DIR) $(GO_LICENSE_FILE) $(GO) run build/generate-go-licenses.go $(GO_LICENSE_TMP_DIR) $(GO_LICENSE_FILE)
@rm -rf $(GO_LICENSE_TMP_DIR) @rm -rf $(GO_LICENSE_TMP_DIR)
@ -602,11 +608,11 @@ generate-ini-sqlite:
.PHONY: test-sqlite .PHONY: test-sqlite
test-sqlite: integrations.sqlite.test generate-ini-sqlite test-sqlite: integrations.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTESTCOMPILEDRUNPREFIX) ./integrations.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test
.PHONY: test-sqlite\#% .PHONY: test-sqlite\#%
test-sqlite\#%: integrations.sqlite.test generate-ini-sqlite test-sqlite\#%: integrations.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTESTCOMPILEDRUNPREFIX) ./integrations.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run $(subst .,/,$*) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.run $(subst .,/,$*)
.PHONY: test-sqlite-migration .PHONY: test-sqlite-migration
test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test
@ -623,11 +629,11 @@ generate-ini-mysql:
.PHONY: test-mysql .PHONY: test-mysql
test-mysql: integrations.mysql.test generate-ini-mysql test-mysql: integrations.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTESTCOMPILEDRUNPREFIX) ./integrations.mysql.test $(GOTESTCOMPILEDRUNSUFFIX) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test
.PHONY: test-mysql\#% .PHONY: test-mysql\#%
test-mysql\#%: integrations.mysql.test generate-ini-mysql test-mysql\#%: integrations.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTESTCOMPILEDRUNPREFIX) ./integrations.mysql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run $(subst .,/,$*) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test -test.run $(subst .,/,$*)
.PHONY: test-mysql-migration .PHONY: test-mysql-migration
test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test
@ -641,16 +647,15 @@ generate-ini-pgsql:
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \ -e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \ -e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|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 tests/pgsql.ini.tmpl > tests/pgsql.ini
.PHONY: test-pgsql .PHONY: test-pgsql
test-pgsql: integrations.pgsql.test generate-ini-pgsql test-pgsql: integrations.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTESTCOMPILEDRUNPREFIX) ./integrations.pgsql.test $(GOTESTCOMPILEDRUNSUFFIX) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test
.PHONY: test-pgsql\#% .PHONY: test-pgsql\#%
test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTESTCOMPILEDRUNPREFIX) ./integrations.pgsql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run $(subst .,/,$*) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.run $(subst .,/,$*)
.PHONY: test-pgsql-migration .PHONY: test-pgsql-migration
test-pgsql-migration: migrations.pgsql.test migrations.individual.pgsql.test test-pgsql-migration: migrations.pgsql.test migrations.individual.pgsql.test
@ -669,34 +674,35 @@ test-e2e: test-e2e-sqlite
.PHONY: test-e2e-sqlite .PHONY: test-e2e-sqlite
test-e2e-sqlite: playwright e2e.sqlite.test generate-ini-sqlite test-e2e-sqlite: playwright e2e.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test -test.run TestE2e
.PHONY: test-e2e-sqlite\#% .PHONY: test-e2e-sqlite\#%
test-e2e-sqlite\#%: playwright e2e.sqlite.test generate-ini-sqlite test-e2e-sqlite\#%: playwright e2e.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e/$* GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test -test.run TestE2e/$*
.PHONY: test-e2e-sqlite-firefox\#% .PHONY: test-e2e-sqlite-firefox\#%
test-e2e-sqlite-firefox\#%: playwright e2e.sqlite.test generate-ini-sqlite test-e2e-sqlite-firefox\#%: playwright e2e.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini PLAYWRIGHT_PROJECT=firefox $(GOTESTCOMPILEDRUNPREFIX) ./e2e.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e/$* GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini PLAYWRIGHT_PROJECT=firefox ./e2e.sqlite.test -test.run TestE2e/$*
.PHONY: test-e2e-mysql .PHONY: test-e2e-mysql
test-e2e-mysql: playwright e2e.mysql.test generate-ini-mysql test-e2e-mysql: playwright e2e.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.mysql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test -test.run TestE2e
.PHONY: test-e2e-mysql\#% .PHONY: test-e2e-mysql\#%
test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.mysql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e/$* GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test -test.run TestE2e/$*
.PHONY: test-e2e-pgsql .PHONY: test-e2e-pgsql
test-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql test-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.pgsql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test -test.run TestE2e
.PHONY: test-e2e-pgsql\#% .PHONY: test-e2e-pgsql\#%
test-e2e-pgsql\#%: playwright e2e.pgsql.test generate-ini-pgsql test-e2e-pgsql\#%: playwright e2e.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.pgsql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e/$* GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test -test.run TestE2e/$*
.PHONY: test-e2e-debugserver .PHONY: test-e2e-debugserver
test-e2e-debugserver: e2e.sqlite.test generate-ini-sqlite 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 GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test -test.run TestDebugserver -test.timeout 24h
.PHONY: bench-sqlite .PHONY: bench-sqlite
@ -720,73 +726,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 GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out
integrations.mysql.test: git-check $(GO_SOURCES) integrations.mysql.test: git-check $(GO_SOURCES)
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -o integrations.mysql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mysql.test
integrations.pgsql.test: git-check $(GO_SOURCES) integrations.pgsql.test: git-check $(GO_SOURCES)
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -o integrations.pgsql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.pgsql.test
integrations.sqlite.test: git-check $(GO_SOURCES) integrations.sqlite.test: git-check $(GO_SOURCES)
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -o integrations.sqlite.test -tags '$(TEST_TAGS)' $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.sqlite.test -tags '$(TEST_TAGS)'
integrations.cover.test: git-check $(GO_SOURCES) integrations.cover.test: git-check $(GO_SOURCES)
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.test
integrations.cover.sqlite.test: git-check $(GO_SOURCES) integrations.cover.sqlite.test: git-check $(GO_SOURCES)
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.sqlite.test -tags '$(TEST_TAGS)' $(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)'
.PHONY: migrations.mysql.test .PHONY: migrations.mysql.test
migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration/migration-test -o migrations.mysql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTESTCOMPILEDRUNPREFIX) ./migrations.mysql.test $(GOTESTCOMPILEDRUNSUFFIX) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./migrations.mysql.test
.PHONY: migrations.pgsql.test .PHONY: migrations.pgsql.test
migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration/migration-test -o migrations.pgsql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTESTCOMPILEDRUNPREFIX) ./migrations.pgsql.test $(GOTESTCOMPILEDRUNSUFFIX) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./migrations.pgsql.test
.PHONY: migrations.sqlite.test .PHONY: migrations.sqlite.test
migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)' $(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 $(GOTESTCOMPILEDRUNPREFIX) ./migrations.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./migrations.sqlite.test
.PHONY: migrations.individual.mysql.test .PHONY: migrations.individual.mysql.test
migrations.individual.mysql.test: $(GO_SOURCES) migrations.individual.mysql.test: $(GO_SOURCES)
for pkg in $(MIGRATION_PACKAGES); do \ for pkg in $(MIGRATION_PACKAGES); do \
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1; \ GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1; \
done done
.PHONY: migrations.individual.sqlite.test\#% .PHONY: migrations.individual.sqlite.test\#%
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$* GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
.PHONY: migrations.individual.pgsql.test .PHONY: migrations.individual.pgsql.test
migrations.individual.pgsql.test: $(GO_SOURCES) migrations.individual.pgsql.test: $(GO_SOURCES)
for pkg in $(MIGRATION_PACKAGES); do \ for pkg in $(MIGRATION_PACKAGES); do \
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1;\ GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1;\
done done
.PHONY: migrations.individual.pgsql.test\#% .PHONY: migrations.individual.pgsql.test\#%
migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$* GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
.PHONY: migrations.individual.sqlite.test .PHONY: migrations.individual.sqlite.test
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
for pkg in $(MIGRATION_PACKAGES); do \ for pkg in $(MIGRATION_PACKAGES); do \
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1; \ GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1; \
done done
.PHONY: migrations.individual.sqlite.test\#% .PHONY: migrations.individual.sqlite.test\#%
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$* GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
e2e.mysql.test: $(GO_SOURCES) e2e.mysql.test: $(GO_SOURCES)
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/e2e -o e2e.mysql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.mysql.test
e2e.pgsql.test: $(GO_SOURCES) e2e.pgsql.test: $(GO_SOURCES)
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/e2e -o e2e.pgsql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.pgsql.test
e2e.sqlite.test: $(GO_SOURCES) e2e.sqlite.test: $(GO_SOURCES)
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/e2e -o e2e.sqlite.test -tags '$(TEST_TAGS)' $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.sqlite.test -tags '$(TEST_TAGS)'
.PHONY: check .PHONY: check
check: test check: test
@ -796,8 +802,8 @@ check: test
### ###
.PHONY: install $(TAGS_PREREQ) .PHONY: install $(TAGS_PREREQ)
install: $(wildcard *.go) | verify-version install: $(wildcard *.go)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
.PHONY: build .PHONY: build
build: frontend backend build: frontend backend
@ -824,14 +830,14 @@ generate-go: $(TAGS_PREREQ)
merge-locales: merge-locales:
@echo "NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY" @echo "NOT NEEDED: THIS IS A NOOP AS OF Forgejo 7.0 BUT KEPT FOR BACKWARD COMPATIBILITY"
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ) | verify-version $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
forgejo: $(EXECUTABLE) forgejo: $(EXECUTABLE)
ln -f $(EXECUTABLE) forgejo ln -f $(EXECUTABLE) forgejo
static-executable: $(GO_SOURCES) $(TAGS_PREREQ) | verify-version static-executable: $(GO_SOURCES) $(TAGS_PREREQ)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -o $(EXECUTABLE) CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -o $(EXECUTABLE)
.PHONY: release .PHONY: release
release: frontend generate release-linux release-copy release-compress vendor release-sources release-check release: frontend generate release-linux release-copy release-compress vendor release-sources release-check
@ -842,19 +848,23 @@ sources-tarbal: frontend generate vendor release-sources release-check
$(DIST_DIRS): $(DIST_DIRS):
mkdir -p $(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 .PHONY: release-linux
release-linux: | $(DIST_DIRS) verify-version release-linux: | $(DIST_DIRS)
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) . 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) ifeq ($(CI),true)
cp /build/* $(DIST)/binaries cp /build/* $(DIST)/binaries
endif endif
.PHONY: release-darwin .PHONY: release-darwin
release-darwin: | $(DIST_DIRS) verify-version release-darwin: | $(DIST_DIRS)
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) . 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 .PHONY: release-freebsd
release-freebsd: | $(DIST_DIRS) verify-version release-freebsd: | $(DIST_DIRS)
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) . 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 .PHONY: release-copy
@ -886,7 +896,7 @@ release-docs: | $(DIST_DIRS) docs
.PHONY: reproduce-build .PHONY: reproduce-build
reproduce-build: reproduce-build:
# Start building the Dockerfile with the RELEASE_VERSION tag set. GOPROXY is set # Start building the Dockerfile with the RELEASE_VERSION tag set. GOPROXY is set
# for convenience, because the default of the Dockerfile is `direct` which can be # for convience, because the default of the Dockerfile is `direct` which can be
# quite slow. # quite slow.
@docker build --build-arg="RELEASE_VERSION=$(RELEASE_VERSION)" --build-arg="GOPROXY=$(shell $(GO) env GOPROXY)" --tag "forgejo-reproducibility" . @docker build --build-arg="RELEASE_VERSION=$(RELEASE_VERSION)" --build-arg="GOPROXY=$(shell $(GO) env GOPROXY)" --tag "forgejo-reproducibility" .
@id=$$(docker create forgejo-reproducibility); \ @id=$$(docker create forgejo-reproducibility); \
@ -908,7 +918,10 @@ reproduce-build\#%:
### ###
.PHONY: deps .PHONY: deps
deps: deps-frontend deps-backend deps-tools deps: deps-frontend deps-backend deps-tools deps-py
.PHONY: deps-py
deps-py: .venv
.PHONY: deps-frontend .PHONY: deps-frontend
deps-frontend: node_modules deps-frontend: node_modules
@ -990,11 +1003,12 @@ generate-gitignore:
.PHONY: generate-gomock .PHONY: generate-gomock
generate-gomock: generate-gomock:
$(GO) run $(GOMOCK_PACKAGE) -package mock -destination ./modules/queue/mock/redisuniversalclient.go forgejo.org/modules/nosql RedisClient $(GO) run $(GOMOCK_PACKAGE) -package mock -destination ./modules/queue/mock/redisuniversalclient.go code.gitea.io/gitea/modules/nosql RedisClient
.PHONY: generate-images .PHONY: generate-images
generate-images: | node_modules generate-images: | node_modules
node tools/generate-images.js npm install --no-save fabric@6 imagemin-zopfli@7
node tools/generate-images.js $(TAGS)
.PHONY: generate-manpage .PHONY: generate-manpage
generate-manpage: generate-manpage:

View file

@ -15,6 +15,11 @@ Our promise: **Independent Free/Libre Software forever!**
## What does Forgejo offer? ## What does Forgejo offer?
<!-- If you want to know what Forgejo is like,
you can check out public instances,
e.g. [Codeberg.org](https://codeberg.org).
-->
If you like any of the following, Forgejo is literally meant for you: If you like any of the following, Forgejo is literally meant for you:
- Lightweight: Forgejo can easily be hosted on nearly **every machine**. - Lightweight: Forgejo can easily be hosted on nearly **every machine**.

View file

@ -4,31 +4,19 @@ 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. 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 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). 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).
## 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 ## 8.0.3
See the [Forgejo 8.0.3 release notes](release-notes-published/8.0.3.md). The Forgejo v8.0.3 release notes are [available in the v8.0.3 milestone](https://codeberg.org/forgejo/forgejo/milestone/8231).
## 8.0.2 ## 8.0.2
See the [Forgejo 8.0.2 release notes](release-notes-published/8.0.2.md). The Forgejo v8.0.2 release notes are [available in the v8.0.2 milestone](https://codeberg.org/forgejo/forgejo/milestone/7728).
## 8.0.1 ## 8.0.1
See the [Forgejo 8.0.1 release notes](release-notes-published/8.0.1.md). The Forgejo v8.0.1 release notes are [available in the v8.0.1 milestone](https://codeberg.org/forgejo/forgejo/milestone/7682).
## 8.0.0 ## 8.0.0
@ -167,25 +155,17 @@ A [companion blog post](https://forgejo.org/2024-07-release-v8-0/) provides addi
- [PR](https://codeberg.org/forgejo/forgejo/pulls/2937): <!--number 2937 --><!--number--><!--description -->31 March updates<!--description--> - [PR](https://codeberg.org/forgejo/forgejo/pulls/2937): <!--number 2937 --><!--number--><!--description -->31 March updates<!--description-->
<!--end release-notes-assistant--> <!--end release-notes-assistant-->
## 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 ## 7.0.9
See the [Forgejo 7.0.9 release notes](release-notes-published/7.0.9.md). The Forgejo v7.0.9 release notes are [available in the v7.0.9 milestone](https://codeberg.org/forgejo/forgejo/milestone/8232).
## 7.0.8 ## 7.0.8
See the [Forgejo 7.0.8 release notes](release-notes-published/7.0.8.md). The Forgejo v7.0.8 release notes are [available in the v7.0.8 milestone](https://codeberg.org/forgejo/forgejo/milestone/7729).
## 7.0.7 ## 7.0.7
See the [Forgejo 7.0.7 release notes](release-notes-published/7.0.7.md). The Forgejo v7.0.7 release notes are [available in the v7.0.7 milestone](https://codeberg.org/forgejo/forgejo/milestone/7683).
## 7.0.6 ## 7.0.6
@ -1590,7 +1570,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. 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 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 the review and stayed at the beginning of the page
* [Recognize OGG as an audio format](https://codeberg.org/forgejo/forgejo/commit/622ec5c79f299c32ac2667a1aa7b4bf5d7c2d6cf) * [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) * [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) * [Fix NuGet registry v2 & v3 API search endpoints](https://codeberg.org/forgejo/forgejo/commit/471138829b0c24fe8c621dbb866ae8bb45ebc674)
@ -1609,7 +1589,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 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 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 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 authenticated](https://codeberg.org/forgejo/forgejo/commit/d89003cc1) * [Fix API leaking the user email when the caller is not authentified](https://codeberg.org/forgejo/forgejo/commit/d89003cc1)
## 1.20.2-0 ## 1.20.2-0
@ -1667,7 +1647,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. The semantic version was updated to `5.0.0+0-gitea-1.20.1` because it contains breaking changes.
- **Breaking:** - **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. - [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 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 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 `-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 ".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: - The section `[git.reflog]` is [now obsolete and its keys have been moved](https://codeberg.org/forgejo/forgejo/commit/2f149c5c9db97f20fbbc65e32d1f3133048b11a2) to the following replacements:
@ -1761,7 +1741,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) - [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 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) - [Add --quiet option to the dump CLI](https://codeberg.org/forgejo/forgejo/commit/cb1536471bcef4d78a3fe5cbd738b9f60fabbcc2)
- [Support searching for an issue with its number in the list of issues](https://codeberg.org/forgejo/forgejo/commit/1144b1d129de530b2c07dfdfaf55de383cd82212) - [Support searching for an issue with its number in the the list of issues](https://codeberg.org/forgejo/forgejo/commit/1144b1d129de530b2c07dfdfaf55de383cd82212)
- [Improve the list of notifications](https://codeberg.org/forgejo/forgejo/commit/f7ede92f82f7f3ec7bb31a1249f9524e5b728f34) - [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) - [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) - [Make release download URLs human readable](https://codeberg.org/forgejo/forgejo/commit/42919ccb7cd32ab67d0878baf2bac6cd007899a8)
@ -1798,7 +1778,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 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) - [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) - [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 repo](https://codeberg.org/forgejo/forgejo/commit/1dd83dbb917d55bd253001646d6743f247a4d98b) - [Add `files` and `verification` parameters to improve performances when listing the commits of a 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) - [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) - [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) - [Add API for Label templates](https://codeberg.org/forgejo/forgejo/commit/25dc1556cd70b567a4920beb002a0addfbfd6ef2)

108
assets/go-licenses.json generated

File diff suppressed because one or more lines are too long

View file

@ -11,4 +11,13 @@ package main
import ( import (
// for embed // for embed
_ "github.com/shurcooL/vfsgen" _ "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"
) )

View file

@ -12,8 +12,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"forgejo.org/modules/container" "code.gitea.io/gitea/modules/container"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
func main() { func main() {

View file

@ -15,7 +15,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"forgejo.org/build/codeformat" "code.gitea.io/gitea/build/codeformat"
) )
// Windows has a limitation for command line arguments, the size can not exceed 32KB. // Windows has a limitation for command line arguments, the size can not exceed 32KB.

View file

@ -14,7 +14,7 @@ import (
var importPackageGroupOrders = map[string]int{ var importPackageGroupOrders = map[string]int{
"": 1, // internal "": 1, // internal
"forgejo.org/": 2, "code.gitea.io/gitea/": 2,
} }
var errInvalidCommentBetweenImports = errors.New("comments between imported packages are invalid, please move comments to the end of the package line") var errInvalidCommentBetweenImports = errors.New("comments between imported packages are invalid, please move comments to the end of the package line")

View file

@ -58,8 +58,8 @@ import (
"code.gitea.io/other/package" "code.gitea.io/other/package"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"forgejo.org/modules/util" "code.gitea.io/gitea/modules/util"
"xorm.io/the/package" "xorm.io/the/package"
@ -82,8 +82,8 @@ import (
_ "image/jpeg" // for processing jpeg images _ "image/jpeg" // for processing jpeg images
_ "image/png" // for processing png images _ "image/png" // for processing png images
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"forgejo.org/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/other/package" "code.gitea.io/other/package"
"github.com/issue9/identicon" "github.com/issue9/identicon"

View file

@ -1,203 +0,0 @@
// 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
})
`

View file

@ -20,7 +20,7 @@ import (
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"forgejo.org/modules/json" "code.gitea.io/gitea/modules/json"
) )
const ( const (
@ -53,6 +53,8 @@ func (e Emoji) MarshalJSON() ([]byte, error) {
} }
func main() { func main() {
var err error
flag.Parse() flag.Parse()
// generate data // generate data
@ -81,6 +83,8 @@ var replacer = strings.NewReplacer(
var emojiRE = regexp.MustCompile(`\{Emoji:"([^"]*)"`) var emojiRE = regexp.MustCompile(`\{Emoji:"([^"]*)"`)
func generate() ([]byte, error) { func generate() ([]byte, error) {
var err error
// load gemoji data // load gemoji data
res, err := http.Get(gemojiURL) res, err := http.Get(gemojiURL)
if err != nil { if err != nil {

View file

@ -15,7 +15,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"forgejo.org/modules/util" "code.gitea.io/gitea/modules/util"
) )
func main() { func main() {

View file

@ -16,7 +16,7 @@ import (
"sort" "sort"
"strings" "strings"
"forgejo.org/modules/container" "code.gitea.io/gitea/modules/container"
) )
// regexp is based on go-license, excluding README and NOTICE // regexp is based on go-license, excluding README and NOTICE
@ -102,9 +102,9 @@ func main() {
pkgName := path.Dir(pkgPath) pkgName := path.Dir(pkgPath)
// There might be a bug somewhere in go-licenses that sometimes interprets the // There might be a bug somewhere in go-licenses that sometimes interprets the
// root package as "." and sometimes as "forgejo.org". Workaround by // root package as "." and sometimes as "code.gitea.io/gitea". Workaround by
// removing both of them for the sake of stable output. // removing both of them for the sake of stable output.
if pkgName == "." || pkgName == "forgejo.org" { if pkgName == "." || pkgName == "code.gitea.io/gitea" {
continue continue
} }

View file

@ -15,7 +15,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"forgejo.org/modules/util" "code.gitea.io/gitea/modules/util"
) )
func main() { func main() {

118
build/gocovmerge.go Normal file
View file

@ -0,0 +1,118 @@
// 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)
}

View file

@ -1,383 +0,0 @@
// 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)
}
}

View file

@ -1,50 +0,0 @@
// 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, "<g1>", "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, "<t1>", "{{ ctx.Locale.Tr \"what.an.example\" }}\n"))
})
}

View file

@ -1,195 +0,0 @@
// 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 <c> 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)
}

View file

@ -1,106 +0,0 @@
// 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 "<user> added/removed <label>" comments.`)))
assert.Equal(t, []string{"key: \x1b[31m<not-an-allowed-key>\x1b[0m REPLACED-TAG"}, checkLocaleContent([]byte(`key = "<not-an-allowed-key> <label>"`)))
assert.Equal(t, []string{"key: \x1b[31m<user@example.com>\x1b[0m REPLACED-TAG"}, checkLocaleContent([]byte(`key = "<user@example.com> <email@example.com>"`)))
assert.Equal(t, []string{"key: \x1b[31m<tag>\x1b[0m REPLACED-TAG \x1b[31m</tag>\x1b[0m"}, checkLocaleContent([]byte(`key = "<tag> <email@example.com> </tag>"`)))
})
t.Run("Specific exception", func(t *testing.T) {
assert.Empty(t, checkLocaleContent([]byte(`workflow.dispatch.trigger_found = This workflow has a <c>workflow_dispatch</c> event trigger.`)))
assert.Empty(t, checkLocaleContent([]byte(`pulls.title_desc_one = wants to merge %[1]d commit from <code>%[2]s</code> into <code id="%[4]s">%[3]s</code>`)))
assert.Empty(t, checkLocaleContent([]byte(`editor.commit_directly_to_this_branch = Commit directly to the <strong class="%[2]s">%[1]s</strong> branch.`)))
assert.Equal(t, []string{"workflow.dispatch.trigger_found: This workflow has a \x1b[31m<d>\x1b[0mworkflow_dispatch\x1b[31m</d>\x1b[0m event trigger."}, checkLocaleContent([]byte(`workflow.dispatch.trigger_found = This workflow has a <d>workflow_dispatch</d> event trigger.`)))
assert.Equal(t, []string{"key: <code\x1b[31m id=\"branch_targe\"\x1b[0m>%[3]s</code>"}, checkLocaleContent([]byte(`key = <code id="branch_targe">%[3]s</code>`)))
assert.Equal(t, []string{"key: <a\x1b[31m class=\"ui sh\"\x1b[0m href=\"https://TO-BE-REPLACED.COM\">"}, checkLocaleContent([]byte(`key = <a class="ui sh" href="%[3]s">`)))
assert.Equal(t, []string{"key: <a\x1b[31m class=\"js-click-me\"\x1b[0m href=\"https://TO-BE-REPLACED.COM\">"}, checkLocaleContent([]byte(`key = <a class="js-click-me" href="%[3]s">`)))
assert.Equal(t, []string{"key: <strong\x1b[31m class=\"branch-target\"\x1b[0m>%[1]s</strong>"}, checkLocaleContent([]byte(`key = <strong class="branch-target">%[1]s</strong>`)))
})
t.Run("General safe tags", func(t *testing.T) {
assert.Empty(t, checkLocaleContent([]byte("error404 = The page you are trying to reach either <strong>does not exist</strong> or <strong>you are not authorized</strong> to view it.")))
assert.Empty(t, checkLocaleContent([]byte("teams.specific_repositories_helper = Members will only have access to repositories explicitly added to the team. Selecting this <strong>will not</strong> automatically remove repositories already added with <i>All repositories</i>.")))
assert.Empty(t, checkLocaleContent([]byte("sqlite_helper = File path for the SQLite3 database.<br>Enter an absolute path if you run Forgejo as a service.")))
assert.Empty(t, checkLocaleContent([]byte("hi_user_x = Hi <b>%s</b>,")))
assert.Equal(t, []string{"error404: The page you are trying to reach either <strong\x1b[31m title='aaa'\x1b[0m>does not exist</strong> or <strong>you are not authorized</strong> to view it."}, checkLocaleContent([]byte("error404 = The page you are trying to reach either <strong title='aaa'>does not exist</strong> or <strong>you are not authorized</strong> to view it.")))
})
t.Run("<a>", func(t *testing.T) {
assert.Empty(t, checkLocaleContent([]byte(`admin.new_user.text = Please <a href="%s">click here</a> to manage this user from the admin panel.`)))
assert.Empty(t, checkLocaleContent([]byte(`access_token_desc = Selected token permissions limit authorization only to the corresponding <a href="%[1]s" target="_blank">API</a> routes. Read the <a href="%[2]s" target="_blank">documentation</a> for more information.`)))
assert.Empty(t, checkLocaleContent([]byte(`webauthn_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the <a rel="noreferrer" target="_blank" href="%s">WebAuthn Authenticator</a> standard.`)))
assert.Empty(t, checkLocaleContent([]byte("issues.closed_at = `closed this issue <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>`")))
assert.Equal(t, []string{"key: \x1b[31m<a href=\"https://example.com\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="https://example.com">`)))
assert.Equal(t, []string{"key: \x1b[31m<a href=\"javascript:alert('1')\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="javascript:alert('1')">`)))
assert.Equal(t, []string{"key: <a href=\"https://TO-BE-REPLACED.COM\"\x1b[31m download\x1b[0m>"}, checkLocaleContent([]byte(`key = <a href="%s" download>`)))
assert.Equal(t, []string{"key: <a href=\"https://TO-BE-REPLACED.COM\"\x1b[31m target=\"_self\"\x1b[0m>"}, checkLocaleContent([]byte(`key = <a href="%s" target="_self">`)))
assert.Equal(t, []string{"key: \x1b[31m<a href=\"https://example.com/%s\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="https://example.com/%s">`)))
assert.Equal(t, []string{"key: \x1b[31m<a href=\"https://example.com/?q=%s\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="https://example.com/?q=%s">`)))
assert.Equal(t, []string{"key: \x1b[31m<a href=\"%s/open-redirect\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="%s/open-redirect">`)))
assert.Equal(t, []string{"key: \x1b[31m<a href=\"%s?q=open-redirect\">\x1b[0m"}, checkLocaleContent([]byte(`key = <a href="%s?q=open-redirect">`)))
})
t.Run("Escaped HTML characters", func(t *testing.T) {
assert.Empty(t, checkLocaleContent([]byte("activity.git_stats_push_to_branch = `إلى %s و\"`")))
assert.Equal(t, []string{"key: و\x1b[31m&nbsp;\x1b[0m\x1b[32m\u00a0\x1b[0m"}, checkLocaleContent([]byte(`key = و&nbsp;`)))
})
}
func TestNextLocalizationPolicy(t *testing.T) {
initBlueMondayPolicy()
initRemoveTags()
t.Run("Nested locales", func(t *testing.T) {
assert.Empty(t, checkLocaleNextContent([]byte(`{
"settings": {
"hidden_comment_types_description": "Comment types checked here will not be shown inside issue pages. Checking \"Label\" for example removes all \"<user> added/removed <label>\" comments."
}
}`)))
assert.Equal(t, []string{"settings.hidden_comment_types_description: \"\x1b[31m<not-an-allowed-key>\x1b[0m REPLACED-TAG\""}, checkLocaleNextContent([]byte(`{
"settings": {
"hidden_comment_types_description": "\"<not-an-allowed-key> <label>\""
}
}`)))
})
t.Run("Flat locales", func(t *testing.T) {
assert.Empty(t, checkLocaleNextContent([]byte(`{
"settings.hidden_comment_types_description": "Comment types checked here will not be shown inside issue pages. Checking \"Label\" for example removes all \"<user> added/removed <label>\" comments."
}`)))
assert.Equal(t, []string{"settings.hidden_comment_types_description: \"\x1b[31m<not-an-allowed-key>\x1b[0m REPLACED-TAG\""}, checkLocaleNextContent([]byte(`{
"settings.hidden_comment_types_description": "\"<not-an-allowed-key> <label>\""
}`)))
})
t.Run("Plural form", func(t *testing.T) {
assert.Equal(t, []string{"repo.pulls.title_desc: key = \x1b[31m<a href=\"https://example.com\">\x1b[0m"}, checkLocaleNextContent([]byte(`{"repo.pulls.title_desc": {
"few": "key = <a href=\"%s\">",
"other": "key = <a href=\"https://example.com\">"
}}`)))
assert.Equal(t, []string{"repo.pulls.title_desc.few: key = \x1b[31m<a href=\"https://example.com\">\x1b[0m"}, checkLocaleNextContent([]byte(`{"repo.pulls.title_desc": {
"few": "key = <a href=\"https://example.com\">",
"other": "key = <a href=\"%s\">"
}}`)))
})
}

52
build/update-locales.sh Executable file
View file

@ -0,0 +1,52 @@
#!/bin/sh
# this script runs in alpine image which only has `sh` shell
set +e
if sed --version 2>/dev/null | grep -q GNU; then
SED_INPLACE="sed -i"
else
SED_INPLACE="sed -i ''"
fi
set -e
if [ ! -f ./options/locale/locale_en-US.ini ]; then
echo "please run this script in the root directory of the project"
exit 1
fi
mv ./options/locale/locale_en-US.ini ./options/
# the "ini" library for locale has many quirks, its behavior is different from Crowdin.
# see i18n_test.go for more details
# this script helps to unquote the Crowdin outputs for the quirky ini library
# * find all `key="...\"..."` lines
# * remove the leading quote
# * remove the trailing quote
# * unescape the quotes
# * eg: key="...\"..." => key=..."...
$SED_INPLACE -r -e '/^[-.A-Za-z0-9_]+[ ]*=[ ]*".*"$/ {
s/^([-.A-Za-z0-9_]+)[ ]*=[ ]*"/\1=/
s/"$//
s/\\"/"/g
}' ./options/locale/*.ini
# * if the escaped line is incomplete like `key="...` or `key=..."`, quote it with backticks
# * eg: key="... => key=`"...`
# * eg: key=..." => key=`..."`
$SED_INPLACE -r -e 's/^([-.A-Za-z0-9_]+)[ ]*=[ ]*(".*[^"])$/\1=`\2`/' ./options/locale/*.ini
$SED_INPLACE -r -e 's/^([-.A-Za-z0-9_]+)[ ]*=[ ]*([^"].*")$/\1=`\2`/' ./options/locale/*.ini
# Remove translation under 25% of en_us
baselines=$(wc -l "./options/locale_en-US.ini" | cut -d" " -f1)
baselines=$((baselines / 4))
for filename in ./options/locale/*.ini; do
lines=$(wc -l "$filename" | cut -d" " -f1)
if [ $lines -lt $baselines ]; then
echo "Removing $filename: $lines/$baselines"
rm "$filename"
fi
done
mv ./options/locale_en-US.ini ./options/locale/

View file

@ -4,28 +4,25 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"forgejo.org/modules/private" "code.gitea.io/gitea/modules/private"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdActions represents the available actions sub-commands. var (
func cmdActions() *cli.Command { // CmdActions represents the available actions sub-commands.
return &cli.Command{ CmdActions = &cli.Command{
Name: "actions", Name: "actions",
Usage: "Manage Forgejo Actions", Usage: "Manage Forgejo Actions",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdActionsGenRunnerToken(), subcmdActionsGenRunnerToken,
}, },
} }
}
func subcmdActionsGenRunnerToken() *cli.Command { subcmdActionsGenRunnerToken = &cli.Command{
return &cli.Command{
Name: "generate-runner-token", Name: "generate-runner-token",
Usage: "Generate a new token for a runner to use to register with the server", Usage: "Generate a new token for a runner to use to register with the server",
Action: runGenerateActionsRunnerToken, Action: runGenerateActionsRunnerToken,
@ -39,10 +36,10 @@ func subcmdActionsGenRunnerToken() *cli.Command {
}, },
}, },
} }
} )
func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error { func runGenerateActionsRunnerToken(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setting.MustInstalled() setting.MustInstalled()

View file

@ -8,71 +8,63 @@ import (
"context" "context"
"fmt" "fmt"
"forgejo.org/models/db" "code.gitea.io/gitea/models/db"
repo_model "forgejo.org/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"forgejo.org/modules/git" "code.gitea.io/gitea/modules/git"
"forgejo.org/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
repo_module "forgejo.org/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdAdmin represents the available admin sub-command. var (
func cmdAdmin() *cli.Command { // CmdAdmin represents the available admin sub-command.
return &cli.Command{ CmdAdmin = &cli.Command{
Name: "admin", Name: "admin",
Usage: "Perform common administrative operations", Usage: "Perform common administrative operations",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdUser(), subcmdUser,
subcmdRepoSyncReleases(), subcmdRepoSyncReleases,
subcmdRegenerate(), subcmdRegenerate,
subcmdAuth(), subcmdAuth,
subcmdSendMail(), subcmdSendMail,
}, },
} }
}
func subcmdRepoSyncReleases() *cli.Command { subcmdRepoSyncReleases = &cli.Command{
return &cli.Command{
Name: "repo-sync-releases", Name: "repo-sync-releases",
Usage: "Synchronize repository releases with tags", Usage: "Synchronize repository releases with tags",
Action: runRepoSyncReleases, Action: runRepoSyncReleases,
} }
}
func subcmdRegenerate() *cli.Command { subcmdRegenerate = &cli.Command{
return &cli.Command{
Name: "regenerate", Name: "regenerate",
Usage: "Regenerate specific files", Usage: "Regenerate specific files",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
microcmdRegenHooks, microcmdRegenHooks,
microcmdRegenKeys, microcmdRegenKeys,
}, },
} }
}
func subcmdAuth() *cli.Command { subcmdAuth = &cli.Command{
return &cli.Command{
Name: "auth", Name: "auth",
Usage: "Modify external auth providers", Usage: "Modify external auth providers",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
microcmdAuthAddOauth(), microcmdAuthAddOauth,
microcmdAuthUpdateOauth(), microcmdAuthUpdateOauth,
microcmdAuthAddLdapBindDn(), microcmdAuthAddLdapBindDn,
microcmdAuthUpdateLdapBindDn(), microcmdAuthUpdateLdapBindDn,
microcmdAuthAddLdapSimpleAuth(), microcmdAuthAddLdapSimpleAuth,
microcmdAuthUpdateLdapSimpleAuth(), microcmdAuthUpdateLdapSimpleAuth,
microcmdAuthAddSMTP(), microcmdAuthAddSMTP,
microcmdAuthUpdateSMTP(), microcmdAuthUpdateSMTP,
microcmdAuthList(), microcmdAuthList,
microcmdAuthDelete(), microcmdAuthDelete,
}, },
} }
}
func subcmdSendMail() *cli.Command { subcmdSendMail = &cli.Command{
return &cli.Command{
Name: "sendmail", Name: "sendmail",
Usage: "Send a message to all users", Usage: "Send a message to all users",
Action: runSendMail, Action: runSendMail,
@ -94,17 +86,15 @@ func subcmdSendMail() *cli.Command {
}, },
}, },
} }
}
func idFlag() *cli.Int64Flag { idFlag = &cli.Int64Flag{
return &cli.Int64Flag{
Name: "id", Name: "id",
Usage: "ID of authentication source", Usage: "ID of authentication source",
} }
} )
func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error { func runRepoSyncReleases(_ *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {

View file

@ -4,30 +4,26 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
"text/tabwriter" "text/tabwriter"
auth_model "forgejo.org/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"forgejo.org/models/db" "code.gitea.io/gitea/models/db"
auth_service "forgejo.org/services/auth" auth_service "code.gitea.io/gitea/services/auth"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdAuthDelete() *cli.Command { var (
return &cli.Command{ microcmdAuthDelete = &cli.Command{
Name: "delete", Name: "delete",
Usage: "Delete specific auth source", Usage: "Delete specific auth source",
Flags: []cli.Flag{idFlag()}, Flags: []cli.Flag{idFlag},
Action: runDeleteAuth, Action: runDeleteAuth,
} }
} microcmdAuthList = &cli.Command{
func microcmdAuthList() *cli.Command {
return &cli.Command{
Name: "list", Name: "list",
Usage: "List auth sources", Usage: "List auth sources",
Action: runListAuth, Action: runListAuth,
@ -58,10 +54,10 @@ func microcmdAuthList() *cli.Command {
}, },
}, },
} }
} )
func runListAuth(ctx context.Context, c *cli.Command) error { func runListAuth(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
@ -85,7 +81,7 @@ func runListAuth(ctx context.Context, c *cli.Command) error {
// loop through each source and print // loop through each source and print
w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags) w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags)
fmt.Fprint(w, "ID\tName\tType\tEnabled\n") fmt.Fprintf(w, "ID\tName\tType\tEnabled\n")
for _, source := range authSources { for _, source := range authSources {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive) fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive)
} }
@ -94,12 +90,12 @@ func runListAuth(ctx context.Context, c *cli.Command) error {
return nil return nil
} }
func runDeleteAuth(ctx context.Context, c *cli.Command) error { func runDeleteAuth(c *cli.Context) error {
if !c.IsSet("id") { if !c.IsSet("id") {
return errors.New("--id flag is missing") return errors.New("--id flag is missing")
} }
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {

View file

@ -8,10 +8,10 @@ import (
"fmt" "fmt"
"strings" "strings"
"forgejo.org/models/auth" "code.gitea.io/gitea/models/auth"
"forgejo.org/services/auth/source/ldap" "code.gitea.io/gitea/services/auth/source/ldap"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
type ( type (
@ -23,8 +23,8 @@ type (
} }
) )
func commonLdapCLIFlags() []cli.Flag { var (
return []cli.Flag{ commonLdapCLIFlags = []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Usage: "Authentication name.", Usage: "Authentication name.",
@ -102,10 +102,8 @@ func commonLdapCLIFlags() []cli.Flag {
Usage: "The attribute of the users LDAP record containing the users avatar.", Usage: "The attribute of the users LDAP record containing the users avatar.",
}, },
} }
}
func ldapBindDnCLIFlags() []cli.Flag { ldapBindDnCLIFlags = append(commonLdapCLIFlags,
return append(commonLdapCLIFlags(),
&cli.StringFlag{ &cli.StringFlag{
Name: "bind-dn", Name: "bind-dn",
Usage: "The DN to bind to the LDAP server with when searching for the user.", Usage: "The DN to bind to the LDAP server with when searching for the user.",
@ -130,59 +128,49 @@ func ldapBindDnCLIFlags() []cli.Flag {
Name: "page-size", Name: "page-size",
Usage: "Search page size.", Usage: "Search page size.",
}) })
}
func ldapSimpleAuthCLIFlags() []cli.Flag { ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags,
return append(commonLdapCLIFlags(),
&cli.StringFlag{ &cli.StringFlag{
Name: "user-dn", Name: "user-dn",
Usage: "The user's DN.", Usage: "The user's DN.",
}) })
}
func microcmdAuthAddLdapBindDn() *cli.Command { microcmdAuthAddLdapBindDn = &cli.Command{
return &cli.Command{
Name: "add-ldap", Name: "add-ldap",
Usage: "Add new LDAP (via Bind DN) authentication source", Usage: "Add new LDAP (via Bind DN) authentication source",
Action: func(ctx context.Context, cli *cli.Command) error { Action: func(c *cli.Context) error {
return newAuthService().addLdapBindDn(ctx, cli) return newAuthService().addLdapBindDn(c)
}, },
Flags: ldapBindDnCLIFlags(), Flags: ldapBindDnCLIFlags,
} }
}
func microcmdAuthUpdateLdapBindDn() *cli.Command { microcmdAuthUpdateLdapBindDn = &cli.Command{
return &cli.Command{
Name: "update-ldap", Name: "update-ldap",
Usage: "Update existing LDAP (via Bind DN) authentication source", Usage: "Update existing LDAP (via Bind DN) authentication source",
Action: func(ctx context.Context, cli *cli.Command) error { Action: func(c *cli.Context) error {
return newAuthService().updateLdapBindDn(ctx, cli) return newAuthService().updateLdapBindDn(c)
}, },
Flags: append([]cli.Flag{idFlag()}, ldapBindDnCLIFlags()...), Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...),
} }
}
func microcmdAuthAddLdapSimpleAuth() *cli.Command { microcmdAuthAddLdapSimpleAuth = &cli.Command{
return &cli.Command{
Name: "add-ldap-simple", Name: "add-ldap-simple",
Usage: "Add new LDAP (simple auth) authentication source", Usage: "Add new LDAP (simple auth) authentication source",
Action: func(ctx context.Context, cli *cli.Command) error { Action: func(c *cli.Context) error {
return newAuthService().addLdapSimpleAuth(ctx, cli) return newAuthService().addLdapSimpleAuth(c)
}, },
Flags: ldapSimpleAuthCLIFlags(), Flags: ldapSimpleAuthCLIFlags,
} }
}
func microcmdAuthUpdateLdapSimpleAuth() *cli.Command { microcmdAuthUpdateLdapSimpleAuth = &cli.Command{
return &cli.Command{
Name: "update-ldap-simple", Name: "update-ldap-simple",
Usage: "Update existing LDAP (simple auth) authentication source", Usage: "Update existing LDAP (simple auth) authentication source",
Action: func(ctx context.Context, cli *cli.Command) error { Action: func(c *cli.Context) error {
return newAuthService().updateLdapSimpleAuth(ctx, cli) return newAuthService().updateLdapSimpleAuth(c)
}, },
Flags: append([]cli.Flag{idFlag()}, ldapSimpleAuthCLIFlags()...), Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...),
} }
} )
// newAuthService creates a service with default functions. // newAuthService creates a service with default functions.
func newAuthService() *authService { func newAuthService() *authService {
@ -195,7 +183,7 @@ func newAuthService() *authService {
} }
// parseAuthSource assigns values on authSource according to command line flags. // parseAuthSource assigns values on authSource according to command line flags.
func parseAuthSource(c *cli.Command, authSource *auth.Source) { func parseAuthSource(c *cli.Context, authSource *auth.Source) {
if c.IsSet("name") { if c.IsSet("name") {
authSource.Name = c.String("name") authSource.Name = c.String("name")
} }
@ -214,7 +202,7 @@ func parseAuthSource(c *cli.Command, authSource *auth.Source) {
} }
// parseLdapConfig assigns values on config according to command line flags. // parseLdapConfig assigns values on config according to command line flags.
func parseLdapConfig(c *cli.Command, config *ldap.Source) error { func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
if c.IsSet("name") { if c.IsSet("name") {
config.Name = c.String("name") config.Name = c.String("name")
} }
@ -301,7 +289,7 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
// getAuthSource gets the login source by its id defined in the command line flags. // getAuthSource gets the login source by its id defined in the command line flags.
// It returns an error if the id is not set, does not match any source or if the source is not of expected type. // It returns an error if the id is not set, does not match any source or if the source is not of expected type.
func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authType auth.Type) (*auth.Source, error) { func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authType auth.Type) (*auth.Source, error) {
if err := argsSet(c, "id"); err != nil { if err := argsSet(c, "id"); err != nil {
return nil, err return nil, err
} }
@ -319,12 +307,12 @@ func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authTyp
} }
// addLdapBindDn adds a new LDAP via Bind DN authentication source. // addLdapBindDn adds a new LDAP via Bind DN authentication source.
func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error { func (a *authService) addLdapBindDn(c *cli.Context) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil { if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil {
return err return err
} }
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := a.initDB(ctx); err != nil { if err := a.initDB(ctx); err != nil {
@ -348,8 +336,8 @@ func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error {
} }
// updateLdapBindDn updates a new LDAP via Bind DN authentication source. // updateLdapBindDn updates a new LDAP via Bind DN authentication source.
func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) error { func (a *authService) updateLdapBindDn(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := a.initDB(ctx); err != nil { if err := a.initDB(ctx); err != nil {
@ -370,12 +358,12 @@ func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) erro
} }
// addLdapSimpleAuth adds a new LDAP (simple auth) authentication source. // addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.
func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) error { func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil { if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil {
return err return err
} }
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := a.initDB(ctx); err != nil { if err := a.initDB(ctx); err != nil {
@ -398,9 +386,9 @@ func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) err
return a.createAuthSource(ctx, authSource) return a.createAuthSource(ctx, authSource)
} }
// updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source. // updateLdapBindDn updates a new LDAP (simple auth) authentication source.
func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command) error { func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := a.initDB(ctx); err != nil { if err := a.initDB(ctx); err != nil {

View file

@ -7,18 +7,19 @@ import (
"context" "context"
"testing" "testing"
"forgejo.org/models/auth" "code.gitea.io/gitea/models/auth"
"forgejo.org/modules/test" "code.gitea.io/gitea/services/auth/source/ldap"
"forgejo.org/services/auth/source/ldap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func TestAddLdapBindDn(t *testing.T) { func TestAddLdapBindDn(t *testing.T) {
// Mock cli functions to do not exit on error // Mock cli functions to do not exit on error
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() osExiter := cli.OsExiter
defer func() { cli.OsExiter = osExiter }()
cli.OsExiter = func(code int) {}
// Test cases // Test cases
cases := []struct { cases := []struct {
@ -215,22 +216,22 @@ func TestAddLdapBindDn(t *testing.T) {
return nil return nil
}, },
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
assert.FailNow(t, "should not call updateAuthSource", "case: %d", n) assert.FailNow(t, "case %d: should not call updateAuthSource", n)
return nil return nil
}, },
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
assert.FailNow(t, "should not call getAuthSourceByID", "case: %d", n) assert.FailNow(t, "case %d: should not call getAuthSourceByID", n)
return nil, nil return nil, nil
}, },
} }
// Create a copy of command to test // Create a copy of command to test
app := cli.Command{} app := cli.NewApp()
app.Flags = microcmdAuthAddLdapBindDn().Flags app.Flags = microcmdAuthAddLdapBindDn.Flags
app.Action = service.addLdapBindDn app.Action = service.addLdapBindDn
// Run it // Run it
err := app.Run(t.Context(), c.args) err := app.Run(c.args)
if c.errMsg != "" { if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else { } else {
@ -242,7 +243,9 @@ func TestAddLdapBindDn(t *testing.T) {
func TestAddLdapSimpleAuth(t *testing.T) { func TestAddLdapSimpleAuth(t *testing.T) {
// Mock cli functions to do not exit on error // Mock cli functions to do not exit on error
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() osExiter := cli.OsExiter
defer func() { cli.OsExiter = osExiter }()
cli.OsExiter = func(code int) {}
// Test cases // Test cases
cases := []struct { cases := []struct {
@ -444,22 +447,22 @@ func TestAddLdapSimpleAuth(t *testing.T) {
return nil return nil
}, },
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
assert.FailNow(t, "should not call updateAuthSource", "case: %d", n) assert.FailNow(t, "case %d: should not call updateAuthSource", n)
return nil return nil
}, },
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
assert.FailNow(t, "should not call getAuthSourceByID", "case: %d", n) assert.FailNow(t, "case %d: should not call getAuthSourceByID", n)
return nil, nil return nil, nil
}, },
} }
// Create a copy of command to test // Create a copy of command to test
app := cli.Command{} app := cli.NewApp()
app.Flags = microcmdAuthAddLdapSimpleAuth().Flags app.Flags = microcmdAuthAddLdapSimpleAuth.Flags
app.Action = service.addLdapSimpleAuth app.Action = service.addLdapSimpleAuth
// Run it // Run it
err := app.Run(t.Context(), c.args) err := app.Run(c.args)
if c.errMsg != "" { if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else { } else {
@ -471,7 +474,9 @@ func TestAddLdapSimpleAuth(t *testing.T) {
func TestUpdateLdapBindDn(t *testing.T) { func TestUpdateLdapBindDn(t *testing.T) {
// Mock cli functions to do not exit on error // Mock cli functions to do not exit on error
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() osExiter := cli.OsExiter
defer func() { cli.OsExiter = osExiter }()
cli.OsExiter = func(code int) {}
// Test cases // Test cases
cases := []struct { cases := []struct {
@ -893,7 +898,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
return nil return nil
}, },
createAuthSource: func(ctx context.Context, authSource *auth.Source) error { createAuthSource: func(ctx context.Context, authSource *auth.Source) error {
assert.FailNow(t, "should not call createAuthSource", "case: %d", n) assert.FailNow(t, "case %d: should not call createAuthSource", n)
return nil return nil
}, },
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
@ -915,12 +920,12 @@ func TestUpdateLdapBindDn(t *testing.T) {
} }
// Create a copy of command to test // Create a copy of command to test
app := cli.Command{} app := cli.NewApp()
app.Flags = microcmdAuthUpdateLdapBindDn().Flags app.Flags = microcmdAuthUpdateLdapBindDn.Flags
app.Action = service.updateLdapBindDn app.Action = service.updateLdapBindDn
// Run it // Run it
err := app.Run(t.Context(), c.args) err := app.Run(c.args)
if c.errMsg != "" { if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else { } else {
@ -932,7 +937,9 @@ func TestUpdateLdapBindDn(t *testing.T) {
func TestUpdateLdapSimpleAuth(t *testing.T) { func TestUpdateLdapSimpleAuth(t *testing.T) {
// Mock cli functions to do not exit on error // Mock cli functions to do not exit on error
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() osExiter := cli.OsExiter
defer func() { cli.OsExiter = osExiter }()
cli.OsExiter = func(code int) {}
// Test cases // Test cases
cases := []struct { cases := []struct {
@ -1281,7 +1288,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
return nil return nil
}, },
createAuthSource: func(ctx context.Context, authSource *auth.Source) error { createAuthSource: func(ctx context.Context, authSource *auth.Source) error {
assert.FailNow(t, "should not call createAuthSource", "case: %d", n) assert.FailNow(t, "case %d: should not call createAuthSource", n)
return nil return nil
}, },
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
@ -1303,12 +1310,12 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
} }
// Create a copy of command to test // Create a copy of command to test
app := cli.Command{} app := cli.NewApp()
app.Flags = microcmdAuthUpdateLdapSimpleAuth().Flags app.Flags = microcmdAuthUpdateLdapSimpleAuth.Flags
app.Action = service.updateLdapSimpleAuth app.Action = service.updateLdapSimpleAuth
// Run it // Run it
err := app.Run(t.Context(), c.args) err := app.Run(c.args)
if c.errMsg != "" { if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else { } else {

View file

@ -4,19 +4,18 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
auth_model "forgejo.org/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"forgejo.org/services/auth/source/oauth2" "code.gitea.io/gitea/services/auth/source/oauth2"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func oauthCLIFlags() []cli.Flag { var (
return []cli.Flag{ oauthCLIFlags = []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Value: "", Value: "",
@ -121,27 +120,23 @@ func oauthCLIFlags() []cli.Flag {
Usage: "Activate automatic team membership removal depending on groups", Usage: "Activate automatic team membership removal depending on groups",
}, },
} }
}
func microcmdAuthAddOauth() *cli.Command { microcmdAuthAddOauth = &cli.Command{
return &cli.Command{
Name: "add-oauth", Name: "add-oauth",
Usage: "Add new Oauth authentication source", Usage: "Add new Oauth authentication source",
Action: runAddOauth, Action: runAddOauth,
Flags: oauthCLIFlags(), Flags: oauthCLIFlags,
} }
}
func microcmdAuthUpdateOauth() *cli.Command { microcmdAuthUpdateOauth = &cli.Command{
return &cli.Command{
Name: "update-oauth", Name: "update-oauth",
Usage: "Update existing Oauth authentication source", Usage: "Update existing Oauth authentication source",
Action: runUpdateOauth, Action: runUpdateOauth,
Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{idFlag()}, oauthCLIFlags()[1:]...)...), Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
} }
} )
func parseOAuth2Config(_ context.Context, c *cli.Command) *oauth2.Source { func parseOAuth2Config(c *cli.Context) *oauth2.Source {
var customURLMapping *oauth2.CustomURLMapping var customURLMapping *oauth2.CustomURLMapping
if c.IsSet("use-custom-urls") { if c.IsSet("use-custom-urls") {
customURLMapping = &oauth2.CustomURLMapping{ customURLMapping = &oauth2.CustomURLMapping{
@ -173,15 +168,15 @@ func parseOAuth2Config(_ context.Context, c *cli.Command) *oauth2.Source {
} }
} }
func runAddOauth(ctx context.Context, c *cli.Command) error { func runAddOauth(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }
config := parseOAuth2Config(ctx, c) config := parseOAuth2Config(c)
if config.Provider == "openidConnect" { if config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL) discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") { if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
@ -197,12 +192,12 @@ func runAddOauth(ctx context.Context, c *cli.Command) error {
}) })
} }
func runUpdateOauth(ctx context.Context, c *cli.Command) error { func runUpdateOauth(c *cli.Context) error {
if !c.IsSet("id") { if !c.IsSet("id") {
return errors.New("--id flag is missing") return errors.New("--id flag is missing")
} }
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {

View file

@ -4,19 +4,18 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"strings" "strings"
auth_model "forgejo.org/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"forgejo.org/modules/util" "code.gitea.io/gitea/modules/util"
"forgejo.org/services/auth/source/smtp" "code.gitea.io/gitea/services/auth/source/smtp"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func smtpCLIFlags() []cli.Flag { var (
return []cli.Flag{ smtpCLIFlags = []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Value: "", Value: "",
@ -72,27 +71,23 @@ func smtpCLIFlags() []cli.Flag {
Value: true, Value: true,
}, },
} }
}
func microcmdAuthAddSMTP() *cli.Command { microcmdAuthAddSMTP = &cli.Command{
return &cli.Command{
Name: "add-smtp", Name: "add-smtp",
Usage: "Add new SMTP authentication source", Usage: "Add new SMTP authentication source",
Action: runAddSMTP, Action: runAddSMTP,
Flags: smtpCLIFlags(), Flags: smtpCLIFlags,
} }
}
func microcmdAuthUpdateSMTP() *cli.Command { microcmdAuthUpdateSMTP = &cli.Command{
return &cli.Command{
Name: "update-smtp", Name: "update-smtp",
Usage: "Update existing SMTP authentication source", Usage: "Update existing SMTP authentication source",
Action: runUpdateSMTP, Action: runUpdateSMTP,
Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{idFlag()}, smtpCLIFlags()[1:]...)...), Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...),
} }
} )
func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error { func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
if c.IsSet("auth-type") { if c.IsSet("auth-type") {
conf.Auth = c.String("auth-type") conf.Auth = c.String("auth-type")
validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"} validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
@ -128,8 +123,8 @@ func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error {
return nil return nil
} }
func runAddSMTP(ctx context.Context, c *cli.Command) error { func runAddSMTP(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
@ -168,12 +163,12 @@ func runAddSMTP(ctx context.Context, c *cli.Command) error {
}) })
} }
func runUpdateSMTP(ctx context.Context, c *cli.Command) error { func runUpdateSMTP(c *cli.Context) error {
if !c.IsSet("id") { if !c.IsSet("id") {
return errors.New("--id flag is missing") return errors.New("--id flag is missing")
} }
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {

View file

@ -4,13 +4,11 @@
package cmd package cmd
import ( import (
"context" asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/modules/graceful"
repo_service "code.gitea.io/gitea/services/repository"
asymkey_model "forgejo.org/models/asymkey" "github.com/urfave/cli/v2"
"forgejo.org/modules/graceful"
repo_service "forgejo.org/services/repository"
"github.com/urfave/cli/v3"
) )
var ( var (
@ -27,8 +25,8 @@ var (
} }
) )
func runRegenerateHooks(ctx context.Context, _ *cli.Command) error { func runRegenerateHooks(_ *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
@ -37,8 +35,8 @@ func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
} }
func runRegenerateKeys(ctx context.Context, _ *cli.Command) error { func runRegenerateKeys(_ *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {

View file

@ -4,20 +4,18 @@
package cmd package cmd
import ( import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func subcmdUser() *cli.Command { var subcmdUser = &cli.Command{
return &cli.Command{
Name: "user", Name: "user",
Usage: "Modify users", Usage: "Modify users",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
microcmdUserCreate(), microcmdUserCreate,
microcmdUserList(), microcmdUserList,
microcmdUserChangePassword(), microcmdUserChangePassword,
microcmdUserDelete(), microcmdUserDelete,
microcmdUserGenerateAccessToken(), microcmdUserGenerateAccessToken,
microcmdUserMustChangePassword(), microcmdUserMustChangePassword,
}, },
}
} }

View file

@ -4,21 +4,19 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
user_model "forgejo.org/models/user" user_model "code.gitea.io/gitea/models/user"
"forgejo.org/modules/auth/password" "code.gitea.io/gitea/modules/auth/password"
"forgejo.org/modules/optional" "code.gitea.io/gitea/modules/optional"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
user_service "forgejo.org/services/user" user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdUserChangePassword() *cli.Command { var microcmdUserChangePassword = &cli.Command{
return &cli.Command{
Name: "change-password", Name: "change-password",
Usage: "Change a user's password", Usage: "Change a user's password",
Action: runChangePassword, Action: runChangePassword,
@ -41,15 +39,14 @@ func microcmdUserChangePassword() *cli.Command {
Value: true, Value: true,
}, },
}, },
}
} }
func runChangePassword(ctx context.Context, c *cli.Command) error { func runChangePassword(c *cli.Context) error {
if err := argsSet(c, "username", "password"); err != nil { if err := argsSet(c, "username", "password"); err != nil {
return err return err
} }
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {

View file

@ -4,23 +4,20 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings"
auth_model "forgejo.org/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"forgejo.org/models/db" "code.gitea.io/gitea/models/db"
user_model "forgejo.org/models/user" user_model "code.gitea.io/gitea/models/user"
pwd "forgejo.org/modules/auth/password" pwd "code.gitea.io/gitea/modules/auth/password"
"forgejo.org/modules/optional" "code.gitea.io/gitea/modules/optional"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdUserCreate() *cli.Command { var microcmdUserCreate = &cli.Command{
return &cli.Command{
Name: "create", Name: "create",
Usage: "Create a new user in database", Usage: "Create a new user in database",
Action: runCreateUser, Action: runCreateUser,
@ -53,6 +50,7 @@ func microcmdUserCreate() *cli.Command {
Name: "must-change-password", Name: "must-change-password",
Usage: "Set this option to false to prevent forcing the user to change their password after initial login", Usage: "Set this option to false to prevent forcing the user to change their password after initial login",
Value: true, Value: true,
DisableDefaultText: true,
}, },
&cli.IntFlag{ &cli.IntFlag{
Name: "random-password-length", Name: "random-password-length",
@ -63,33 +61,14 @@ func microcmdUserCreate() *cli.Command {
Name: "access-token", Name: "access-token",
Usage: "Generate access token for the user", Usage: "Generate access token for the user",
}, },
&cli.StringFlag{
Name: "access-token-name",
Usage: `Name of the generated access token`,
Value: "gitea-admin",
},
&cli.StringFlag{
Name: "access-token-scopes",
Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
Value: "all",
},
&cli.BoolFlag{ &cli.BoolFlag{
Name: "restricted", Name: "restricted",
Usage: "Make a restricted user account", Usage: "Make a restricted user account",
}, },
&cli.StringFlag{
Name: "fullname",
Usage: `The full, human-readable name of the user`,
}, },
},
}
} }
func runCreateUser(ctx context.Context, c *cli.Command) error { func runCreateUser(c *cli.Context) error {
// this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first
// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
setting.LoadSettings()
if err := argsSet(c, "email"); err != nil { if err := argsSet(c, "email"); err != nil {
return err return err
} }
@ -110,10 +89,10 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
username = c.String("username") username = c.String("username")
} else { } else {
username = c.String("name") username = c.String("name")
_, _ = fmt.Fprint(c.Root().ErrWriter, "--name flag is deprecated. Use --username instead.\n") _, _ = fmt.Fprintf(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n")
} }
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
@ -167,7 +146,6 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
IsAdmin: isAdmin, IsAdmin: isAdmin,
MustChangePassword: mustChangePassword, MustChangePassword: mustChangePassword,
Visibility: visibility, Visibility: visibility,
FullName: c.String("fullname"),
} }
overwriteDefault := &user_model.CreateUserOverwriteOptions{ overwriteDefault := &user_model.CreateUserOverwriteOptions{
@ -175,40 +153,23 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
IsRestricted: restricted, IsRestricted: restricted,
} }
var accessTokenName string
var accessTokenScope auth_model.AccessTokenScope
if c.IsSet("access-token") {
accessTokenName = strings.TrimSpace(c.String("access-token-name"))
if accessTokenName == "" {
return errors.New("access-token-name cannot be empty")
}
var err error
accessTokenScope, err = auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize()
if err != nil {
return fmt.Errorf("invalid access token scope provided: %w", err)
}
if !accessTokenScope.HasPermissionScope() {
return errors.New("access token does not have any permission")
}
} else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") {
return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set")
}
// arguments should be prepared before creating the user & access token, in case there is anything wrong
// create the user
if err := user_model.CreateUser(ctx, u, overwriteDefault); err != nil { if err := user_model.CreateUser(ctx, u, overwriteDefault); err != nil {
return fmt.Errorf("CreateUser: %w", err) return fmt.Errorf("CreateUser: %w", err)
} }
fmt.Printf("New user '%s' has been successfully created!\n", username)
// create the access token if c.Bool("access-token") {
if accessTokenScope != "" { t := &auth_model.AccessToken{
t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope} Name: "gitea-admin",
UID: u.ID,
}
if err := auth_model.NewAccessToken(ctx, t); err != nil { if err := auth_model.NewAccessToken(ctx, t); err != nil {
return err return err
} }
fmt.Printf("Access token was successfully created... %s\n", t.Token) fmt.Printf("Access token was successfully created... %s\n", t.Token)
} }
fmt.Printf("New user '%s' has been successfully created!\n", username)
return nil return nil
} }

View file

@ -4,20 +4,18 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
user_model "forgejo.org/models/user" user_model "code.gitea.io/gitea/models/user"
"forgejo.org/modules/storage" "code.gitea.io/gitea/modules/storage"
user_service "forgejo.org/services/user" user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdUserDelete() *cli.Command { var microcmdUserDelete = &cli.Command{
return &cli.Command{
Name: "delete", Name: "delete",
Usage: "Delete specific user by id, name or email", Usage: "Delete specific user by id, name or email",
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -41,15 +39,14 @@ func microcmdUserDelete() *cli.Command {
}, },
}, },
Action: runDeleteUser, Action: runDeleteUser,
}
} }
func runDeleteUser(ctx context.Context, c *cli.Command) error { func runDeleteUser(c *cli.Context) error {
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") { if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
return errors.New("You must provide the id, username or email of a user to delete") return errors.New("You must provide the id, username or email of a user to delete")
} }
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {

View file

@ -4,18 +4,16 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
auth_model "forgejo.org/models/auth" auth_model "code.gitea.io/gitea/models/auth"
user_model "forgejo.org/models/user" user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdUserGenerateAccessToken() *cli.Command { var microcmdUserGenerateAccessToken = &cli.Command{
return &cli.Command{
Name: "generate-access-token", Name: "generate-access-token",
Usage: "Generate an access token for a specific user", Usage: "Generate an access token for a specific user",
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -36,20 +34,19 @@ func microcmdUserGenerateAccessToken() *cli.Command {
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "scopes", Name: "scopes",
Value: "all", Value: "",
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`, Usage: "Comma separated list of scopes to apply to access token",
}, },
}, },
Action: runGenerateAccessToken, Action: runGenerateAccessToken,
}
} }
func runGenerateAccessToken(ctx context.Context, c *cli.Command) error { func runGenerateAccessToken(c *cli.Context) error {
if !c.IsSet("username") { if !c.IsSet("username") {
return errors.New("you must provide a username to generate a token for") return errors.New("You must provide a username to generate a token for")
} }
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
@ -80,9 +77,6 @@ func runGenerateAccessToken(ctx context.Context, c *cli.Command) error {
if err != nil { if err != nil {
return fmt.Errorf("invalid access token scope provided: %w", err) return fmt.Errorf("invalid access token scope provided: %w", err)
} }
if !accessTokenScope.HasPermissionScope() {
return errors.New("access token does not have any permission")
}
t.Scope = accessTokenScope t.Scope = accessTokenScope
// create the token // create the token

View file

@ -4,18 +4,16 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"text/tabwriter" "text/tabwriter"
user_model "forgejo.org/models/user" user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdUserList() *cli.Command { var microcmdUserList = &cli.Command{
return &cli.Command{
Name: "list", Name: "list",
Usage: "List users", Usage: "List users",
Action: runListUsers, Action: runListUsers,
@ -25,11 +23,10 @@ func microcmdUserList() *cli.Command {
Usage: "List only admin users", Usage: "List only admin users",
}, },
}, },
}
} }
func runListUsers(ctx context.Context, c *cli.Command) error { func runListUsers(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
@ -44,7 +41,7 @@ func runListUsers(ctx context.Context, c *cli.Command) error {
w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0)
if c.IsSet("admin") { if c.IsSet("admin") {
fmt.Fprint(w, "ID\tUsername\tEmail\tIsActive\n") fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\n")
for _, u := range users { for _, u := range users {
if u.IsAdmin { if u.IsAdmin {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive) fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive)
@ -52,7 +49,7 @@ func runListUsers(ctx context.Context, c *cli.Command) error {
} }
} else { } else {
twofa := user_model.UserList(users).GetTwoFaStatus(ctx) twofa := user_model.UserList(users).GetTwoFaStatus(ctx)
fmt.Fprint(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n") fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n")
for _, u := range users { for _, u := range users {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID]) fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID])
} }

View file

@ -4,17 +4,15 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
user_model "forgejo.org/models/user" user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdUserMustChangePassword() *cli.Command { var microcmdUserMustChangePassword = &cli.Command{
return &cli.Command{
Name: "must-change-password", Name: "must-change-password",
Usage: "Set the must change password flag for the provided users or all users", Usage: "Set the must change password flag for the provided users or all users",
Action: runMustChangePassword, Action: runMustChangePassword,
@ -34,11 +32,10 @@ func microcmdUserMustChangePassword() *cli.Command {
Usage: "Instead of setting the must-change-password flag, unset it", Usage: "Instead of setting the must-change-password flag, unset it",
}, },
}, },
}
} }
func runMustChangePassword(ctx context.Context, c *cli.Command) error { func runMustChangePassword(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if c.NArg() == 0 && !c.IsSet("all") { if c.NArg() == 0 && !c.IsSet("all") {

View file

@ -6,7 +6,6 @@
package cmd package cmd
import ( import (
"context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
@ -21,12 +20,11 @@ import (
"strings" "strings"
"time" "time"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdCert represents the available cert sub-command. // CmdCert represents the available cert sub-command.
func cmdCert() *cli.Command { var CmdCert = &cli.Command{
return &cli.Command{
Name: "cert", Name: "cert",
Usage: "Generate self-signed certificate", Usage: "Generate self-signed certificate",
Description: `Generate a self-signed X.509 certificate for a TLS server. Description: `Generate a self-signed X.509 certificate for a TLS server.
@ -63,7 +61,6 @@ Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
Usage: "whether this cert should be its own Certificate Authority", Usage: "whether this cert should be its own Certificate Authority",
}, },
}, },
}
} }
func publicKey(priv any) any { func publicKey(priv any) any {
@ -92,7 +89,7 @@ func pemBlockForKey(priv any) *pem.Block {
} }
} }
func runCert(ctx context.Context, c *cli.Command) error { func runCert(c *cli.Context) error {
if err := argsSet(c, "host"); err != nil { if err := argsSet(c, "host"); err != nil {
return err return err
} }

View file

@ -15,28 +15,26 @@ import (
"strings" "strings"
"syscall" "syscall"
"forgejo.org/models/db" "code.gitea.io/gitea/models/db"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"forgejo.org/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// argsSet checks that all the required arguments are set. args is a list of // argsSet checks that all the required arguments are set. args is a list of
// arguments that must be set in the passed Context. // arguments that must be set in the passed Context.
func argsSet(c *cli.Command, args ...string) error { func argsSet(c *cli.Context, args ...string) error {
for _, a := range args { for _, a := range args {
if !c.IsSet(a) { if !c.IsSet(a) {
return errors.New(a + " is not set") return errors.New(a + " is not set")
} }
if s, ok := c.Value(a).(string); ok { if util.IsEmptyString(c.String(a)) {
if util.IsEmptyString(s) {
return errors.New(a + " is required") return errors.New(a + " is required")
} }
} }
}
return nil return nil
} }
@ -75,8 +73,8 @@ If this is the intended configuration file complete the [database] section.`, se
return nil return nil
} }
func installSignals(ctx context.Context) (context.Context, context.CancelFunc) { func installSignals() (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(context.Background())
go func() { go func() {
// install notify // install notify
signalChannel := make(chan os.Signal, 1) signalChannel := make(chan os.Signal, 1)
@ -111,7 +109,7 @@ func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) {
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer) log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
} }
func globalBool(c *cli.Command, name string) bool { func globalBool(c *cli.Context, name string) bool {
for _, ctx := range c.Lineage() { for _, ctx := range c.Lineage() {
if ctx.Bool(name) { if ctx.Bool(name) {
return true return true
@ -122,16 +120,16 @@ func globalBool(c *cli.Command, name string) bool {
// PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout. // PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout.
// Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever. // Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever.
func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(ctx context.Context, cli *cli.Command) (context.Context, error) { func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error {
return func(ctx context.Context, cli *cli.Command) (context.Context, error) { return func(c *cli.Context) error {
level := defaultLevel level := defaultLevel
if globalBool(cli, "quiet") { if globalBool(c, "quiet") {
level = log.FATAL level = log.FATAL
} }
if globalBool(cli, "debug") || globalBool(cli, "verbose") { if globalBool(c, "debug") || globalBool(c, "verbose") {
level = log.TRACE level = log.TRACE
} }
log.SetConsoleLogger(log.DEFAULT, "console-default", level) log.SetConsoleLogger(log.DEFAULT, "console-default", level)
return ctx, nil return nil
} }
} }

65
cmd/docs.go Normal file
View file

@ -0,0 +1,65 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"fmt"
"os"
"strings"
"github.com/urfave/cli/v2"
)
// CmdDocs represents the available docs sub-command.
var CmdDocs = &cli.Command{
Name: "docs",
Usage: "Output CLI documentation",
Description: "A command to output Forgejo's CLI documentation, optionally to a file.",
Action: runDocs,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "man",
Usage: "Output man pages instead",
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Path to output to instead of stdout (will overwrite if exists)",
},
},
}
func runDocs(ctx *cli.Context) error {
docs, err := ctx.App.ToMarkdown()
if ctx.Bool("man") {
docs, err = ctx.App.ToMan()
}
if err != nil {
return err
}
if !ctx.Bool("man") {
// Clean up markdown. The following bug was fixed in v2, but is present in v1.
// It affects markdown output (even though the issue is referring to man pages)
// https://github.com/urfave/cli/issues/1040
firstHashtagIndex := strings.Index(docs, "#")
if firstHashtagIndex > 0 {
docs = docs[firstHashtagIndex:]
}
}
out := os.Stdout
if ctx.String("output") != "" {
fi, err := os.Create(ctx.String("output"))
if err != nil {
return err
}
defer fi.Close()
out = fi
}
_, err = fmt.Fprintln(out, docs)
return err
}

View file

@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
golog "log" golog "log"
"os" "os"
@ -12,34 +11,32 @@ import (
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"forgejo.org/models/db" "code.gitea.io/gitea/models/db"
"forgejo.org/models/migrations" "code.gitea.io/gitea/models/migrations"
migrate_base "forgejo.org/models/migrations/base" migrate_base "code.gitea.io/gitea/models/migrations/base"
"forgejo.org/modules/container" "code.gitea.io/gitea/modules/container"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"forgejo.org/services/doctor" "code.gitea.io/gitea/services/doctor"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
"xorm.io/xorm"
) )
// CmdDoctor represents the available doctor sub-command. // CmdDoctor represents the available doctor sub-command.
func cmdDoctor() *cli.Command { var CmdDoctor = &cli.Command{
return &cli.Command{
Name: "doctor", Name: "doctor",
Usage: "Diagnose and optionally fix problems, convert or re-create database tables", Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
cmdDoctorCheck(), cmdDoctorCheck,
cmdRecreateTable(), cmdRecreateTable,
cmdDoctorConvert(), cmdDoctorConvert,
}, },
}
} }
func cmdDoctorCheck() *cli.Command { var cmdDoctorCheck = &cli.Command{
return &cli.Command{
Name: "check", Name: "check",
Usage: "Diagnose and optionally fix problems", Usage: "Diagnose and optionally fix problems",
Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
@ -75,11 +72,9 @@ func cmdDoctorCheck() *cli.Command {
Usage: "Use color for outputted information", Usage: "Use color for outputted information",
}, },
}, },
}
} }
func cmdRecreateTable() *cli.Command { var cmdRecreateTable = &cli.Command{
return &cli.Command{
Name: "recreate-table", Name: "recreate-table",
Usage: "Recreate tables from XORM definitions and copy the data.", Usage: "Recreate tables from XORM definitions and copy the data.",
ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)", ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)",
@ -95,11 +90,10 @@ This command will cause Xorm to recreate tables, copying over the data and delet
You should back-up your database before doing this and ensure that your database is up-to-date first.`, You should back-up your database before doing this and ensure that your database is up-to-date first.`,
Action: runRecreateTable, Action: runRecreateTable,
}
} }
func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error { func runRecreateTable(ctx *cli.Context) error {
stdCtx, cancel := installSignals(stdCtx) stdCtx, cancel := installSignals()
defer cancel() defer cancel()
// Redirect the default golog to here // Redirect the default golog to here
@ -126,7 +120,7 @@ func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error {
args := ctx.Args() args := ctx.Args()
names := make([]string, 0, ctx.NArg()) names := make([]string, 0, ctx.NArg())
for i := range ctx.NArg() { for i := 0; i < ctx.NArg(); i++ {
names = append(names, args.Get(i)) names = append(names, args.Get(i))
} }
@ -136,31 +130,24 @@ func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error {
} }
recreateTables := migrate_base.RecreateTables(beans...) recreateTables := migrate_base.RecreateTables(beans...)
return db.InitEngineWithMigration(stdCtx, func(x db.Engine) error { return db.InitEngineWithMigration(stdCtx, func(x *xorm.Engine) error {
engine, err := db.GetMasterEngine(x) if err := migrations.EnsureUpToDate(x); err != nil {
if err != nil {
return err return err
} }
return recreateTables(x)
if err := migrations.EnsureUpToDate(engine); err != nil {
return err
}
return recreateTables(engine)
}) })
} }
func setupDoctorDefaultLogger(ctx *cli.Command, colorize bool) { func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
// Silence the default loggers // Silence the default loggers
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr) setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
logFile := ctx.String("log-file") logFile := ctx.String("log-file")
switch logFile { if logFile == "" {
case "":
return // if no doctor log-file is set, do not show any log from default logger return // if no doctor log-file is set, do not show any log from default logger
case "-": } else if logFile == "-" {
setupConsoleLogger(log.TRACE, colorize, os.Stdout) setupConsoleLogger(log.TRACE, colorize, os.Stdout)
default: } else {
logFile, _ = filepath.Abs(logFile) logFile, _ = filepath.Abs(logFile)
writeMode := log.WriterMode{Level: log.TRACE, WriterOption: log.WriterFileOption{FileName: logFile}} writeMode := log.WriterMode{Level: log.TRACE, WriterOption: log.WriterFileOption{FileName: logFile}}
writer, err := log.NewEventWriter("console-to-file", "file", writeMode) writer, err := log.NewEventWriter("console-to-file", "file", writeMode)
@ -172,8 +159,8 @@ func setupDoctorDefaultLogger(ctx *cli.Command, colorize bool) {
} }
} }
func runDoctorCheck(stdCtx context.Context, ctx *cli.Command) error { func runDoctorCheck(ctx *cli.Context) error {
stdCtx, cancel := installSignals(stdCtx) stdCtx, cancel := installSignals()
defer cancel() defer cancel()
colorize := log.CanColorStdout colorize := log.CanColorStdout

View file

@ -4,28 +4,25 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"forgejo.org/models/db" "code.gitea.io/gitea/models/db"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// cmdDoctorConvert represents the available convert sub-command. // cmdDoctorConvert represents the available convert sub-command.
func cmdDoctorConvert() *cli.Command { var cmdDoctorConvert = &cli.Command{
return &cli.Command{
Name: "convert", Name: "convert",
Usage: "Convert the database", Usage: "Convert the database",
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4", Description: "A command to convert an existing MySQL database from utf8 to utf8mb4",
Action: runDoctorConvert, Action: runDoctorConvert,
}
} }
func runDoctorConvert(stdCtx context.Context, ctx *cli.Command) error { func runDoctorConvert(ctx *cli.Context) error {
stdCtx, cancel := installSignals(stdCtx) stdCtx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(stdCtx); err != nil { if err := initDB(stdCtx); err != nil {

View file

@ -7,11 +7,11 @@ import (
"context" "context"
"testing" "testing"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/services/doctor" "code.gitea.io/gitea/services/doctor"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func TestDoctorRun(t *testing.T) { func TestDoctorRun(t *testing.T) {
@ -22,12 +22,12 @@ func TestDoctorRun(t *testing.T) {
SkipDatabaseInitialization: true, SkipDatabaseInitialization: true,
}) })
app := cli.Command{} app := cli.NewApp()
app.Commands = []*cli.Command{cmdDoctorCheck()} app.Commands = []*cli.Command{cmdDoctorCheck}
err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"}) err := app.Run([]string{"./gitea", "check", "--run", "test-check"})
require.NoError(t, err) require.NoError(t, err)
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "no-such"}) err = app.Run([]string{"./gitea", "check", "--run", "no-such"})
require.ErrorContains(t, err, `unknown checks: "no-such"`) require.ErrorContains(t, err, `unknown checks: "no-such"`)
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check,no-such"}) err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"})
require.ErrorContains(t, err, `unknown checks: "no-such"`) require.ErrorContains(t, err, `unknown checks: "no-such"`)
} }

View file

@ -5,8 +5,6 @@
package cmd package cmd
import ( import (
"context"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -15,16 +13,16 @@ import (
"strings" "strings"
"time" "time"
"forgejo.org/models/db" "code.gitea.io/gitea/models/db"
"forgejo.org/modules/json" "code.gitea.io/gitea/modules/json"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"forgejo.org/modules/storage" "code.gitea.io/gitea/modules/storage"
"forgejo.org/modules/util" "code.gitea.io/gitea/modules/util"
"code.forgejo.org/go-chi/session" "code.forgejo.org/go-chi/session"
"github.com/mholt/archiver/v3" "github.com/mholt/archiver/v3"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error { func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error {
@ -85,10 +83,6 @@ func (o *outputType) Set(value string) error {
return fmt.Errorf("allowed values are %s", o.Join()) return fmt.Errorf("allowed values are %s", o.Join())
} }
func (o *outputType) Get() any {
return o.String()
}
func (o outputType) String() string { func (o outputType) String() string {
if o.selected == "" { if o.selected == "" {
return o.Default return o.Default
@ -102,8 +96,7 @@ var outputTypeEnum = &outputType{
} }
// CmdDump represents the available dump sub-command. // CmdDump represents the available dump sub-command.
func cmdDump() *cli.Command { var CmdDump = &cli.Command{
return &cli.Command{
Name: "dump", Name: "dump",
Usage: "Dump Forgejo files and database", Usage: "Dump Forgejo files and database",
Description: `Dump compresses all related files and database into zip file. Description: `Dump compresses all related files and database into zip file.
@ -129,6 +122,7 @@ It can be used for backup and capture Forgejo server image to send to maintainer
&cli.StringFlag{ &cli.StringFlag{
Name: "tempdir", Name: "tempdir",
Aliases: []string{"t"}, Aliases: []string{"t"},
Value: os.TempDir(),
Usage: "Temporary dir path", Usage: "Temporary dir path",
}, },
&cli.StringFlag{ &cli.StringFlag{
@ -139,12 +133,12 @@ It can be used for backup and capture Forgejo server image to send to maintainer
&cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-repository", Name: "skip-repository",
Aliases: []string{"R"}, Aliases: []string{"R"},
Usage: "Skip repositories", Usage: "Skip the repository dumping",
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-log", Name: "skip-log",
Aliases: []string{"L"}, Aliases: []string{"L"},
Usage: "Skip logs", Usage: "Skip the log dumping",
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-custom-dir", Name: "skip-custom-dir",
@ -166,17 +160,12 @@ It can be used for backup and capture Forgejo server image to send to maintainer
Name: "skip-index", Name: "skip-index",
Usage: "Skip bleve index data", Usage: "Skip bleve index data",
}, },
&cli.BoolFlag{
Name: "skip-repo-archives",
Usage: "Skip repository archives",
},
&cli.GenericFlag{ &cli.GenericFlag{
Name: "type", Name: "type",
Value: outputTypeEnum, Value: outputTypeEnum,
Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()), Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),
}, },
}, },
}
} }
func fatal(format string, args ...any) { func fatal(format string, args ...any) {
@ -184,7 +173,7 @@ func fatal(format string, args ...any) {
log.Fatal(format, args...) log.Fatal(format, args...)
} }
func runDump(stdCtx context.Context, ctx *cli.Command) error { func runDump(ctx *cli.Context) error {
var file *os.File var file *os.File
fileName := ctx.String("file") fileName := ctx.String("file")
outType := ctx.String("type") outType := ctx.String("type")
@ -220,16 +209,16 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
if !setting.InstallLock { if !setting.InstallLock {
log.Error("Is '%s' really the right config path?\n", setting.CustomConf) log.Error("Is '%s' really the right config path?\n", setting.CustomConf)
return errors.New("forgejo is not initialized") return fmt.Errorf("forgejo is not initialized")
} }
setting.LoadSettings() // cannot access session settings otherwise setting.LoadSettings() // cannot access session settings otherwise
verbose := ctx.Bool("verbose") verbose := ctx.Bool("verbose")
if verbose && ctx.Bool("quiet") { if verbose && ctx.Bool("quiet") {
return errors.New("--quiet and --verbose cannot both be set") return fmt.Errorf("--quiet and --verbose cannot both be set")
} }
stdCtx, cancel := installSignals(stdCtx) stdCtx, cancel := installSignals()
defer cancel() defer cancel()
err := db.InitEngine(stdCtx) err := db.InitEngine(stdCtx)
@ -244,7 +233,7 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
if file == nil { if file == nil {
file, err = os.Create(fileName) file, err = os.Create(fileName)
if err != nil { if err != nil {
fatal("Failed to open %s: %v", fileName, err) fatal("Unable to open %s: %v", fileName, err)
} }
} }
defer file.Close() defer file.Close()
@ -261,7 +250,7 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
iface, err = archiver.ByExtension(fileName) iface, err = archiver.ByExtension(fileName)
} }
if err != nil { if err != nil {
fatal("Failed to get archiver for extension: %v", err) fatal("Unable to get archiver for extension: %v", err)
} }
w, _ := iface.(archiver.Writer) w, _ := iface.(archiver.Writer)
@ -271,7 +260,7 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
defer w.Close() defer w.Close()
if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") { if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
log.Info("Skipping local repositories") log.Info("Skip dumping local repositories")
} else { } else {
log.Info("Dumping local repositories... %s", setting.RepoRootPath) log.Info("Dumping local repositories... %s", setting.RepoRootPath)
if err := addRecursiveExclude(w, "repos", setting.RepoRootPath, []string{absFileName}, verbose); err != nil { if err := addRecursiveExclude(w, "repos", setting.RepoRootPath, []string{absFileName}, verbose); err != nil {
@ -279,9 +268,9 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
} }
if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") { if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") {
log.Info("Skipping LFS data") log.Info("Skip dumping LFS data")
} else if !setting.LFS.StartServer { } else if !setting.LFS.StartServer {
log.Info("LFS not enabled - skipping") log.Info("LFS isn't enabled. Skip dumping LFS data")
} else if err := storage.LFS.IterateObjects("", func(objPath string, object storage.Object) error { } else if err := storage.LFS.IterateObjects("", func(objPath string, object storage.Object) error {
info, err := object.Stat() info, err := object.Stat()
if err != nil { if err != nil {
@ -295,31 +284,18 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
} }
tmpDir := ctx.String("tempdir") tmpDir := ctx.String("tempdir")
if tmpDir == "" {
tmpDir, err = os.MkdirTemp("", "forgejo-dump-*")
if err != nil {
fatal("Failed to create temporary directory: %v", err)
}
defer func() {
if err := util.Remove(tmpDir); err != nil {
log.Warn("Failed to remove temporary directory: %s: Error: %v", tmpDir, err)
}
}()
}
if _, err := os.Stat(tmpDir); os.IsNotExist(err) { if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
fatal("Path does not exist: %s", tmpDir) fatal("Path does not exist: %s", tmpDir)
} }
dbDump, err := os.CreateTemp(tmpDir, "forgejo-db.sql") dbDump, err := os.CreateTemp(tmpDir, "forgejo-db.sql")
if err != nil { if err != nil {
fatal("Failed to create temporary file: %v", err) fatal("Failed to create tmp file: %v", err)
} }
defer func() { defer func() {
_ = dbDump.Close() _ = dbDump.Close()
if err := util.Remove(dbDump.Name()); err != nil { if err := util.Remove(dbDump.Name()); err != nil {
log.Warn("Failed to remove temporary database file: %s: Error: %v", dbDump.Name(), err) log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err)
} }
}() }()
@ -355,16 +331,16 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
fatal("Failed to include custom: %v", err) fatal("Failed to include custom: %v", err)
} }
} else { } else {
log.Info("Custom dir %s is inside data dir %s, skipping", setting.CustomPath, setting.AppDataPath) log.Info("Custom dir %s is inside data dir %s, skipped", setting.CustomPath, setting.AppDataPath)
} }
} else { } else {
log.Info("Custom dir %s does not exist, skipping", setting.CustomPath) log.Info("Custom dir %s doesn't exist, skipped", setting.CustomPath)
} }
} }
isExist, err := util.IsExist(setting.AppDataPath) isExist, err := util.IsExist(setting.AppDataPath)
if err != nil { if err != nil {
log.Error("Failed to check if %s exists: %v", setting.AppDataPath, err) log.Error("Unable to check if %s exists. Error: %v", setting.AppDataPath, err)
} }
if isExist { if isExist {
log.Info("Packing data directory...%s", setting.AppDataPath) log.Info("Packing data directory...%s", setting.AppDataPath)
@ -379,16 +355,10 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
} }
if ctx.IsSet("skip-index") && ctx.Bool("skip-index") { if ctx.IsSet("skip-index") && ctx.Bool("skip-index") {
log.Info("Skipping bleve index data")
excludes = append(excludes, setting.Indexer.RepoPath) excludes = append(excludes, setting.Indexer.RepoPath)
excludes = append(excludes, setting.Indexer.IssuePath) excludes = append(excludes, setting.Indexer.IssuePath)
} }
if ctx.IsSet("skip-repo-archives") && ctx.Bool("skip-repo-archives") {
log.Info("Skipping repository archives data")
excludes = append(excludes, setting.RepoArchive.Storage.Path)
}
excludes = append(excludes, setting.RepoRootPath) excludes = append(excludes, setting.RepoRootPath)
excludes = append(excludes, setting.LFS.Storage.Path) excludes = append(excludes, setting.LFS.Storage.Path)
excludes = append(excludes, setting.Attachment.Storage.Path) excludes = append(excludes, setting.Attachment.Storage.Path)
@ -401,7 +371,7 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
} }
if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") { if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
log.Info("Skipping attachment data") log.Info("Skip dumping attachment data")
} else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error { } else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
info, err := object.Stat() info, err := object.Stat()
if err != nil { if err != nil {
@ -414,9 +384,9 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
} }
if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") { if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
log.Info("Skipping package data") log.Info("Skip dumping package data")
} else if !setting.Packages.Enabled { } else if !setting.Packages.Enabled {
log.Info("Package registry not enabled - skipping") log.Info("Packages isn't enabled. Skip dumping package data")
} else if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error { } else if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error {
info, err := object.Stat() info, err := object.Stat()
if err != nil { if err != nil {
@ -432,11 +402,11 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
// ensuring that it's clear the dump is skipped whether the directory's initialized // ensuring that it's clear the dump is skipped whether the directory's initialized
// yet or not. // yet or not.
if ctx.IsSet("skip-log") && ctx.Bool("skip-log") { if ctx.IsSet("skip-log") && ctx.Bool("skip-log") {
log.Info("Skipping log files") log.Info("Skip dumping log files")
} else { } else {
isExist, err := util.IsExist(setting.Log.RootPath) isExist, err := util.IsExist(setting.Log.RootPath)
if err != nil { if err != nil {
log.Error("Failed to check if %s exists: %v", setting.Log.RootPath, err) log.Error("Unable to check if %s exists. Error: %v", setting.Log.RootPath, err)
} }
if isExist { if isExist {
if err := addRecursiveExclude(w, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil { if err := addRecursiveExclude(w, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil {
@ -486,7 +456,7 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA
currentInsidePath := path.Join(insidePath, file.Name()) currentInsidePath := path.Join(insidePath, file.Name())
if util.SliceContainsString(excludeAbsPath, currentAbsPath) { if util.SliceContainsString(excludeAbsPath, currentAbsPath) {
log.Debug("Skipping %q (matched an excluded path)", currentAbsPath) log.Debug("Skipping %q because matched an excluded path.", currentAbsPath)
continue continue
} }

View file

@ -10,21 +10,20 @@ import (
"os" "os"
"strings" "strings"
"forgejo.org/modules/git" "code.gitea.io/gitea/modules/git"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
base "forgejo.org/modules/migration" base "code.gitea.io/gitea/modules/migration"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"forgejo.org/modules/structs" "code.gitea.io/gitea/modules/structs"
"forgejo.org/modules/util" "code.gitea.io/gitea/modules/util"
"forgejo.org/services/convert" "code.gitea.io/gitea/services/convert"
"forgejo.org/services/migrations" "code.gitea.io/gitea/services/migrations"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdDumpRepository represents the available dump repository sub-command. // CmdDumpRepository represents the available dump repository sub-command.
func cmdDumpRepository() *cli.Command { var CmdDumpRepository = &cli.Command{
return &cli.Command{
Name: "dump-repo", Name: "dump-repo",
Usage: "Dump the repository from git/github/gitea/gitlab", Usage: "Dump the repository from git/github/gitea/gitlab",
Description: "This is a command for dumping the repository data.", Description: "This is a command for dumping the repository data.",
@ -78,11 +77,10 @@ func cmdDumpRepository() *cli.Command {
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`, wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
}, },
}, },
}
} }
func runDumpRepository(stdCtx context.Context, ctx *cli.Command) error { func runDumpRepository(ctx *cli.Context) error {
stdCtx, cancel := installSignals(stdCtx) stdCtx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(stdCtx); err != nil { if err := initDB(stdCtx); err != nil {

View file

@ -4,41 +4,38 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"forgejo.org/modules/assetfs" "code.gitea.io/gitea/modules/assetfs"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/options" "code.gitea.io/gitea/modules/options"
"forgejo.org/modules/public" "code.gitea.io/gitea/modules/public"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"forgejo.org/modules/templates" "code.gitea.io/gitea/modules/templates"
"forgejo.org/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob" "github.com/gobwas/glob"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdEmbedded represents the available extract sub-command. // CmdEmbedded represents the available extract sub-command.
func cmdEmbedded() *cli.Command { var (
return &cli.Command{ CmdEmbedded = &cli.Command{
Name: "embedded", Name: "embedded",
Usage: "Extract embedded resources", Usage: "Extract embedded resources",
Description: "A command for extracting embedded resources, like templates and images", Description: "A command for extracting embedded resources, like templates and images",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdList(), subcmdList,
subcmdView(), subcmdView,
subcmdExtract(), subcmdExtract,
}, },
} }
}
func subcmdList() *cli.Command { subcmdList = &cli.Command{
return &cli.Command{
Name: "list", Name: "list",
Usage: "List files matching the given pattern", Usage: "List files matching the given pattern",
Action: runList, Action: runList,
@ -50,10 +47,8 @@ func subcmdList() *cli.Command {
}, },
}, },
} }
}
func subcmdView() *cli.Command { subcmdView = &cli.Command{
return &cli.Command{
Name: "view", Name: "view",
Usage: "View a file matching the given pattern", Usage: "View a file matching the given pattern",
Action: runView, Action: runView,
@ -65,10 +60,8 @@ func subcmdView() *cli.Command {
}, },
}, },
} }
}
func subcmdExtract() *cli.Command { subcmdExtract = &cli.Command{
return &cli.Command{
Name: "extract", Name: "extract",
Usage: "Extract resources", Usage: "Extract resources",
Action: runExtract, Action: runExtract,
@ -97,9 +90,9 @@ func subcmdExtract() *cli.Command {
}, },
}, },
} }
}
var matchedAssetFiles []assetFile matchedAssetFiles []assetFile
)
type assetFile struct { type assetFile struct {
fs *assetfs.LayeredFS fs *assetfs.LayeredFS
@ -107,7 +100,7 @@ type assetFile struct {
path string path string
} }
func initEmbeddedExtractor(_ context.Context, c *cli.Command) error { func initEmbeddedExtractor(c *cli.Context) error {
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr) setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
patterns, err := compileCollectPatterns(c.Args().Slice()) patterns, err := compileCollectPatterns(c.Args().Slice())
@ -122,32 +115,32 @@ func initEmbeddedExtractor(_ context.Context, c *cli.Command) error {
return nil return nil
} }
func runList(ctx context.Context, c *cli.Command) error { func runList(c *cli.Context) error {
if err := runListDo(ctx, c); err != nil { if err := runListDo(c); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err) fmt.Fprintf(os.Stderr, "%v\n", err)
return err return err
} }
return nil return nil
} }
func runView(ctx context.Context, c *cli.Command) error { func runView(c *cli.Context) error {
if err := runViewDo(ctx, c); err != nil { if err := runViewDo(c); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err) fmt.Fprintf(os.Stderr, "%v\n", err)
return err return err
} }
return nil return nil
} }
func runExtract(ctx context.Context, c *cli.Command) error { func runExtract(c *cli.Context) error {
if err := runExtractDo(ctx, c); err != nil { if err := runExtractDo(c); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err) fmt.Fprintf(os.Stderr, "%v\n", err)
return err return err
} }
return nil return nil
} }
func runListDo(ctx context.Context, c *cli.Command) error { func runListDo(c *cli.Context) error {
if err := initEmbeddedExtractor(ctx, c); err != nil { if err := initEmbeddedExtractor(c); err != nil {
return err return err
} }
@ -158,8 +151,8 @@ func runListDo(ctx context.Context, c *cli.Command) error {
return nil return nil
} }
func runViewDo(ctx context.Context, c *cli.Command) error { func runViewDo(c *cli.Context) error {
if err := initEmbeddedExtractor(ctx, c); err != nil { if err := initEmbeddedExtractor(c); err != nil {
return err return err
} }
@ -181,8 +174,8 @@ func runViewDo(ctx context.Context, c *cli.Command) error {
return nil return nil
} }
func runExtractDo(ctx context.Context, c *cli.Command) error { func runExtractDo(c *cli.Context) error {
if err := initEmbeddedExtractor(ctx, c); err != nil { if err := initEmbeddedExtractor(c); err != nil {
return err return err
} }
@ -278,7 +271,7 @@ func extractAsset(d string, a assetFile, overwrite, rename bool) error {
return nil return nil
} }
func collectAssetFilesByPattern(c *cli.Command, globs []glob.Glob, path string, layer *assetfs.Layer) { func collectAssetFilesByPattern(c *cli.Context, globs []glob.Glob, path string, layer *assetfs.Layer) {
fs := assetfs.Layered(layer) fs := assetfs.Layered(layer)
files, err := fs.ListAllFiles(".", true) files, err := fs.ListAllFiles(".", true)
if err != nil { if err != nil {

View file

@ -6,25 +6,24 @@ package forgejo
import ( import (
"context" "context"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"strings" "strings"
actions_model "forgejo.org/models/actions" actions_model "code.gitea.io/gitea/models/actions"
"forgejo.org/modules/private" "code.gitea.io/gitea/modules/private"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
private_routers "forgejo.org/routers/private" private_routers "code.gitea.io/gitea/routers/private"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func CmdActions(ctx context.Context) *cli.Command { func CmdActions(ctx context.Context) *cli.Command {
return &cli.Command{ return &cli.Command{
Name: "actions", Name: "actions",
Usage: "Commands for managing Forgejo Actions", Usage: "Commands for managing Forgejo Actions",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
SubcmdActionsGenerateRunnerToken(ctx), SubcmdActionsGenerateRunnerToken(ctx),
SubcmdActionsGenerateRunnerSecret(ctx), SubcmdActionsGenerateRunnerSecret(ctx),
SubcmdActionsRegister(ctx), SubcmdActionsRegister(ctx),
@ -37,7 +36,7 @@ func SubcmdActionsGenerateRunnerToken(ctx context.Context) *cli.Command {
Name: "generate-runner-token", Name: "generate-runner-token",
Usage: "Generate a new token for a runner to use to register with the server", Usage: "Generate a new token for a runner to use to register with the server",
Before: prepareWorkPathAndCustomConf(ctx), Before: prepareWorkPathAndCustomConf(ctx),
Action: RunGenerateActionsRunnerToken, Action: func(cliCtx *cli.Context) error { return RunGenerateActionsRunnerToken(ctx, cliCtx) },
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "scope", Name: "scope",
@ -53,7 +52,7 @@ func SubcmdActionsGenerateRunnerSecret(ctx context.Context) *cli.Command {
return &cli.Command{ return &cli.Command{
Name: "generate-secret", Name: "generate-secret",
Usage: "Generate a secret suitable for input to the register subcommand", Usage: "Generate a secret suitable for input to the register subcommand",
Action: RunGenerateSecret, Action: func(cliCtx *cli.Context) error { return RunGenerateSecret(ctx, cliCtx) },
} }
} }
@ -62,7 +61,7 @@ func SubcmdActionsRegister(ctx context.Context) *cli.Command {
Name: "register", Name: "register",
Usage: "Idempotent registration of a runner using a shared secret", Usage: "Idempotent registration of a runner using a shared secret",
Before: prepareWorkPathAndCustomConf(ctx), Before: prepareWorkPathAndCustomConf(ctx),
Action: RunRegister, Action: func(cliCtx *cli.Context) error { return RunRegister(ctx, cliCtx) },
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "secret", Name: "secret",
@ -106,26 +105,26 @@ func SubcmdActionsRegister(ctx context.Context) *cli.Command {
} }
} }
func readSecret(ctx context.Context, cli *cli.Command) (string, error) { func readSecret(ctx context.Context, cliCtx *cli.Context) (string, error) {
if cli.IsSet("secret") { if cliCtx.IsSet("secret") {
return cli.String("secret"), nil return cliCtx.String("secret"), nil
} }
if cli.IsSet("secret-stdin") { if cliCtx.IsSet("secret-stdin") {
buf, err := io.ReadAll(ContextGetStdin(ctx)) buf, err := io.ReadAll(ContextGetStdin(ctx))
if err != nil { if err != nil {
return "", err return "", err
} }
return string(buf), nil return string(buf), nil
} }
if cli.IsSet("secret-file") { if cliCtx.IsSet("secret-file") {
path := cli.String("secret-file") path := cliCtx.String("secret-file")
buf, err := os.ReadFile(path) buf, err := os.ReadFile(path)
if err != nil { if err != nil {
return "", err return "", err
} }
return string(buf), nil return string(buf), nil
} }
return "", errors.New("at least one of the --secret, --secret-stdin, --secret-file options is required") return "", fmt.Errorf("at least one of the --secret, --secret-stdin, --secret-file options is required")
} }
func validateSecret(secret string) error { func validateSecret(secret string) error {
@ -139,18 +138,18 @@ func validateSecret(secret string) error {
return nil return nil
} }
func getLabels(cli *cli.Command) (*[]string, error) { func getLabels(cliCtx *cli.Context) (*[]string, error) {
if !cli.Bool("keep-labels") { if !cliCtx.Bool("keep-labels") {
lblValue := strings.Split(cli.String("labels"), ",") lblValue := strings.Split(cliCtx.String("labels"), ",")
return &lblValue, nil return &lblValue, nil
} }
if cli.String("labels") != "" { if cliCtx.String("labels") != "" {
return nil, errors.New("--labels and --keep-labels should not be used together") return nil, fmt.Errorf("--labels and --keep-labels should not be used together")
} }
return nil, nil return nil, nil
} }
func RunRegister(ctx context.Context, cli *cli.Command) error { func RunRegister(ctx context.Context, cliCtx *cli.Context) error {
var cancel context.CancelFunc var cancel context.CancelFunc
if !ContextGetNoInit(ctx) { if !ContextGetNoInit(ctx) {
ctx, cancel = installSignals(ctx) ctx, cancel = installSignals(ctx)
@ -162,17 +161,17 @@ func RunRegister(ctx context.Context, cli *cli.Command) error {
} }
setting.MustInstalled() setting.MustInstalled()
secret, err := readSecret(ctx, cli) secret, err := readSecret(ctx, cliCtx)
if err != nil { if err != nil {
return err return err
} }
if err := validateSecret(secret); err != nil { if err := validateSecret(secret); err != nil {
return err return err
} }
scope := cli.String("scope") scope := cliCtx.String("scope")
name := cli.String("name") name := cliCtx.String("name")
version := cli.String("version") version := cliCtx.String("version")
labels, err := getLabels(cli) labels, err := getLabels(cliCtx)
if err != nil { if err != nil {
return err return err
} }
@ -210,7 +209,7 @@ func RunRegister(ctx context.Context, cli *cli.Command) error {
return nil return nil
} }
func RunGenerateSecret(ctx context.Context, cli *cli.Command) error { func RunGenerateSecret(ctx context.Context, cliCtx *cli.Context) error {
runner := actions_model.ActionRunner{} runner := actions_model.ActionRunner{}
if err := runner.GenerateToken(); err != nil { if err := runner.GenerateToken(); err != nil {
return err return err
@ -221,7 +220,7 @@ func RunGenerateSecret(ctx context.Context, cli *cli.Command) error {
return nil return nil
} }
func RunGenerateActionsRunnerToken(ctx context.Context, cli *cli.Command) error { func RunGenerateActionsRunnerToken(ctx context.Context, cliCtx *cli.Context) error {
if !ContextGetNoInit(ctx) { if !ContextGetNoInit(ctx) {
var cancel context.CancelFunc var cancel context.CancelFunc
ctx, cancel = installSignals(ctx) ctx, cancel = installSignals(ctx)
@ -230,7 +229,7 @@ func RunGenerateActionsRunnerToken(ctx context.Context, cli *cli.Command) error
setting.MustInstalled() setting.MustInstalled()
scope := cli.String("scope") scope := cliCtx.String("scope")
respText, extra := private.GenerateActionsRunnerToken(ctx, scope) respText, extra := private.GenerateActionsRunnerToken(ctx, scope)
if extra.HasError() { if extra.HasError() {

View file

@ -4,13 +4,14 @@
package forgejo package forgejo
import ( import (
"context"
"fmt" "fmt"
"testing" "testing"
"code.gitea.io/gitea/services/context"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func TestActions_getLabels(t *testing.T) { func TestActions_getLabels(t *testing.T) {
@ -53,21 +54,21 @@ func TestActions_getLabels(t *testing.T) {
}, },
} }
flags := SubcmdActionsRegister(t.Context()).Flags flags := SubcmdActionsRegister(context.Context{}).Flags
for _, c := range cases { for _, c := range cases {
t.Run(fmt.Sprintf("args: %v", c.args), func(t *testing.T) { t.Run(fmt.Sprintf("args: %v", c.args), func(t *testing.T) {
// Create a copy of command to test // Create a copy of command to test
var result *resultType var result *resultType
app := cli.Command{} app := cli.NewApp()
app.Flags = flags app.Flags = flags
app.Action = func(_ context.Context, ctx *cli.Command) error { app.Action = func(ctx *cli.Context) error {
labels, err := getLabels(ctx) labels, err := getLabels(ctx)
result = &resultType{labels, err} result = &resultType{labels, err}
return nil return nil
} }
// Run it // Run it
_ = app.Run(t.Context(), c.args) _ = app.Run(c.args)
// Test the results // Test the results
require.NotNil(t, result) require.NotNil(t, result)

View file

@ -8,19 +8,19 @@ import (
"context" "context"
"errors" "errors"
"forgejo.org/models" "code.gitea.io/gitea/models"
"forgejo.org/modules/git" "code.gitea.io/gitea/modules/git"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"forgejo.org/modules/storage" "code.gitea.io/gitea/modules/storage"
"forgejo.org/services/f3/util" "code.gitea.io/gitea/services/f3/util"
_ "forgejo.org/services/f3/driver" // register the driver _ "code.gitea.io/gitea/services/f3/driver" // register the driver
f3_cmd "code.forgejo.org/f3/gof3/v3/cmd" f3_cmd "code.forgejo.org/f3/gof3/v3/cmd"
f3_logger "code.forgejo.org/f3/gof3/v3/logger" f3_logger "code.forgejo.org/f3/gof3/v3/logger"
f3_util "code.forgejo.org/f3/gof3/v3/util" f3_util "code.forgejo.org/f3/gof3/v3/util"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func CmdF3(ctx context.Context) *cli.Command { func CmdF3(ctx context.Context) *cli.Command {
@ -28,21 +28,21 @@ func CmdF3(ctx context.Context) *cli.Command {
return &cli.Command{ return &cli.Command{
Name: "f3", Name: "f3",
Usage: "F3", Usage: "F3",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
SubcmdF3Mirror(ctx), SubcmdF3Mirror(ctx),
}, },
} }
} }
func SubcmdF3Mirror(ctx context.Context) *cli.Command { func SubcmdF3Mirror(ctx context.Context) *cli.Command {
mirrorCmd := f3_cmd.CreateCmdMirror() mirrorCmd := f3_cmd.CreateCmdMirror(ctx)
mirrorCmd.Before = prepareWorkPathAndCustomConf(ctx) mirrorCmd.Before = prepareWorkPathAndCustomConf(ctx)
f3Action := mirrorCmd.Action f3Action := mirrorCmd.Action
mirrorCmd.Action = func(ctx context.Context, cli *cli.Command) error { return runMirror(ctx, cli, f3Action) } mirrorCmd.Action = func(c *cli.Context) error { return runMirror(ctx, c, f3Action) }
return mirrorCmd return mirrorCmd
} }
func runMirror(ctx context.Context, c *cli.Command, action cli.ActionFunc) error { func runMirror(ctx context.Context, c *cli.Context, action cli.ActionFunc) error {
setting.LoadF3Setting() setting.LoadF3Setting()
if !setting.F3.Enabled { if !setting.F3.Enabled {
return errors.New("F3 is disabled, it is not ready to be used and is only present for development purposes") return errors.New("F3 is disabled, it is not ready to be used and is only present for development purposes")
@ -69,7 +69,7 @@ func runMirror(ctx context.Context, c *cli.Command, action cli.ActionFunc) error
} }
} }
err := action(ctx, c) err := action(c)
if panicError, ok := err.(f3_util.PanicError); ok { if panicError, ok := err.(f3_util.PanicError); ok {
log.Debug("F3 Stack trace\n%s", panicError.Stack()) log.Debug("F3 Stack trace\n%s", panicError.Stack())
} }

View file

@ -11,12 +11,12 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
"forgejo.org/models/db" "code.gitea.io/gitea/models/db"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/private" "code.gitea.io/gitea/modules/private"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
type key int type key int
@ -34,7 +34,7 @@ func CmdForgejo(ctx context.Context) *cli.Command {
Name: "forgejo-cli", Name: "forgejo-cli",
Usage: "Forgejo CLI", Usage: "Forgejo CLI",
Flags: []cli.Flag{}, Flags: []cli.Flag{},
Commands: []*cli.Command{ Subcommands: []*cli.Command{
CmdActions(ctx), CmdActions(ctx),
CmdF3(ctx), CmdF3(ctx),
}, },
@ -147,12 +147,12 @@ func handleCliResponseExtra(ctx context.Context, extra private.ResponseExtra) er
return cli.Exit(extra.Error, 1) return cli.Exit(extra.Error, 1)
} }
func prepareWorkPathAndCustomConf(ctx context.Context) func(ctx context.Context, cli *cli.Command) (context.Context, error) { func prepareWorkPathAndCustomConf(ctx context.Context) func(c *cli.Context) error {
return func(ctx context.Context, cli *cli.Command) (context.Context, error) { return func(c *cli.Context) error {
if !ContextGetNoInit(ctx) { if !ContextGetNoInit(ctx) {
var args setting.ArgWorkPathAndCustomConf var args setting.ArgWorkPathAndCustomConf
// from children to parent, check the global flags // from children to parent, check the global flags
for _, curCtx := range cli.Lineage() { for _, curCtx := range c.Lineage() {
if curCtx.IsSet("work-path") && args.WorkPath == "" { if curCtx.IsSet("work-path") && args.WorkPath == "" {
args.WorkPath = curCtx.String("work-path") args.WorkPath = curCtx.String("work-path")
} }
@ -165,6 +165,6 @@ func prepareWorkPathAndCustomConf(ctx context.Context) func(ctx context.Context,
} }
setting.InitWorkPathAndCommonConfig(os.Getenv, args) setting.InitWorkPathAndCommonConfig(os.Getenv, args)
} }
return ctx, nil return nil
} }
} }

View file

@ -5,65 +5,56 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"forgejo.org/modules/generate" "code.gitea.io/gitea/modules/generate"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdGenerate represents the available generate sub-command. var (
func cmdGenerate() *cli.Command { // CmdGenerate represents the available generate sub-command.
return &cli.Command{ CmdGenerate = &cli.Command{
Name: "generate", Name: "generate",
Usage: "Generate Gitea's secrets/keys/tokens", Usage: "Generate Gitea's secrets/keys/tokens",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdSecret(), subcmdSecret,
}, },
} }
}
func subcmdSecret() *cli.Command { subcmdSecret = &cli.Command{
return &cli.Command{
Name: "secret", Name: "secret",
Usage: "Generate a secret token", Usage: "Generate a secret token",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
microcmdGenerateInternalToken(), microcmdGenerateInternalToken,
microcmdGenerateLfsJwtSecret(), microcmdGenerateLfsJwtSecret,
microcmdGenerateSecretKey(), microcmdGenerateSecretKey,
}, },
} }
}
func microcmdGenerateInternalToken() *cli.Command { microcmdGenerateInternalToken = &cli.Command{
return &cli.Command{
Name: "INTERNAL_TOKEN", Name: "INTERNAL_TOKEN",
Usage: "Generate a new INTERNAL_TOKEN", Usage: "Generate a new INTERNAL_TOKEN",
Action: runGenerateInternalToken, Action: runGenerateInternalToken,
} }
}
func microcmdGenerateLfsJwtSecret() *cli.Command { microcmdGenerateLfsJwtSecret = &cli.Command{
return &cli.Command{
Name: "JWT_SECRET", Name: "JWT_SECRET",
Aliases: []string{"LFS_JWT_SECRET"}, Aliases: []string{"LFS_JWT_SECRET"},
Usage: "Generate a new JWT_SECRET", Usage: "Generate a new JWT_SECRET",
Action: runGenerateLfsJwtSecret, Action: runGenerateLfsJwtSecret,
} }
}
func microcmdGenerateSecretKey() *cli.Command { microcmdGenerateSecretKey = &cli.Command{
return &cli.Command{
Name: "SECRET_KEY", Name: "SECRET_KEY",
Usage: "Generate a new SECRET_KEY", Usage: "Generate a new SECRET_KEY",
Action: runGenerateSecretKey, Action: runGenerateSecretKey,
} }
} )
func runGenerateInternalToken(ctx context.Context, c *cli.Command) error { func runGenerateInternalToken(c *cli.Context) error {
internalToken, err := generate.NewInternalToken() internalToken, err := generate.NewInternalToken()
if err != nil { if err != nil {
return err return err
@ -72,25 +63,28 @@ func runGenerateInternalToken(ctx context.Context, c *cli.Command) error {
fmt.Printf("%s", internalToken) fmt.Printf("%s", internalToken)
if isatty.IsTerminal(os.Stdout.Fd()) { if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Println() fmt.Printf("\n")
} }
return nil return nil
} }
func runGenerateLfsJwtSecret(ctx context.Context, c *cli.Command) error { func runGenerateLfsJwtSecret(c *cli.Context) error {
_, jwtSecretBase64 := generate.NewJwtSecret() _, jwtSecretBase64, err := generate.NewJwtSecret()
if err != nil {
return err
}
fmt.Printf("%s", jwtSecretBase64) fmt.Printf("%s", jwtSecretBase64)
if isatty.IsTerminal(os.Stdout.Fd()) { if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Print("\n") fmt.Printf("\n")
} }
return nil return nil
} }
func runGenerateSecretKey(ctx context.Context, c *cli.Command) error { func runGenerateSecretKey(c *cli.Context) error {
secretKey, err := generate.NewSecretKey() secretKey, err := generate.NewSecretKey()
if err != nil { if err != nil {
return err return err
@ -99,7 +93,7 @@ func runGenerateSecretKey(ctx context.Context, c *cli.Command) error {
fmt.Printf("%s", secretKey) fmt.Printf("%s", secretKey)
if isatty.IsTerminal(os.Stdout.Fd()) { if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Print("\n") fmt.Printf("\n")
} }
return nil return nil

View file

@ -14,38 +14,36 @@ import (
"strings" "strings"
"time" "time"
"forgejo.org/modules/git" "code.gitea.io/gitea/modules/git"
"forgejo.org/modules/git/pushoptions" "code.gitea.io/gitea/modules/git/pushoptions"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/private" "code.gitea.io/gitea/modules/private"
repo_module "forgejo.org/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
const ( const (
hookBatchSize = 30 hookBatchSize = 30
) )
// CmdHook represents the available hooks sub-command. var (
func cmdHook() *cli.Command { // CmdHook represents the available hooks sub-command.
return &cli.Command{ CmdHook = &cli.Command{
Name: "hook", Name: "hook",
Usage: "(internal) Should only be called by Git", Usage: "(internal) Should only be called by Git",
Description: "Delegate commands to corresponding Git hooks", Description: "Delegate commands to corresponding Git hooks",
Before: PrepareConsoleLoggerLevel(log.FATAL), Before: PrepareConsoleLoggerLevel(log.FATAL),
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdHookPreReceive(), subcmdHookPreReceive,
subcmdHookUpdate(), subcmdHookUpdate,
subcmdHookPostReceive(), subcmdHookPostReceive,
subcmdHookProcReceive(), subcmdHookProcReceive,
}, },
} }
}
func subcmdHookPreReceive() *cli.Command { subcmdHookPreReceive = &cli.Command{
return &cli.Command{
Name: "pre-receive", Name: "pre-receive",
Usage: "Delegate pre-receive Git hook", Usage: "Delegate pre-receive Git hook",
Description: "This command should only be called by Git", Description: "This command should only be called by Git",
@ -56,10 +54,7 @@ func subcmdHookPreReceive() *cli.Command {
}, },
}, },
} }
} subcmdHookUpdate = &cli.Command{
func subcmdHookUpdate() *cli.Command {
return &cli.Command{
Name: "update", Name: "update",
Usage: "Delegate update Git hook", Usage: "Delegate update Git hook",
Description: "This command should only be called by Git", Description: "This command should only be called by Git",
@ -70,10 +65,7 @@ func subcmdHookUpdate() *cli.Command {
}, },
}, },
} }
} subcmdHookPostReceive = &cli.Command{
func subcmdHookPostReceive() *cli.Command {
return &cli.Command{
Name: "post-receive", Name: "post-receive",
Usage: "Delegate post-receive Git hook", Usage: "Delegate post-receive Git hook",
Description: "This command should only be called by Git", Description: "This command should only be called by Git",
@ -84,11 +76,8 @@ func subcmdHookPostReceive() *cli.Command {
}, },
}, },
} }
} // Note: new hook since git 2.29
subcmdHookProcReceive = &cli.Command{
// Note: new hook since git 2.29
func subcmdHookProcReceive() *cli.Command {
return &cli.Command{
Name: "proc-receive", Name: "proc-receive",
Usage: "Delegate proc-receive Git hook", Usage: "Delegate proc-receive Git hook",
Description: "This command should only be called by Git", Description: "This command should only be called by Git",
@ -99,7 +88,7 @@ func subcmdHookProcReceive() *cli.Command {
}, },
}, },
} }
} )
type delayWriter struct { type delayWriter struct {
internal io.Writer internal io.Writer
@ -172,14 +161,14 @@ func (n *nilWriter) WriteString(s string) (int, error) {
return len(s), nil return len(s), nil
} }
func runHookPreReceive(ctx context.Context, c *cli.Command) error { func runHookPreReceive(c *cli.Context) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil return nil
} }
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), true) setup(ctx, c.Bool("debug"))
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
if setting.OnlyAllowPushIfGiteaEnvironmentSet { if setting.OnlyAllowPushIfGiteaEnvironmentSet {
@ -231,7 +220,10 @@ Forgejo or set your environment appropriately.`, "")
} }
} }
supportProcReceive := git.CheckGitVersionAtLeast("2.29") == nil supportProcReceive := false
if git.CheckGitVersionAtLeast("2.29") == nil {
supportProcReceive = true
}
for scanner.Scan() { for scanner.Scan() {
// TODO: support news feeds for wiki // TODO: support news feeds for wiki
@ -258,7 +250,7 @@ Forgejo or set your environment appropriately.`, "")
newCommitIDs[count] = newCommitID newCommitIDs[count] = newCommitID
refFullNames[count] = refFullName refFullNames[count] = refFullName
count++ count++
fmt.Fprint(out, "*") fmt.Fprintf(out, "*")
if count >= hookBatchSize { if count >= hookBatchSize {
fmt.Fprintf(out, " Checking %d references\n", count) fmt.Fprintf(out, " Checking %d references\n", count)
@ -274,10 +266,10 @@ Forgejo or set your environment appropriately.`, "")
lastline = 0 lastline = 0
} }
} else { } else {
fmt.Fprint(out, ".") fmt.Fprintf(out, ".")
} }
if lastline >= hookBatchSize { if lastline >= hookBatchSize {
fmt.Fprint(out, "\n") fmt.Fprintf(out, "\n")
lastline = 0 lastline = 0
} }
} }
@ -294,7 +286,7 @@ Forgejo or set your environment appropriately.`, "")
return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error) return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error)
} }
} else if lastline > 0 { } else if lastline > 0 {
fmt.Fprint(out, "\n") fmt.Fprintf(out, "\n")
} }
fmt.Fprintf(out, "Checked %d references in total\n", total) fmt.Fprintf(out, "Checked %d references in total\n", total)
@ -302,13 +294,13 @@ Forgejo or set your environment appropriately.`, "")
} }
// runHookUpdate process the update hook: https://git-scm.com/docs/githooks#update // runHookUpdate process the update hook: https://git-scm.com/docs/githooks#update
func runHookUpdate(ctx context.Context, c *cli.Command) error { func runHookUpdate(c *cli.Context) error {
// Now if we're an internal don't do anything else // Now if we're an internal don't do anything else
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil return nil
} }
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
if c.NArg() != 3 { if c.NArg() != 3 {
@ -330,15 +322,14 @@ func runHookUpdate(ctx context.Context, c *cli.Command) error {
return fail(ctx, fmt.Sprintf("The deletion of %s is skipped as it's an internal reference.", refFullName), "") return fail(ctx, fmt.Sprintf("The deletion of %s is skipped as it's an internal reference.", refFullName), "")
} }
// If the new comment isn't empty it means modification. return nil
return fail(ctx, fmt.Sprintf("The modification of %s is skipped as it's an internal reference.", refFullName), "")
} }
func runHookPostReceive(ctx context.Context, c *cli.Command) error { func runHookPostReceive(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), true) setup(ctx, c.Bool("debug"))
// First of all run update-server-info no matter what // First of all run update-server-info no matter what
if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil { if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil {
@ -410,7 +401,7 @@ Forgejo or set your environment appropriately.`, "")
continue continue
} }
fmt.Fprint(out, ".") fmt.Fprintf(out, ".")
oldCommitIDs[count] = string(fields[0]) oldCommitIDs[count] = string(fields[0])
newCommitIDs[count] = string(fields[1]) newCommitIDs[count] = string(fields[1])
refFullNames[count] = git.RefName(fields[2]) refFullNames[count] = git.RefName(fields[2])
@ -498,11 +489,11 @@ func hookPrintResults(results []private.HookPostReceiveBranchResult) {
} }
} }
func runHookProcReceive(ctx context.Context, c *cli.Command) error { func runHookProcReceive(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), true) setup(ctx, c.Bool("debug"))
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
if setting.OnlyAllowPushIfGiteaEnvironmentSet { if setting.OnlyAllowPushIfGiteaEnvironmentSet {

View file

@ -6,6 +6,7 @@ package cmd
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -14,12 +15,12 @@ import (
"testing" "testing"
"time" "time"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"forgejo.org/modules/test" "code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// Capture what's being written into a standard file descriptor. // Capture what's being written into a standard file descriptor.
@ -41,7 +42,7 @@ func captureOutput(t *testing.T, stdFD *os.File) (finish func() (output string))
} }
func TestPktLine(t *testing.T) { func TestPktLine(t *testing.T) {
ctx := t.Context() ctx := context.Background()
t.Run("Read", func(t *testing.T) { t.Run("Read", func(t *testing.T) {
s := strings.NewReader("0000") s := strings.NewReader("0000")
@ -134,14 +135,14 @@ func TestDelayWriter(t *testing.T) {
defer ts.Close() defer ts.Close()
defer test.MockVariableValue(&setting.LocalURL, ts.URL+"/")() defer test.MockVariableValue(&setting.LocalURL, ts.URL+"/")()
app := cli.Command{} app := cli.NewApp()
app.Commands = []*cli.Command{subcmdHookPreReceive()} app.Commands = []*cli.Command{subcmdHookPreReceive}
t.Run("Should delay", func(t *testing.T) { t.Run("Should delay", func(t *testing.T) {
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Millisecond*500)() defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Millisecond*500)()
finish := captureOutput(t, os.Stdout) finish := captureOutput(t, os.Stdout)
err = app.Run(t.Context(), []string{"./forgejo", "pre-receive"}) err = app.Run([]string{"./forgejo", "pre-receive"})
require.NoError(t, err) require.NoError(t, err)
out := finish() out := finish()
@ -153,7 +154,7 @@ func TestDelayWriter(t *testing.T) {
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Second*5)() defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Second*5)()
finish := captureOutput(t, os.Stdout) finish := captureOutput(t, os.Stdout)
err = app.Run(t.Context(), []string{"./forgejo", "pre-receive"}) err = app.Run([]string{"./forgejo", "pre-receive"})
require.NoError(t, err) require.NoError(t, err)
out := finish() out := finish()
@ -163,15 +164,15 @@ func TestDelayWriter(t *testing.T) {
} }
func TestRunHookUpdate(t *testing.T) { func TestRunHookUpdate(t *testing.T) {
app := cli.Command{} app := cli.NewApp()
app.Commands = []*cli.Command{subcmdHookUpdate()} app.Commands = []*cli.Command{subcmdHookUpdate}
t.Run("Removal of internal reference", func(t *testing.T) { t.Run("Removal of internal reference", func(t *testing.T) {
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
defer test.MockVariableValue(&setting.IsProd, false)() defer test.MockVariableValue(&setting.IsProd, false)()
finish := captureOutput(t, os.Stderr) finish := captureOutput(t, os.Stderr)
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"}) err := app.Run([]string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
out := finish() out := finish()
require.Error(t, err) require.Error(t, err)
@ -179,24 +180,17 @@ func TestRunHookUpdate(t *testing.T) {
}) })
t.Run("Update of internal reference", func(t *testing.T) { t.Run("Update of internal reference", func(t *testing.T) {
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() err := app.Run([]string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000001"})
defer test.MockVariableValue(&setting.IsProd, false)() require.NoError(t, err)
finish := captureOutput(t, os.Stderr)
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000001"})
out := finish()
require.Error(t, err)
assert.Contains(t, out, "The modification of refs/pull/1/head is skipped as it's an internal reference.")
}) })
t.Run("Removal of branch", func(t *testing.T) { t.Run("Removal of branch", func(t *testing.T) {
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/head/main", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"}) err := app.Run([]string{"./forgejo", "update", "refs/head/main", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
require.NoError(t, err) require.NoError(t, err)
}) })
t.Run("Not enough arguments", func(t *testing.T) { t.Run("Not enough arguments", func(t *testing.T) {
err := app.Run(t.Context(), []string{"./forgejo", "update"}) err := app.Run([]string{"./forgejo", "update"})
require.NoError(t, err) require.NoError(t, err)
}) })
} }

View file

@ -4,20 +4,18 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/private" "code.gitea.io/gitea/modules/private"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdKeys represents the available keys sub-command // CmdKeys represents the available keys sub-command
func cmdKeys() *cli.Command { var CmdKeys = &cli.Command{
return &cli.Command{
Name: "keys", Name: "keys",
Usage: "(internal) Should only be called by SSH server", Usage: "(internal) Should only be called by SSH server",
Description: "Queries the Forgejo database to get the authorized command for a given ssh key fingerprint", Description: "Queries the Forgejo database to get the authorized command for a given ssh key fingerprint",
@ -49,10 +47,9 @@ func cmdKeys() *cli.Command {
Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)", Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)",
}, },
}, },
}
} }
func runKeys(ctx context.Context, c *cli.Command) error { func runKeys(c *cli.Context) error {
if !c.IsSet("username") { if !c.IsSet("username") {
return errors.New("No username provided") return errors.New("No username provided")
} }
@ -71,16 +68,16 @@ func runKeys(ctx context.Context, c *cli.Command) error {
return errors.New("No key type and content provided") return errors.New("No key type and content provided")
} }
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), true) setup(ctx, c.Bool("debug"))
authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content) authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content)
// do not use handleCliResponseExtra or cli.NewExitError, if it exists immediately, it breaks some tests like Test_CmdKeys // do not use handleCliResponseExtra or cli.NewExitError, if it exists immediately, it breaks some tests like Test_CmdKeys
if extra.Error != nil { if extra.Error != nil {
return extra.Error return extra.Error
} }
_, _ = fmt.Fprintln(c.Root().Writer, strings.TrimSpace(authorizedString.Text)) _, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString.Text))
return nil return nil
} }

View file

@ -4,17 +4,16 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"forgejo.org/modules/private" "code.gitea.io/gitea/modules/private"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func runSendMail(ctx context.Context, c *cli.Command) error { func runSendMail(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setting.MustInstalled() setting.MustInstalled()

View file

@ -10,11 +10,11 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"forgejo.org/cmd/forgejo" "code.gitea.io/gitea/cmd/forgejo"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// cmdHelp is our own help subcommand with more information // cmdHelp is our own help subcommand with more information
@ -25,18 +25,18 @@ func cmdHelp() *cli.Command {
Aliases: []string{"h"}, Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command", Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]", ArgsUsage: "[command]",
Action: func(ctx context.Context, c *cli.Command) (err error) { Action: func(c *cli.Context) (err error) {
lineage := c.Lineage() // The order is from child to parent: help, doctor, Forgejo lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil}
targetCmdIdx := 0 targetCmdIdx := 0
if c.Name == "help" { if c.Command.Name == "help" {
targetCmdIdx = 1 targetCmdIdx = 1
} }
if targetCmdIdx+1 < len(lineage) { if lineage[targetCmdIdx+1].Command != nil {
err = cli.ShowCommandHelp(ctx, lineage[targetCmdIdx+1], lineage[targetCmdIdx].Name) err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name)
} else { } else {
err = cli.ShowAppHelp(c) err = cli.ShowAppHelp(c)
} }
_, _ = fmt.Fprintf(c.Root().Writer, ` _, _ = fmt.Fprintf(c.App.Writer, `
DEFAULT CONFIGURATION: DEFAULT CONFIGURATION:
AppPath: %s AppPath: %s
WorkPath: %s WorkPath: %s
@ -77,25 +77,25 @@ func appGlobalFlags() []cli.Flag {
} }
} }
func prepareSubcommandWithConfig(command *cli.Command, globalFlags func() []cli.Flag) { func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) {
command.Flags = append(globalFlags(), command.Flags...) command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...)
command.Action = prepareWorkPathAndCustomConf(command.Action) command.Action = prepareWorkPathAndCustomConf(command.Action)
command.HideHelp = true command.HideHelp = true
if command.Name != "help" { if command.Name != "help" {
command.Commands = append(command.Commands, cmdHelp()) command.Subcommands = append(command.Subcommands, cmdHelp())
} }
for i := range command.Commands { for i := range command.Subcommands {
prepareSubcommandWithConfig(command.Commands[i], globalFlags) prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
} }
} }
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config // prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
// It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times // It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times
func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(_ context.Context, _ *cli.Command) error { func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error {
return func(ctx context.Context, cli *cli.Command) error { return func(ctx *cli.Context) error {
var args setting.ArgWorkPathAndCustomConf var args setting.ArgWorkPathAndCustomConf
// from children to parent, check the global flags // from children to parent, check the global flags
for _, curCtx := range cli.Lineage() { for _, curCtx := range ctx.Lineage() {
if curCtx.IsSet("work-path") && args.WorkPath == "" { if curCtx.IsSet("work-path") && args.WorkPath == "" {
args.WorkPath = curCtx.String("work-path") args.WorkPath = curCtx.String("work-path")
} }
@ -107,24 +107,24 @@ func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(_ context.Context,
} }
} }
setting.InitWorkPathAndCommonConfig(os.Getenv, args) setting.InitWorkPathAndCommonConfig(os.Getenv, args)
if cli.Bool("help") || action == nil { if ctx.Bool("help") || action == nil {
// the default behavior of "urfave/cli": "nil action" means "show help" // the default behavior of "urfave/cli": "nil action" means "show help"
return cmdHelp().Action(ctx, cli) return cmdHelp().Action(ctx)
} }
return action(ctx, cli) return action(ctx)
} }
} }
func NewMainApp(version, versionExtra string) *cli.Command { func NewMainApp(version, versionExtra string) *cli.App {
path, err := os.Executable() path, err := os.Executable()
if err != nil { if err != nil {
panic(err) panic(err)
} }
executable := filepath.Base(path) executable := filepath.Base(path)
subCmdsStandalone := make([]*cli.Command, 0, 10) var subCmdsStandalone []*cli.Command = make([]*cli.Command, 0, 10)
subCmdWithConfig := make([]*cli.Command, 0, 10) var subCmdWithConfig []*cli.Command = make([]*cli.Command, 0, 10)
globalFlags := func() []cli.Flag { return []cli.Flag{} } var globalFlags []cli.Flag = make([]cli.Flag, 0, 10)
// //
// If the executable is forgejo-cli, provide a Forgejo specific CLI // If the executable is forgejo-cli, provide a Forgejo specific CLI
@ -133,16 +133,14 @@ func NewMainApp(version, versionExtra string) *cli.Command {
if executable == "forgejo-cli" { if executable == "forgejo-cli" {
subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdActions(context.Background())) subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdActions(context.Background()))
subCmdWithConfig = append(subCmdWithConfig, forgejo.CmdF3(context.Background())) subCmdWithConfig = append(subCmdWithConfig, forgejo.CmdF3(context.Background()))
globalFlags = func() []cli.Flag { globalFlags = append(globalFlags, []cli.Flag{
return []cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{
Name: "quiet", Name: "quiet",
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "verbose", Name: "verbose",
}, },
} }...)
}
} else { } else {
// //
// Otherwise provide a Gitea compatible CLI which includes Forgejo // Otherwise provide a Gitea compatible CLI which includes Forgejo
@ -151,54 +149,55 @@ func NewMainApp(version, versionExtra string) *cli.Command {
// binary and rename it to forgejo if they want. // binary and rename it to forgejo if they want.
// //
subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdForgejo(context.Background())) subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdForgejo(context.Background()))
subCmdWithConfig = append(subCmdWithConfig, cmdActions()) subCmdWithConfig = append(subCmdWithConfig, CmdActions)
} }
return innerNewMainApp(version, versionExtra, subCmdsStandalone, subCmdWithConfig, globalFlags) return innerNewMainApp(version, versionExtra, subCmdsStandalone, subCmdWithConfig, globalFlags)
} }
func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmdWithConfigArgs []*cli.Command, globalFlagsArgs func() []cli.Flag) *cli.Command { func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmdWithConfigArgs []*cli.Command, globalFlagsArgs []cli.Flag) *cli.App {
app := &cli.Command{} app := cli.NewApp()
app.HelpName = "forgejo"
app.Name = "Forgejo" app.Name = "Forgejo"
app.Usage = "Beyond coding. We forge." app.Usage = "Beyond coding. We forge."
app.Description = `By default, forgejo will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".` app.Description = `By default, forgejo will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".`
app.Version = version + versionExtra app.Version = version + versionExtra
app.EnableShellCompletion = true app.EnableBashCompletion = true
// these sub-commands need to use config file // these sub-commands need to use config file
subCmdWithConfig := []*cli.Command{ subCmdWithConfig := []*cli.Command{
cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config" cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
cmdWeb(), CmdWeb,
cmdServ(), CmdServ,
cmdHook(), CmdHook,
cmdKeys(), CmdKeys,
cmdDump(), CmdDump,
cmdAdmin(), CmdAdmin,
cmdMigrate(), CmdMigrate,
cmdDoctor(), CmdDoctor,
cmdManager(), CmdManager,
cmdEmbedded(), CmdEmbedded,
cmdMigrateStorage(), CmdMigrateStorage,
cmdDumpRepository(), CmdDumpRepository,
cmdRestoreRepository(), CmdRestoreRepository,
} }
subCmdWithConfig = append(subCmdWithConfig, subCmdWithConfigArgs...) subCmdWithConfig = append(subCmdWithConfig, subCmdWithConfigArgs...)
// these sub-commands do not need the config file, and they do not depend on any path or environment variable. // these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []*cli.Command{ subCmdStandalone := []*cli.Command{
cmdCert(), CmdCert,
cmdGenerate(), CmdGenerate,
CmdDocs,
} }
subCmdStandalone = append(subCmdStandalone, subCmdsStandaloneArgs...) subCmdStandalone = append(subCmdStandalone, subCmdsStandaloneArgs...)
app.DefaultCommand = cmdWeb().Name app.DefaultCommand = CmdWeb.Name
globalFlags := func() []cli.Flag { globalFlags := appGlobalFlags()
return append(appGlobalFlags(), globalFlagsArgs()...) globalFlags = append(globalFlags, globalFlagsArgs...)
}
app.Flags = append(app.Flags, cli.VersionFlag) app.Flags = append(app.Flags, cli.VersionFlag)
app.Flags = append(app.Flags, globalFlags()...) app.Flags = append(app.Flags, globalFlags...)
app.HideHelp = true // use our own help action to show helps (with more information like default config) app.HideHelp = true // use our own help action to show helps (with more information like default config)
app.Before = PrepareConsoleLoggerLevel(log.INFO) app.Before = PrepareConsoleLoggerLevel(log.INFO)
for i := range subCmdWithConfig { for i := range subCmdWithConfig {
@ -207,12 +206,11 @@ func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmd
app.Commands = append(app.Commands, subCmdWithConfig...) app.Commands = append(app.Commands, subCmdWithConfig...)
app.Commands = append(app.Commands, subCmdStandalone...) app.Commands = append(app.Commands, subCmdStandalone...)
setting.InitGiteaEnvVars()
return app return app
} }
func RunMainApp(app *cli.Command, args ...string) error { func RunMainApp(app *cli.App, args ...string) error {
err := app.Run(context.Background(), args) err := app.Run(args)
if err == nil { if err == nil {
return nil return nil
} }
@ -221,7 +219,7 @@ func RunMainApp(app *cli.Command, args ...string) error {
cli.OsExiter(1) cli.OsExiter(1)
return err return err
} }
_, _ = fmt.Fprintf(app.Root().ErrWriter, "Command error: %v\n", err) _, _ = fmt.Fprintf(app.ErrWriter, "Command error: %v\n", err)
cli.OsExiter(1) cli.OsExiter(1)
return err return err
} }

View file

@ -4,21 +4,20 @@
package cmd package cmd
import ( import (
"context"
"errors"
"fmt" "fmt"
"io" "io"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"forgejo.org/models/unittest" "code.gitea.io/gitea/models/unittest"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"forgejo.org/modules/test" "code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -29,10 +28,10 @@ func makePathOutput(workPath, customPath, customConf string) string {
return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf) return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf)
} }
func newTestApp(testCmdAction func(_ context.Context, ctx *cli.Command) error) *cli.Command { func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App {
app := NewMainApp("version", "version-extra") app := NewMainApp("version", "version-extra")
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction} testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
prepareSubcommandWithConfig(testCmd, appGlobalFlags) prepareSubcommandWithConfig(testCmd, appGlobalFlags())
app.Commands = append(app.Commands, testCmd) app.Commands = append(app.Commands, testCmd)
app.DefaultCommand = testCmd.Name app.DefaultCommand = testCmd.Name
return app return app
@ -44,7 +43,7 @@ type runResult struct {
ExitCode int ExitCode int
} }
func runTestApp(app *cli.Command, args ...string) (runResult, error) { func runTestApp(app *cli.App, args ...string) (runResult, error) {
outBuf := new(strings.Builder) outBuf := new(strings.Builder)
errBuf := new(strings.Builder) errBuf := new(strings.Builder)
app.Writer = outBuf app.Writer = outBuf
@ -67,6 +66,7 @@ func TestCliCmd(t *testing.T) {
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini") defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
cli.CommandHelpTemplate = "(command help template)" cli.CommandHelpTemplate = "(command help template)"
cli.AppHelpTemplate = "(app help template)"
cli.SubcommandHelpTemplate = "(subcommand help template)" cli.SubcommandHelpTemplate = "(subcommand help template)"
cases := []struct { cases := []struct {
@ -110,55 +110,70 @@ func TestCliCmd(t *testing.T) {
}, },
} }
for _, c := range cases { app := newTestApp(func(ctx *cli.Context) error {
t.Run(c.cmd, func(t *testing.T) { _, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
defer test.MockProtect(&setting.AppWorkPath)()
defer test.MockProtect(&setting.CustomPath)()
defer test.MockProtect(&setting.CustomConf)()
app := newTestApp(func(_ context.Context, ctx *cli.Command) error {
_, _ = fmt.Fprint(ctx.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil return nil
}) })
var envBackup []string
for _, s := range os.Environ() {
if strings.HasPrefix(s, "GITEA_") && strings.Contains(s, "=") {
envBackup = append(envBackup, s)
}
}
clearGiteaEnv := func() {
for _, s := range os.Environ() {
if strings.HasPrefix(s, "GITEA_") {
_ = os.Unsetenv(s)
}
}
}
defer func() {
clearGiteaEnv()
for _, s := range envBackup {
k, v, _ := strings.Cut(s, "=")
_ = os.Setenv(k, v)
}
}()
for _, c := range cases {
clearGiteaEnv()
for k, v := range c.env { for k, v := range c.env {
t.Setenv(k, v) _ = os.Setenv(k, v)
} }
args := strings.Split(c.cmd, " ") // for test only, "split" is good enough args := strings.Split(c.cmd, " ") // for test only, "split" is good enough
r, err := runTestApp(app, args...) r, err := runTestApp(app, args...)
require.NoError(t, err, c.cmd) require.NoError(t, err, c.cmd)
assert.NotEmpty(t, c.exp, c.cmd) assert.NotEmpty(t, c.exp, c.cmd)
assert.Contains(t, r.Stdout, c.exp, c.cmd+"\n"+r.Stdout) assert.Contains(t, r.Stdout, c.exp, c.cmd)
})
} }
} }
func TestCliCmdError(t *testing.T) { func TestCliCmdError(t *testing.T) {
app := newTestApp(func(_ context.Context, ctx *cli.Command) error { return errors.New("normal error") }) app := newTestApp(func(ctx *cli.Context) error { return fmt.Errorf("normal error") })
r, err := runTestApp(app, "./gitea", "test-cmd") r, err := runTestApp(app, "./gitea", "test-cmd")
require.Error(t, err) require.Error(t, err)
assert.Equal(t, 1, r.ExitCode) assert.Equal(t, 1, r.ExitCode)
assert.Empty(t, r.Stdout) assert.Equal(t, "", r.Stdout)
assert.Equal(t, "Command error: normal error\n", r.Stderr) assert.Equal(t, "Command error: normal error\n", r.Stderr)
app = newTestApp(func(_ context.Context, ctx *cli.Command) error { return cli.Exit("exit error", 2) }) app = newTestApp(func(ctx *cli.Context) error { return cli.Exit("exit error", 2) })
r, err = runTestApp(app, "./gitea", "test-cmd") r, err = runTestApp(app, "./gitea", "test-cmd")
require.Error(t, err) require.Error(t, err)
assert.Equal(t, 2, r.ExitCode) assert.Equal(t, 2, r.ExitCode)
assert.Empty(t, r.Stdout) assert.Equal(t, "", r.Stdout)
assert.Equal(t, "exit error\n", r.Stderr) assert.Equal(t, "exit error\n", r.Stderr)
app = newTestApp(func(_ context.Context, ctx *cli.Command) error { return nil }) app = newTestApp(func(ctx *cli.Context) error { return nil })
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such") r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
require.Error(t, err) require.Error(t, err)
assert.Equal(t, 1, r.ExitCode) assert.Equal(t, 1, r.ExitCode)
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr) assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stdout)
assert.Empty(t, r.Stdout) assert.Equal(t, "", r.Stderr) // the cli package's strange behavior, the error message is not in stderr ....
app = newTestApp(func(_ context.Context, ctx *cli.Command) error { return nil }) app = newTestApp(func(ctx *cli.Context) error { return nil })
r, err = runTestApp(app, "./gitea", "test-cmd") r, err = runTestApp(app, "./gitea", "test-cmd")
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called
assert.Empty(t, r.Stdout) assert.Equal(t, "", r.Stdout)
assert.Empty(t, r.Stderr) assert.Equal(t, "", r.Stderr)
} }

View file

@ -4,34 +4,30 @@
package cmd package cmd
import ( import (
"context"
"os" "os"
"time" "time"
"forgejo.org/modules/private" "code.gitea.io/gitea/modules/private"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdManager represents the manager command var (
func cmdManager() *cli.Command { // CmdManager represents the manager command
return &cli.Command{ CmdManager = &cli.Command{
Name: "manager", Name: "manager",
Usage: "Manage the running forgejo process", Usage: "Manage the running forgejo process",
Description: "This is a command for managing the running forgejo process", Description: "This is a command for managing the running forgejo process",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdShutdown(), subcmdShutdown,
subcmdRestart(), subcmdRestart,
subcmdReloadTemplates(), subcmdReloadTemplates,
subcmdFlushQueues(), subcmdFlushQueues,
subcmdLogging(), subcmdLogging,
subCmdProcesses(), subCmdProcesses,
}, },
} }
} subcmdShutdown = &cli.Command{
func subcmdShutdown() *cli.Command {
return &cli.Command{
Name: "shutdown", Name: "shutdown",
Usage: "Gracefully shutdown the running process", Usage: "Gracefully shutdown the running process",
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -41,10 +37,7 @@ func subcmdShutdown() *cli.Command {
}, },
Action: runShutdown, Action: runShutdown,
} }
} subcmdRestart = &cli.Command{
func subcmdRestart() *cli.Command {
return &cli.Command{
Name: "restart", Name: "restart",
Usage: "Gracefully restart the running process - (not implemented for windows servers)", Usage: "Gracefully restart the running process - (not implemented for windows servers)",
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -54,10 +47,7 @@ func subcmdRestart() *cli.Command {
}, },
Action: runRestart, Action: runRestart,
} }
} subcmdReloadTemplates = &cli.Command{
func subcmdReloadTemplates() *cli.Command {
return &cli.Command{
Name: "reload-templates", Name: "reload-templates",
Usage: "Reload template files in the running process", Usage: "Reload template files in the running process",
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -67,10 +57,7 @@ func subcmdReloadTemplates() *cli.Command {
}, },
Action: runReloadTemplates, Action: runReloadTemplates,
} }
} subcmdFlushQueues = &cli.Command{
func subcmdFlushQueues() *cli.Command {
return &cli.Command{
Name: "flush-queues", Name: "flush-queues",
Usage: "Flush queues in the running process", Usage: "Flush queues in the running process",
Action: runFlushQueues, Action: runFlushQueues,
@ -89,10 +76,7 @@ func subcmdFlushQueues() *cli.Command {
}, },
}, },
} }
} subCmdProcesses = &cli.Command{
func subCmdProcesses() *cli.Command {
return &cli.Command{
Name: "processes", Name: "processes",
Usage: "Display running processes within the current process", Usage: "Display running processes within the current process",
Action: runProcesses, Action: runProcesses,
@ -122,49 +106,49 @@ func subCmdProcesses() *cli.Command {
}, },
}, },
} }
} )
func runShutdown(ctx context.Context, c *cli.Command) error { func runShutdown(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), false) setup(ctx, c.Bool("debug"))
extra := private.Shutdown(ctx) extra := private.Shutdown(ctx)
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runRestart(ctx context.Context, c *cli.Command) error { func runRestart(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), false) setup(ctx, c.Bool("debug"))
extra := private.Restart(ctx) extra := private.Restart(ctx)
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runReloadTemplates(ctx context.Context, c *cli.Command) error { func runReloadTemplates(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), false) setup(ctx, c.Bool("debug"))
extra := private.ReloadTemplates(ctx) extra := private.ReloadTemplates(ctx)
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runFlushQueues(ctx context.Context, c *cli.Command) error { func runFlushQueues(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), false) setup(ctx, c.Bool("debug"))
extra := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking")) extra := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking"))
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runProcesses(ctx context.Context, c *cli.Command) error { func runProcesses(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), false) setup(ctx, c.Bool("debug"))
extra := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel")) extra := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel"))
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }

View file

@ -4,19 +4,18 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/private" "code.gitea.io/gitea/modules/private"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func defaultLoggingFlags() []cli.Flag { var (
return []cli.Flag{ defaultLoggingFlags = []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "logger", Name: "logger",
Usage: `Logger name - will default to "default"`, Usage: `Logger name - will default to "default"`,
@ -57,13 +56,11 @@ func defaultLoggingFlags() []cli.Flag {
Name: "debug", Name: "debug",
}, },
} }
}
func subcmdLogging() *cli.Command { subcmdLogging = &cli.Command{
return &cli.Command{
Name: "logging", Name: "logging",
Usage: "Adjust logging commands", Usage: "Adjust logging commands",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "pause", Name: "pause",
Usage: "Pause logging (Forgejo will buffer logs up to a certain point and will drop them after that point)", Usage: "Pause logging (Forgejo will buffer logs up to a certain point and will drop them after that point)",
@ -107,11 +104,11 @@ func subcmdLogging() *cli.Command {
}, { }, {
Name: "add", Name: "add",
Usage: "Add a logger", Usage: "Add a logger",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "file", Name: "file",
Usage: "Add a file logger", Usage: "Add a file logger",
Flags: append(defaultLoggingFlags(), []cli.Flag{ Flags: append(defaultLoggingFlags, []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "filename", Name: "filename",
Aliases: []string{"f"}, Aliases: []string{"f"},
@ -155,7 +152,7 @@ func subcmdLogging() *cli.Command {
}, { }, {
Name: "conn", Name: "conn",
Usage: "Add a net conn logger", Usage: "Add a net conn logger",
Flags: append(defaultLoggingFlags(), []cli.Flag{ Flags: append(defaultLoggingFlags, []cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{
Name: "reconnect-on-message", Name: "reconnect-on-message",
Aliases: []string{"R"}, Aliases: []string{"R"},
@ -196,13 +193,13 @@ func subcmdLogging() *cli.Command {
}, },
}, },
} }
} )
func runRemoveLogger(ctx context.Context, c *cli.Command) error { func runRemoveLogger(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), false) setup(ctx, c.Bool("debug"))
logger := c.String("logger") logger := c.String("logger")
if len(logger) == 0 { if len(logger) == 0 {
logger = log.DEFAULT logger = log.DEFAULT
@ -213,11 +210,11 @@ func runRemoveLogger(ctx context.Context, c *cli.Command) error {
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runAddConnLogger(ctx context.Context, c *cli.Command) error { func runAddConnLogger(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), false) setup(ctx, c.Bool("debug"))
vals := map[string]any{} vals := map[string]any{}
mode := "conn" mode := "conn"
vals["net"] = "tcp" vals["net"] = "tcp"
@ -240,14 +237,14 @@ func runAddConnLogger(ctx context.Context, c *cli.Command) error {
if c.IsSet("reconnect-on-message") { if c.IsSet("reconnect-on-message") {
vals["reconnectOnMsg"] = c.Bool("reconnect-on-message") vals["reconnectOnMsg"] = c.Bool("reconnect-on-message")
} }
return commonAddLogger(ctx, c, mode, vals) return commonAddLogger(c, mode, vals)
} }
func runAddFileLogger(ctx context.Context, c *cli.Command) error { func runAddFileLogger(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), false) setup(ctx, c.Bool("debug"))
vals := map[string]any{} vals := map[string]any{}
mode := "file" mode := "file"
if c.IsSet("filename") { if c.IsSet("filename") {
@ -273,10 +270,10 @@ func runAddFileLogger(ctx context.Context, c *cli.Command) error {
if c.IsSet("compression-level") { if c.IsSet("compression-level") {
vals["compressionLevel"] = c.Int("compression-level") vals["compressionLevel"] = c.Int("compression-level")
} }
return commonAddLogger(ctx, c, mode, vals) return commonAddLogger(c, mode, vals)
} }
func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[string]any) error { func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error {
if len(c.String("level")) > 0 { if len(c.String("level")) > 0 {
vals["level"] = log.LevelFromString(c.String("level")).String() vals["level"] = log.LevelFromString(c.String("level")).String()
} }
@ -303,47 +300,47 @@ func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[
if c.IsSet("writer") { if c.IsSet("writer") {
writer = c.String("writer") writer = c.String("writer")
} }
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
extra := private.AddLogger(ctx, logger, writer, mode, vals) extra := private.AddLogger(ctx, logger, writer, mode, vals)
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runPauseLogging(ctx context.Context, c *cli.Command) error { func runPauseLogging(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), false) setup(ctx, c.Bool("debug"))
userMsg := private.PauseLogging(ctx) userMsg := private.PauseLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg) _, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil return nil
} }
func runResumeLogging(ctx context.Context, c *cli.Command) error { func runResumeLogging(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), false) setup(ctx, c.Bool("debug"))
userMsg := private.ResumeLogging(ctx) userMsg := private.ResumeLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg) _, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil return nil
} }
func runReleaseReopenLogging(ctx context.Context, c *cli.Command) error { func runReleaseReopenLogging(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), false) setup(ctx, c.Bool("debug"))
userMsg := private.ReleaseReopenLogging(ctx) userMsg := private.ReleaseReopenLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg) _, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil return nil
} }
func runSetLogSQL(ctx context.Context, c *cli.Command) error { func runSetLogSQL(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setup(ctx, c.Bool("debug"), false) setup(ctx, c.Bool("debug"))
extra := private.SetLogSQL(ctx, !c.Bool("off")) extra := private.SetLogSQL(ctx, !c.Bool("off"))
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)

View file

@ -6,26 +6,24 @@ package cmd
import ( import (
"context" "context"
"forgejo.org/models/db" "code.gitea.io/gitea/models/db"
"forgejo.org/models/migrations" "code.gitea.io/gitea/models/migrations"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdMigrate represents the available migrate sub-command. // CmdMigrate represents the available migrate sub-command.
func cmdMigrate() *cli.Command { var CmdMigrate = &cli.Command{
return &cli.Command{
Name: "migrate", Name: "migrate",
Usage: "Migrate the database", Usage: "Migrate the database",
Description: "This is a command for migrating the database, so that you can run 'forgejo admin user create' before starting the server.", Description: "This is a command for migrating the database, so that you can run gitea admin user create before starting the server.",
Action: runMigrate, Action: runMigrate,
}
} }
func runMigrate(stdCtx context.Context, ctx *cli.Command) error { func runMigrate(ctx *cli.Context) error {
stdCtx, cancel := installSignals(stdCtx) stdCtx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(stdCtx); err != nil { if err := initDB(stdCtx); err != nil {
@ -38,13 +36,7 @@ func runMigrate(stdCtx context.Context, ctx *cli.Command) error {
log.Info("Log path: %s", setting.Log.RootPath) log.Info("Log path: %s", setting.Log.RootPath)
log.Info("Configuration file: %s", setting.CustomConf) log.Info("Configuration file: %s", setting.CustomConf)
if err := db.InitEngineWithMigration(context.Background(), func(dbEngine db.Engine) error { if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
masterEngine, err := db.GetMasterEngine(dbEngine)
if err != nil {
return err
}
return migrations.Migrate(masterEngine)
}); err != nil {
log.Fatal("Failed to initialize ORM engine: %v", err) log.Fatal("Failed to initialize ORM engine: %v", err)
return err return err
} }

View file

@ -10,25 +10,23 @@ import (
"io/fs" "io/fs"
"strings" "strings"
actions_model "forgejo.org/models/actions" actions_model "code.gitea.io/gitea/models/actions"
"forgejo.org/models/db" "code.gitea.io/gitea/models/db"
git_model "forgejo.org/models/git" git_model "code.gitea.io/gitea/models/git"
"forgejo.org/models/migrations" "code.gitea.io/gitea/models/migrations"
packages_model "forgejo.org/models/packages" packages_model "code.gitea.io/gitea/models/packages"
repo_model "forgejo.org/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "forgejo.org/models/user" user_model "code.gitea.io/gitea/models/user"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "forgejo.org/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"forgejo.org/modules/storage" "code.gitea.io/gitea/modules/storage"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
"xorm.io/xorm"
) )
// CmdMigrateStorage represents the available migrate storage sub-command. // CmdMigrateStorage represents the available migrate storage sub-command.
func cmdMigrateStorage() *cli.Command { var CmdMigrateStorage = &cli.Command{
return &cli.Command{
Name: "migrate-storage", Name: "migrate-storage",
Usage: "Migrate the storage", Usage: "Migrate the storage",
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage", Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
@ -96,7 +94,6 @@ func cmdMigrateStorage() *cli.Command {
Usage: "Minio checksum algorithm (default/md5)", Usage: "Minio checksum algorithm (default/md5)",
}, },
}, },
}
} }
func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error { func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error {
@ -184,8 +181,8 @@ func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStora
}) })
} }
func runMigrateStorage(stdCtx context.Context, ctx *cli.Command) error { func runMigrateStorage(ctx *cli.Context) error {
stdCtx, cancel := installSignals(stdCtx) stdCtx, cancel := installSignals()
defer cancel() defer cancel()
if err := initDB(stdCtx); err != nil { if err := initDB(stdCtx); err != nil {
@ -198,9 +195,7 @@ func runMigrateStorage(stdCtx context.Context, ctx *cli.Command) error {
log.Info("Log path: %s", setting.Log.RootPath) log.Info("Log path: %s", setting.Log.RootPath)
log.Info("Configuration file: %s", setting.CustomConf) log.Info("Configuration file: %s", setting.CustomConf)
if err := db.InitEngineWithMigration(context.Background(), func(e db.Engine) error { if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
return migrations.Migrate(e.(*xorm.Engine))
}); err != nil {
log.Fatal("Failed to initialize ORM engine: %v", err) log.Fatal("Failed to initialize ORM engine: %v", err)
return err return err
} }

View file

@ -4,21 +4,22 @@
package cmd package cmd
import ( import (
"context"
"io" "io"
"os" "os"
"strings" "strings"
"testing" "testing"
"forgejo.org/models/actions" "code.gitea.io/gitea/models/actions"
"forgejo.org/models/db" "code.gitea.io/gitea/models/db"
"forgejo.org/models/packages" "code.gitea.io/gitea/models/packages"
"forgejo.org/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "forgejo.org/models/user" user_model "code.gitea.io/gitea/models/user"
packages_module "forgejo.org/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"forgejo.org/modules/storage" "code.gitea.io/gitea/modules/storage"
"forgejo.org/modules/test" "code.gitea.io/gitea/modules/test"
packages_service "forgejo.org/services/packages" packages_service "code.gitea.io/gitea/services/packages"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -30,7 +31,7 @@ func createLocalStorage(t *testing.T) (storage.ObjectStorage, string) {
p := t.TempDir() p := t.TempDir()
storage, err := storage.NewLocalStorage( storage, err := storage.NewLocalStorage(
t.Context(), context.Background(),
&setting.Storage{ &setting.Storage{
Path: p, Path: p,
}) })
@ -71,7 +72,7 @@ func TestMigratePackages(t *testing.T) {
assert.NotNil(t, v) assert.NotNil(t, v)
assert.NotNil(t, f) assert.NotNil(t, f)
ctx := t.Context() ctx := context.Background()
dstStorage, p := createLocalStorage(t) dstStorage, p := createLocalStorage(t)
@ -81,8 +82,8 @@ func TestMigratePackages(t *testing.T) {
entries, err := os.ReadDir(p) entries, err := os.ReadDir(p)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, entries, 2) assert.Len(t, entries, 2)
assert.Equal(t, "01", entries[0].Name()) assert.EqualValues(t, "01", entries[0].Name())
assert.Equal(t, "tmp", entries[1].Name()) assert.EqualValues(t, "tmp", entries[1].Name())
} }
func TestMigrateActionsArtifacts(t *testing.T) { func TestMigrateActionsArtifacts(t *testing.T) {
@ -90,7 +91,7 @@ func TestMigrateActionsArtifacts(t *testing.T) {
srcStorage, _ := createLocalStorage(t) srcStorage, _ := createLocalStorage(t)
defer test.MockVariableValue(&storage.ActionsArtifacts, srcStorage)() defer test.MockVariableValue(&storage.ActionsArtifacts, srcStorage)()
id := int64(42) id := int64(0)
addArtifact := func(storagePath string, status actions.ArtifactStatus) { addArtifact := func(storagePath string, status actions.ArtifactStatus) {
id++ id++

View file

@ -4,18 +4,16 @@
package cmd package cmd
import ( import (
"context"
"strings" "strings"
"forgejo.org/modules/private" "code.gitea.io/gitea/modules/private"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdRestoreRepository represents the available restore a repository sub-command. // CmdRestoreRepository represents the available restore a repository sub-command.
func cmdRestoreRepository() *cli.Command { var CmdRestoreRepository = &cli.Command{
return &cli.Command{
Name: "restore-repo", Name: "restore-repo",
Usage: "Restore the repository from disk", Usage: "Restore the repository from disk",
Description: "This is a command for restoring the repository data.", Description: "This is a command for restoring the repository data.",
@ -48,11 +46,10 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
Usage: "Sanity check the content of the files before trying to load them", Usage: "Sanity check the content of the files before trying to load them",
}, },
}, },
}
} }
func runRestoreRepository(ctx context.Context, c *cli.Command) error { func runRestoreRepository(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
setting.MustInstalled() setting.MustInstalled()

View file

@ -18,22 +18,22 @@ import (
"time" "time"
"unicode" "unicode"
asymkey_model "forgejo.org/models/asymkey" asymkey_model "code.gitea.io/gitea/models/asymkey"
git_model "forgejo.org/models/git" git_model "code.gitea.io/gitea/models/git"
"forgejo.org/models/perm" "code.gitea.io/gitea/models/perm"
"forgejo.org/modules/git" "code.gitea.io/gitea/modules/git"
"forgejo.org/modules/json" "code.gitea.io/gitea/modules/json"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/pprof" "code.gitea.io/gitea/modules/pprof"
"forgejo.org/modules/private" "code.gitea.io/gitea/modules/private"
"forgejo.org/modules/process" "code.gitea.io/gitea/modules/process"
repo_module "forgejo.org/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"forgejo.org/services/lfs" "code.gitea.io/gitea/services/lfs"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/kballard/go-shellquote" "github.com/kballard/go-shellquote"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
const ( const (
@ -41,8 +41,7 @@ const (
) )
// CmdServ represents the available serv sub-command. // CmdServ represents the available serv sub-command.
func cmdServ() *cli.Command { var CmdServ = &cli.Command{
return &cli.Command{
Name: "serv", Name: "serv",
Usage: "(internal) Should only be called by SSH shell", Usage: "(internal) Should only be called by SSH shell",
Description: "Serv provides access auth for repositories", Description: "Serv provides access auth for repositories",
@ -56,26 +55,22 @@ func cmdServ() *cli.Command {
Name: "debug", Name: "debug",
}, },
}, },
}
} }
func setup(ctx context.Context, debug, gitNeeded bool) { func setup(ctx context.Context, debug bool) {
if debug { if debug {
setupConsoleLogger(log.TRACE, false, os.Stderr) setupConsoleLogger(log.TRACE, false, os.Stderr)
} else { } else {
setupConsoleLogger(log.FATAL, false, os.Stderr) setupConsoleLogger(log.FATAL, false, os.Stderr)
} }
setting.MustInstalled() setting.MustInstalled()
// Sanity check to ensure path is not relative, see: https://github.com/go-gitea/gitea/pull/19317
if _, err := os.Stat(setting.RepoRootPath); err != nil { if _, err := os.Stat(setting.RepoRootPath); err != nil {
_ = fail(ctx, "Unable to access repository path", "Unable to access repository path %q, err: %v", setting.RepoRootPath, err) _ = fail(ctx, "Unable to access repository path", "Unable to access repository path %q, err: %v", setting.RepoRootPath, err)
return return
} }
if gitNeeded {
if err := git.InitSimple(context.Background()); err != nil { if err := git.InitSimple(context.Background()); err != nil {
_ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err) _ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err)
} }
}
} }
var ( var (
@ -133,12 +128,12 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
return nil return nil
} }
func runServ(ctx context.Context, c *cli.Command) error { func runServ(c *cli.Context) error {
ctx, cancel := installSignals(ctx) ctx, cancel := installSignals()
defer cancel() defer cancel()
// FIXME: This needs to internationalised // FIXME: This needs to internationalised
setup(ctx, c.Bool("debug"), true) setup(ctx, c.Bool("debug"))
if setting.SSH.Disabled { if setting.SSH.Disabled {
fmt.Println("Forgejo: SSH has been disabled") fmt.Println("Forgejo: SSH has been disabled")
@ -152,12 +147,6 @@ func runServ(ctx context.Context, c *cli.Command) error {
return nil return nil
} }
defer func() {
if err := recover(); err != nil {
_ = fail(ctx, "Internal Server Error", "Panic: %v\n%s", err, log.Stack(2))
}
}()
keys := strings.Split(c.Args().First(), "-") keys := strings.Split(c.Args().First(), "-")
if len(keys) != 2 || keys[0] != "key" { if len(keys) != 2 || keys[0] != "key" {
return fail(ctx, "Key ID format error", "Invalid key argument: %s", c.Args().First()) return fail(ctx, "Key ID format error", "Invalid key argument: %s", c.Args().First())
@ -196,7 +185,7 @@ func runServ(ctx context.Context, c *cli.Command) error {
if git.CheckGitVersionAtLeast("2.29") == nil { if git.CheckGitVersionAtLeast("2.29") == nil {
// for AGit Flow // for AGit Flow
if cmd == "ssh_info" { if cmd == "ssh_info" {
fmt.Print(`{"type":"agit","version":1}`) fmt.Print(`{"type":"gitea","version":1}`)
return nil return nil
} }
} }
@ -204,7 +193,10 @@ func runServ(ctx context.Context, c *cli.Command) error {
} }
verb := words[0] verb := words[0]
repoPath := strings.TrimPrefix(words[1], "/") repoPath := words[1]
if repoPath[0] == '/' {
repoPath = repoPath[1:]
}
var lfsVerb string var lfsVerb string
if verb == lfsAuthenticateVerb { if verb == lfsAuthenticateVerb {
@ -258,12 +250,11 @@ func runServ(ctx context.Context, c *cli.Command) error {
} }
if verb == lfsAuthenticateVerb { if verb == lfsAuthenticateVerb {
switch lfsVerb { if lfsVerb == "upload" {
case "upload":
requestedMode = perm.AccessModeWrite requestedMode = perm.AccessModeWrite
case "download": } else if lfsVerb == "download" {
requestedMode = perm.AccessModeRead requestedMode = perm.AccessModeRead
default: } else {
return fail(ctx, "Unknown LFS verb", "Unknown lfs verb %s", lfsVerb) return fail(ctx, "Unknown LFS verb", "Unknown lfs verb %s", lfsVerb)
} }
} }

View file

@ -12,29 +12,27 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"time"
_ "net/http/pprof" // Used for debugging if enabled and a web server is running _ "net/http/pprof" // Used for debugging if enabled and a web server is running
"forgejo.org/modules/container" "code.gitea.io/gitea/modules/container"
"forgejo.org/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/process" "code.gitea.io/gitea/modules/process"
"forgejo.org/modules/public" "code.gitea.io/gitea/modules/public"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"forgejo.org/routers" "code.gitea.io/gitea/routers"
"forgejo.org/routers/install" "code.gitea.io/gitea/routers/install"
"github.com/felixge/fgprof" "github.com/felixge/fgprof"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// PIDFile could be set from build tag // PIDFile could be set from build tag
var PIDFile = "/run/gitea.pid" var PIDFile = "/run/gitea.pid"
// CmdWeb represents the available web sub-command. // CmdWeb represents the available web sub-command.
func cmdWeb() *cli.Command { var CmdWeb = &cli.Command{
return &cli.Command{
Name: "web", Name: "web",
Usage: "Start the Forgejo web server", Usage: "Start the Forgejo web server",
Description: `The Forgejo web server is the only thing you need to run, Description: `The Forgejo web server is the only thing you need to run,
@ -69,7 +67,6 @@ and it takes care of all the other things for you`,
Usage: "Set initial logging to TRACE level until logging is properly set-up", Usage: "Set initial logging to TRACE level until logging is properly set-up",
}, },
}, },
}
} }
func runHTTPRedirector() { func runHTTPRedirector() {
@ -118,19 +115,9 @@ func showWebStartupMessage(msg string) {
log.Info("* CustomPath: %s", setting.CustomPath) log.Info("* CustomPath: %s", setting.CustomPath)
log.Info("* ConfigFile: %s", setting.CustomConf) log.Info("* ConfigFile: %s", setting.CustomConf)
log.Info("%s", msg) // show startup message log.Info("%s", msg) // show startup message
if setting.CORSConfig.Enabled {
log.Info("CORS Service Enabled")
}
if setting.DefaultUILocation != time.Local {
log.Info("Default UI Location is %v", setting.DefaultUILocation.String())
}
if setting.MailService != nil {
log.Info("Mail Service Enabled: RegisterEmailConfirm=%v, Service.EnableNotifyMail=%v", setting.Service.RegisterEmailConfirm, setting.Service.EnableNotifyMail)
}
} }
func serveInstall(_ context.Context, ctx *cli.Command) error { func serveInstall(ctx *cli.Context) error {
showWebStartupMessage("Prepare to run install page") showWebStartupMessage("Prepare to run install page")
routers.InitWebInstallPage(graceful.GetManager().HammerContext()) routers.InitWebInstallPage(graceful.GetManager().HammerContext())
@ -163,7 +150,7 @@ func serveInstall(_ context.Context, ctx *cli.Command) error {
return nil return nil
} }
func serveInstalled(_ context.Context, ctx *cli.Command) error { func serveInstalled(ctx *cli.Context) error {
setting.InitCfgProvider(setting.CustomConf) setting.InitCfgProvider(setting.CustomConf)
setting.LoadCommonSettings() setting.LoadCommonSettings()
setting.MustInstalled() setting.MustInstalled()
@ -197,9 +184,12 @@ func serveInstalled(_ context.Context, ctx *cli.Command) error {
publicFilesSet.Remove(".well-known") publicFilesSet.Remove(".well-known")
publicFilesSet.Remove("assets") publicFilesSet.Remove("assets")
publicFilesSet.Remove("robots.txt") publicFilesSet.Remove("robots.txt")
for fn := range publicFilesSet.Seq() { for _, fn := range publicFilesSet.Values() {
log.Error("Found legacy public asset %q in CustomPath. Please move it to %s/public/assets/%s", fn, setting.CustomPath, fn) log.Error("Found legacy public asset %q in CustomPath. Please move it to %s/public/assets/%s", fn, setting.CustomPath, fn)
} }
if _, err := os.Stat(filepath.Join(setting.CustomPath, "robots.txt")); err == nil {
log.Error(`Found legacy public asset "robots.txt" in CustomPath. Please move it to %s/public/robots.txt`, setting.CustomPath)
}
routers.InitWebInstalled(graceful.GetManager().HammerContext()) routers.InitWebInstalled(graceful.GetManager().HammerContext())
@ -235,7 +225,7 @@ func servePprof() {
finished() finished()
} }
func runWeb(ctx context.Context, cli *cli.Command) error { func runWeb(ctx *cli.Context) error {
defer func() { defer func() {
if panicked := recover(); panicked != nil { if panicked := recover(); panicked != nil {
log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2)) log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2))
@ -253,12 +243,12 @@ func runWeb(ctx context.Context, cli *cli.Command) error {
} }
// Set pid file setting // Set pid file setting
if cli.IsSet("pid") { if ctx.IsSet("pid") {
createPIDFile(cli.String("pid")) createPIDFile(ctx.String("pid"))
} }
if !setting.InstallLock { if !setting.InstallLock {
if err := serveInstall(ctx, cli); err != nil { if err := serveInstall(ctx); err != nil {
return err return err
} }
} else { } else {
@ -269,7 +259,7 @@ func runWeb(ctx context.Context, cli *cli.Command) error {
go servePprof() go servePprof()
} }
return serveInstalled(ctx, cli) return serveInstalled(ctx)
} }
func setPort(port string) error { func setPort(port string) error {

View file

@ -12,10 +12,10 @@ import (
"strconv" "strconv"
"strings" "strings"
"forgejo.org/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/process" "code.gitea.io/gitea/modules/process"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
) )
@ -54,8 +54,8 @@ func runACME(listenAddr string, m http.Handler) error {
altTLSALPNPort = p altTLSALPNPort = p
} }
certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory} magic := certmagic.NewDefault()
magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
// Try to use private CA root if provided, otherwise defaults to system's trust // Try to use private CA root if provided, otherwise defaults to system's trust
var certPool *x509.CertPool var certPool *x509.CertPool
if setting.AcmeCARoot != "" { if setting.AcmeCARoot != "" {
@ -65,8 +65,7 @@ func runACME(listenAddr string, m http.Handler) error {
log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err) log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
} }
} }
myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
certmagic.DefaultACME = certmagic.ACMEIssuer{
CA: setting.AcmeURL, CA: setting.AcmeURL,
TrustedRoots: certPool, TrustedRoots: certPool,
Email: setting.AcmeEmail, Email: setting.AcmeEmail,
@ -76,11 +75,7 @@ func runACME(listenAddr string, m http.Handler) error {
ListenHost: setting.HTTPAddr, ListenHost: setting.HTTPAddr,
AltTLSALPNPort: altTLSALPNPort, AltTLSALPNPort: altTLSALPNPort,
AltHTTPPort: altHTTPPort, AltHTTPPort: altHTTPPort,
} })
magic := certmagic.NewDefault()
myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
magic.Issuers = []certmagic.Issuer{myACME} magic.Issuers = []certmagic.Issuer{myACME}

View file

@ -9,9 +9,9 @@ import (
"net/http/fcgi" "net/http/fcgi"
"strings" "strings"
"forgejo.org/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
func runHTTP(network, listenAddr, name string, m http.Handler, useProxyProtocol bool) error { func runHTTP(network, listenAddr, name string, m http.Handler, useProxyProtocol bool) error {

View file

@ -9,9 +9,9 @@ import (
"os" "os"
"strings" "strings"
"forgejo.org/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"forgejo.org/modules/log" "code.gitea.io/gitea/modules/log"
"forgejo.org/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/klauspost/cpuid/v2" "github.com/klauspost/cpuid/v2"
) )

Some files were not shown because too many files have changed in this diff Show more