mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-03 08:02:36 +09:00
Compare commits
2 Commits
v1.20.0-de
...
v1.13.0-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89b1b662b3 | ||
|
|
cf86abaf3c |
9
.air.conf
Normal file
9
.air.conf
Normal file
@@ -0,0 +1,9 @@
|
||||
root = "."
|
||||
tmp_dir = ".air"
|
||||
|
||||
[build]
|
||||
cmd = "make backend"
|
||||
bin = "gitea"
|
||||
include_ext = ["go", "tmpl"]
|
||||
exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata"]
|
||||
include_dir = ["cmd", "models", "modules", "options", "routers", "services", "templates"]
|
||||
10
.air.toml
10
.air.toml
@@ -1,10 +0,0 @@
|
||||
root = "."
|
||||
tmp_dir = ".air"
|
||||
|
||||
[build]
|
||||
cmd = "make backend"
|
||||
bin = "gitea"
|
||||
include_ext = ["go", "tmpl"]
|
||||
exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata", "models/fixtures", "models/migrations/fixtures", "modules/migration/file_format_testdata", "modules/avatar/identicon/testdata"]
|
||||
include_dir = ["cmd", "models", "modules", "options", "routers", "services"]
|
||||
exclude_regex = ["_test.go$", "_gen.go$"]
|
||||
@@ -14,28 +14,28 @@ groups:
|
||||
name: BREAKING
|
||||
labels:
|
||||
- kind/breaking
|
||||
-
|
||||
name: SECURITY
|
||||
labels:
|
||||
- kind/security
|
||||
-
|
||||
name: FEATURES
|
||||
labels:
|
||||
- kind/feature
|
||||
-
|
||||
name: SECURITY
|
||||
labels:
|
||||
- kind/security
|
||||
-
|
||||
name: API
|
||||
labels:
|
||||
- kind/api
|
||||
-
|
||||
name: BUGFIXES
|
||||
labels:
|
||||
- kind/bug
|
||||
-
|
||||
name: ENHANCEMENTS
|
||||
labels:
|
||||
- kind/enhancement
|
||||
- kind/refactor
|
||||
- kind/ui
|
||||
-
|
||||
name: BUGFIXES
|
||||
labels:
|
||||
- kind/bug
|
||||
-
|
||||
name: TESTING
|
||||
labels:
|
||||
|
||||
114
.dockerignore
114
.dockerignore
@@ -1,114 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# IntelliJ
|
||||
.idea
|
||||
# Goland's output filename can not be set manually
|
||||
/go_build_*
|
||||
|
||||
# MS VSCode
|
||||
.vscode
|
||||
__debug_bin
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
*coverage.out
|
||||
coverage.all
|
||||
cpu.out
|
||||
|
||||
/modules/migration/bindata.go
|
||||
/modules/migration/bindata.go.hash
|
||||
/modules/options/bindata.go
|
||||
/modules/options/bindata.go.hash
|
||||
/modules/public/bindata.go
|
||||
/modules/public/bindata.go.hash
|
||||
/modules/templates/bindata.go
|
||||
/modules/templates/bindata.go.hash
|
||||
|
||||
*.db
|
||||
*.log
|
||||
|
||||
/gitea
|
||||
/gitea-vet
|
||||
/debug
|
||||
/integrations.test
|
||||
|
||||
/bin
|
||||
/dist
|
||||
/custom/*
|
||||
!/custom/conf
|
||||
/custom/conf/*
|
||||
!/custom/conf/app.example.ini
|
||||
/data
|
||||
/indexers
|
||||
/log
|
||||
/public/img/avatar
|
||||
/tests/integration/gitea-integration-*
|
||||
/tests/integration/indexers-*
|
||||
/tests/e2e/gitea-e2e-*
|
||||
/tests/e2e/indexers-*
|
||||
/tests/e2e/reports
|
||||
/tests/e2e/test-artifacts
|
||||
/tests/e2e/test-snapshots
|
||||
/tests/*.ini
|
||||
/node_modules
|
||||
/yarn.lock
|
||||
/yarn-error.log
|
||||
/npm-debug.log*
|
||||
/public/js
|
||||
/public/serviceworker.js
|
||||
/public/css
|
||||
/public/fonts
|
||||
/public/img/webpack
|
||||
/vendor
|
||||
/web_src/fomantic/node_modules
|
||||
/web_src/fomantic/build/*
|
||||
!/web_src/fomantic/build/semantic.js
|
||||
!/web_src/fomantic/build/semantic.css
|
||||
!/web_src/fomantic/build/themes
|
||||
/web_src/fomantic/build/themes/*
|
||||
!/web_src/fomantic/build/themes/default
|
||||
/web_src/fomantic/build/themes/default/assets/*
|
||||
!/web_src/fomantic/build/themes/default/assets/fonts
|
||||
/web_src/fomantic/build/themes/default/assets/fonts/*
|
||||
!/web_src/fomantic/build/themes/default/assets/fonts/icons.woff2
|
||||
!/web_src/fomantic/build/themes/default/assets/fonts/outline-icons.woff2
|
||||
/VERSION
|
||||
/.air
|
||||
/.go-licenses
|
||||
|
||||
# Snapcraft
|
||||
snap/.snapcraft/
|
||||
parts/
|
||||
stage/
|
||||
prime/
|
||||
*.snap
|
||||
*.snap-build
|
||||
*_source.tar.bz2
|
||||
.DS_Store
|
||||
|
||||
# Make evidence files
|
||||
/.make_evidence
|
||||
|
||||
# Manpage
|
||||
/man
|
||||
1310
.drone.yml
1310
.drone.yml
File diff suppressed because it is too large
Load Diff
@@ -12,17 +12,11 @@ insert_final_newline = true
|
||||
[*.{go,tmpl,html}]
|
||||
indent_style = tab
|
||||
|
||||
[templates/custom/*.tmpl]
|
||||
insert_final_newline = false
|
||||
|
||||
[templates/swagger/v1_json.tmpl]
|
||||
indent_style = space
|
||||
|
||||
[templates/user/auth/oidc_wellknown.tmpl]
|
||||
indent_style = space
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[*.svg]
|
||||
insert_final_newline = false
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
389
.eslintrc
Normal file
389
.eslintrc
Normal file
@@ -0,0 +1,389 @@
|
||||
root: true
|
||||
reportUnusedDisableDirectives: true
|
||||
|
||||
ignorePatterns:
|
||||
- /web_src/js/vendor
|
||||
|
||||
parserOptions:
|
||||
sourceType: module
|
||||
ecmaVersion: 2020
|
||||
|
||||
plugins:
|
||||
- eslint-plugin-unicorn
|
||||
- eslint-plugin-import
|
||||
|
||||
env:
|
||||
es2021: true
|
||||
node: true
|
||||
|
||||
globals:
|
||||
__webpack_public_path__: true
|
||||
CodeMirror: false
|
||||
Dropzone: false
|
||||
SimpleMDE: false
|
||||
u2fApi: false
|
||||
|
||||
overrides:
|
||||
- files: ["web_src/**/*.js"]
|
||||
env:
|
||||
browser: true
|
||||
jquery: true
|
||||
node: false
|
||||
- files: ["web_src/**/*worker.js"]
|
||||
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, 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, status, statusbar, stop, toolbar, top]
|
||||
- files: ["build/generate-images.js"]
|
||||
rules:
|
||||
import/no-unresolved: [0]
|
||||
import/no-extraneous-dependencies: [0]
|
||||
|
||||
rules:
|
||||
accessor-pairs: [2]
|
||||
array-bracket-newline: [0]
|
||||
array-bracket-spacing: [2, never]
|
||||
array-callback-return: [0]
|
||||
array-element-newline: [0]
|
||||
arrow-body-style: [0]
|
||||
arrow-parens: [2, always]
|
||||
arrow-spacing: [2, {before: true, after: true}]
|
||||
block-scoped-var: [2]
|
||||
brace-style: [2, 1tbs, {allowSingleLine: true}]
|
||||
camelcase: [0]
|
||||
capitalized-comments: [0]
|
||||
class-methods-use-this: [0]
|
||||
comma-dangle: [2, only-multiline]
|
||||
comma-spacing: [2, {before: false, after: true}]
|
||||
comma-style: [2, last]
|
||||
complexity: [0]
|
||||
computed-property-spacing: [2, never]
|
||||
consistent-return: [0]
|
||||
consistent-this: [0]
|
||||
constructor-super: [2]
|
||||
curly: [0]
|
||||
default-case-last: [2]
|
||||
default-case: [0]
|
||||
default-param-last: [0]
|
||||
dot-location: [2, property]
|
||||
dot-notation: [0]
|
||||
eol-last: [2]
|
||||
eqeqeq: [2]
|
||||
for-direction: [2]
|
||||
func-call-spacing: [2, never]
|
||||
func-name-matching: [2]
|
||||
func-names: [0]
|
||||
func-style: [0]
|
||||
function-call-argument-newline: [0]
|
||||
function-paren-newline: [0]
|
||||
generator-star-spacing: [0]
|
||||
getter-return: [2]
|
||||
grouped-accessor-pairs: [2]
|
||||
guard-for-in: [0]
|
||||
id-blacklist: [0]
|
||||
id-length: [0]
|
||||
id-match: [0]
|
||||
implicit-arrow-linebreak: [0]
|
||||
import/default: [0]
|
||||
import/dynamic-import-chunkname: [0]
|
||||
import/export: [2]
|
||||
import/exports-last: [0]
|
||||
import/extensions: [2, always, {ignorePackages: true}]
|
||||
import/first: [2]
|
||||
import/group-exports: [0]
|
||||
import/max-dependencies: [0]
|
||||
import/named: [2]
|
||||
import/namespace: [0]
|
||||
import/newline-after-import: [0]
|
||||
import/no-absolute-path: [0]
|
||||
import/no-amd: [0]
|
||||
import/no-anonymous-default-export: [0]
|
||||
import/no-commonjs: [0]
|
||||
import/no-cycle: [0]
|
||||
import/no-default-export: [0]
|
||||
import/no-deprecated: [0]
|
||||
import/no-dynamic-require: [0]
|
||||
import/no-extraneous-dependencies: [2]
|
||||
import/no-internal-modules: [0]
|
||||
import/no-mutable-exports: [2]
|
||||
import/no-named-as-default-member: [0]
|
||||
import/no-named-as-default: [2]
|
||||
import/no-named-default: [0]
|
||||
import/no-named-export: [0]
|
||||
import/no-namespace: [0]
|
||||
import/no-nodejs-modules: [0]
|
||||
import/no-relative-parent-imports: [0]
|
||||
import/no-restricted-paths: [0]
|
||||
import/no-self-import: [2]
|
||||
import/no-unassigned-import: [0]
|
||||
import/no-unresolved: [2, {commonjs: true}]
|
||||
import/no-unused-modules: [0]
|
||||
import/no-useless-path-segments: [2, {commonjs: true}]
|
||||
import/no-webpack-loader-syntax: [2]
|
||||
import/order: [0]
|
||||
import/prefer-default-export: [0]
|
||||
import/unambiguous: [0]
|
||||
indent: [2, 2, {SwitchCase: 1}]
|
||||
init-declarations: [0]
|
||||
key-spacing: [2]
|
||||
keyword-spacing: [2]
|
||||
line-comment-position: [0]
|
||||
linebreak-style: [2, unix]
|
||||
lines-around-comment: [0]
|
||||
lines-between-class-members: [0]
|
||||
max-classes-per-file: [0]
|
||||
max-depth: [0]
|
||||
max-len: [0]
|
||||
max-lines-per-function: [0]
|
||||
max-lines: [0]
|
||||
max-nested-callbacks: [0]
|
||||
max-params: [0]
|
||||
max-statements-per-line: [0]
|
||||
max-statements: [0]
|
||||
multiline-comment-style: [2, separate-lines]
|
||||
multiline-ternary: [0]
|
||||
new-cap: [0]
|
||||
new-parens: [2]
|
||||
newline-per-chained-call: [0]
|
||||
no-alert: [0]
|
||||
no-array-constructor: [2]
|
||||
no-async-promise-executor: [2]
|
||||
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-confusing-arrow: [0]
|
||||
no-console: [1, {allow: [info, warn, error]}]
|
||||
no-const-assign: [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: [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-extra-parens: [0]
|
||||
no-extra-semi: [2]
|
||||
no-fallthrough: [2]
|
||||
no-floating-decimal: [0]
|
||||
no-func-assign: [2]
|
||||
no-global-assign: [2]
|
||||
no-implicit-coercion: [0]
|
||||
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-label-var: [2]
|
||||
no-labels: [2]
|
||||
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-mixed-operators: [0]
|
||||
no-mixed-spaces-and-tabs: [2]
|
||||
no-multi-assign: [0]
|
||||
no-multi-spaces: [2, {ignoreEOLComments: true, exceptions: {Property: true, VariableDeclarator: true}}]
|
||||
no-multi-str: [2]
|
||||
no-negated-condition: [0]
|
||||
no-nested-ternary: [0]
|
||||
no-new-func: [2]
|
||||
no-new-object: [2]
|
||||
no-new-symbol: [2]
|
||||
no-new-wrappers: [2]
|
||||
no-new: [0]
|
||||
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]
|
||||
no-restricted-imports: [0]
|
||||
no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement]
|
||||
no-return-assign: [0]
|
||||
no-return-await: [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-tabs: [2]
|
||||
no-template-curly-in-string: [2]
|
||||
no-ternary: [0]
|
||||
no-this-before-super: [2]
|
||||
no-throw-literal: [2]
|
||||
no-trailing-spaces: [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: [0]
|
||||
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-vars: [2, {args: all, argsIgnorePattern: ^_, varsIgnorePattern: ^_, caughtErrorsIgnorePattern: ^_, ignoreRestSiblings: false}]
|
||||
no-use-before-define: [2, nofunc]
|
||||
no-useless-backreference: [0]
|
||||
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-whitespace-before-property: [2]
|
||||
no-with: [2]
|
||||
nonblock-statement-body-position: [2]
|
||||
object-curly-newline: [0]
|
||||
object-curly-spacing: [2, never]
|
||||
object-shorthand: [2, always]
|
||||
one-var-declaration-per-line: [0]
|
||||
one-var: [0]
|
||||
operator-assignment: [2, always]
|
||||
operator-linebreak: [2, after]
|
||||
padded-blocks: [2, never]
|
||||
padding-line-between-statements: [0]
|
||||
prefer-arrow-callback: [2, {allowNamedFunctions: true, allowUnboundThis: true}]
|
||||
prefer-const: [2, {destructuring: all}]
|
||||
prefer-destructuring: [0]
|
||||
prefer-exponentiation-operator: [2]
|
||||
prefer-named-capture-group: [0]
|
||||
prefer-numeric-literals: [2]
|
||||
prefer-object-spread: [0]
|
||||
prefer-promise-reject-errors: [2, {allowEmptyReject: false}]
|
||||
prefer-regex-literals: [2]
|
||||
prefer-rest-params: [2]
|
||||
prefer-spread: [2]
|
||||
prefer-template: [2]
|
||||
quote-props: [0]
|
||||
quotes: [2, single, {avoidEscape: true, allowTemplateLiterals: true}]
|
||||
radix: [2, as-needed]
|
||||
require-atomic-updates: [0]
|
||||
require-await: [0]
|
||||
require-unicode-regexp: [0]
|
||||
require-yield: [2]
|
||||
rest-spread-spacing: [2, never]
|
||||
semi-spacing: [2, {before: false, after: true}]
|
||||
semi-style: [2, last]
|
||||
semi: [2, always, {omitLastInOneLineBlock: true}]
|
||||
sort-imports: [0]
|
||||
sort-keys: [0]
|
||||
sort-vars: [0]
|
||||
space-before-blocks: [2, always]
|
||||
space-in-parens: [2, never]
|
||||
space-infix-ops: [2]
|
||||
space-unary-ops: [2]
|
||||
spaced-comment: [2, always]
|
||||
strict: [0]
|
||||
switch-colon-spacing: [2]
|
||||
symbol-description: [2]
|
||||
template-curly-spacing: [2, never]
|
||||
template-tag-spacing: [2, never]
|
||||
unicode-bom: [2, never]
|
||||
unicorn/better-regex: [0]
|
||||
unicorn/catch-error-name: [0]
|
||||
unicorn/consistent-function-scoping: [2]
|
||||
unicorn/custom-error-definition: [0]
|
||||
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/new-for-builtins: [2]
|
||||
unicorn/no-abusive-eslint-disable: [0]
|
||||
unicorn/no-array-instanceof: [0]
|
||||
unicorn/no-console-spaces: [0]
|
||||
unicorn/no-fn-reference-in-iterator: [0]
|
||||
unicorn/no-for-loop: [0]
|
||||
unicorn/no-hex-escape: [0]
|
||||
unicorn/no-keyword-prefix: [0]
|
||||
unicorn/no-nested-ternary: [0]
|
||||
unicorn/no-new-buffer: [0]
|
||||
unicorn/no-null: [0]
|
||||
unicorn/no-object-as-default-parameter: [2]
|
||||
unicorn/no-process-exit: [0]
|
||||
unicorn/no-reduce: [2]
|
||||
unicorn/no-unreadable-array-destructuring: [0]
|
||||
unicorn/no-unsafe-regex: [0]
|
||||
unicorn/no-unused-properties: [2]
|
||||
unicorn/no-useless-undefined: [0]
|
||||
unicorn/no-zero-fractions: [2]
|
||||
unicorn/number-literal-case: [0]
|
||||
unicorn/prefer-add-event-listener: [2]
|
||||
unicorn/prefer-array-find: [2]
|
||||
unicorn/prefer-dataset: [2]
|
||||
unicorn/prefer-event-key: [2]
|
||||
unicorn/prefer-includes: [2]
|
||||
unicorn/prefer-modern-dom-apis: [0]
|
||||
unicorn/prefer-negative-index: [2]
|
||||
unicorn/prefer-node-append: [0]
|
||||
unicorn/prefer-node-remove: [0]
|
||||
unicorn/prefer-number-properties: [0]
|
||||
unicorn/prefer-optional-catch-binding: [2]
|
||||
unicorn/prefer-query-selector: [0]
|
||||
unicorn/prefer-reflect-apply: [0]
|
||||
unicorn/prefer-replace-all: [0]
|
||||
unicorn/prefer-set-has: [0]
|
||||
unicorn/prefer-spread: [0]
|
||||
unicorn/prefer-starts-ends-with: [2]
|
||||
unicorn/prefer-string-slice: [0]
|
||||
unicorn/prefer-text-content: [2]
|
||||
unicorn/prefer-trim-start-end: [2]
|
||||
unicorn/prefer-type-error: [0]
|
||||
unicorn/prevent-abbreviations: [0]
|
||||
unicorn/string-content: [0]
|
||||
unicorn/throw-new-error: [2]
|
||||
use-isnan: [2]
|
||||
valid-typeof: [2, {requireStringLiterals: true}]
|
||||
vars-on-top: [0]
|
||||
wrap-iife: [2, inside]
|
||||
wrap-regex: [0]
|
||||
yield-star-spacing: [2, after]
|
||||
yoda: [2, never]
|
||||
538
.eslintrc.yaml
538
.eslintrc.yaml
@@ -1,538 +0,0 @@
|
||||
root: true
|
||||
reportUnusedDisableDirectives: true
|
||||
|
||||
ignorePatterns:
|
||||
- /web_src/js/vendor
|
||||
|
||||
parserOptions:
|
||||
sourceType: module
|
||||
ecmaVersion: latest
|
||||
|
||||
plugins:
|
||||
- eslint-plugin-unicorn
|
||||
- eslint-plugin-import
|
||||
- eslint-plugin-jquery
|
||||
- eslint-plugin-sonarjs
|
||||
|
||||
env:
|
||||
es2022: true
|
||||
node: true
|
||||
|
||||
globals:
|
||||
__webpack_public_path__: true
|
||||
|
||||
overrides:
|
||||
- files: ["web_src/**/*.js", "docs/**/*.js"]
|
||||
env:
|
||||
browser: true
|
||||
node: false
|
||||
- files: ["web_src/**/*worker.js"]
|
||||
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: ["build/generate-images.js"]
|
||||
rules:
|
||||
import/no-unresolved: [0]
|
||||
import/no-extraneous-dependencies: [0]
|
||||
- files: ["*.config.js"]
|
||||
rules:
|
||||
import/no-unused-modules: [0]
|
||||
|
||||
rules:
|
||||
accessor-pairs: [2]
|
||||
array-bracket-newline: [0]
|
||||
array-bracket-spacing: [2, never]
|
||||
array-callback-return: [2, {checkForEach: true}]
|
||||
array-element-newline: [0]
|
||||
arrow-body-style: [0]
|
||||
arrow-parens: [2, always]
|
||||
arrow-spacing: [2, {before: true, after: true}]
|
||||
block-scoped-var: [2]
|
||||
brace-style: [2, 1tbs, {allowSingleLine: true}]
|
||||
camelcase: [0]
|
||||
capitalized-comments: [0]
|
||||
class-methods-use-this: [0]
|
||||
comma-dangle: [2, only-multiline]
|
||||
comma-spacing: [2, {before: false, after: true}]
|
||||
comma-style: [2, last]
|
||||
complexity: [0]
|
||||
computed-property-spacing: [2, never]
|
||||
consistent-return: [0]
|
||||
consistent-this: [0]
|
||||
constructor-super: [2]
|
||||
curly: [0]
|
||||
default-case-last: [2]
|
||||
default-case: [0]
|
||||
default-param-last: [0]
|
||||
dot-location: [2, property]
|
||||
dot-notation: [0]
|
||||
eol-last: [2]
|
||||
eqeqeq: [2]
|
||||
for-direction: [2]
|
||||
func-call-spacing: [2, never]
|
||||
func-name-matching: [2]
|
||||
func-names: [0]
|
||||
func-style: [0]
|
||||
function-call-argument-newline: [0]
|
||||
function-paren-newline: [0]
|
||||
generator-star-spacing: [0]
|
||||
getter-return: [2]
|
||||
grouped-accessor-pairs: [2]
|
||||
guard-for-in: [0]
|
||||
id-blacklist: [0]
|
||||
id-length: [0]
|
||||
id-match: [0]
|
||||
implicit-arrow-linebreak: [0]
|
||||
import/consistent-type-specifier-style: [0]
|
||||
import/default: [0]
|
||||
import/dynamic-import-chunkname: [0]
|
||||
import/export: [2]
|
||||
import/exports-last: [0]
|
||||
import/extensions: [2, always, {ignorePackages: true}]
|
||||
import/first: [2]
|
||||
import/group-exports: [0]
|
||||
import/max-dependencies: [0]
|
||||
import/named: [2]
|
||||
import/namespace: [0]
|
||||
import/newline-after-import: [0]
|
||||
import/no-absolute-path: [0]
|
||||
import/no-amd: [0]
|
||||
import/no-anonymous-default-export: [0]
|
||||
import/no-commonjs: [0]
|
||||
import/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}]
|
||||
import/no-default-export: [0]
|
||||
import/no-deprecated: [0]
|
||||
import/no-dynamic-require: [0]
|
||||
import/no-empty-named-blocks: [2]
|
||||
import/no-extraneous-dependencies: [2]
|
||||
import/no-import-module-exports: [0]
|
||||
import/no-internal-modules: [0]
|
||||
import/no-mutable-exports: [0]
|
||||
import/no-named-as-default-member: [0]
|
||||
import/no-named-as-default: [2]
|
||||
import/no-named-default: [0]
|
||||
import/no-named-export: [0]
|
||||
import/no-namespace: [0]
|
||||
import/no-nodejs-modules: [0]
|
||||
import/no-relative-packages: [0]
|
||||
import/no-relative-parent-imports: [0]
|
||||
import/no-restricted-paths: [0]
|
||||
import/no-self-import: [2]
|
||||
import/no-unassigned-import: [0]
|
||||
import/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$"]}]
|
||||
import/no-unused-modules: [2, {unusedExports: true}]
|
||||
import/no-useless-path-segments: [2, {commonjs: true}]
|
||||
import/no-webpack-loader-syntax: [2]
|
||||
import/order: [0]
|
||||
import/prefer-default-export: [0]
|
||||
import/unambiguous: [0]
|
||||
indent: [2, 2, {SwitchCase: 1}]
|
||||
init-declarations: [0]
|
||||
jquery/no-ajax-events: [2]
|
||||
jquery/no-ajax: [0]
|
||||
jquery/no-animate: [2]
|
||||
jquery/no-attr: [0]
|
||||
jquery/no-bind: [2]
|
||||
jquery/no-class: [0]
|
||||
jquery/no-clone: [2]
|
||||
jquery/no-closest: [0]
|
||||
jquery/no-css: [0]
|
||||
jquery/no-data: [0]
|
||||
jquery/no-deferred: [2]
|
||||
jquery/no-delegate: [2]
|
||||
jquery/no-each: [0]
|
||||
jquery/no-extend: [2]
|
||||
jquery/no-fade: [0]
|
||||
jquery/no-filter: [0]
|
||||
jquery/no-find: [0]
|
||||
jquery/no-global-eval: [2]
|
||||
jquery/no-grep: [2]
|
||||
jquery/no-has: [2]
|
||||
jquery/no-hide: [2]
|
||||
jquery/no-html: [0]
|
||||
jquery/no-in-array: [2]
|
||||
jquery/no-is-array: [2]
|
||||
jquery/no-is-function: [2]
|
||||
jquery/no-is: [0]
|
||||
jquery/no-load: [2]
|
||||
jquery/no-map: [0]
|
||||
jquery/no-merge: [2]
|
||||
jquery/no-param: [2]
|
||||
jquery/no-parent: [0]
|
||||
jquery/no-parents: [0]
|
||||
jquery/no-parse-html: [2]
|
||||
jquery/no-prop: [0]
|
||||
jquery/no-proxy: [2]
|
||||
jquery/no-ready: [0]
|
||||
jquery/no-serialize: [2]
|
||||
jquery/no-show: [2]
|
||||
jquery/no-size: [2]
|
||||
jquery/no-sizzle: [0]
|
||||
jquery/no-slide: [0]
|
||||
jquery/no-submit: [0]
|
||||
jquery/no-text: [0]
|
||||
jquery/no-toggle: [2]
|
||||
jquery/no-trigger: [0]
|
||||
jquery/no-trim: [2]
|
||||
jquery/no-val: [0]
|
||||
jquery/no-when: [2]
|
||||
jquery/no-wrap: [2]
|
||||
key-spacing: [2]
|
||||
keyword-spacing: [2]
|
||||
line-comment-position: [0]
|
||||
linebreak-style: [2, unix]
|
||||
lines-around-comment: [0]
|
||||
lines-between-class-members: [0]
|
||||
logical-assignment-operators: [0]
|
||||
max-classes-per-file: [0]
|
||||
max-depth: [0]
|
||||
max-len: [0]
|
||||
max-lines-per-function: [0]
|
||||
max-lines: [0]
|
||||
max-nested-callbacks: [0]
|
||||
max-params: [0]
|
||||
max-statements-per-line: [0]
|
||||
max-statements: [0]
|
||||
multiline-comment-style: [2, separate-lines]
|
||||
multiline-ternary: [0]
|
||||
new-cap: [0]
|
||||
new-parens: [2]
|
||||
newline-per-chained-call: [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-confusing-arrow: [0]
|
||||
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-extra-parens: [0]
|
||||
no-extra-semi: [2]
|
||||
no-fallthrough: [2]
|
||||
no-floating-decimal: [0]
|
||||
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-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-mixed-operators: [0]
|
||||
no-mixed-spaces-and-tabs: [2]
|
||||
no-multi-assign: [0]
|
||||
no-multi-spaces: [2, {ignoreEOLComments: true, exceptions: {Property: true}}]
|
||||
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]
|
||||
no-return-assign: [0]
|
||||
no-return-await: [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-tabs: [2]
|
||||
no-template-curly-in-string: [2]
|
||||
no-ternary: [0]
|
||||
no-this-before-super: [2]
|
||||
no-throw-literal: [2]
|
||||
no-trailing-spaces: [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: [0]
|
||||
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-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-whitespace-before-property: [2]
|
||||
no-with: [0] # handled by no-restricted-syntax
|
||||
nonblock-statement-body-position: [2]
|
||||
object-curly-newline: [0]
|
||||
object-curly-spacing: [2, never]
|
||||
object-shorthand: [2, always]
|
||||
one-var-declaration-per-line: [0]
|
||||
one-var: [0]
|
||||
operator-assignment: [2, always]
|
||||
operator-linebreak: [2, after]
|
||||
padded-blocks: [2, never]
|
||||
padding-line-between-statements: [0]
|
||||
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: [0]
|
||||
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]
|
||||
quote-props: [0]
|
||||
quotes: [2, single, {avoidEscape: true, allowTemplateLiterals: true}]
|
||||
radix: [2, as-needed]
|
||||
require-atomic-updates: [0]
|
||||
require-await: [0]
|
||||
require-unicode-regexp: [0]
|
||||
require-yield: [2]
|
||||
rest-spread-spacing: [2, never]
|
||||
semi-spacing: [2, {before: false, after: true}]
|
||||
semi-style: [2, last]
|
||||
semi: [2, always, {omitLastInOneLineBlock: true}]
|
||||
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: [0]
|
||||
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]
|
||||
space-before-blocks: [2, always]
|
||||
space-in-parens: [2, never]
|
||||
space-infix-ops: [2]
|
||||
space-unary-ops: [2]
|
||||
spaced-comment: [2, always]
|
||||
strict: [0]
|
||||
switch-colon-spacing: [2]
|
||||
symbol-description: [2]
|
||||
template-curly-spacing: [2, never]
|
||||
template-tag-spacing: [2, never]
|
||||
unicode-bom: [2, never]
|
||||
unicorn/better-regex: [0]
|
||||
unicorn/catch-error-name: [0]
|
||||
unicorn/consistent-destructuring: [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-array-for-each: [2]
|
||||
unicorn/no-array-instanceof: [0]
|
||||
unicorn/no-array-method-this-argument: [2]
|
||||
unicorn/no-array-push-push: [2]
|
||||
unicorn/no-await-expression-member: [0]
|
||||
unicorn/no-console-spaces: [0]
|
||||
unicorn/no-document-cookie: [2]
|
||||
unicorn/no-empty-file: [2]
|
||||
unicorn/no-fn-reference-in-iterator: [0]
|
||||
unicorn/no-for-loop: [0]
|
||||
unicorn/no-hex-escape: [0]
|
||||
unicorn/no-invalid-remove-event-listener: [2]
|
||||
unicorn/no-keyword-prefix: [0]
|
||||
unicorn/no-lonely-if: [2]
|
||||
unicorn/no-negated-condition: [0]
|
||||
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-reduce: [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-unreadable-array-destructuring: [0]
|
||||
unicorn/no-unreadable-iife: [2]
|
||||
unicorn/no-unsafe-regex: [0]
|
||||
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-code-point: [0]
|
||||
unicorn/prefer-dataset: [2]
|
||||
unicorn/prefer-date-now: [2]
|
||||
unicorn/prefer-default-parameters: [0]
|
||||
unicorn/prefer-event-key: [2]
|
||||
unicorn/prefer-event-target: [2]
|
||||
unicorn/prefer-export-from: [2]
|
||||
unicorn/prefer-includes: [2]
|
||||
unicorn/prefer-json-parse-buffer: [0]
|
||||
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-append: [0]
|
||||
unicorn/prefer-node-protocol: [2]
|
||||
unicorn/prefer-node-remove: [0]
|
||||
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-replace-all: [0]
|
||||
unicorn/prefer-set-has: [0]
|
||||
unicorn/prefer-set-size: [2]
|
||||
unicorn/prefer-spread: [0]
|
||||
unicorn/prefer-starts-ends-with: [2]
|
||||
unicorn/prefer-string-slice: [0]
|
||||
unicorn/prefer-switch: [0]
|
||||
unicorn/prefer-ternary: [0]
|
||||
unicorn/prefer-text-content: [2]
|
||||
unicorn/prefer-top-level-await: [0]
|
||||
unicorn/prefer-trim-start-end: [2]
|
||||
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]
|
||||
wrap-iife: [2, inside]
|
||||
wrap-regex: [0]
|
||||
yield-star-spacing: [2, after]
|
||||
yoda: [2, never]
|
||||
7
.gitattributes
vendored
7
.gitattributes
vendored
@@ -1,8 +1,3 @@
|
||||
* text=auto eol=lf
|
||||
*.tmpl linguist-language=Handlebars
|
||||
/assets/*.json linguist-generated
|
||||
/public/vendor/** -text -eol linguist-vendored
|
||||
/vendor/** -text -eol linguist-vendored
|
||||
/web_src/fomantic/build/** linguist-generated
|
||||
/web_src/js/vendor/** -text -eol linguist-vendored
|
||||
Dockerfile.* linguist-language=Dockerfile
|
||||
/public/vendor/** -text -eol linguist-vendored
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
<!-- NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue -->
|
||||
|
||||
<!--
|
||||
1. Please speak English, this is the language all maintainers can speak and write.
|
||||
2. Please ask questions or configuration/deploy problems on our Discord
|
||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
||||
3. Please take a moment to check that your issue doesn't already exist.
|
||||
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq)
|
||||
5. Please give all relevant information below for bug reports, because
|
||||
incomplete details will be handled as an invalid report.
|
||||
-->
|
||||
|
||||
- Gitea version (or commit ref):
|
||||
- Git version:
|
||||
- Operating system:
|
||||
<!-- Please include information on whether you built gitea yourself, used one of our downloads or are using some other package -->
|
||||
<!-- Please also tell us how you are running gitea, e.g. if it is being run from docker, a command-line, systemd etc. --->
|
||||
<!-- If you are using a package or systemd tell us what distribution you are using -->
|
||||
- Database (use `[x]`):
|
||||
- [ ] PostgreSQL
|
||||
- [ ] MySQL
|
||||
- [ ] MSSQL
|
||||
- [ ] SQLite
|
||||
- Can you reproduce the bug at https://try.gitea.io:
|
||||
- [ ] Yes (provide example URL)
|
||||
- [ ] No
|
||||
- Log gist:
|
||||
<!-- It really is important to provide pertinent logs -->
|
||||
<!-- Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems -->
|
||||
<!-- In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini -->
|
||||
|
||||
## Description
|
||||
<!-- If using a proxy or a CDN (e.g. CloudFlare) in front of gitea, please
|
||||
disable the proxy/CDN fully and connect to gitea directly to confirm
|
||||
the issue still persists without those services. -->
|
||||
|
||||
...
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||
<!-- **If this issue involves the Web Interface, please include a screenshot** -->
|
||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1,2 +1 @@
|
||||
open_collective: gitea
|
||||
custom: https://www.bountysource.com/teams/gitea
|
||||
|
||||
94
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
94
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
@@ -1,94 +0,0 @@
|
||||
name: Bug Report
|
||||
description: Found something you weren't expecting? Report it here!
|
||||
labels: kind/bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
1. Please speak English, this is the language all maintainers can speak and write.
|
||||
2. Please ask questions or configuration/deploy problems on our Discord
|
||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
||||
3. Make sure you are using the latest release and
|
||||
take a moment to check that your issue hasn't been reported before.
|
||||
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq)
|
||||
5. Please give all relevant information below for bug reports, because
|
||||
incomplete details will be handled as an invalid report.
|
||||
6. In particular it's really important to provide pertinent logs. You must give us DEBUG level logs.
|
||||
Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems
|
||||
In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)
|
||||
If you are using a proxy or a CDN (e.g. Cloudflare) in front of Gitea, please disable the proxy/CDN fully and access Gitea directly to confirm the issue still persists without those services.
|
||||
- type: input
|
||||
id: gitea-ver
|
||||
attributes:
|
||||
label: Gitea Version
|
||||
description: Gitea version (or commit reference) of your instance
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: can-reproduce
|
||||
attributes:
|
||||
label: Can you reproduce the bug on the Gitea demo site?
|
||||
description: |
|
||||
If so, please provide a URL in the Description field
|
||||
URL of Gitea demo: https://try.gitea.io
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
It's really important to provide pertinent logs
|
||||
Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems
|
||||
In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini
|
||||
- type: input
|
||||
id: logs
|
||||
attributes:
|
||||
label: Log Gist
|
||||
description: Please provide a gist URL of your logs, with any sensitive information (e.g. API keys) removed/hidden
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If this issue involves the Web Interface, please provide one or more screenshots
|
||||
- type: input
|
||||
id: git-ver
|
||||
attributes:
|
||||
label: Git Version
|
||||
description: The version of git running on the server
|
||||
- type: input
|
||||
id: os-ver
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: The operating system you are using to run Gitea
|
||||
- type: textarea
|
||||
id: run-info
|
||||
attributes:
|
||||
label: How are you running Gitea?
|
||||
description: |
|
||||
Please include information on whether you built Gitea yourself, used one of our downloads, are using https://try.gitea.io or are using some other package
|
||||
Please also tell us how you are running Gitea, e.g. if it is being run from docker, a command-line, systemd etc.
|
||||
If you are using a package or systemd tell us what distribution you are using
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: database
|
||||
attributes:
|
||||
label: Database
|
||||
description: What database system are you running?
|
||||
options:
|
||||
- PostgreSQL
|
||||
- MySQL
|
||||
- MSSQL
|
||||
- SQLite
|
||||
17
.github/ISSUE_TEMPLATE/config.yml
vendored
17
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,17 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Security Concern
|
||||
url: https://tinyurl.com/security-gitea
|
||||
about: For security concerns, please send a mail to security@gitea.io instead of opening a public issue.
|
||||
- name: Discord Server
|
||||
url: https://discord.gg/Gitea
|
||||
about: Please ask questions and discuss configuration or deployment problems here.
|
||||
- name: Discourse Forum
|
||||
url: https://discourse.gitea.io
|
||||
about: Questions and configuration or deployment problems can also be discussed on our forum.
|
||||
- name: Frequently Asked Questions
|
||||
url: https://docs.gitea.io/en-us/faq
|
||||
about: Please check if your question isn't mentioned here.
|
||||
- name: Crowdin Translations
|
||||
url: https://crowdin.com/project/gitea
|
||||
about: Translations are managed here.
|
||||
24
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
24
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
@@ -1,24 +0,0 @@
|
||||
name: Feature Request
|
||||
description: Got an idea for a feature that Gitea doesn't have currently? Submit your idea here!
|
||||
labels: ["kind/feature", "kind/proposal"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
1. Please speak English, this is the language all maintainers can speak and write.
|
||||
2. Please ask questions or configuration/deploy problems on our Discord
|
||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
||||
3. Please take a moment to check that your feature hasn't already been suggested.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Feature Description
|
||||
placeholder: |
|
||||
I think it would be great if Gitea had...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If you can, provide screenshots of an implementation on another site e.g. GitHub
|
||||
66
.github/ISSUE_TEMPLATE/ui.bug-report.yaml
vendored
66
.github/ISSUE_TEMPLATE/ui.bug-report.yaml
vendored
@@ -1,66 +0,0 @@
|
||||
name: Web Interface Bug Report
|
||||
description: Something doesn't look quite as it should? Report it here!
|
||||
labels: ["kind/bug", "kind/ui"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
1. Please speak English, this is the language all maintainers can speak and write.
|
||||
2. Please ask questions or configuration/deploy problems on our Discord
|
||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
||||
3. Please take a moment to check that your issue doesn't already exist.
|
||||
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq)
|
||||
5. Please give all relevant information below for bug reports, because
|
||||
incomplete details will be handled as an invalid report.
|
||||
6. In particular it's really important to provide pertinent logs. If you are certain that this is a javascript
|
||||
error, show us the javascript console. If the error appears to relate to Gitea the server you must also give us
|
||||
DEBUG level logs. (See https://docs.gitea.io/en-us/logging-configuration/#debugging-problems)
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)
|
||||
If using a proxy or a CDN (e.g. CloudFlare) in front of gitea, please disable the proxy/CDN fully and connect to gitea directly to confirm the issue still persists without those services.
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: Please provide at least 1 screenshot showing the issue.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: gitea-ver
|
||||
attributes:
|
||||
label: Gitea Version
|
||||
description: Gitea version (or commit reference) your instance is running
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: can-reproduce
|
||||
attributes:
|
||||
label: Can you reproduce the bug on the Gitea demo site?
|
||||
description: |
|
||||
If so, please provide a URL in the Description field
|
||||
URL of Gitea demo: https://try.gitea.io
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os-ver
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: The operating system you are using to access Gitea
|
||||
- type: input
|
||||
id: browser-ver
|
||||
attributes:
|
||||
label: Browser Version
|
||||
description: The browser and version that you are using to access Gitea
|
||||
validations:
|
||||
required: true
|
||||
33
.github/issue_template.md
vendored
Normal file
33
.github/issue_template.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
<!-- NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue -->
|
||||
|
||||
<!--
|
||||
1. Please speak English, this is the language all maintainers can speak and write.
|
||||
2. Please ask questions or configuration/deploy problems on our Discord
|
||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
||||
3. Please take a moment to check that your issue doesn't already exist.
|
||||
4. Please give all relevant information below for bug reports, because
|
||||
incomplete details will be handled as an invalid report.
|
||||
-->
|
||||
|
||||
- Gitea version (or commit ref):
|
||||
- Git version:
|
||||
- Operating system:
|
||||
- Database (use `[x]`):
|
||||
- [ ] PostgreSQL
|
||||
- [ ] MySQL
|
||||
- [ ] MSSQL
|
||||
- [ ] SQLite
|
||||
- Can you reproduce the bug at https://try.gitea.io:
|
||||
- [ ] Yes (provide example URL)
|
||||
- [ ] No
|
||||
- [ ] Not relevant
|
||||
- Log gist:
|
||||
|
||||
## Description
|
||||
|
||||
...
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||
<!-- **If this issue involves the Web Interface, please include a screenshot** -->
|
||||
23
.github/lock.yml
vendored
23
.github/lock.yml
vendored
@@ -1,23 +0,0 @@
|
||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads-app
|
||||
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 60
|
||||
|
||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||
# follow ISO 8601 (`YYYY-MM-DD`). `false` is disabled
|
||||
skipCreatedBefore: false
|
||||
|
||||
# Issues and pull requests with these labels will be ignored.
|
||||
exemptLabels: []
|
||||
|
||||
# Label to add before locking, such as `outdated`. `false` is disabled
|
||||
lockLabel: false
|
||||
|
||||
# Comment to post before locking.
|
||||
lockComment: >
|
||||
This thread has been automatically locked since there has not been
|
||||
any recent activity after it was closed. Please open a new issue for
|
||||
related bugs and link to relevant comments in this thread.
|
||||
|
||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||
setLockReason: true
|
||||
14
.github/pull_request_template.md
vendored
14
.github/pull_request_template.md
vendored
@@ -1,9 +1,7 @@
|
||||
<!-- start tips -->
|
||||
Please check the following:
|
||||
1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for backports.
|
||||
2. Make sure you have read contributing guidelines: https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md .
|
||||
3. Describe what your pull request does and which issue you're targeting (if any).
|
||||
4. It is recommended to enable "Allow edits by maintainers", so maintainers can help more easily.
|
||||
5. Your input here will be included in the commit message when this PR has been merged. If you don't want some content to be included, please separate them with a line like `---`.
|
||||
6. Delete all these tips before posting.
|
||||
<!-- end tips -->
|
||||
|
||||
1. Make sure you are targeting the `master` branch, pull requests on release branches are only allowed for bug fixes.
|
||||
2. Read contributing guidelines: https://github.com/go-gitea/gitea/blob/master/CONTRIBUTING.md
|
||||
3. Describe what your pull request does and which issue you're targeting (if any)
|
||||
|
||||
**You MUST delete the content above including this line before posting, otherwise your pull request will be invalid.**
|
||||
|
||||
48
.gitignore
vendored
48
.gitignore
vendored
@@ -9,8 +9,6 @@ _test
|
||||
|
||||
# IntelliJ
|
||||
.idea
|
||||
# Goland's output filename can not be set manually
|
||||
/go_build_*
|
||||
|
||||
# MS VSCode
|
||||
.vscode
|
||||
@@ -34,10 +32,7 @@ _testmain.go
|
||||
|
||||
*coverage.out
|
||||
coverage.all
|
||||
cpu.out
|
||||
|
||||
/modules/migration/bindata.go
|
||||
/modules/migration/bindata.go.hash
|
||||
/modules/options/bindata.go
|
||||
/modules/options/bindata.go.hash
|
||||
/modules/public/bindata.go
|
||||
@@ -63,39 +58,31 @@ cpu.out
|
||||
/indexers
|
||||
/log
|
||||
/public/img/avatar
|
||||
/tests/integration/gitea-integration-*
|
||||
/tests/integration/indexers-*
|
||||
/tests/e2e/gitea-e2e-*
|
||||
/tests/e2e/indexers-*
|
||||
/tests/e2e/reports
|
||||
/tests/e2e/test-artifacts
|
||||
/tests/e2e/test-snapshots
|
||||
/tests/*.ini
|
||||
/integrations/gitea-integration-mysql
|
||||
/integrations/gitea-integration-mysql8
|
||||
/integrations/gitea-integration-pgsql
|
||||
/integrations/gitea-integration-sqlite
|
||||
/integrations/gitea-integration-mssql
|
||||
/integrations/indexers-mysql
|
||||
/integrations/indexers-mysql8
|
||||
/integrations/indexers-pgsql
|
||||
/integrations/indexers-sqlite
|
||||
/integrations/indexers-mssql
|
||||
/integrations/sqlite.ini
|
||||
/integrations/mysql.ini
|
||||
/integrations/mysql8.ini
|
||||
/integrations/pgsql.ini
|
||||
/integrations/mssql.ini
|
||||
/node_modules
|
||||
/yarn.lock
|
||||
/yarn-error.log
|
||||
/npm-debug.log*
|
||||
/public/js
|
||||
/public/serviceworker.js
|
||||
/public/css
|
||||
/public/fonts
|
||||
/public/img/webpack
|
||||
/vendor
|
||||
/web_src/fomantic/node_modules
|
||||
/web_src/fomantic/build/*
|
||||
!/web_src/fomantic/build/semantic.js
|
||||
!/web_src/fomantic/build/semantic.css
|
||||
!/web_src/fomantic/build/themes
|
||||
/web_src/fomantic/build/themes/*
|
||||
!/web_src/fomantic/build/themes/default
|
||||
/web_src/fomantic/build/themes/default/assets/*
|
||||
!/web_src/fomantic/build/themes/default/assets/fonts
|
||||
/web_src/fomantic/build/themes/default/assets/fonts/*
|
||||
!/web_src/fomantic/build/themes/default/assets/fonts/icons.woff2
|
||||
!/web_src/fomantic/build/themes/default/assets/fonts/outline-icons.woff2
|
||||
/web_src/fomantic/build
|
||||
/VERSION
|
||||
/.air
|
||||
/.go-licenses
|
||||
|
||||
# Snapcraft
|
||||
snap/.snapcraft/
|
||||
@@ -109,6 +96,3 @@ prime/
|
||||
|
||||
# Make evidence files
|
||||
/.make_evidence
|
||||
|
||||
# Manpage
|
||||
/man
|
||||
|
||||
44
.gitpod.yml
44
.gitpod.yml
@@ -1,44 +0,0 @@
|
||||
tasks:
|
||||
- name: Setup
|
||||
init: |
|
||||
cp -r contrib/ide/vscode .vscode
|
||||
make deps
|
||||
make build
|
||||
command: |
|
||||
gp sync-done setup
|
||||
exit 0
|
||||
- name: Run backend
|
||||
command: |
|
||||
gp sync-await setup
|
||||
mkdir -p custom/conf/
|
||||
echo -e "[server]\nROOT_URL=$(gp url 3000)/" > custom/conf/app.ini
|
||||
echo -e "\n[database]\nDB_TYPE = sqlite3\nPATH = $GITPOD_REPO_ROOT/data/gitea.db" >> custom/conf/app.ini
|
||||
export TAGS="sqlite sqlite_unlock_notify"
|
||||
make watch-backend
|
||||
- name: Run frontend
|
||||
command: |
|
||||
gp sync-await setup
|
||||
make watch-frontend
|
||||
openMode: split-right
|
||||
- name: Run docs
|
||||
before: sudo bash -c "$(grep 'https://github.com/gohugoio/hugo/releases/download' Makefile | tr -d '\')" # install hugo
|
||||
command: cd docs && make clean update && hugo server -D -F --baseUrl $(gp url 1313) --liveReloadPort=443 --appendPort=false --bind=0.0.0.0
|
||||
openMode: split-right
|
||||
|
||||
vscode:
|
||||
extensions:
|
||||
- editorconfig.editorconfig
|
||||
- dbaeumer.vscode-eslint
|
||||
- golang.go
|
||||
- stylelint.vscode-stylelint
|
||||
- DavidAnson.vscode-markdownlint
|
||||
- johnsoncodehk.volar
|
||||
- ms-azuretools.vscode-docker
|
||||
- zixuanchen.vitest-explorer
|
||||
- alexcvzz.vscode-sqlite
|
||||
|
||||
ports:
|
||||
- name: Gitea
|
||||
port: 3000
|
||||
- name: Docs
|
||||
port: 1313
|
||||
108
.golangci.yml
108
.golangci.yml
@@ -1,94 +1,34 @@
|
||||
linters:
|
||||
enable:
|
||||
- bidichk
|
||||
# - deadcode # deprecated - https://github.com/golangci/golangci-lint/issues/1841
|
||||
- depguard
|
||||
- dupl
|
||||
- errcheck
|
||||
- gocritic
|
||||
# - gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time.
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- revive
|
||||
- staticcheck
|
||||
# - structcheck # deprecated - https://github.com/golangci/golangci-lint/issues/1841
|
||||
- stylecheck
|
||||
- deadcode
|
||||
- typecheck
|
||||
- unconvert
|
||||
- govet
|
||||
- errcheck
|
||||
- staticcheck
|
||||
- unused
|
||||
# - varcheck # deprecated - https://github.com/golangci/golangci-lint/issues/1841
|
||||
# - wastedassign # disabled - https://github.com/golangci/golangci-lint/issues/2649
|
||||
- structcheck
|
||||
- varcheck
|
||||
- golint
|
||||
- dupl
|
||||
#- gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time.
|
||||
- gofmt
|
||||
- misspell
|
||||
- gocritic
|
||||
enable-all: false
|
||||
disable-all: true
|
||||
fast: false
|
||||
|
||||
run:
|
||||
go: "1.20"
|
||||
timeout: 10m
|
||||
skip-dirs:
|
||||
- node_modules
|
||||
- public
|
||||
- web_src
|
||||
timeout: 3m
|
||||
|
||||
linters-settings:
|
||||
stylecheck:
|
||||
checks: ["all", "-ST1005", "-ST1003"]
|
||||
nakedret:
|
||||
max-func-lines: 0
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- ifElseChain
|
||||
- singleCaseSwitch # Every time this occurred in the code, there was no other way.
|
||||
revive:
|
||||
ignore-generated-header: false
|
||||
severity: warning
|
||||
confidence: 0.8
|
||||
errorCode: 1
|
||||
warningCode: 1
|
||||
rules:
|
||||
- name: blank-imports
|
||||
- name: context-as-argument
|
||||
- name: context-keys-type
|
||||
- name: dot-imports
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
- name: error-naming
|
||||
- name: exported
|
||||
- name: if-return
|
||||
- name: increment-decrement
|
||||
- name: var-naming
|
||||
- name: var-declaration
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
- name: time-naming
|
||||
- name: unexported-return
|
||||
- name: indent-error-flow
|
||||
- name: errorf
|
||||
- name: duplicated-imports
|
||||
- name: modifies-value-receiver
|
||||
gofumpt:
|
||||
extra-rules: true
|
||||
lang-version: "1.20"
|
||||
depguard:
|
||||
list-type: denylist
|
||||
# Check the list against standard lib.
|
||||
include-go-root: true
|
||||
packages-with-error-message:
|
||||
- encoding/json: "use gitea's modules/json instead of encoding/json"
|
||||
- github.com/unknwon/com: "use gitea's util and replacements"
|
||||
- io/ioutil: "use os or io instead"
|
||||
- golang.org/x/exp: "it's experimental and unreliable."
|
||||
- code.gitea.io/gitea/modules/git/internal: "do not use the internal package, use AddXxx function instead"
|
||||
- singleCaseSwitch # Every time this occured in the code, there was no other way.
|
||||
|
||||
issues:
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
exclude-rules:
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
@@ -130,6 +70,9 @@ issues:
|
||||
- path: modules/log/
|
||||
linters:
|
||||
- errcheck
|
||||
- path: routers/routes/routes.go
|
||||
linters:
|
||||
- dupl
|
||||
- path: routers/api/v1/repo/issue_subscription.go
|
||||
linters:
|
||||
- dupl
|
||||
@@ -149,29 +92,18 @@ issues:
|
||||
- path: models/issue_comment_list.go
|
||||
linters:
|
||||
- dupl
|
||||
- linters:
|
||||
- misspell
|
||||
text: '`Unknwon` is a misspelling of `Unknown`'
|
||||
- path: models/update.go
|
||||
linters:
|
||||
- unused
|
||||
- path: cmd/dump.go
|
||||
linters:
|
||||
- dupl
|
||||
- path: services/webhook/webhook.go
|
||||
linters:
|
||||
- structcheck
|
||||
- text: "commentFormatting: put a space between `//` and comment text"
|
||||
linters:
|
||||
- gocritic
|
||||
- text: "exitAfterDefer:"
|
||||
linters:
|
||||
- gocritic
|
||||
- path: modules/graceful/manager_windows.go
|
||||
linters:
|
||||
- staticcheck
|
||||
text: "svc.IsAnInteractiveSession is deprecated: Use IsWindowsService instead."
|
||||
- path: models/user/openid.go
|
||||
linters:
|
||||
- golint
|
||||
- path: models/user/badge.go
|
||||
linters:
|
||||
- revive
|
||||
text: "exported: type name will be used as user.UserBadge by other packages, and that stutters; consider calling this Badge"
|
||||
|
||||
7
.ignore
7
.ignore
@@ -1,8 +1,5 @@
|
||||
*.min.css
|
||||
*.min.js
|
||||
/vendor
|
||||
/public/vendor/plugins
|
||||
/modules/options/bindata.go
|
||||
/modules/public/bindata.go
|
||||
/modules/templates/bindata.go
|
||||
/public/vendor/plugins
|
||||
/vendor
|
||||
node_modules
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
commands-show-output: false
|
||||
fenced-code-language: false
|
||||
first-line-h1: false
|
||||
header-increment: false
|
||||
line-length: {code_blocks: false, tables: false, stern: true, line_length: -1}
|
||||
no-alt-text: false
|
||||
no-bare-urls: false
|
||||
no-blanks-blockquote: false
|
||||
no-duplicate-header: {allow_different_nesting: true}
|
||||
no-emphasis-as-header: false
|
||||
no-empty-links: false
|
||||
no-hard-tabs: {code_blocks: false}
|
||||
no-inline-html: false
|
||||
no-space-in-code: false
|
||||
no-space-in-emphasis: false
|
||||
no-trailing-punctuation: false
|
||||
no-trailing-spaces: {br_spaces: 0}
|
||||
single-h1: false
|
||||
3
.npmrc
3
.npmrc
@@ -1,5 +1,2 @@
|
||||
audit=false
|
||||
fund=false
|
||||
update-notifier=false
|
||||
package-lock=true
|
||||
save-exact=true
|
||||
|
||||
25
.revive.toml
Normal file
25
.revive.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
ignoreGeneratedHeader = false
|
||||
severity = "warning"
|
||||
confidence = 0.8
|
||||
errorCode = 1
|
||||
warningCode = 1
|
||||
|
||||
[rule.blank-imports]
|
||||
[rule.context-as-argument]
|
||||
[rule.context-keys-type]
|
||||
[rule.dot-imports]
|
||||
[rule.error-return]
|
||||
[rule.error-strings]
|
||||
[rule.error-naming]
|
||||
[rule.exported]
|
||||
[rule.if-return]
|
||||
[rule.increment-decrement]
|
||||
[rule.var-naming]
|
||||
[rule.var-declaration]
|
||||
[rule.package-comments]
|
||||
[rule.range]
|
||||
[rule.receiver-naming]
|
||||
[rule.time-naming]
|
||||
[rule.unexported-return]
|
||||
[rule.indent-error-flow]
|
||||
[rule.errorf]
|
||||
@@ -1,12 +0,0 @@
|
||||
extends: [[spectral:oas, all]]
|
||||
|
||||
rules:
|
||||
info-contact: off
|
||||
oas2-api-host: off
|
||||
oas2-parameter-description: off
|
||||
oas2-schema: off
|
||||
oas2-valid-schema-example: off
|
||||
openapi-tags: off
|
||||
operation-description: off
|
||||
operation-singular-tag: off
|
||||
operation-tag-defined: off
|
||||
15
.stylelintrc
Normal file
15
.stylelintrc
Normal file
@@ -0,0 +1,15 @@
|
||||
extends: stylelint-config-standard
|
||||
|
||||
rules:
|
||||
at-rule-empty-line-before: null
|
||||
block-closing-brace-empty-line-before: null
|
||||
color-hex-length: null
|
||||
comment-empty-line-before: null
|
||||
declaration-block-single-line-max-declarations: null
|
||||
declaration-empty-line-before: null
|
||||
indentation: 2
|
||||
no-descending-specificity: null
|
||||
number-leading-zero: never
|
||||
rule-empty-line-before: null
|
||||
selector-pseudo-element-colon-notation: null
|
||||
shorthand-property-no-redundant-values: true
|
||||
@@ -1,133 +0,0 @@
|
||||
plugins:
|
||||
- stylelint-declaration-strict-value
|
||||
|
||||
overrides:
|
||||
- files: ["**/*.less"]
|
||||
customSyntax: postcss-less
|
||||
- files: ["**/chroma/*", "**/codemirror/*", "**/standalone/*", "**/console/*"]
|
||||
rules:
|
||||
scale-unlimited/declaration-strict-value: null
|
||||
|
||||
rules:
|
||||
alpha-value-notation: null
|
||||
annotation-no-unknown: true
|
||||
at-rule-allowed-list: null
|
||||
at-rule-disallowed-list: null
|
||||
at-rule-empty-line-before: null
|
||||
at-rule-no-unknown: true
|
||||
at-rule-no-vendor-prefix: true
|
||||
at-rule-property-required-list: null
|
||||
block-no-empty: true
|
||||
color-function-notation: null
|
||||
color-hex-alpha: null
|
||||
color-hex-length: null
|
||||
color-named: null
|
||||
color-no-hex: null
|
||||
color-no-invalid-hex: true
|
||||
comment-empty-line-before: null
|
||||
comment-no-empty: true
|
||||
comment-pattern: null
|
||||
comment-whitespace-inside: null
|
||||
comment-word-disallowed-list: null
|
||||
custom-media-pattern: null
|
||||
custom-property-empty-line-before: null
|
||||
custom-property-no-missing-var-function: true
|
||||
custom-property-pattern: null
|
||||
declaration-block-no-duplicate-custom-properties: true
|
||||
declaration-block-no-duplicate-properties: [true, {ignore: [consecutive-duplicates-with-different-values]}]
|
||||
declaration-block-no-redundant-longhand-properties: null
|
||||
declaration-block-no-shorthand-property-overrides: null
|
||||
declaration-block-single-line-max-declarations: null
|
||||
declaration-empty-line-before: null
|
||||
declaration-no-important: null
|
||||
declaration-property-max-values: null
|
||||
declaration-property-unit-allowed-list: null
|
||||
declaration-property-unit-disallowed-list: null
|
||||
declaration-property-value-allowed-list: null
|
||||
declaration-property-value-disallowed-list: null
|
||||
declaration-property-value-no-unknown: true
|
||||
font-family-name-quotes: always-where-recommended
|
||||
font-family-no-duplicate-names: true
|
||||
font-family-no-missing-generic-family-keyword: true
|
||||
font-weight-notation: null
|
||||
function-allowed-list: null
|
||||
function-calc-no-unspaced-operator: true
|
||||
function-disallowed-list: null
|
||||
function-linear-gradient-no-nonstandard-direction: true
|
||||
function-name-case: lower
|
||||
function-no-unknown: null
|
||||
function-url-no-scheme-relative: null
|
||||
function-url-quotes: always
|
||||
function-url-scheme-allowed-list: null
|
||||
function-url-scheme-disallowed-list: null
|
||||
hue-degree-notation: null
|
||||
import-notation: string
|
||||
keyframe-block-no-duplicate-selectors: true
|
||||
keyframe-declaration-no-important: true
|
||||
keyframe-selector-notation: null
|
||||
keyframes-name-pattern: null
|
||||
length-zero-no-unit: true
|
||||
max-nesting-depth: null
|
||||
media-feature-name-allowed-list: null
|
||||
media-feature-name-disallowed-list: null
|
||||
media-feature-name-no-unknown: true
|
||||
media-feature-name-no-vendor-prefix: true
|
||||
media-feature-name-unit-allowed-list: null
|
||||
media-feature-name-value-allowed-list: null
|
||||
media-feature-range-notation: null
|
||||
named-grid-areas-no-invalid: true
|
||||
no-descending-specificity: null
|
||||
no-duplicate-at-import-rules: true
|
||||
no-duplicate-selectors: true
|
||||
no-empty-source: true
|
||||
no-invalid-double-slash-comments: true
|
||||
no-invalid-position-at-import-rule: null
|
||||
no-irregular-whitespace: true
|
||||
no-unknown-animations: null
|
||||
number-max-precision: null
|
||||
property-allowed-list: null
|
||||
property-disallowed-list: null
|
||||
property-no-unknown: true
|
||||
property-no-vendor-prefix: null
|
||||
rule-empty-line-before: null
|
||||
rule-selector-property-disallowed-list: null
|
||||
scale-unlimited/declaration-strict-value: [color, {ignoreValues: /^(inherit|transparent|unset|initial|currentcolor)$/}]
|
||||
selector-attribute-name-disallowed-list: null
|
||||
selector-attribute-operator-allowed-list: null
|
||||
selector-attribute-operator-disallowed-list: null
|
||||
selector-attribute-quotes: always
|
||||
selector-class-pattern: null
|
||||
selector-combinator-allowed-list: null
|
||||
selector-combinator-disallowed-list: null
|
||||
selector-disallowed-list: null
|
||||
selector-id-pattern: null
|
||||
selector-max-attribute: null
|
||||
selector-max-class: null
|
||||
selector-max-combinators: null
|
||||
selector-max-compound-selectors: null
|
||||
selector-max-id: null
|
||||
selector-max-pseudo-class: null
|
||||
selector-max-specificity: null
|
||||
selector-max-type: null
|
||||
selector-max-universal: null
|
||||
selector-nested-pattern: null
|
||||
selector-no-qualifying-type: null
|
||||
selector-no-vendor-prefix: true
|
||||
selector-not-notation: null
|
||||
selector-pseudo-class-allowed-list: null
|
||||
selector-pseudo-class-disallowed-list: null
|
||||
selector-pseudo-class-no-unknown: true
|
||||
selector-pseudo-element-allowed-list: null
|
||||
selector-pseudo-element-colon-notation: double
|
||||
selector-pseudo-element-disallowed-list: null
|
||||
selector-pseudo-element-no-unknown: true
|
||||
selector-type-case: lower
|
||||
selector-type-no-unknown: [true, {ignore: [custom-elements]}]
|
||||
shorthand-property-no-redundant-values: true
|
||||
string-no-newline: true
|
||||
time-min-milliseconds: null
|
||||
unit-allowed-list: null
|
||||
unit-disallowed-list: null
|
||||
unit-no-unknown: true
|
||||
value-keyword-case: null
|
||||
value-no-vendor-prefix: [true, {ignoreValues: [box, inline-box]}]
|
||||
2504
CHANGELOG.md
2504
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
235
CONTRIBUTING.md
235
CONTRIBUTING.md
@@ -3,14 +3,12 @@
|
||||
## Table of Contents
|
||||
|
||||
- [Contribution Guidelines](#contribution-guidelines)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Introduction](#introduction)
|
||||
- [Bug reports](#bug-reports)
|
||||
- [Discuss your design](#discuss-your-design)
|
||||
- [Testing redux](#testing-redux)
|
||||
- [Vendoring](#vendoring)
|
||||
- [Translation](#translation)
|
||||
- [Building Gitea](#building-gitea)
|
||||
- [Code review](#code-review)
|
||||
- [Styleguide](#styleguide)
|
||||
- [Design guideline](#design-guideline)
|
||||
@@ -81,24 +79,23 @@ Here's how to run the test suite:
|
||||
|``make lint-frontend`` | lint frontend files |
|
||||
|``make lint-backend`` | lint backend files |
|
||||
|
||||
- run test code (Suggest run in Linux)
|
||||
- run test code (Suggest run in linux)
|
||||
|
||||
| | |
|
||||
| :------------------------------------- | :----------------------------------------------- |
|
||||
|``make test[\#TestSpecificName]`` | run unit test |
|
||||
|``make test-sqlite[\#TestSpecificName]``| run [integration](tests/integration) test for SQLite |
|
||||
|[More details about integration tests](tests/integration/README.md) |
|
||||
|``make test-e2e-sqlite[\#TestSpecificFileName]``| run [end-to-end](tests/e2e) test for SQLite |
|
||||
|[More details about e2e tests](tests/e2e/README.md) |
|
||||
|``make test-sqlite[\#TestSpecificName]``| run [integration](integrations) test for sqlite |
|
||||
|[More detail message about integrations](integrations/README.md) |
|
||||
|
||||
## Vendoring
|
||||
|
||||
We manage dependencies via [Go Modules](https://golang.org/cmd/go/#hdr-Module_maintenance), more details: [go mod](https://go.dev/ref/mod).
|
||||
We keep a cached copy of dependencies within the `vendor/` directory,
|
||||
managing updates via [Modules](https://golang.org/cmd/go/#hdr-Module_maintenance).
|
||||
|
||||
Pull requests should only include `go.mod`, `go.sum` updates if they are part of
|
||||
Pull requests should only include `vendor/` updates if they are part of
|
||||
the same change, be it a bugfix or a feature addition.
|
||||
|
||||
The `go.mod`, `go.sum` update needs to be justified as part of the PR description,
|
||||
The `vendor/` update needs to be justified as part of the PR description,
|
||||
and must be verified by the reviewers and/or merger to always reference
|
||||
an existing upstream commit.
|
||||
|
||||
@@ -107,7 +104,7 @@ You can find more information on how to get started with it on the [Modules Wiki
|
||||
## Translation
|
||||
|
||||
We do all translation work inside [Crowdin](https://crowdin.com/project/gitea).
|
||||
The only translation that is maintained in this Git repository is
|
||||
The only translation that is maintained in this git repository is
|
||||
[`en_US.ini`](https://github.com/go-gitea/gitea/blob/master/options/locale/locale_en-US.ini)
|
||||
and is synced regularly to Crowdin. Once a translation has reached
|
||||
A SATISFACTORY PERCENTAGE it will be synced back into this repo and
|
||||
@@ -129,42 +126,22 @@ the *[How to get faster PR reviews](https://github.com/kubernetes/community/blob
|
||||
it has lots of useful tips for any project you may want to contribute.
|
||||
Some of the key points:
|
||||
|
||||
- Make small pull requests. The smaller, the faster to review and the
|
||||
* Make small pull requests. The smaller, the faster to review and the
|
||||
more likely it will be merged soon.
|
||||
- Don't make changes unrelated to your PR. Maybe there are typos on
|
||||
* Don't make changes unrelated to your PR. Maybe there are typos on
|
||||
some comments, maybe refactoring would be welcome on a function... but
|
||||
if that is not related to your PR, please make *another* PR for that.
|
||||
- Split big pull requests into multiple small ones. An incremental change
|
||||
* Split big pull requests into multiple small ones. An incremental change
|
||||
will be faster to review than a huge PR.
|
||||
- Use the first comment as a summary explainer of your PR and you should keep this up-to-date as the PR evolves.
|
||||
|
||||
If your PR could cause a breaking change you must add a BREAKING section to this comment e.g.:
|
||||
|
||||
```
|
||||
## :warning: BREAKING :warning:
|
||||
```
|
||||
|
||||
To explain how this could affect users and how to mitigate these changes.
|
||||
|
||||
Once code review starts on your PR, do not rebase nor squash your branch as it makes it
|
||||
difficult to review the new changes. Only if there is a need, sync your branch by merging
|
||||
the base branch into yours. Don't worry about merge commits messing up your tree as
|
||||
the final merge process squashes all commits into one, with the visible commit message (first
|
||||
line) being the PR title + PR index and description being the PR's first comment.
|
||||
|
||||
Once your PR gets the `lgtm/done` label, don't worry about keeping it up-to-date or breaking
|
||||
builds (unless there's a merge conflict or a request is made by a maintainer to make
|
||||
modifications). It is the maintainer team's responsibility from this point to get it merged.
|
||||
|
||||
## Styleguide
|
||||
|
||||
For imports you should use the following format (*without* the comments)
|
||||
|
||||
For imports you should use the following format (_without_ the comments)
|
||||
```go
|
||||
import (
|
||||
// stdlib
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
// local packages
|
||||
"code.gitea.io/gitea/models"
|
||||
@@ -178,100 +155,56 @@ import (
|
||||
|
||||
## Design guideline
|
||||
|
||||
To maintain understandable code and avoid circular dependencies it is important to have a good structure of the code. The Gitea code is divided into the following parts:
|
||||
To maintain understandable code and avoid circular dependencies it is important to have a good structure of the code. The gitea code is divided into the following parts:
|
||||
|
||||
- **models:** Contains the data structures used by xorm to construct database tables. It also contains supporting functions to query and update the database. Dependencies to other code in Gitea should be avoided although some modules might be needed (for example for logging).
|
||||
- **integration:** Integrations tests
|
||||
- **models:** Contains the data structures used by xorm to construct database tables. It also contains supporting functions to query and update the database. Dependecies to other code in Gitea should be avoided although some modules might be needed (for example for logging).
|
||||
- **models/fixtures:** Sample model data used in integration tests.
|
||||
- **models/migrations:** Handling of database migrations between versions. PRs that changes a database structure shall also have a migration step.
|
||||
- **modules:** Different modules to handle specific functionality in Gitea. Shall only depend on other modules but not other packages (models, services).
|
||||
- **modules:** Different modules to handle specific functionality in Gitea.
|
||||
- **public:** Frontend files (javascript, images, css, etc.)
|
||||
- **routers:** Handling of server requests. As it uses other Gitea packages to serve the request, other packages (models, modules or services) shall not depend on routers.
|
||||
- **routers:** Handling of server requests. As it uses other Gitea packages to serve the request, other packages (models, modules or services) shall not depend on routers
|
||||
- **services:** Support functions for common routing operations. Uses models and modules to handle the request.
|
||||
- **templates:** Golang templates for generating the html output.
|
||||
- **tests/e2e:** End to end tests
|
||||
- **tests/integration:** Integration tests
|
||||
- **tests/gitea-repositories-meta:** Sample repos used in integration tests. Adding a new repo requires editing `models/fixtures/repositories.yml` and `models/fixtures/repo_unit.yml` to match.
|
||||
- **tests/gitea-lfs-meta:** Sample LFS objects used in integration tests. Adding a new object requires editing `models/fixtures/lfs_meta_object.yml` to match.
|
||||
- **vendor:** External code that Gitea depends on.
|
||||
|
||||
## Documentation
|
||||
|
||||
If you add a new feature or change an existing aspect of Gitea, the documentation for that feature must be created or updated.
|
||||
|
||||
## API v1
|
||||
|
||||
The API is documented by [swagger](http://try.gitea.io/api/swagger) and is based on [GitHub API v3](https://developer.github.com/v3/).
|
||||
|
||||
Thus, Gitea´s API should use the same endpoints and fields as GitHub´s API as far as possible, unless there are good reasons to deviate.
|
||||
|
||||
If Gitea provides functionality that GitHub does not, a new endpoint can be created.
|
||||
|
||||
Thus, Gitea´s API should use the same endpoints and fields as GitHub´s API as far as possible, unless there are good reasons to deviate.
|
||||
If Gitea provides functionality that GitHub does not, a new endpoint can be created.
|
||||
If information is provided by Gitea that is not provided by the GitHub API, a new field can be used that doesn't collide with any GitHub fields.
|
||||
|
||||
Updating an existing API should not remove existing fields unless there is a really good reason to do so.
|
||||
|
||||
The same applies to status responses. If you notice a problem, feel free to leave a comment in the code for future refactoring to APIv2 (which is currently not planned).
|
||||
|
||||
All expected results (errors, success, fail messages) should be documented
|
||||
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L319-L327)).
|
||||
|
||||
All JSON input types must be defined as a struct in [modules/structs/](modules/structs/)
|
||||
All JSON input types must be defined as a struct in `models/structs/`
|
||||
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L76-L91))
|
||||
and referenced in
|
||||
[routers/api/v1/swagger/options.go](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/options.go).
|
||||
|
||||
[routers/api/v1/swagger/options.go](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/options.go).
|
||||
They can then be used like the following:
|
||||
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L318)).
|
||||
|
||||
All JSON responses must be defined as a struct in [modules/structs/](modules/structs/)
|
||||
All JSON responses must be defined as a struct in `models/structs/`
|
||||
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L36-L68))
|
||||
and referenced in its category in [routers/api/v1/swagger/](routers/api/v1/swagger/)
|
||||
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/issue.go#L11-L16))
|
||||
|
||||
and referenced in its category in `routers/api/v1/swagger/`
|
||||
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/issue.go#L11-L16))
|
||||
They can be used like the following:
|
||||
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L277-L279))
|
||||
|
||||
In general, HTTP methods are chosen as follows:
|
||||
* **GET** endpoints return requested object and status **OK (200)**
|
||||
* **DELETE** endpoints return status **No Content (204)**
|
||||
* **POST** endpoints return status **Created (201)**, used to **create** new objects (e.g. a User)
|
||||
* **PUT** endpoints return status **No Content (204)**, used to **add/assign** existing Obejcts (e.g. User) to something (e.g. Org-Team)
|
||||
* **PATCH** endpoints return changed object and status **OK (200)**, used to **edit/change** an existing object
|
||||
|
||||
- **GET** endpoints return requested object and status **OK (200)**
|
||||
- **DELETE** endpoints return status **No Content (204)**
|
||||
- **POST** endpoints return status **Created (201)**, used to **create** new objects (e.g. a User)
|
||||
- **PUT** endpoints return status **No Content (204)**, used to **add/assign** existing Objects (e.g. User) to something (e.g. Org-Team)
|
||||
- **PATCH** endpoints return changed object and status **OK (200)**, used to **edit/change** an existing object
|
||||
|
||||
An endpoint which changes/edits an object expects all fields to be optional (except ones to identify the object, which are required).
|
||||
|
||||
### Endpoints returning lists should
|
||||
|
||||
- support pagination (`page` & `limit` options in query)
|
||||
- set `X-Total-Count` header via **SetTotalCountHeader** ([example](https://github.com/go-gitea/gitea/blob/7aae98cc5d4113f1e9918b7ee7dd09f67c189e3e/routers/api/v1/repo/issue.go#L444))
|
||||
|
||||
## Backports and Frontports
|
||||
|
||||
Occasionally backports of PRs are required.
|
||||
|
||||
The backported PR title should be:
|
||||
|
||||
```
|
||||
Title of backported PR (#ORIGINAL_PR_NUMBER)
|
||||
```
|
||||
|
||||
The first two lines of the summary of the backporting PR should be:
|
||||
|
||||
```
|
||||
Backport #ORIGINAL_PR_NUMBER
|
||||
|
||||
```
|
||||
|
||||
with the rest of the summary matching the original PR. Similarly for frontports
|
||||
|
||||
---
|
||||
|
||||
A command to help create backports can be found in `contrib/backport` and can be installed (from inside the gitea repo root directory) using:
|
||||
|
||||
```bash
|
||||
go install contrib/backport/backport.go
|
||||
```
|
||||
|
||||
## Developer Certificate of Origin (DCO)
|
||||
|
||||
@@ -284,7 +217,7 @@ Additionally you could add a line at the end of your commit message.
|
||||
Signed-off-by: Joe Smith <joe.smith@email.com>
|
||||
```
|
||||
|
||||
If you set your `user.name` and `user.email` Git configs, you can add the
|
||||
If you set your `user.name` and `user.email` git configs, you can add the
|
||||
line to the end of your commit automatically with `git commit -s`.
|
||||
|
||||
We assume in good faith that the information you provide is legally binding.
|
||||
@@ -293,32 +226,24 @@ We assume in good faith that the information you provide is legally binding.
|
||||
|
||||
We adopted a release schedule to streamline the process of working
|
||||
on, finishing, and issuing releases. The overall goal is to make a
|
||||
minor release every three or four months, which breaks down into two or three months of
|
||||
minor release every two months, which breaks down into one month of
|
||||
general development followed by one month of testing and polishing
|
||||
known as the release freeze. All the feature pull requests should be
|
||||
merged before feature freeze. And, during the frozen period, a corresponding
|
||||
release branch is open for fixes backported from main branch. Release candidates
|
||||
are made during this period for user testing to
|
||||
obtain a final version that is maintained in this branch.
|
||||
merged in the first month of one release period. And, during the frozen
|
||||
period, a corresponding release branch is open for fixes backported from
|
||||
master. Release candidates are made during this period for user testing to
|
||||
obtain a final version that is maintained in this branch. A release is
|
||||
maintained by issuing patch releases to only correct critical problems
|
||||
such as crashes or security issues.
|
||||
|
||||
Major release cycles are seasonal. They always begin on the 25th and end on
|
||||
the 24th (i.e., the 25th of December to March 24th).
|
||||
Major release cycles are bimonthly. They always begin on the 25th and end on
|
||||
the 24th (i.e., the 25th of December to February 24th).
|
||||
|
||||
During a development cycle, we may also publish any necessary minor releases
|
||||
for the previous version. For example, if the latest, published release is
|
||||
v1.2, then minor changes for the previous release—e.g., v1.1.0 -> v1.1.1—are
|
||||
still possible.
|
||||
|
||||
The previous release gets fixes for:
|
||||
|
||||
- Security issues
|
||||
- Critical bugs
|
||||
- Regressions
|
||||
- Build issues
|
||||
- Necessary enhancements (including necessary UI/UX fixes)
|
||||
|
||||
The backported fixes should avoid breaking downgrade between minor releases as much as possible.
|
||||
|
||||
## Maintainers
|
||||
|
||||
To make sure every PR is checked, we have [team
|
||||
@@ -337,7 +262,7 @@ to the maintainers team. If a maintainer is inactive for more than 3
|
||||
months and forgets to leave the maintainers team, the owners may move
|
||||
him or her from the maintainers team to the advisors team.
|
||||
For security reasons, Maintainers should use 2FA for their accounts and
|
||||
if possible provide GPG signed commits.
|
||||
if possible provide gpg signed commits.
|
||||
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
|
||||
https://help.github.com/articles/signing-commits-with-gpg/
|
||||
|
||||
@@ -368,74 +293,60 @@ and lead the development of Gitea.
|
||||
To honor the past owners, here's the history of the owners and the time
|
||||
they served:
|
||||
|
||||
- 2022-01-01 ~ 2022-12-31 - https://github.com/go-gitea/gitea/issues/17872
|
||||
- [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
|
||||
- [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.io>
|
||||
- [Andrew Thornton](https://gitea.com/zeripath) <art27@cantab.net>
|
||||
* 2020-01-01 ~ 2020-12-31 - https://github.com/go-gitea/gitea/issues/9230
|
||||
* [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
|
||||
* [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) <lauris@nix.lv>
|
||||
* [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.io>
|
||||
|
||||
- 2021-01-01 ~ 2021-12-31 - https://github.com/go-gitea/gitea/issues/13801
|
||||
- [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
|
||||
- [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) <lauris@nix.lv>
|
||||
- [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.io>
|
||||
* 2019-01-01 ~ 2019-12-31 - https://github.com/go-gitea/gitea/issues/5572
|
||||
* [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
|
||||
* [Lauris Bukšis-Haberkorns](https://github.com/lafriks) <lauris@nix.lv>
|
||||
* [Matti Ranta](https://github.com/techknowlogick) <techknowlogick@gitea.io>
|
||||
|
||||
- 2020-01-01 ~ 2020-12-31 - https://github.com/go-gitea/gitea/issues/9230
|
||||
- [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
|
||||
- [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) <lauris@nix.lv>
|
||||
- [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.io>
|
||||
* 2018-01-01 ~ 2018-12-31 - https://github.com/go-gitea/gitea/issues/3255
|
||||
* [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
|
||||
* [Lauris Bukšis-Haberkorns](https://github.com/lafriks) <lauris@nix.lv>
|
||||
* [Kim Carlbäcker](https://github.com/bkcsoft) <kim.carlbacker@gmail.com>
|
||||
|
||||
- 2019-01-01 ~ 2019-12-31 - https://github.com/go-gitea/gitea/issues/5572
|
||||
- [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
|
||||
- [Lauris Bukšis-Haberkorns](https://github.com/lafriks) <lauris@nix.lv>
|
||||
- [Matti Ranta](https://github.com/techknowlogick) <techknowlogick@gitea.io>
|
||||
|
||||
- 2018-01-01 ~ 2018-12-31 - https://github.com/go-gitea/gitea/issues/3255
|
||||
- [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
|
||||
- [Lauris Bukšis-Haberkorns](https://github.com/lafriks) <lauris@nix.lv>
|
||||
- [Kim Carlbäcker](https://github.com/bkcsoft) <kim.carlbacker@gmail.com>
|
||||
|
||||
- 2016-11-04 ~ 2017-12-31
|
||||
- [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
|
||||
- [Thomas Boerger](https://github.com/tboerger) <thomas@webhippie.de>
|
||||
- [Kim Carlbäcker](https://github.com/bkcsoft) <kim.carlbacker@gmail.com>
|
||||
* 2016-11-04 ~ 2017-12-31
|
||||
* [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
|
||||
* [Thomas Boerger](https://github.com/tboerger) <thomas@webhippie.de>
|
||||
* [Kim Carlbäcker](https://github.com/bkcsoft) <kim.carlbacker@gmail.com>
|
||||
|
||||
## Versions
|
||||
|
||||
Gitea has the `main` branch as a tip branch and has version branches
|
||||
Gitea has the `master` branch as a tip branch and has version branches
|
||||
such as `release/v0.9`. `release/v0.9` is a release branch and we will
|
||||
tag `v0.9.0` for binary download. If `v0.9.0` has bugs, we will accept
|
||||
pull requests on the `release/v0.9` branch and publish a `v0.9.1` tag,
|
||||
after bringing the bug fix also to the main branch.
|
||||
after bringing the bug fix also to the master branch.
|
||||
|
||||
Since the `main` branch is a tip version, if you wish to use Gitea
|
||||
Since the `master` branch is a tip version, if you wish to use Gitea
|
||||
in production, please download the latest release tag version. All the
|
||||
branches will be protected via GitHub, all the PRs to every branch must
|
||||
be reviewed by two maintainers and must pass the automatic tests.
|
||||
|
||||
## Releasing Gitea
|
||||
|
||||
- Let $vmaj, $vmin and $vpat be Major, Minor and Patch version numbers, $vpat should be rc1, rc2, 0, 1, ...... $vmaj.$vmin will be kept the same as milestones on github or gitea in future.
|
||||
- Before releasing, confirm all the version's milestone issues or PRs has been resolved. Then discuss the release on Discord channel #maintainers and get agreed with almost all the owners and mergers. Or you can declare the version and if nobody against in about serval hours.
|
||||
- If this is a big version first you have to create PR for changelog on branch `main` with PRs with label `changelog` and after it has been merged do following steps:
|
||||
- Create `-dev` tag as `git tag -s -F release.notes v$vmaj.$vmin.0-dev` and push the tag as `git push origin v$vmaj.$vmin.0-dev`.
|
||||
- When CI has finished building tag then you have to create a new branch named `release/v$vmaj.$vmin`
|
||||
- If it is bugfix version create PR for changelog on branch `release/v$vmaj.$vmin` and wait till it is reviewed and merged.
|
||||
- Add a tag as `git tag -s -F release.notes v$vmaj.$vmin.$`, release.notes file could be a temporary file to only include the changelog this version which you added to `CHANGELOG.md`.
|
||||
- And then push the tag as `git push origin v$vmaj.$vmin.$`. Drone CI will automatically create a release and upload all the compiled binary. (But currently it doesn't add the release notes automatically. Maybe we should fix that.)
|
||||
- If needed send a frontport PR for the changelog to branch `main` and update the version in `docs/config.yaml` to refer to the new version.
|
||||
- Send PR to [blog repository](https://gitea.com/gitea/blog) announcing the release.
|
||||
- Verify all release assets were correctly published through CI on dl.gitea.io and GitHub releases. Once ACKed:
|
||||
- bump the version of https://dl.gitea.io/gitea/version.json
|
||||
- merge the blog post PR
|
||||
- announce the release in discord `#announcements`
|
||||
* Let $vmaj, $vmin and $vpat be Major, Minor and Patch version numbers, $vpat should be rc1, rc2, 0, 1, ...... $vmaj.$vmin will be kept the same as milestones on github or gitea in future.
|
||||
* Before releasing, confirm all the version's milestone issues or PRs has been resolved. Then discuss the release on discord channel #maintainers and get agreed with almost all the owners and mergers. Or you can declare the version and if nobody against in about serval hours.
|
||||
* If this is a big version first you have to create PR for changelog on branch `master` with PRs with label `changelog` and after it has been merged do following steps:
|
||||
* Create `-dev` tag as `git tag -s -F release.notes v$vmaj.$vmin.0-dev` and push the tag as `git push origin v$vmaj.$vmin.0-dev`.
|
||||
* When CI has finished building tag then you have to create a new branch named `release/v$vmaj.$vmin`
|
||||
* If it is bugfix version create PR for changelog on branch `release/v$vmaj.$vmin` and wait till it is reviewed and merged.
|
||||
* Add a tag as `git tag -s -F release.notes v$vmaj.$vmin.$`, release.notes file could be a temporary file to only include the changelog this version which you added to `CHANGELOG.md`.
|
||||
* And then push the tag as `git push origin v$vmaj.$vmin.$`. Drone CI will automatically created a release and upload all the compiled binary. (But currently it didn't add the release notes automatically. Maybe we should fix that.)
|
||||
* If needed send PR for changelog on branch `master`.
|
||||
* Send PR to [blog repository](https://gitea.com/gitea/blog) announcing the release.
|
||||
|
||||
## Copyright
|
||||
|
||||
Code that you contribute should use the standard copyright header:
|
||||
|
||||
```
|
||||
// Copyright <year> The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
```
|
||||
|
||||
Files in the repository contain copyright from the year they are added
|
||||
|
||||
4
DCO
4
DCO
@@ -2,6 +2,8 @@ Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
660 York Street, Suite 102,
|
||||
San Francisco, CA 94110 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
@@ -31,4 +33,4 @@ By making a contribution to this project, I certify that:
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
this project or the open source license(s) involved.
|
||||
17
Dockerfile
17
Dockerfile
@@ -1,5 +1,7 @@
|
||||
|
||||
###################################
|
||||
#Build stage
|
||||
FROM golang:1.20-alpine3.17 AS build-env
|
||||
FROM golang:1.15-alpine3.12 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY ${GOPROXY:-direct}
|
||||
@@ -20,10 +22,7 @@ WORKDIR ${GOPATH}/src/code.gitea.io/gitea
|
||||
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
|
||||
&& make clean-all build
|
||||
|
||||
# Begin env-to-ini build
|
||||
RUN go build contrib/environment-to-ini/environment-to-ini.go
|
||||
|
||||
FROM alpine:3.17
|
||||
FROM alpine:3.12
|
||||
LABEL maintainer="maintainers@gitea.io"
|
||||
|
||||
EXPOSE 22 3000
|
||||
@@ -51,7 +50,7 @@ RUN addgroup \
|
||||
-u 1000 \
|
||||
-G git \
|
||||
git && \
|
||||
echo "git:*" | chpasswd -e
|
||||
echo "git:$(dd if=/dev/urandom bs=24 count=1 status=none | base64)" | chpasswd
|
||||
|
||||
ENV USER git
|
||||
ENV GITEA_CUSTOM /data/gitea
|
||||
@@ -63,8 +62,4 @@ CMD ["/bin/s6-svscan", "/etc/s6"]
|
||||
|
||||
COPY docker/root /
|
||||
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
||||
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
|
||||
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
|
||||
RUN chmod 755 /usr/bin/entrypoint /app/gitea/gitea /usr/local/bin/gitea /usr/local/bin/environment-to-ini
|
||||
RUN chmod 755 /etc/s6/gitea/* /etc/s6/openssh/* /etc/s6/.s6-svscan/*
|
||||
RUN chmod 644 /etc/profile.d/gitea_bash_autocomplete.sh
|
||||
RUN ln -s /app/gitea/gitea /usr/local/bin/gitea
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
#Build stage
|
||||
FROM golang:1.20-alpine3.17 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY ${GOPROXY:-direct}
|
||||
|
||||
ARG GITEA_VERSION
|
||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
||||
ENV TAGS "bindata timetzdata $TAGS"
|
||||
ARG CGO_EXTRA_CFLAGS
|
||||
|
||||
#Build deps
|
||||
RUN apk --no-cache add build-base git nodejs npm
|
||||
|
||||
#Setup repo
|
||||
COPY . ${GOPATH}/src/code.gitea.io/gitea
|
||||
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
|
||||
|
||||
#Checkout version if set
|
||||
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
|
||||
&& make clean-all build
|
||||
|
||||
# Begin env-to-ini build
|
||||
RUN go build contrib/environment-to-ini/environment-to-ini.go
|
||||
|
||||
FROM alpine:3.17
|
||||
LABEL maintainer="maintainers@gitea.io"
|
||||
|
||||
EXPOSE 2222 3000
|
||||
|
||||
RUN apk --no-cache add \
|
||||
bash \
|
||||
ca-certificates \
|
||||
dumb-init \
|
||||
gettext \
|
||||
git \
|
||||
curl \
|
||||
gnupg
|
||||
|
||||
RUN addgroup \
|
||||
-S -g 1000 \
|
||||
git && \
|
||||
adduser \
|
||||
-S -H -D \
|
||||
-h /var/lib/gitea/git \
|
||||
-s /bin/bash \
|
||||
-u 1000 \
|
||||
-G git \
|
||||
git
|
||||
|
||||
RUN mkdir -p /var/lib/gitea /etc/gitea
|
||||
RUN chown git:git /var/lib/gitea /etc/gitea
|
||||
|
||||
COPY docker/rootless /
|
||||
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
||||
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
|
||||
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
|
||||
RUN chmod 755 /usr/local/bin/docker-entrypoint.sh /usr/local/bin/docker-setup.sh /app/gitea/gitea /usr/local/bin/gitea /usr/local/bin/environment-to-ini
|
||||
RUN chmod 644 /etc/profile.d/gitea_bash_autocomplete.sh
|
||||
|
||||
#git:git
|
||||
USER 1000:1000
|
||||
ENV GITEA_WORK_DIR /var/lib/gitea
|
||||
ENV GITEA_CUSTOM /var/lib/gitea/custom
|
||||
ENV GITEA_TEMP /tmp/gitea
|
||||
ENV TMPDIR /tmp/gitea
|
||||
|
||||
#TODO add to docs the ability to define the ini to load (useful to test and revert a config)
|
||||
ENV GITEA_APP_INI /etc/gitea/app.ini
|
||||
ENV HOME "/var/lib/gitea/git"
|
||||
VOLUME ["/var/lib/gitea", "/etc/gitea"]
|
||||
WORKDIR /var/lib/gitea
|
||||
|
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"]
|
||||
CMD []
|
||||
|
||||
19
MAINTAINERS
19
MAINTAINERS
@@ -1,4 +1,5 @@
|
||||
Alexey Makhov <amakhov@avito.ru> (@makhov)
|
||||
Andrey Nering <andrey.nering@gmail.com> (@andreynering)
|
||||
Bo-Yi Wu <appleboy.tw@gmail.com> (@appleboy)
|
||||
Ethan Koenig <ethantkoenig@gmail.com> (@ethantkoenig)
|
||||
Kees de Vries <bouwko@gmail.com> (@Bwko)
|
||||
@@ -6,6 +7,7 @@ Kim Carlbäcker <kim.carlbacker@gmail.com> (@bkcsoft)
|
||||
LefsFlare <nobody@nobody.tld> (@LefsFlarey)
|
||||
Lunny Xiao <xiaolunwen@gmail.com> (@lunny)
|
||||
Matthias Loibl <mail@matthiasloibl.com> (@metalmatze)
|
||||
Morgan Bazalgette <the@howl.moe> (@thehowl)
|
||||
Rachid Zarouali <nobody@nobody.tld> (@xinity)
|
||||
Rémy Boulanouar <admin@dblk.org> (@DblK)
|
||||
Sandro Santilli <strk@kbt.io> (@strk)
|
||||
@@ -34,19 +36,4 @@ Mura Li <typeless@ctli.io> (@typeless)
|
||||
6543 <6543@obermui.de> (@6543)
|
||||
jaqra <jaqra@hotmail.com> (@jaqra)
|
||||
David Svantesson <davidsvantesson@gmail.com> (@davidsvantesson)
|
||||
a1012112796 <1012112796@qq.com> (@a1012112796)
|
||||
Karl Heinz Marbaise <kama@soebes.de> (@khmarbaise)
|
||||
Norwin Roosen <git@nroo.de> (@noerw)
|
||||
Kyle Dumont <kdumontnu@gmail.com> (@kdumontnu)
|
||||
Patrick Schratz <patrick.schratz@gmail.com> (@pat-s)
|
||||
Janis Estelmann <admin@oldschoolhack.me> (@KN4CK3R)
|
||||
Steven Kriegler <sk.bunsenbrenner@gmail.com> (@justusbunsi)
|
||||
Jimmy Praet <jimmy.praet@telenet.be> (@jpraet)
|
||||
Leon Hofmeister <dev.lh@web.de> (@delvh)
|
||||
Wim <wim@42.be> (@42wim)
|
||||
Xinyu Zhou <i@sourcehut.net> (@xin-u)
|
||||
Jason Song <i@wolfogre.com> (@wolfogre)
|
||||
Yarden Shoham <hrsi88@gmail.com> (@yardenshoham)
|
||||
Yu Tian <zettat123@gmail.com> (@Zettat123)
|
||||
Eddie Yang <576951401@qq.com> (@yp05327)
|
||||
Dong Ge <gedong_1994@163.com> (@sillyguodong)
|
||||
CirnoT <gitea.m@i32.pl> (@CirnoT)
|
||||
|
||||
124
README.md
124
README.md
@@ -1,71 +1,32 @@
|
||||
<p align="center">
|
||||
<a href="https://gitea.io/">
|
||||
<img alt="Gitea" src="https://raw.githubusercontent.com/go-gitea/gitea/main/public/img/gitea.svg" width="220"/>
|
||||
</a>
|
||||
</p>
|
||||
<h1 align="center">Gitea - Git with a cup of tea</h1>
|
||||
[简体中文](README_ZH.md)
|
||||
|
||||
<p align="center">
|
||||
<a href="https://drone.gitea.io/go-gitea/gitea" title="Build Status">
|
||||
<img src="https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg?ref=refs/heads/main">
|
||||
</a>
|
||||
<a href="https://discord.gg/Gitea" title="Join the Discord chat at https://discord.gg/Gitea">
|
||||
<img src="https://img.shields.io/discord/322538954119184384.svg">
|
||||
</a>
|
||||
<a href="https://app.codecov.io/gh/go-gitea/gitea" title="Codecov">
|
||||
<img src="https://codecov.io/gh/go-gitea/gitea/branch/main/graph/badge.svg">
|
||||
</a>
|
||||
<a href="https://goreportcard.com/report/code.gitea.io/gitea" title="Go Report Card">
|
||||
<img src="https://goreportcard.com/badge/code.gitea.io/gitea">
|
||||
</a>
|
||||
<a href="https://pkg.go.dev/code.gitea.io/gitea" title="GoDoc">
|
||||
<img src="https://pkg.go.dev/badge/code.gitea.io/gitea?status.svg">
|
||||
</a>
|
||||
<a href="https://github.com/go-gitea/gitea/releases/latest" title="GitHub release">
|
||||
<img src="https://img.shields.io/github/release/go-gitea/gitea.svg">
|
||||
</a>
|
||||
<a href="https://www.codetriage.com/go-gitea/gitea" title="Help Contribute to Open Source">
|
||||
<img src="https://www.codetriage.com/go-gitea/gitea/badges/users.svg">
|
||||
</a>
|
||||
<a href="https://opencollective.com/gitea" title="Become a backer/sponsor of gitea">
|
||||
<img src="https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen">
|
||||
</a>
|
||||
<a href="https://opensource.org/licenses/MIT" title="License: MIT">
|
||||
<img src="https://img.shields.io/badge/License-MIT-blue.svg">
|
||||
</a>
|
||||
<a href="https://gitpod.io/#https://github.com/go-gitea/gitea">
|
||||
<img
|
||||
src="https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod"
|
||||
alt="Contribute with Gitpod"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://crowdin.com/project/gitea" title="Crowdin">
|
||||
<img src="https://badges.crowdin.net/gitea/localized.svg">
|
||||
</a>
|
||||
<a href="https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea&branch=main" title="TODOs">
|
||||
<img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea/main">
|
||||
</a>
|
||||
<a href="https://app.bountysource.com/teams/gitea" title="Bountysource">
|
||||
<img src="https://img.shields.io/bountysource/team/gitea/activity">
|
||||
</a>
|
||||
</p>
|
||||
<h1> <img src="https://raw.githubusercontent.com/go-gitea/gitea/master/public/img/gitea-192.png" alt="logo" width="30" height="30"> Gitea - Git with a cup of tea</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="README_ZH.md">View this document in Chinese</a>
|
||||
</p>
|
||||
[](https://drone.gitea.io/go-gitea/gitea)
|
||||
[](https://discord.gg/Gitea)
|
||||
[](https://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com")
|
||||
[](https://codecov.io/gh/go-gitea/gitea)
|
||||
[](https://goreportcard.com/report/code.gitea.io/gitea)
|
||||
[](https://godoc.org/code.gitea.io/gitea)
|
||||
[](https://github.com/go-gitea/gitea/releases/latest)
|
||||
[](https://www.codetriage.com/go-gitea/gitea)
|
||||
[](https://opencollective.com/gitea)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://crowdin.com/project/gitea)
|
||||
[](https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea)
|
||||
|
||||
## Purpose
|
||||
|
||||
The goal of this project is to make the easiest, fastest, and most
|
||||
painless way of setting up a self-hosted Git service.
|
||||
|
||||
As Gitea is written in Go, it works across **all** the platforms and
|
||||
architectures that are supported by Go, including Linux, macOS, and
|
||||
Windows on x86, amd64, ARM and PowerPC architectures.
|
||||
You can try it out using [the online demo](https://try.gitea.io/).
|
||||
Using Go, this can be done with an independent binary distribution across
|
||||
**all platforms** which Go supports, including Linux, macOS, and Windows
|
||||
on x86, amd64, ARM and PowerPC architectures.
|
||||
Want to try it before doing anything else?
|
||||
Do it [with the online demo](https://try.gitea.io/)!
|
||||
This project has been
|
||||
[forked](https://blog.gitea.io/2016/12/welcome-to-gitea/) from
|
||||
[Gogs](https://gogs.io) since November of 2016, but a lot has changed.
|
||||
[Gogs](https://gogs.io) since 2016.11 but changed a lot.
|
||||
|
||||
## Building
|
||||
|
||||
@@ -73,18 +34,20 @@ From the root of the source tree, run:
|
||||
|
||||
TAGS="bindata" make build
|
||||
|
||||
or if SQLite support is required:
|
||||
or if sqlite support is required:
|
||||
|
||||
TAGS="bindata sqlite sqlite_unlock_notify" make build
|
||||
|
||||
The `build` target is split into two sub-targets:
|
||||
|
||||
- `make backend` which requires [Go Stable](https://go.dev/dl/), required version is defined in [go.mod](/go.mod).
|
||||
- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater and Internet connectivity to download npm dependencies.
|
||||
- `make backend` which requires [Go 1.13](https://golang.org/dl/) or greater.
|
||||
- `make frontend` which requires [Node.js 10.13](https://nodejs.org/en/download/) or greater.
|
||||
|
||||
When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js and Internet connectivity.
|
||||
If pre-built frontend files are present it is possible to only build the backend:
|
||||
|
||||
Parallelism (`make -j <num>`) is not supported.
|
||||
TAGS="bindata" make backend
|
||||
|
||||
Parallelism is not supported for these targets, so please don't include `-j <num>`.
|
||||
|
||||
More info: https://docs.gitea.io/en-us/install-from-source/
|
||||
|
||||
@@ -104,32 +67,19 @@ NOTES:
|
||||
1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.**
|
||||
2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks!
|
||||
|
||||
## Translating
|
||||
|
||||
Translations are done through Crowdin. If you want to translate to a new language ask one of the managers in the Crowdin project to add a new language there.
|
||||
|
||||
You can also just create an issue for adding a language or ask on discord on the #translation channel. If you need context or find some translation issues, you can leave a comment on the string or ask on Discord. For general translation questions there is a section in the docs. Currently a bit empty but we hope to fill it as questions pop up.
|
||||
|
||||
https://docs.gitea.io/en-us/translation-guidelines/
|
||||
|
||||
[](https://crowdin.com/project/gitea)
|
||||
|
||||
## Further information
|
||||
|
||||
For more information and instructions about how to install Gitea, please look at our [documentation](https://docs.gitea.io/en-us/).
|
||||
If you have questions that are not covered by the documentation, you can get in contact with us on our [Discord server](https://discord.gg/Gitea) or create a post in the [discourse forum](https://discourse.gitea.io/).
|
||||
|
||||
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea).
|
||||
|
||||
The Hugo-based documentation theme is hosted at [gitea/theme](https://gitea.com/gitea/theme).
|
||||
|
||||
The official Gitea CLI is developed at [gitea/tea](https://gitea.com/gitea/tea).
|
||||
For more information and instructions about how to install Gitea, please look
|
||||
at our [documentation](https://docs.gitea.io/en-us/). If you have questions
|
||||
that are not covered by the documentation, you can get in contact with us on
|
||||
our [Discord server](https://discord.gg/Gitea),
|
||||
or [forum](https://discourse.gitea.io/)!
|
||||
|
||||
## Authors
|
||||
|
||||
- [Maintainers](https://github.com/orgs/go-gitea/people)
|
||||
- [Contributors](https://github.com/go-gitea/gitea/graphs/contributors)
|
||||
- [Translators](options/locale/TRANSLATORS)
|
||||
* [Maintainers](https://github.com/orgs/go-gitea/people)
|
||||
* [Contributors](https://github.com/go-gitea/gitea/graphs/contributors)
|
||||
* [Translators](options/locale/TRANSLATORS)
|
||||
|
||||
## Backers
|
||||
|
||||
@@ -151,7 +101,6 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
|
||||
<a href="https://opencollective.com/gitea/sponsor/7/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
|
||||
<a href="https://cynkra.com/" target="_blank"><img src="https://images.opencollective.com/cynkra/logo/square/64/192.png"></a>
|
||||
|
||||
## FAQ
|
||||
|
||||
@@ -166,11 +115,10 @@ We're [working on it](https://github.com/go-gitea/gitea/issues/1029).
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License.
|
||||
See the [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) file
|
||||
See the [LICENSE](https://github.com/go-gitea/gitea/blob/master/LICENSE) file
|
||||
for the full license text.
|
||||
|
||||
## Screenshots
|
||||
|
||||
Looking for an overview of the interface? Check it out!
|
||||
|
||||
||||
|
||||
|
||||
73
README_ZH.md
73
README_ZH.md
@@ -1,58 +1,18 @@
|
||||
<p align="center">
|
||||
<a href="https://gitea.io/">
|
||||
<img alt="Gitea" src="https://raw.githubusercontent.com/go-gitea/gitea/main/public/img/gitea.svg" width="220"/>
|
||||
</a>
|
||||
</p>
|
||||
<h1 align="center">Gitea - Git with a cup of tea</h1>
|
||||
[English](README.md)
|
||||
|
||||
<p align="center">
|
||||
<a href="https://drone.gitea.io/go-gitea/gitea" title="Build Status">
|
||||
<img src="https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg?ref=refs/heads/main">
|
||||
</a>
|
||||
<a href="https://discord.gg/Gitea" title="Join the Discord chat at https://discord.gg/Gitea">
|
||||
<img src="https://img.shields.io/discord/322538954119184384.svg">
|
||||
</a>
|
||||
<a href="https://app.codecov.io/gh/go-gitea/gitea" title="Codecov">
|
||||
<img src="https://codecov.io/gh/go-gitea/gitea/branch/main/graph/badge.svg">
|
||||
</a>
|
||||
<a href="https://goreportcard.com/report/code.gitea.io/gitea" title="Go Report Card">
|
||||
<img src="https://goreportcard.com/badge/code.gitea.io/gitea">
|
||||
</a>
|
||||
<a href="https://pkg.go.dev/code.gitea.io/gitea" title="GoDoc">
|
||||
<img src="https://pkg.go.dev/badge/code.gitea.io/gitea?status.svg">
|
||||
</a>
|
||||
<a href="https://github.com/go-gitea/gitea/releases/latest" title="GitHub release">
|
||||
<img src="https://img.shields.io/github/release/go-gitea/gitea.svg">
|
||||
</a>
|
||||
<a href="https://www.codetriage.com/go-gitea/gitea" title="Help Contribute to Open Source">
|
||||
<img src="https://www.codetriage.com/go-gitea/gitea/badges/users.svg">
|
||||
</a>
|
||||
<a href="https://opencollective.com/gitea" title="Become a backer/sponsor of gitea">
|
||||
<img src="https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen">
|
||||
</a>
|
||||
<a href="https://opensource.org/licenses/MIT" title="License: MIT">
|
||||
<img src="https://img.shields.io/badge/License-MIT-blue.svg">
|
||||
</a>
|
||||
<a href="https://gitpod.io/#https://github.com/go-gitea/gitea">
|
||||
<img
|
||||
src="https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod"
|
||||
alt="Contribute with Gitpod"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://crowdin.com/project/gitea" title="Crowdin">
|
||||
<img src="https://badges.crowdin.net/gitea/localized.svg">
|
||||
</a>
|
||||
<a href="https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea&branch=main" title="TODOs">
|
||||
<img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea/main">
|
||||
</a>
|
||||
<a href="https://app.bountysource.com/teams/gitea" title="Bountysource">
|
||||
<img src="https://img.shields.io/bountysource/team/gitea/activity">
|
||||
</a>
|
||||
</p>
|
||||
<h1> <img src="https://raw.githubusercontent.com/go-gitea/gitea/master/public/img/gitea-192.png" alt="logo" width="30" height="30"> Gitea - Git with a cup of tea</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="README.md">View this document in English</a>
|
||||
</p>
|
||||
[](https://drone.gitea.io/go-gitea/gitea)
|
||||
[](https://discord.gg/Gitea)
|
||||
[](https://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com")
|
||||
[](https://codecov.io/gh/go-gitea/gitea)
|
||||
[](https://goreportcard.com/report/code.gitea.io/gitea)
|
||||
[](https://godoc.org/code.gitea.io/gitea)
|
||||
[](https://github.com/go-gitea/gitea/releases/latest)
|
||||
[](https://www.codetriage.com/go-gitea/gitea)
|
||||
[](https://opencollective.com/gitea)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://crowdin.com/project/gitea)
|
||||
|
||||
## 目标
|
||||
|
||||
@@ -74,11 +34,6 @@ Gitea 的首要目标是创建一个极易安装,运行非常快速,安装
|
||||
|
||||
Fork -> Patch -> Push -> Pull Request
|
||||
|
||||
## 翻译
|
||||
|
||||
多语言翻译是基于Crowdin进行的.
|
||||
[](https://crowdin.com/project/gitea)
|
||||
|
||||
## 作者
|
||||
|
||||
* [Maintainers](https://github.com/orgs/go-gitea/people)
|
||||
@@ -87,7 +42,7 @@ Fork -> Patch -> Push -> Pull Request
|
||||
|
||||
## 授权许可
|
||||
|
||||
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件中。
|
||||
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/go-gitea/gitea/blob/master/LICENSE) 文件中。
|
||||
|
||||
## 截图
|
||||
|
||||
|
||||
77
SECURITY.md
77
SECURITY.md
@@ -1,83 +1,10 @@
|
||||
# Reporting security issues
|
||||
|
||||
The Gitea maintainers take security seriously.
|
||||
|
||||
The Gitea maintainers take security seriously.
|
||||
If you discover a security issue, please bring it to their attention right away!
|
||||
|
||||
## Reporting a Vulnerability
|
||||
### Reporting a Vulnerability
|
||||
|
||||
Please **DO NOT** file a public issue, instead send your report privately to `security@gitea.io`.
|
||||
|
||||
## Protecting Security Information
|
||||
|
||||
Due to the sensitive nature of security information, you can use below GPG public key encrypt your mail body.
|
||||
|
||||
The PGP key is valid until June 24, 2024.
|
||||
|
||||
```
|
||||
Key ID: 6FCD2D5B
|
||||
Key Type: RSA
|
||||
Expires: 6/24/2024
|
||||
Key Size: 4096/4096
|
||||
Fingerprint: 3DE0 3D1E 144A 7F06 9359 99DC AAFD 2381 6FCD 2D5B
|
||||
```
|
||||
|
||||
UserID: Gitea Security <security@gitea.io>
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBGK1Z/4BEADFMqXA9DeeChmSxUjF0Be5sq99ZUhgrZjcN/wOzz0wuCJZC0l8
|
||||
4uC+d6mfv7JpJYlzYzOK97/x5UguKHkYNZ6mm1G9KHaXmoIBDLKDzfPdJopVNv2r
|
||||
OajijaE0uMCnMjadlg5pbhMLRQG8a9J32yyaz7ZEAw72Ab31fvvcA53NkuqO4j2w
|
||||
k7dtFQzhbNOYV0VffQT90WDZdalYHB1JHyEQ+70U9OjVD5ggNYSzX98Eu3Hjn7V7
|
||||
kqFrcAxr5TE1elf0IXJcuBJtFzQSTUGlQldKOHtGTGgGjj9r/FFAE5ioBgVD05bV
|
||||
rEEgIMM/GqYaG/nbNpWE6P3mEc2Mnn3pZaRJL0LuF26TLjnqEcMMDp5iIhLdFzXR
|
||||
3tMdtKgQFu+Mtzs3ipwWARYgHyU09RJsI2HeBx7RmZO/Xqrec763Z7zdJ7SpCn0Z
|
||||
q+pHZl24JYR0Kf3T/ZiOC0cGd2QJqpJtg5J6S/OqfX9NH6MsCczO8pUC1N/aHH2X
|
||||
CTme2nF56izORqDWKoiICteL3GpYsCV9nyCidcCmoQsS+DKvE86YhIhVIVWGRY2F
|
||||
lzpAjnN9/KLtQroutrm+Ft0mdjDiJUeFVl1cOHDhoyfCsQh62HumoyZoZvqzQd6e
|
||||
AbN11nq6aViMe2Q3je1AbiBnRnQSHxt1Tc8X4IshO3MQK1Sk7oPI6LA5oQARAQAB
|
||||
tCJHaXRlYSBTZWN1cml0eSA8c2VjdXJpdHlAZ2l0ZWEuaW8+iQJXBBMBCABBFiEE
|
||||
PeA9HhRKfwaTWZncqv0jgW/NLVsFAmK1Z/4CGwMFCQPCZwAFCwkIBwICIgIGFQoJ
|
||||
CAsCBBYCAwECHgcCF4AACgkQqv0jgW/NLVvnyxAAhxyNnWzw/rQO2qhzqicmZM94
|
||||
njSbOg+U2qMBvCdaqCQQeC+uaMmMzkDPanUUmLcyCkWqfCjPNjeSXAkE9npepVJI
|
||||
4HtmgxZQ94OU/h3CLbft+9GVRzUkVI29TSYGdvNtV2/BkNGoFFnKWQr119um0o6A
|
||||
bgha2Uy5uY8o3ZIoiKkiHRaEoWIjjeBxJxYAojsZY4YElUmsQ3ik2joG6rhFesTa
|
||||
ofVt/bL8G2xzpOG26WGIxBbqf2qjV6OtZ0hu/vtTPHeIWMLq0Mz0V3PEDQWfkGPE
|
||||
i2RYxxYDs2xzJhSQWqTNVLSq0m5xTJnbHhQPfdCX4C2jvFKgLdfmytQq49S7jiJb
|
||||
Z03HVOZ/PsyBlQfH9xJi06R5yQCMEA8h8Z5r3/NXW09kQ6OFRe6xshoTcxZGRPTo
|
||||
srhwr3uPbmCRh+YEl7qBLU6+BC5k8IRTZXqhrj/aPJu3MxgbgwV8u3vLoFSXM2lb
|
||||
a61FgeCQ0O7lkgVswwF0RppCaH9Ul3ZDapet/vCRg4NVwm9zOI/8q/Vj0FKA1GDR
|
||||
JhRu8+Ce8zlFL65D34t+PprAzSeTlbv9um3x/ZIjCco7EEKSBylt+AZj/VyA6+e5
|
||||
kjOQwRRc6dFJWBcorsSI2dG+H+QMF7ZabzmeCcz1v9HjLOPzYHoZAHhCmSppWTvX
|
||||
AJy6+lhfW2OUTqQeYSi5Ag0EYrVn/gEQALrFLQjCR3GjuHSindz0rd3Fnx/t7Sen
|
||||
T+p07yCSSoSlmnJHCQmwh4vfg1blyz0zZ4vkIhtpHsEgc+ZAG+WQXSsJ2iRz+eSN
|
||||
GwoOQl4XC3n+QWkc1ws+btr48+6UqXIQU+F8TPQyx/PIgi2nZXJB7f5+mjCqsk46
|
||||
XvH4nTr4kJjuqMSR/++wvre2qNQRa/q/dTsK0OaN/mJsdX6Oi+aGNaQJUhIG7F+E
|
||||
ZDMkn/O6xnwWNzy/+bpg43qH/Gk0eakOmz5NmQLRkV58SZLiJvuCUtkttf6CyhnX
|
||||
03OcWaajv5W8qA39dBYQgDrrPbBWUnwfO3yMveqhwV4JjDoe8sPAyn1NwzakNYqP
|
||||
RzsWyLrLS7R7J9s3FkZXhQw/QQcsaSMcGNQO047dm1P83N8JY5aEpiRo9zSWjoiw
|
||||
qoExANj5lUTZPe8M50lI182FrcjAN7dClO3QI6pg7wy0erMxfFly3j8UQ91ysS9T
|
||||
s+GsP9I3cmWWQcKYxWHtE8xTXnNCVPFZQj2nwhJzae8ypfOtulBRA3dUKWGKuDH/
|
||||
axFENhUsT397aOU3qkP/od4a64JyNIEo4CTTSPVeWd7njsGqli2U3A4xL2CcyYvt
|
||||
D/MWcMBGEoLSNTswwKdom4FaJpn5KThnK/T0bQcmJblJhoCtppXisbexZnCpuS0x
|
||||
Zdlm2T14KJ3LABEBAAGJAjwEGAEIACYWIQQ94D0eFEp/BpNZmdyq/SOBb80tWwUC
|
||||
YrVn/gIbDAUJA8JnAAAKCRCq/SOBb80tWyTBD/9AGpW6QoDF7zYjHAozH9S5RGCA
|
||||
Y7E82dG/0xmFUwPprAG0BKmmgU6TiipyVGmKIXGYYYU92pMnbvXkYQMoa+WJNncN
|
||||
D3fY52UeXeffTf4cFpStlzi9xgYtOLhFamzYu/4xhkjOX+xhOSXscCiFRyT8cF3B
|
||||
O6c5BHU+Zj0/rGPgOyPUbx7l7B9MubB/41nNX35k08e+8T3wtWDb4XF+15HnRfva
|
||||
6fblO8wgU25Orv2Rm1jnKGa9DxJ8nE40IMrqDapENtDuL+zKJsvR0+ptWvEyL56U
|
||||
GtJJG5un6mXiLKuRQT0DEv4MdZRHDgDstDnqcbEiazVEbUuvhZZob6lRY2A19m1+
|
||||
7zfnDxkhqCA1RCnv4fdvcPdCMMFHwLpdhjgW0aI/uwgwrvsEz5+JRlnLvdQHlPAg
|
||||
q7l2fGcBSpz9U0ayyfRPjPntsNCtZl1UDxGLeciPkZhyG84zEWQbk/j52ZpRN+Ik
|
||||
ALpRLa8RBFmFSmXDUmwQrmm1EmARyQXwweKU31hf8ZGbCp2lPuRYm1LuGiirXSVP
|
||||
GysjRAJgW+VRpBKOzFQoUAUbReVWSaCwT8s17THzf71DdDb6CTj31jMLLYWwBpA/
|
||||
i73DgobDZMIGEZZC1EKqza8eh11xfyHFzGec03tbh+lIen+5IiRtWiEWkDS9ll0G
|
||||
zgS/ZdziCvdAutqnGA==
|
||||
=gZWO
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
```
|
||||
|
||||
Security reports are greatly appreciated and we will publicly thank you for it, although we keep your name confidential if you request it.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" id="main_outline" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" viewBox="0 0 640 640" style="enable-background:new 0 0 640 640;" xml:space="preserve">
|
||||
<g>
|
||||
<path id="teabag" style="fill:#FFFFFF" d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8
|
||||
c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4
|
||||
c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z"/>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#609926" d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2
|
||||
c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5
|
||||
c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5
|
||||
c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3
|
||||
c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1
|
||||
C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4
|
||||
c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7
|
||||
S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55
|
||||
c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8
|
||||
l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z"/>
|
||||
<path style="fill:#609926" d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4
|
||||
c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1
|
||||
c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9
|
||||
c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3
|
||||
c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3
|
||||
c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29
|
||||
c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8
|
||||
C343.2,346.5,335,363.3,326.8,380.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
File diff suppressed because one or more lines are too long
192
assets/logo.svg
192
assets/logo.svg
@@ -1,31 +1,161 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" id="main_outline" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" viewBox="0 0 640 640" style="enable-background:new 0 0 640 640;" xml:space="preserve">
|
||||
<g>
|
||||
<path id="teabag" style="fill:#FFFFFF" d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8
|
||||
c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4
|
||||
c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z"/>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#609926" d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2
|
||||
c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5
|
||||
c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5
|
||||
c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3
|
||||
c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1
|
||||
C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4
|
||||
c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7
|
||||
S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55
|
||||
c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8
|
||||
l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z"/>
|
||||
<path style="fill:#609926" d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4
|
||||
c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1
|
||||
c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9
|
||||
c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3
|
||||
c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3
|
||||
c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29
|
||||
c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8
|
||||
C343.2,346.5,335,363.3,326.8,380.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 135.46667 135.46667"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="logo.svg"
|
||||
inkscape:version="0.92.1 r15371"
|
||||
inkscape:export-filename=""
|
||||
inkscape:export-xdpi="48.000004"
|
||||
inkscape:export-ydpi="48.000004">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.70710678"
|
||||
inkscape:cx="418.13805"
|
||||
inkscape:cy="177.57445"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer2"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
width="256px"
|
||||
showguides="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1137"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:pagecheckerboard="false"
|
||||
inkscape:measure-start="283.373,243.952"
|
||||
inkscape:measure-end="290.267,236.527">
|
||||
<sodipodi:guide
|
||||
position="0,0"
|
||||
orientation="0,512"
|
||||
id="guide3699"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
position="135.46667,0"
|
||||
orientation="-512,0"
|
||||
id="guide3701"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
position="135.46667,135.46667"
|
||||
orientation="0,-512"
|
||||
id="guide3703"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
position="0,135.46667"
|
||||
orientation="512,0"
|
||||
id="guide3705"
|
||||
inkscape:locked="false" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-161.53334)"
|
||||
style="display:inline">
|
||||
<path
|
||||
style="fill:#609926;fill-opacity:1;stroke:#428f29;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 27.709937,195.15095 c -9.546573,-0.0272 -22.3392732,6.79805 -21.6317552,23.90397 1.105534,26.72889 25.4565952,29.20839 35.1916502,29.42301 1.068023,5.01357 12.521798,22.30563 21.001818,23.21667 h 37.15277 c 22.27763,-1.66785 38.9607,-75.75671 26.59321,-76.03825 -46.781583,2.47691 -49.995146,2.13838 -88.599758,0 -2.495053,-0.0266 -5.972321,-0.49474 -9.707935,-0.5054 z m 2.491319,9.45886 c 1.351378,13.69267 3.555849,21.70359 8.018216,33.94345 -11.382872,-1.50473 -21.069822,-5.22443 -22.851515,-19.10984 -0.950962,-7.4112 2.390428,-15.16769 14.833299,-14.83361 z"
|
||||
id="path3722"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sscccccsccsc" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Layer 2"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.24757317;stroke-opacity:1"
|
||||
id="rect4599"
|
||||
width="34.762054"
|
||||
height="34.762054"
|
||||
x="87.508659"
|
||||
y="18.291576"
|
||||
transform="rotate(25.914715)"
|
||||
ry="5.4825778" />
|
||||
<path
|
||||
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26644793px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 79.804947,57.359056 3.241146,1.609954 V 35.255731 h -3.262698 z"
|
||||
id="path4525"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
class="detail-remove"
|
||||
inkscape:label="Layer 3"
|
||||
style="display:inline">
|
||||
<g
|
||||
style="display:inline"
|
||||
id="g4539">
|
||||
<circle
|
||||
transform="rotate(-19.796137)"
|
||||
r="3.4745038"
|
||||
cy="90.077766"
|
||||
cx="49.064713"
|
||||
id="path4606"
|
||||
style="fill:#609926;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" />
|
||||
<circle
|
||||
transform="rotate(-19.796137)"
|
||||
r="3.4745038"
|
||||
cy="102.1049"
|
||||
cx="36.810425"
|
||||
id="path4606-3"
|
||||
style="fill:#609926;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" />
|
||||
<circle
|
||||
transform="rotate(-19.796137)"
|
||||
r="3.4745038"
|
||||
cy="111.43928"
|
||||
cx="46.484283"
|
||||
id="path4606-1"
|
||||
style="fill:#609926;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" />
|
||||
<rect
|
||||
transform="rotate(26.024158)"
|
||||
y="18.061695"
|
||||
x="97.333458"
|
||||
height="27.261492"
|
||||
width="2.6726954"
|
||||
id="rect4629-8"
|
||||
style="fill:#609926;fill-opacity:1;stroke:none;stroke-width:0.27444693;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4514"
|
||||
d="m 76.558096,68.116343 c 12.97589,6.395378 13.012989,4.101862 4.890858,20.907244"
|
||||
style="fill:none;stroke:#609926;stroke-width:2.68000007;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 5.6 KiB |
14
build.go
14
build.go
@@ -1,8 +1,8 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
||||
//go:build vendor
|
||||
//+build vendor
|
||||
|
||||
package main
|
||||
|
||||
@@ -10,6 +10,14 @@ package main
|
||||
// These libraries will not be included in a normal compilation.
|
||||
|
||||
import (
|
||||
// for lint
|
||||
_ "github.com/mgechev/dots"
|
||||
_ "github.com/mgechev/revive/formatter"
|
||||
_ "github.com/mgechev/revive/lint"
|
||||
_ "github.com/mgechev/revive/rule"
|
||||
_ "github.com/mitchellh/go-homedir"
|
||||
_ "github.com/pelletier/go-toml"
|
||||
|
||||
// for embed
|
||||
_ "github.com/shurcooL/vfsgen"
|
||||
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/build/codeformat"
|
||||
)
|
||||
|
||||
// Windows has a limitation for command line arguments, the size can not exceed 32KB.
|
||||
// So we have to feed the files to some tools (like gofmt) batch by batch
|
||||
|
||||
// We also introduce a `gitea-fmt` command, it does better import formatting than gofmt/goimports. `gitea-fmt` calls `gofmt` internally.
|
||||
|
||||
var optionLogVerbose bool
|
||||
|
||||
func logVerbose(msg string, args ...interface{}) {
|
||||
if optionLogVerbose {
|
||||
log.Printf(msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func passThroughCmd(cmd string, args []string) error {
|
||||
foundCmd, err := exec.LookPath(cmd)
|
||||
if err != nil {
|
||||
log.Fatalf("can not find cmd: %s", cmd)
|
||||
}
|
||||
c := exec.Cmd{
|
||||
Path: foundCmd,
|
||||
Args: append([]string{cmd}, args...),
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
return c.Run()
|
||||
}
|
||||
|
||||
type fileCollector struct {
|
||||
dirs []string
|
||||
includePatterns []*regexp.Regexp
|
||||
excludePatterns []*regexp.Regexp
|
||||
batchSize int
|
||||
}
|
||||
|
||||
func newFileCollector(fileFilter string, batchSize int) (*fileCollector, error) {
|
||||
co := &fileCollector{batchSize: batchSize}
|
||||
if fileFilter == "go-own" {
|
||||
co.dirs = []string{
|
||||
"build",
|
||||
"cmd",
|
||||
"contrib",
|
||||
"tests",
|
||||
"models",
|
||||
"modules",
|
||||
"routers",
|
||||
"services",
|
||||
"tools",
|
||||
}
|
||||
co.includePatterns = append(co.includePatterns, regexp.MustCompile(`.*\.go$`))
|
||||
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`.*\bbindata\.go$`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/gitea-repositories-meta`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/integration/migration-test`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/fixtures`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/migrations/fixtures`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`services/gitdiff/testdata`))
|
||||
}
|
||||
|
||||
if co.dirs == nil {
|
||||
return nil, fmt.Errorf("unknown file-filter: %s", fileFilter)
|
||||
}
|
||||
return co, nil
|
||||
}
|
||||
|
||||
func (fc *fileCollector) matchPatterns(path string, regexps []*regexp.Regexp) bool {
|
||||
path = strings.ReplaceAll(path, "\\", "/")
|
||||
for _, re := range regexps {
|
||||
if re.MatchString(path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (fc *fileCollector) collectFiles() (res [][]string, err error) {
|
||||
var batch []string
|
||||
for _, dir := range fc.dirs {
|
||||
err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
|
||||
include := len(fc.includePatterns) == 0 || fc.matchPatterns(path, fc.includePatterns)
|
||||
exclude := fc.matchPatterns(path, fc.excludePatterns)
|
||||
process := include && !exclude
|
||||
if !process {
|
||||
if d.IsDir() {
|
||||
if exclude {
|
||||
logVerbose("exclude dir %s", path)
|
||||
return filepath.SkipDir
|
||||
}
|
||||
// for a directory, if it is not excluded explicitly, we should walk into
|
||||
return nil
|
||||
}
|
||||
// for a file, we skip it if it shouldn't be processed
|
||||
logVerbose("skip process %s", path)
|
||||
return nil
|
||||
}
|
||||
if d.IsDir() {
|
||||
// skip dir, we don't add dirs to the file list now
|
||||
return nil
|
||||
}
|
||||
if len(batch) >= fc.batchSize {
|
||||
res = append(res, batch)
|
||||
batch = nil
|
||||
}
|
||||
batch = append(batch, path)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
res = append(res, batch)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// substArgFiles expands the {file-list} to a real file list for commands
|
||||
func substArgFiles(args, files []string) []string {
|
||||
for i, s := range args {
|
||||
if s == "{file-list}" {
|
||||
newArgs := append(args[:i], files...)
|
||||
newArgs = append(newArgs, args[i+1:]...)
|
||||
return newArgs
|
||||
}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func exitWithCmdErrors(subCmd string, subArgs []string, cmdErrors []error) {
|
||||
for _, err := range cmdErrors {
|
||||
if err != nil {
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
exitCode := exitError.ExitCode()
|
||||
log.Printf("run command failed (code=%d): %s %v", exitCode, subCmd, subArgs)
|
||||
os.Exit(exitCode)
|
||||
} else {
|
||||
log.Fatalf("run command failed (err=%s) %s %v", err, subCmd, subArgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseArgs() (mainOptions map[string]string, subCmd string, subArgs []string) {
|
||||
mainOptions = map[string]string{}
|
||||
for i := 1; i < len(os.Args); i++ {
|
||||
arg := os.Args[i]
|
||||
if arg == "" {
|
||||
break
|
||||
}
|
||||
if arg[0] == '-' {
|
||||
arg = strings.TrimPrefix(arg, "-")
|
||||
arg = strings.TrimPrefix(arg, "-")
|
||||
fields := strings.SplitN(arg, "=", 2)
|
||||
if len(fields) == 1 {
|
||||
mainOptions[fields[0]] = "1"
|
||||
} else {
|
||||
mainOptions[fields[0]] = fields[1]
|
||||
}
|
||||
} else {
|
||||
subCmd = arg
|
||||
subArgs = os.Args[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func showUsage() {
|
||||
fmt.Printf(`Usage: %[1]s [options] {command} [arguments]
|
||||
|
||||
Options:
|
||||
--verbose
|
||||
--file-filter=go-own
|
||||
--batch-size=100
|
||||
|
||||
Commands:
|
||||
%[1]s gofmt ...
|
||||
|
||||
Arguments:
|
||||
{file-list} the file list
|
||||
|
||||
Example:
|
||||
%[1]s gofmt -s -d {file-list}
|
||||
|
||||
`, "file-batch-exec")
|
||||
}
|
||||
|
||||
func getGoVersion() string {
|
||||
goModFile, err := os.ReadFile("go.mod")
|
||||
if err != nil {
|
||||
log.Fatalf(`Faild to read "go.mod": %v`, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
goModVersionRegex := regexp.MustCompile(`go \d+\.\d+`)
|
||||
goModVersionLine := goModVersionRegex.Find(goModFile)
|
||||
return string(goModVersionLine[3:])
|
||||
}
|
||||
|
||||
func newFileCollectorFromMainOptions(mainOptions map[string]string) (fc *fileCollector, err error) {
|
||||
fileFilter := mainOptions["file-filter"]
|
||||
if fileFilter == "" {
|
||||
fileFilter = "go-own"
|
||||
}
|
||||
batchSize, _ := strconv.Atoi(mainOptions["batch-size"])
|
||||
if batchSize == 0 {
|
||||
batchSize = 100
|
||||
}
|
||||
|
||||
return newFileCollector(fileFilter, batchSize)
|
||||
}
|
||||
|
||||
func containsString(a []string, s string) bool {
|
||||
for _, v := range a {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func giteaFormatGoImports(files []string, doWriteFile bool) error {
|
||||
for _, file := range files {
|
||||
if err := codeformat.FormatGoImports(file, doWriteFile); err != nil {
|
||||
log.Printf("failed to format go imports: %s, err=%v", file, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
mainOptions, subCmd, subArgs := parseArgs()
|
||||
if subCmd == "" {
|
||||
showUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
optionLogVerbose = mainOptions["verbose"] != ""
|
||||
|
||||
fc, err := newFileCollectorFromMainOptions(mainOptions)
|
||||
if err != nil {
|
||||
log.Fatalf("can not create file collector: %s", err.Error())
|
||||
}
|
||||
|
||||
fileBatches, err := fc.collectFiles()
|
||||
if err != nil {
|
||||
log.Fatalf("can not collect files: %s", err.Error())
|
||||
}
|
||||
|
||||
processed := 0
|
||||
var cmdErrors []error
|
||||
for _, files := range fileBatches {
|
||||
if len(files) == 0 {
|
||||
break
|
||||
}
|
||||
substArgs := substArgFiles(subArgs, files)
|
||||
logVerbose("batch cmd: %s %v", subCmd, substArgs)
|
||||
switch subCmd {
|
||||
case "gitea-fmt":
|
||||
if containsString(subArgs, "-d") {
|
||||
log.Print("the -d option is not supported by gitea-fmt")
|
||||
}
|
||||
cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-w")))
|
||||
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra", "-lang", getGoVersion()}, substArgs...)))
|
||||
default:
|
||||
log.Fatalf("unknown cmd: %s %v", subCmd, subArgs)
|
||||
}
|
||||
processed += len(files)
|
||||
}
|
||||
|
||||
logVerbose("processed %d files", processed)
|
||||
exitWithCmdErrors(subCmd, subArgs, cmdErrors)
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package codeformat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var importPackageGroupOrders = map[string]int{
|
||||
"": 1, // internal
|
||||
"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 (
|
||||
importBlockBegin = []byte("\nimport (\n")
|
||||
importBlockEnd = []byte("\n)")
|
||||
)
|
||||
|
||||
type importLineParsed struct {
|
||||
group string
|
||||
pkg string
|
||||
content string
|
||||
}
|
||||
|
||||
func parseImportLine(line string) (*importLineParsed, error) {
|
||||
il := &importLineParsed{content: line}
|
||||
p1 := strings.IndexRune(line, '"')
|
||||
if p1 == -1 {
|
||||
return nil, errors.New("invalid import line: " + line)
|
||||
}
|
||||
p1++
|
||||
p := strings.IndexRune(line[p1:], '"')
|
||||
if p == -1 {
|
||||
return nil, errors.New("invalid import line: " + line)
|
||||
}
|
||||
p2 := p1 + p
|
||||
il.pkg = line[p1:p2]
|
||||
|
||||
pDot := strings.IndexRune(il.pkg, '.')
|
||||
pSlash := strings.IndexRune(il.pkg, '/')
|
||||
if pDot != -1 && pDot < pSlash {
|
||||
il.group = "domain-package"
|
||||
}
|
||||
for groupName := range importPackageGroupOrders {
|
||||
if groupName == "" {
|
||||
continue // skip internal
|
||||
}
|
||||
if strings.HasPrefix(il.pkg, groupName) {
|
||||
il.group = groupName
|
||||
}
|
||||
}
|
||||
return il, nil
|
||||
}
|
||||
|
||||
type (
|
||||
importLineGroup []*importLineParsed
|
||||
importLineGroupMap map[string]importLineGroup
|
||||
)
|
||||
|
||||
func formatGoImports(contentBytes []byte) ([]byte, error) {
|
||||
p1 := bytes.Index(contentBytes, importBlockBegin)
|
||||
if p1 == -1 {
|
||||
return nil, nil
|
||||
}
|
||||
p1 += len(importBlockBegin)
|
||||
p := bytes.Index(contentBytes[p1:], importBlockEnd)
|
||||
if p == -1 {
|
||||
return nil, nil
|
||||
}
|
||||
p2 := p1 + p
|
||||
|
||||
importGroups := importLineGroupMap{}
|
||||
r := bytes.NewBuffer(contentBytes[p1:p2])
|
||||
eof := false
|
||||
for !eof {
|
||||
line, err := r.ReadString('\n')
|
||||
eof = err == io.EOF
|
||||
if err != nil && !eof {
|
||||
return nil, err
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" {
|
||||
if strings.HasPrefix(line, "//") || strings.HasPrefix(line, "/*") {
|
||||
return nil, errInvalidCommentBetweenImports
|
||||
}
|
||||
importLine, err := parseImportLine(line)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
importGroups[importLine.group] = append(importGroups[importLine.group], importLine)
|
||||
}
|
||||
}
|
||||
|
||||
var groupNames []string
|
||||
for groupName, importLines := range importGroups {
|
||||
groupNames = append(groupNames, groupName)
|
||||
sort.Slice(importLines, func(i, j int) bool {
|
||||
return strings.Compare(importLines[i].pkg, importLines[j].pkg) < 0
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(groupNames, func(i, j int) bool {
|
||||
n1 := groupNames[i]
|
||||
n2 := groupNames[j]
|
||||
o1 := importPackageGroupOrders[n1]
|
||||
o2 := importPackageGroupOrders[n2]
|
||||
if o1 != 0 && o2 != 0 {
|
||||
return o1 < o2
|
||||
}
|
||||
if o1 == 0 && o2 == 0 {
|
||||
return strings.Compare(n1, n2) < 0
|
||||
}
|
||||
return o1 != 0
|
||||
})
|
||||
|
||||
formattedBlock := bytes.Buffer{}
|
||||
for _, groupName := range groupNames {
|
||||
hasNormalImports := false
|
||||
hasDummyImports := false
|
||||
// non-dummy import comes first
|
||||
for _, importLine := range importGroups[groupName] {
|
||||
if strings.HasPrefix(importLine.content, "_") {
|
||||
hasDummyImports = true
|
||||
} else {
|
||||
formattedBlock.WriteString("\t" + importLine.content + "\n")
|
||||
hasNormalImports = true
|
||||
}
|
||||
}
|
||||
// dummy (_ "pkg") comes later
|
||||
if hasDummyImports {
|
||||
if hasNormalImports {
|
||||
formattedBlock.WriteString("\n")
|
||||
}
|
||||
for _, importLine := range importGroups[groupName] {
|
||||
if strings.HasPrefix(importLine.content, "_") {
|
||||
formattedBlock.WriteString("\t" + importLine.content + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
formattedBlock.WriteString("\n")
|
||||
}
|
||||
formattedBlockBytes := bytes.TrimRight(formattedBlock.Bytes(), "\n")
|
||||
|
||||
var formattedBytes []byte
|
||||
formattedBytes = append(formattedBytes, contentBytes[:p1]...)
|
||||
formattedBytes = append(formattedBytes, formattedBlockBytes...)
|
||||
formattedBytes = append(formattedBytes, contentBytes[p2:]...)
|
||||
return formattedBytes, nil
|
||||
}
|
||||
|
||||
// FormatGoImports format the imports by our rules (see unit tests)
|
||||
func FormatGoImports(file string, doWriteFile bool) error {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var contentBytes []byte
|
||||
{
|
||||
defer f.Close()
|
||||
contentBytes, err = io.ReadAll(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
formattedBytes, err := formatGoImports(contentBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if formattedBytes == nil {
|
||||
return nil
|
||||
}
|
||||
if bytes.Equal(contentBytes, formattedBytes) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if doWriteFile {
|
||||
f, err = os.OpenFile(file, os.O_TRUNC|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.Write(formattedBytes)
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package codeformat
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFormatImportsSimple(t *testing.T) {
|
||||
formatted, err := formatGoImports([]byte(`
|
||||
package codeformat
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
`))
|
||||
|
||||
expected := `
|
||||
package codeformat
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
`
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, string(formatted))
|
||||
}
|
||||
|
||||
func TestFormatImportsGroup(t *testing.T) {
|
||||
// gofmt/goimports won't group the packages, for example, they produce such code:
|
||||
// "bytes"
|
||||
// "image"
|
||||
// (a blank line)
|
||||
// "fmt"
|
||||
// "image/color/palette"
|
||||
// our formatter does better, and these packages are grouped into one.
|
||||
|
||||
formatted, err := formatGoImports([]byte(`
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
_ "image/gif" // for processing gif images
|
||||
_ "image/jpeg" // for processing jpeg images
|
||||
_ "image/png" // for processing png images
|
||||
|
||||
"code.gitea.io/other/package"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/the/package"
|
||||
|
||||
"github.com/issue9/identicon"
|
||||
"github.com/nfnt/resize"
|
||||
"github.com/oliamb/cutter"
|
||||
)
|
||||
`))
|
||||
|
||||
expected := `
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
_ "image/gif" // for processing gif images
|
||||
_ "image/jpeg" // for processing jpeg images
|
||||
_ "image/png" // for processing png images
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"code.gitea.io/other/package"
|
||||
"github.com/issue9/identicon"
|
||||
"github.com/nfnt/resize"
|
||||
"github.com/oliamb/cutter"
|
||||
"xorm.io/the/package"
|
||||
)
|
||||
`
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, string(formatted))
|
||||
}
|
||||
|
||||
func TestFormatImportsInvalidComment(t *testing.T) {
|
||||
// why we shouldn't write comments between imports: it breaks the grouping of imports
|
||||
// for example:
|
||||
// "pkg1"
|
||||
// "pkg2"
|
||||
// // a comment
|
||||
// "pkgA"
|
||||
// "pkgB"
|
||||
// the comment splits the packages into two groups, pkg1/2 are sorted separately, pkgA/B are sorted separately
|
||||
// we don't want such code, so the code should be:
|
||||
// "pkg1"
|
||||
// "pkg2"
|
||||
// "pkgA" // a comment
|
||||
// "pkgB"
|
||||
|
||||
_, err := formatGoImports([]byte(`
|
||||
package test
|
||||
|
||||
import (
|
||||
"image/jpeg"
|
||||
// for processing gif images
|
||||
"image/gif"
|
||||
)
|
||||
`))
|
||||
assert.ErrorIs(t, err, errInvalidCommentBetweenImports)
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -18,29 +20,25 @@ import (
|
||||
"github.com/shurcooL/vfsgen"
|
||||
)
|
||||
|
||||
func needsUpdate(dir, filename string) (bool, []byte) {
|
||||
func needsUpdate(dir string, filename string) (bool, []byte) {
|
||||
needRegen := false
|
||||
_, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
needRegen = true
|
||||
}
|
||||
|
||||
oldHash, err := os.ReadFile(filename + ".hash")
|
||||
oldHash, err := ioutil.ReadFile(filename + ".hash")
|
||||
if err != nil {
|
||||
oldHash = []byte{}
|
||||
}
|
||||
|
||||
hasher := sha1.New()
|
||||
|
||||
err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
|
||||
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := d.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = hasher.Write([]byte(d.Name()))
|
||||
_, _ = hasher.Write([]byte(info.Name()))
|
||||
_, _ = hasher.Write([]byte(info.ModTime().String()))
|
||||
_, _ = hasher.Write([]byte(strconv.FormatInt(info.Size(), 16)))
|
||||
return nil
|
||||
@@ -52,6 +50,7 @@ func needsUpdate(dir, filename string) (bool, []byte) {
|
||||
newHash := hasher.Sum([]byte{})
|
||||
|
||||
if bytes.Compare(oldHash, newHash) != 0 {
|
||||
|
||||
return true, newHash
|
||||
}
|
||||
|
||||
@@ -59,15 +58,11 @@ func needsUpdate(dir, filename string) (bool, []byte) {
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 4 {
|
||||
if len(os.Args) != 4 {
|
||||
log.Fatal("Insufficient number of arguments. Need: directory packageName filename")
|
||||
}
|
||||
|
||||
dir, packageName, filename := os.Args[1], os.Args[2], os.Args[3]
|
||||
var useGlobalModTime bool
|
||||
if len(os.Args) == 5 {
|
||||
useGlobalModTime, _ = strconv.ParseBool(os.Args[4])
|
||||
}
|
||||
|
||||
update, newHash := needsUpdate(dir, filename)
|
||||
|
||||
@@ -79,14 +74,13 @@ func main() {
|
||||
fmt.Printf("generating bindata for %s\n", packageName)
|
||||
var fsTemplates http.FileSystem = http.Dir(dir)
|
||||
err := vfsgen.Generate(fsTemplates, vfsgen.Options{
|
||||
PackageName: packageName,
|
||||
BuildTags: "bindata",
|
||||
VariableName: "Assets",
|
||||
Filename: filename,
|
||||
UseGlobalModTime: useGlobalModTime,
|
||||
PackageName: packageName,
|
||||
BuildTags: "bindata",
|
||||
VariableName: "Assets",
|
||||
Filename: filename,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("%v\n", err)
|
||||
}
|
||||
_ = os.WriteFile(filename+".hash", newHash, 0o666)
|
||||
_ = ioutil.WriteFile(filename+".hash", newHash, 0666)
|
||||
}
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2015 Kenneth Shaw
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
)
|
||||
|
||||
const (
|
||||
gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json"
|
||||
maxUnicodeVersion = 14
|
||||
maxUnicodeVersion = 12
|
||||
)
|
||||
|
||||
var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")
|
||||
var (
|
||||
flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")
|
||||
)
|
||||
|
||||
// Gemoji is a set of emoji data.
|
||||
type Gemoji []Emoji
|
||||
@@ -64,7 +65,7 @@ func main() {
|
||||
}
|
||||
|
||||
// write
|
||||
err = os.WriteFile(*flagOut, buf, 0o644)
|
||||
err = ioutil.WriteFile(*flagOut, buf, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -93,7 +94,7 @@ func generate() ([]byte, error) {
|
||||
defer res.Body.Close()
|
||||
|
||||
// read all
|
||||
body, err := io.ReadAll(res.Body)
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -105,7 +106,7 @@ func generate() ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
skinTones := make(map[string]string)
|
||||
var skinTones = make(map[string]string)
|
||||
|
||||
skinTones["\U0001f3fb"] = "Light Skin Tone"
|
||||
skinTones["\U0001f3fc"] = "Medium-Light Skin Tone"
|
||||
@@ -115,7 +116,7 @@ func generate() ([]byte, error) {
|
||||
|
||||
var tmp Gemoji
|
||||
|
||||
// filter out emoji that require greater than max unicode version
|
||||
//filter out emoji that require greater than max unicode version
|
||||
for i := range data {
|
||||
val, _ := strconv.ParseFloat(data[i].UnicodeVersion, 64)
|
||||
if int(val) <= maxUnicodeVersion {
|
||||
@@ -154,7 +155,7 @@ func generate() ([]byte, error) {
|
||||
|
||||
// write a JSON file to use with tribute (write before adding skin tones since we can't support them there yet)
|
||||
file, _ := json.Marshal(data)
|
||||
_ = os.WriteFile("assets/emoji.json", file, 0o644)
|
||||
_ = ioutil.WriteFile("assets/emoji.json", file, 0644)
|
||||
|
||||
// Add skin tones to emoji that support it
|
||||
var (
|
||||
@@ -173,7 +174,7 @@ func generate() ([]byte, error) {
|
||||
s = append(s, k)
|
||||
} else {
|
||||
// insert into slice after first element because all emoji that support skin tones
|
||||
// have that modifier placed at this spot
|
||||
// have that modifer placed at this spot
|
||||
s = append(s, "")
|
||||
copy(s[2:], s[1:])
|
||||
s[1] = k
|
||||
@@ -189,10 +190,6 @@ func generate() ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(data, func(i, j int) bool {
|
||||
return data[i].Aliases[0] < data[j].Aliases[0]
|
||||
})
|
||||
|
||||
// add header
|
||||
str := replacer.Replace(fmt.Sprintf(hdr, gemojiURL, data))
|
||||
|
||||
@@ -212,12 +209,13 @@ func generate() ([]byte, error) {
|
||||
|
||||
const hdr = `
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package emoji
|
||||
|
||||
// Code generated by build/generate-emoji.go. DO NOT EDIT.
|
||||
// Code generated by gen.go. DO NOT EDIT.
|
||||
// Sourced from %s
|
||||
//
|
||||
var GemojiData = %#v
|
||||
`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -32,7 +33,8 @@ func main() {
|
||||
flag.StringVar(&githubApiToken, "token", "", "github api token")
|
||||
flag.Parse()
|
||||
|
||||
file, err := os.CreateTemp(os.TempDir(), prefix)
|
||||
file, err := ioutil.TempFile(os.TempDir(), prefix)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create temp file. %s", err)
|
||||
}
|
||||
@@ -63,6 +65,7 @@ func main() {
|
||||
}
|
||||
|
||||
gz, err := gzip.NewReader(file)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to gunzip the archive. %s", err)
|
||||
}
|
||||
@@ -93,6 +96,7 @@ func main() {
|
||||
}
|
||||
|
||||
out, err := os.Create(path.Join(destination, strings.TrimSuffix(filepath.Base(hdr.Name), ".gitignore")))
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create new file. %s", err)
|
||||
}
|
||||
@@ -109,13 +113,13 @@ func main() {
|
||||
for dst, src := range filesToCopy {
|
||||
// Read all content of src to data
|
||||
src = path.Join(destination, src)
|
||||
data, err := os.ReadFile(src)
|
||||
data, err := ioutil.ReadFile(src)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read src file. %s", err)
|
||||
}
|
||||
// Write data to dst
|
||||
dst = path.Join(destination, dst)
|
||||
err = os.WriteFile(dst, data, 0o644)
|
||||
err = ioutil.WriteFile(dst, data, 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to write new file. %s", err)
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/fs"
|
||||
"os"
|
||||
goPath "path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// regexp is based on go-license, excluding README and NOTICE
|
||||
// https://github.com/google/go-licenses/blob/master/licenses/find.go
|
||||
var licenseRe = regexp.MustCompile(`^(?i)((UN)?LICEN(S|C)E|COPYING).*$`)
|
||||
|
||||
type LicenseEntry struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
LicenseText string `json:"licenseText"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
base, out := os.Args[1], os.Args[2]
|
||||
|
||||
paths := []string{}
|
||||
err := filepath.WalkDir(base, func(path string, entry fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if entry.IsDir() || !licenseRe.MatchString(entry.Name()) {
|
||||
return nil
|
||||
}
|
||||
paths = append(paths, path)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sort.Strings(paths)
|
||||
|
||||
entries := []LicenseEntry{}
|
||||
for _, path := range paths {
|
||||
path := filepath.ToSlash(path)
|
||||
|
||||
licenseText, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
path = strings.Replace(path, base+"/", "", 1)
|
||||
name := goPath.Dir(path)
|
||||
|
||||
// There might be a bug somewhere in go-licenses that sometimes interprets the
|
||||
// root package as "." and sometimes as "code.gitea.io/gitea". Workaround by
|
||||
// removing both of them for the sake of stable output.
|
||||
if name == "." || name == "code.gitea.io/gitea" {
|
||||
continue
|
||||
}
|
||||
|
||||
entries = append(entries, LicenseEntry{
|
||||
Name: name,
|
||||
Path: path,
|
||||
LicenseText: string(licenseText),
|
||||
})
|
||||
}
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(entries, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(out, jsonBytes, 0o644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
import imageminZopfli from 'imagemin-zopfli';
|
||||
import {optimize} from 'svgo';
|
||||
import {fabric} from 'fabric';
|
||||
import {readFile, writeFile} from 'node:fs/promises';
|
||||
'use strict';
|
||||
|
||||
const imageminZopfli = require('imagemin-zopfli');
|
||||
const {fabric} = require('fabric');
|
||||
const {DOMParser, XMLSerializer} = require('xmldom');
|
||||
const {readFile, writeFile} = require('fs').promises;
|
||||
const {resolve} = require('path');
|
||||
const Svgo = require('svgo');
|
||||
|
||||
function exit(err) {
|
||||
if (err) console.error(err);
|
||||
@@ -17,24 +21,42 @@ function loadSvg(svg) {
|
||||
});
|
||||
}
|
||||
|
||||
async function generate(svg, path, {size, bg}) {
|
||||
const outputFile = new URL(path, import.meta.url);
|
||||
|
||||
if (String(outputFile).endsWith('.svg')) {
|
||||
const {data} = optimize(svg, {
|
||||
plugins: [
|
||||
'preset-default',
|
||||
'removeDimensions',
|
||||
{
|
||||
name: 'addAttributesToSVGElement',
|
||||
params: {attributes: [{width: size}, {height: size}]}
|
||||
async function generateSvgFavicon(svg, outputFile) {
|
||||
const svgo = new Svgo({
|
||||
plugins: [
|
||||
{removeDimensions: true},
|
||||
{
|
||||
addAttributesToSVGElement: {
|
||||
attributes: [
|
||||
{'width': '32'},
|
||||
{'height': '32'},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
await writeFile(outputFile, data);
|
||||
return;
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const {data} = await svgo.optimize(svg);
|
||||
await writeFile(outputFile, data);
|
||||
}
|
||||
|
||||
async function generate(svg, outputFile, {size, bg, removeDetail} = {}) {
|
||||
const parser = new DOMParser();
|
||||
const serializer = new XMLSerializer();
|
||||
const document = parser.parseFromString(svg);
|
||||
|
||||
if (removeDetail) {
|
||||
for (const el of Array.from(document.getElementsByTagName('g') || [])) {
|
||||
for (const attribute of Array.from(el.attributes || [])) {
|
||||
if (attribute.name === 'class' && attribute.value === 'detail-remove') {
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
svg = serializer.serializeToString(document);
|
||||
|
||||
const {objects, options} = await loadSvg(svg);
|
||||
const canvas = new fabric.Canvas();
|
||||
canvas.setDimensions({width: size, height: size});
|
||||
@@ -64,19 +86,16 @@ async function generate(svg, path, {size, bg}) {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const gitea = process.argv.slice(2).includes('gitea');
|
||||
const logoSvg = await readFile(new URL('../assets/logo.svg', import.meta.url), 'utf8');
|
||||
const faviconSvg = await readFile(new URL('../assets/favicon.svg', import.meta.url), 'utf8');
|
||||
|
||||
await Promise.all([
|
||||
generate(logoSvg, '../public/img/logo.svg', {size: 32}),
|
||||
generate(logoSvg, '../public/img/logo.png', {size: 512}),
|
||||
generate(faviconSvg, '../public/img/favicon.svg', {size: 32}),
|
||||
generate(faviconSvg, '../public/img/favicon.png', {size: 180}),
|
||||
generate(logoSvg, '../public/img/avatar_default.png', {size: 200}),
|
||||
generate(logoSvg, '../public/img/apple-touch-icon.png', {size: 180, bg: true}),
|
||||
gitea && generate(logoSvg, '../public/img/gitea.svg', {size: 32}),
|
||||
]);
|
||||
const svg = await readFile(resolve(__dirname, '../assets/logo.svg'), 'utf8');
|
||||
await generateSvgFavicon(svg, resolve(__dirname, '../public/img/favicon.svg'));
|
||||
await generate(svg, resolve(__dirname, '../public/img/gitea-lg.png'), {size: 880});
|
||||
await generate(svg, resolve(__dirname, '../public/img/gitea-512.png'), {size: 512});
|
||||
await generate(svg, resolve(__dirname, '../public/img/gitea-192.png'), {size: 192});
|
||||
await generate(svg, resolve(__dirname, '../public/img/gitea-sm.png'), {size: 120});
|
||||
await generate(svg, resolve(__dirname, '../public/img/avatar_default.png'), {size: 200});
|
||||
await generate(svg, resolve(__dirname, '../public/img/favicon.png'), {size: 180, removeDetail: true});
|
||||
await generate(svg, resolve(__dirname, '../public/img/apple-touch-icon.png'), {size: 180, bg: true});
|
||||
}
|
||||
|
||||
main().then(exit).catch(exit);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -32,21 +33,14 @@ func main() {
|
||||
flag.StringVar(&githubApiToken, "token", "", "github api token")
|
||||
flag.Parse()
|
||||
|
||||
file, err := os.CreateTemp(os.TempDir(), prefix)
|
||||
file, err := ioutil.TempFile(os.TempDir(), prefix)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create temp file. %s", err)
|
||||
}
|
||||
|
||||
defer util.Remove(file.Name())
|
||||
|
||||
if err := os.RemoveAll(destination); err != nil {
|
||||
log.Fatalf("Cannot clean destination folder: %v", err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(destination, 0o755); err != nil {
|
||||
log.Fatalf("Cannot create destination: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to download archive. %s", err)
|
||||
@@ -72,6 +66,7 @@ func main() {
|
||||
}
|
||||
|
||||
gz, err := gzip.NewReader(file)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to gunzip the archive. %s", err)
|
||||
}
|
||||
@@ -105,6 +100,7 @@ func main() {
|
||||
continue
|
||||
}
|
||||
out, err := os.Create(path.Join(destination, strings.TrimSuffix(filepath.Base(hdr.Name), ".txt")))
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create new file. %s", err)
|
||||
}
|
||||
|
||||
@@ -1,58 +1,65 @@
|
||||
#!/usr/bin/env node
|
||||
import fastGlob from 'fast-glob';
|
||||
import {optimize} from 'svgo';
|
||||
import {parse} from 'node:path';
|
||||
import {readFile, writeFile, mkdir} from 'node:fs/promises';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
'use strict';
|
||||
|
||||
const glob = (pattern) => fastGlob.sync(pattern, {
|
||||
cwd: fileURLToPath(new URL('..', import.meta.url)),
|
||||
absolute: true,
|
||||
});
|
||||
const fastGlob = require('fast-glob');
|
||||
const Svgo = require('svgo');
|
||||
const {resolve, parse} = require('path');
|
||||
const {readFile, writeFile, mkdir} = require('fs').promises;
|
||||
|
||||
const glob = (pattern) => fastGlob.sync(pattern, {cwd: resolve(__dirname), absolute: true});
|
||||
const outputDir = resolve(__dirname, '../public/img/svg');
|
||||
|
||||
function exit(err) {
|
||||
if (err) console.error(err);
|
||||
process.exit(err ? 1 : 0);
|
||||
}
|
||||
|
||||
async function processFile(file, {prefix, fullName} = {}) {
|
||||
let name;
|
||||
if (fullName) {
|
||||
name = fullName;
|
||||
} else {
|
||||
name = parse(file).name;
|
||||
if (prefix) name = `${prefix}-${name}`;
|
||||
if (prefix === 'octicon') name = name.replace(/-[0-9]+$/, ''); // chop of '-16' on octicons
|
||||
}
|
||||
async function processFile(file, {prefix = ''} = {}) {
|
||||
let name = parse(file).name;
|
||||
if (prefix) name = `${prefix}-${name}`;
|
||||
if (prefix === 'octicon') name = name.replace(/-[0-9]+$/, ''); // chop of '-16' on octicons
|
||||
|
||||
const {data} = optimize(await readFile(file, 'utf8'), {
|
||||
const svgo = new Svgo({
|
||||
plugins: [
|
||||
{name: 'preset-default'},
|
||||
{name: 'removeXMLNS'},
|
||||
{name: 'removeDimensions'},
|
||||
{name: 'prefixIds', params: {prefix: () => name}},
|
||||
{name: 'addClassesToSVGElement', params: {classNames: ['svg', name]}},
|
||||
{name: 'addAttributesToSVGElement', params: {attributes: [{'width': '16'}, {'height': '16'}, {'aria-hidden': 'true'}]}},
|
||||
{removeXMLNS: true},
|
||||
{removeDimensions: true},
|
||||
{
|
||||
addClassesToSVGElement: {
|
||||
classNames: [
|
||||
'svg',
|
||||
name,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
addAttributesToSVGElement: {
|
||||
attributes: [
|
||||
{'width': '16'},
|
||||
{'height': '16'},
|
||||
{'aria-hidden': 'true'},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await writeFile(fileURLToPath(new URL(`../public/img/svg/${name}.svg`, import.meta.url)), data);
|
||||
}
|
||||
|
||||
function processFiles(pattern, opts) {
|
||||
return glob(pattern).map((file) => processFile(file, opts));
|
||||
const {data} = await svgo.optimize(await readFile(file, 'utf8'));
|
||||
await writeFile(resolve(outputDir, `${name}.svg`), data);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
await mkdir(fileURLToPath(new URL('../public/img/svg', import.meta.url)), {recursive: true});
|
||||
await mkdir(outputDir);
|
||||
} catch {}
|
||||
|
||||
await Promise.all([
|
||||
...processFiles('node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}),
|
||||
...processFiles('web_src/svg/*.svg'),
|
||||
...processFiles('public/img/gitea.svg', {fullName: 'gitea-gitea'}),
|
||||
]);
|
||||
for (const file of glob('../node_modules/@primer/octicons/build/svg/*-16.svg')) {
|
||||
await processFile(file, {prefix: 'octicon'});
|
||||
}
|
||||
|
||||
for (const file of glob('../web_src/svg/*.svg')) {
|
||||
await processFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
main().then(exit).catch(exit);
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Copyright (c) 2015, Wade Simmons
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// gocovmerge takes the results from multiple `go test -coverprofile` runs and
|
||||
// merges them into one profile
|
||||
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
@@ -20,7 +21,7 @@ import (
|
||||
"golang.org/x/tools/cover"
|
||||
)
|
||||
|
||||
func mergeProfiles(p, merge *cover.Profile) {
|
||||
func mergeProfiles(p *cover.Profile, merge *cover.Profile) {
|
||||
if p.Mode != merge.Mode {
|
||||
log.Fatalf("cannot merge profiles with different modes")
|
||||
}
|
||||
@@ -107,7 +108,7 @@ func main() {
|
||||
for _, file := range flag.Args() {
|
||||
profiles, err := cover.ParseProfiles(file)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse profile '%s': %v", file, err)
|
||||
log.Fatalf("failed to parse profiles: %v", err)
|
||||
}
|
||||
for _, p := range profiles {
|
||||
merged = addProfile(merged, p)
|
||||
|
||||
325
build/lint.go
Normal file
325
build/lint.go
Normal file
@@ -0,0 +1,325 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Copyright (c) 2018 Minko Gechev. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mgechev/dots"
|
||||
"github.com/mgechev/revive/formatter"
|
||||
"github.com/mgechev/revive/lint"
|
||||
"github.com/mgechev/revive/rule"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pelletier/go-toml"
|
||||
)
|
||||
|
||||
func fail(err string) {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var defaultRules = []lint.Rule{
|
||||
&rule.VarDeclarationsRule{},
|
||||
&rule.PackageCommentsRule{},
|
||||
&rule.DotImportsRule{},
|
||||
&rule.BlankImportsRule{},
|
||||
&rule.ExportedRule{},
|
||||
&rule.VarNamingRule{},
|
||||
&rule.IndentErrorFlowRule{},
|
||||
&rule.IfReturnRule{},
|
||||
&rule.RangeRule{},
|
||||
&rule.ErrorfRule{},
|
||||
&rule.ErrorNamingRule{},
|
||||
&rule.ErrorStringsRule{},
|
||||
&rule.ReceiverNamingRule{},
|
||||
&rule.IncrementDecrementRule{},
|
||||
&rule.ErrorReturnRule{},
|
||||
&rule.UnexportedReturnRule{},
|
||||
&rule.TimeNamingRule{},
|
||||
&rule.ContextKeysType{},
|
||||
&rule.ContextAsArgumentRule{},
|
||||
}
|
||||
|
||||
var allRules = append([]lint.Rule{
|
||||
&rule.ArgumentsLimitRule{},
|
||||
&rule.CyclomaticRule{},
|
||||
&rule.FileHeaderRule{},
|
||||
&rule.EmptyBlockRule{},
|
||||
&rule.SuperfluousElseRule{},
|
||||
&rule.ConfusingNamingRule{},
|
||||
&rule.GetReturnRule{},
|
||||
&rule.ModifiesParamRule{},
|
||||
&rule.ConfusingResultsRule{},
|
||||
&rule.DeepExitRule{},
|
||||
&rule.UnusedParamRule{},
|
||||
&rule.UnreachableCodeRule{},
|
||||
&rule.AddConstantRule{},
|
||||
&rule.FlagParamRule{},
|
||||
&rule.UnnecessaryStmtRule{},
|
||||
&rule.StructTagRule{},
|
||||
&rule.ModifiesValRecRule{},
|
||||
&rule.ConstantLogicalExprRule{},
|
||||
&rule.BoolLiteralRule{},
|
||||
&rule.RedefinesBuiltinIDRule{},
|
||||
&rule.ImportsBlacklistRule{},
|
||||
&rule.FunctionResultsLimitRule{},
|
||||
&rule.MaxPublicStructsRule{},
|
||||
&rule.RangeValInClosureRule{},
|
||||
&rule.RangeValAddress{},
|
||||
&rule.WaitGroupByValueRule{},
|
||||
&rule.AtomicRule{},
|
||||
&rule.EmptyLinesRule{},
|
||||
&rule.LineLengthLimitRule{},
|
||||
&rule.CallToGCRule{},
|
||||
&rule.DuplicatedImportsRule{},
|
||||
&rule.ImportShadowingRule{},
|
||||
&rule.BareReturnRule{},
|
||||
&rule.UnusedReceiverRule{},
|
||||
&rule.UnhandledErrorRule{},
|
||||
&rule.CognitiveComplexityRule{},
|
||||
&rule.StringOfIntRule{},
|
||||
}, defaultRules...)
|
||||
|
||||
var allFormatters = []lint.Formatter{
|
||||
&formatter.Stylish{},
|
||||
&formatter.Friendly{},
|
||||
&formatter.JSON{},
|
||||
&formatter.NDJSON{},
|
||||
&formatter.Default{},
|
||||
&formatter.Unix{},
|
||||
&formatter.Checkstyle{},
|
||||
&formatter.Plain{},
|
||||
}
|
||||
|
||||
func getFormatters() map[string]lint.Formatter {
|
||||
result := map[string]lint.Formatter{}
|
||||
for _, f := range allFormatters {
|
||||
result[f.Name()] = f
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func getLintingRules(config *lint.Config) []lint.Rule {
|
||||
rulesMap := map[string]lint.Rule{}
|
||||
for _, r := range allRules {
|
||||
rulesMap[r.Name()] = r
|
||||
}
|
||||
|
||||
lintingRules := []lint.Rule{}
|
||||
for name := range config.Rules {
|
||||
rule, ok := rulesMap[name]
|
||||
if !ok {
|
||||
fail("cannot find rule: " + name)
|
||||
}
|
||||
lintingRules = append(lintingRules, rule)
|
||||
}
|
||||
|
||||
return lintingRules
|
||||
}
|
||||
|
||||
func parseConfig(path string) *lint.Config {
|
||||
config := &lint.Config{}
|
||||
file, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
fail("cannot read the config file")
|
||||
}
|
||||
err = toml.Unmarshal(file, config)
|
||||
if err != nil {
|
||||
fail("cannot parse the config file: " + err.Error())
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func normalizeConfig(config *lint.Config) {
|
||||
if config.Confidence == 0 {
|
||||
config.Confidence = 0.8
|
||||
}
|
||||
severity := config.Severity
|
||||
if severity != "" {
|
||||
for k, v := range config.Rules {
|
||||
if v.Severity == "" {
|
||||
v.Severity = severity
|
||||
}
|
||||
config.Rules[k] = v
|
||||
}
|
||||
for k, v := range config.Directives {
|
||||
if v.Severity == "" {
|
||||
v.Severity = severity
|
||||
}
|
||||
config.Directives[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getConfig() *lint.Config {
|
||||
config := defaultConfig()
|
||||
if configPath != "" {
|
||||
config = parseConfig(configPath)
|
||||
}
|
||||
normalizeConfig(config)
|
||||
return config
|
||||
}
|
||||
|
||||
func getFormatter() lint.Formatter {
|
||||
formatters := getFormatters()
|
||||
formatter := formatters["default"]
|
||||
if formatterName != "" {
|
||||
f, ok := formatters[formatterName]
|
||||
if !ok {
|
||||
fail("unknown formatter " + formatterName)
|
||||
}
|
||||
formatter = f
|
||||
}
|
||||
return formatter
|
||||
}
|
||||
|
||||
func buildDefaultConfigPath() string {
|
||||
var result string
|
||||
if homeDir, err := homedir.Dir(); err == nil {
|
||||
result = filepath.Join(homeDir, "revive.toml")
|
||||
if _, err := os.Stat(result); err != nil {
|
||||
result = ""
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func defaultConfig() *lint.Config {
|
||||
defaultConfig := lint.Config{
|
||||
Confidence: 0.0,
|
||||
Severity: lint.SeverityWarning,
|
||||
Rules: map[string]lint.RuleConfig{},
|
||||
}
|
||||
for _, r := range defaultRules {
|
||||
defaultConfig.Rules[r.Name()] = lint.RuleConfig{}
|
||||
}
|
||||
return &defaultConfig
|
||||
}
|
||||
|
||||
func normalizeSplit(strs []string) []string {
|
||||
res := []string{}
|
||||
for _, s := range strs {
|
||||
t := strings.Trim(s, " \t")
|
||||
if len(t) > 0 {
|
||||
res = append(res, t)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func getPackages() [][]string {
|
||||
globs := normalizeSplit(flag.Args())
|
||||
if len(globs) == 0 {
|
||||
globs = append(globs, ".")
|
||||
}
|
||||
|
||||
packages, err := dots.ResolvePackages(globs, normalizeSplit(excludePaths))
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
|
||||
return packages
|
||||
}
|
||||
|
||||
type arrayFlags []string
|
||||
|
||||
func (i *arrayFlags) String() string {
|
||||
return strings.Join([]string(*i), " ")
|
||||
}
|
||||
|
||||
func (i *arrayFlags) Set(value string) error {
|
||||
*i = append(*i, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
var configPath string
|
||||
var excludePaths arrayFlags
|
||||
var formatterName string
|
||||
var help bool
|
||||
|
||||
var originalUsage = flag.Usage
|
||||
|
||||
func init() {
|
||||
flag.Usage = func() {
|
||||
originalUsage()
|
||||
}
|
||||
// command line help strings
|
||||
const (
|
||||
configUsage = "path to the configuration TOML file, defaults to $HOME/revive.toml, if present (i.e. -config myconf.toml)"
|
||||
excludeUsage = "list of globs which specify files to be excluded (i.e. -exclude foo/...)"
|
||||
formatterUsage = "formatter to be used for the output (i.e. -formatter stylish)"
|
||||
)
|
||||
|
||||
defaultConfigPath := buildDefaultConfigPath()
|
||||
|
||||
flag.StringVar(&configPath, "config", defaultConfigPath, configUsage)
|
||||
flag.Var(&excludePaths, "exclude", excludeUsage)
|
||||
flag.StringVar(&formatterName, "formatter", "", formatterUsage)
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
config := getConfig()
|
||||
formatter := getFormatter()
|
||||
packages := getPackages()
|
||||
|
||||
revive := lint.New(func(file string) ([]byte, error) {
|
||||
return ioutil.ReadFile(file)
|
||||
})
|
||||
|
||||
lintingRules := getLintingRules(config)
|
||||
|
||||
failures, err := revive.Lint(packages, lintingRules, *config)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
|
||||
formatChan := make(chan lint.Failure)
|
||||
exitChan := make(chan bool)
|
||||
|
||||
var output string
|
||||
go (func() {
|
||||
output, err = formatter.Format(formatChan, *config)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
exitChan <- true
|
||||
})()
|
||||
|
||||
exitCode := 0
|
||||
for f := range failures {
|
||||
if f.Confidence < config.Confidence {
|
||||
continue
|
||||
}
|
||||
if exitCode == 0 {
|
||||
exitCode = config.WarningCode
|
||||
}
|
||||
if c, ok := config.Rules[f.RuleName]; ok && c.Severity == lint.SeverityError {
|
||||
exitCode = config.ErrorCode
|
||||
}
|
||||
if c, ok := config.Directives[f.RuleName]; ok && c.Severity == lint.SeverityError {
|
||||
exitCode = config.ErrorCode
|
||||
}
|
||||
|
||||
formatChan <- f
|
||||
}
|
||||
|
||||
close(formatChan)
|
||||
<-exitChan
|
||||
if output != "" {
|
||||
fmt.Println(output)
|
||||
}
|
||||
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
if [ ! -f ./build/test-env-check.sh ]; then
|
||||
echo "${0} can only be executed in gitea source root directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
echo "check uid ..."
|
||||
|
||||
# the uid of gitea defined in "https://gitea.com/gitea/test-env" is 1000
|
||||
gitea_uid=$(id -u gitea)
|
||||
if [ "$gitea_uid" != "1000" ]; then
|
||||
echo "The uid of linux user 'gitea' is expected to be 1000, but it is $gitea_uid"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cur_uid=$(id -u)
|
||||
if [ "$cur_uid" != "0" -a "$cur_uid" != "$gitea_uid" ]; then
|
||||
echo "The uid of current linux user is expected to be 0 or $gitea_uid, but it is $cur_uid"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
if [ ! -f ./build/test-env-prepare.sh ]; then
|
||||
echo "${0} can only be executed in gitea source root directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "change the owner of files to gitea ..."
|
||||
chown -R gitea:gitea .
|
||||
@@ -10,10 +10,10 @@ sed -i -r -e '/^[a-zA-Z0-9_.-]+[ ]*=[ ]*".*"$/ {
|
||||
}' ./options/locale/*.ini
|
||||
|
||||
# Remove translation under 25% of en_us
|
||||
baselines=$(wc -l "./options/locale_en-US.ini" | cut -d" " -f1)
|
||||
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)
|
||||
lines=`wc -l "$filename" | cut -d" " -f1`
|
||||
if [ $lines -lt $baselines ]; then
|
||||
echo "Removing $filename: $lines/$baselines"
|
||||
rm "$filename"
|
||||
|
||||
600
cmd/admin.go
600
cmd/admin.go
@@ -1,29 +1,25 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2016 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/auth/oauth2"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
pwd "code.gitea.io/gitea/modules/password"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
auth_service "code.gitea.io/gitea/services/auth"
|
||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||
"code.gitea.io/gitea/services/auth/source/smtp"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@@ -34,11 +30,74 @@ var (
|
||||
Name: "admin",
|
||||
Usage: "Command line interface to perform common administrative operations",
|
||||
Subcommands: []cli.Command{
|
||||
subcmdUser,
|
||||
subcmdCreateUser,
|
||||
subcmdChangePassword,
|
||||
subcmdRepoSyncReleases,
|
||||
subcmdRegenerate,
|
||||
subcmdAuth,
|
||||
subcmdSendMail,
|
||||
},
|
||||
}
|
||||
|
||||
subcmdCreateUser = cli.Command{
|
||||
Name: "create-user",
|
||||
Usage: "Create a new user in database",
|
||||
Action: runCreateUser,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "Username. DEPRECATED: use username instead",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "username",
|
||||
Usage: "Username",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "User password",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "email",
|
||||
Usage: "User email address",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "admin",
|
||||
Usage: "User is an admin",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "random-password",
|
||||
Usage: "Generate a random password for the user",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "must-change-password",
|
||||
Usage: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: true)",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "random-password-length",
|
||||
Usage: "Length of the random password to be generated",
|
||||
Value: 12,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "access-token",
|
||||
Usage: "Generate access token for the user",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
subcmdChangePassword = cli.Command{
|
||||
Name: "change-password",
|
||||
Usage: "Change a user's password",
|
||||
Action: runChangePassword,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "username,u",
|
||||
Value: "",
|
||||
Usage: "The user to change password for",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "password,p",
|
||||
Value: "",
|
||||
Usage: "New password to set for user",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -79,8 +138,6 @@ var (
|
||||
cmdAuthUpdateLdapBindDn,
|
||||
cmdAuthAddLdapSimpleAuth,
|
||||
cmdAuthUpdateLdapSimpleAuth,
|
||||
microcmdAuthAddSMTP,
|
||||
microcmdAuthUpdateSMTP,
|
||||
microcmdAuthList,
|
||||
microcmdAuthDelete,
|
||||
},
|
||||
@@ -161,11 +218,6 @@ var (
|
||||
Value: "false",
|
||||
Usage: "Use custom URLs for GitLab/GitHub OAuth endpoints",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "custom-tenant-id",
|
||||
Value: "",
|
||||
Usage: "Use custom Tenant ID for OAuth endpoints",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "custom-auth-url",
|
||||
Value: "",
|
||||
@@ -186,54 +238,6 @@ var (
|
||||
Value: "",
|
||||
Usage: "Use a custom Email URL (option for GitHub)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "icon-url",
|
||||
Value: "",
|
||||
Usage: "Custom icon URL for OAuth2 login source",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-local-2fa",
|
||||
Usage: "Set to true to skip local 2fa for users authenticated by this source",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "scopes",
|
||||
Value: nil,
|
||||
Usage: "Scopes to request when to authenticate against this OAuth2 source",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "required-claim-name",
|
||||
Value: "",
|
||||
Usage: "Claim name that has to be set to allow users to login with this source",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "required-claim-value",
|
||||
Value: "",
|
||||
Usage: "Claim value that has to be set to allow users to login with this source",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "group-claim-name",
|
||||
Value: "",
|
||||
Usage: "Claim name providing group names for this source",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "admin-group",
|
||||
Value: "",
|
||||
Usage: "Group Claim value for administrator users",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "restricted-group",
|
||||
Value: "",
|
||||
Usage: "Group Claim value for restricted users",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "group-team-map",
|
||||
Value: "",
|
||||
Usage: "JSON mapping between groups and org teams",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "group-team-map-removal",
|
||||
Usage: "Activate automatic team membership removal depending on groups",
|
||||
},
|
||||
}
|
||||
|
||||
microcmdAuthUpdateOauth = cli.Command{
|
||||
@@ -249,115 +253,146 @@ var (
|
||||
Action: runAddOauth,
|
||||
Flags: oauthCLIFlags,
|
||||
}
|
||||
|
||||
subcmdSendMail = cli.Command{
|
||||
Name: "sendmail",
|
||||
Usage: "Send a message to all users",
|
||||
Action: runSendMail,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "title",
|
||||
Usage: `a title of a message`,
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "content",
|
||||
Usage: "a content of a message",
|
||||
Value: "",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "force,f",
|
||||
Usage: "A flag to bypass a confirmation step",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
smtpCLIFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name",
|
||||
Value: "",
|
||||
Usage: "Application Name",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "auth-type",
|
||||
Value: "PLAIN",
|
||||
Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "host",
|
||||
Value: "",
|
||||
Usage: "SMTP Host",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "port",
|
||||
Usage: "SMTP Port",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "force-smtps",
|
||||
Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "skip-verify",
|
||||
Usage: "Skip TLS verify.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "helo-hostname",
|
||||
Value: "",
|
||||
Usage: "Hostname sent with HELO. Leave blank to send current hostname",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "disable-helo",
|
||||
Usage: "Disable SMTP helo.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "allowed-domains",
|
||||
Value: "",
|
||||
Usage: "Leave empty to allow all domains. Separate multiple domains with a comma (',')",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "skip-local-2fa",
|
||||
Usage: "Skip 2FA to log on.",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "active",
|
||||
Usage: "This Authentication Source is Activated.",
|
||||
},
|
||||
}
|
||||
|
||||
microcmdAuthAddSMTP = cli.Command{
|
||||
Name: "add-smtp",
|
||||
Usage: "Add new SMTP authentication source",
|
||||
Action: runAddSMTP,
|
||||
Flags: smtpCLIFlags,
|
||||
}
|
||||
|
||||
microcmdAuthUpdateSMTP = cli.Command{
|
||||
Name: "update-smtp",
|
||||
Usage: "Update existing SMTP authentication source",
|
||||
Action: runUpdateSMTP,
|
||||
Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...),
|
||||
}
|
||||
)
|
||||
|
||||
func runRepoSyncReleases(_ *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
func runChangePassword(c *cli.Context) error {
|
||||
if err := argsSet(c, "username", "password"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
if err := initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !pwd.IsComplexEnough(c.String("password")) {
|
||||
return errors.New("Password does not meet complexity requirements")
|
||||
}
|
||||
pwned, err := pwd.IsPwned(context.Background(), c.String("password"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pwned {
|
||||
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
|
||||
}
|
||||
uname := c.String("username")
|
||||
user, err := models.GetUserByName(uname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user.Salt, err = models.GetUserSalt(); err != nil {
|
||||
return err
|
||||
}
|
||||
user.HashPassword(c.String("password"))
|
||||
|
||||
if err := models.UpdateUserCols(user, "passwd", "salt"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s's password has been successfully updated!\n", user.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCreateUser(c *cli.Context) error {
|
||||
if err := argsSet(c, "email"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.IsSet("name") && c.IsSet("username") {
|
||||
return errors.New("Cannot set both --name and --username flags")
|
||||
}
|
||||
if !c.IsSet("name") && !c.IsSet("username") {
|
||||
return errors.New("One of --name or --username flags must be set")
|
||||
}
|
||||
|
||||
if c.IsSet("password") && c.IsSet("random-password") {
|
||||
return errors.New("cannot set both -random-password and -password flags")
|
||||
}
|
||||
|
||||
var username string
|
||||
if c.IsSet("username") {
|
||||
username = c.String("username")
|
||||
} else {
|
||||
username = c.String("name")
|
||||
fmt.Fprintf(os.Stderr, "--name flag is deprecated. Use --username instead.\n")
|
||||
}
|
||||
|
||||
if err := initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var password string
|
||||
if c.IsSet("password") {
|
||||
password = c.String("password")
|
||||
} else if c.IsSet("random-password") {
|
||||
var err error
|
||||
password, err = pwd.Generate(c.Int("random-password-length"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("generated random password is '%s'\n", password)
|
||||
} else {
|
||||
return errors.New("must set either password or random-password flag")
|
||||
}
|
||||
|
||||
// always default to true
|
||||
var changePassword = true
|
||||
|
||||
// If this is the first user being created.
|
||||
// Take it as the admin and don't force a password update.
|
||||
if n := models.CountUsers(); n == 0 {
|
||||
changePassword = false
|
||||
}
|
||||
|
||||
if c.IsSet("must-change-password") {
|
||||
changePassword = c.Bool("must-change-password")
|
||||
}
|
||||
|
||||
u := &models.User{
|
||||
Name: username,
|
||||
Email: c.String("email"),
|
||||
Passwd: password,
|
||||
IsActive: true,
|
||||
IsAdmin: c.Bool("admin"),
|
||||
MustChangePassword: changePassword,
|
||||
Theme: setting.UI.DefaultTheme,
|
||||
}
|
||||
|
||||
if err := models.CreateUser(u); err != nil {
|
||||
return fmt.Errorf("CreateUser: %v", err)
|
||||
}
|
||||
|
||||
if c.Bool("access-token") {
|
||||
t := &models.AccessToken{
|
||||
Name: "gitea-admin",
|
||||
UID: u.ID,
|
||||
}
|
||||
|
||||
if err := models.NewAccessToken(t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Access token was successfully created... %s\n", t.Token)
|
||||
}
|
||||
|
||||
fmt.Printf("New user '%s' has been successfully created!\n", username)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runRepoSyncReleases(c *cli.Context) error {
|
||||
if err := initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Trace("Synchronizing repository releases (this may take a while)")
|
||||
for page := 1; ; page++ {
|
||||
repos, count, err := repo_model.SearchRepositoryByName(ctx, &repo_model.SearchRepoOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
PageSize: repo_model.RepositoryListDefaultPageSize,
|
||||
repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{
|
||||
ListOptions: models.ListOptions{
|
||||
PageSize: models.RepositoryListDefaultPageSize,
|
||||
Page: page,
|
||||
},
|
||||
Private: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("SearchRepositoryByName: %w", err)
|
||||
return fmt.Errorf("SearchRepositoryByName: %v", err)
|
||||
}
|
||||
if len(repos) == 0 {
|
||||
break
|
||||
@@ -365,7 +400,7 @@ func runRepoSyncReleases(_ *cli.Context) error {
|
||||
log.Trace("Processing next %d repos of %d", len(repos), count)
|
||||
for _, repo := range repos {
|
||||
log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RepoPath())
|
||||
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
|
||||
gitRepo, err := git.OpenRepository(repo.RepoPath())
|
||||
if err != nil {
|
||||
log.Warn("OpenRepository: %v", err)
|
||||
continue
|
||||
@@ -400,36 +435,29 @@ func runRepoSyncReleases(_ *cli.Context) error {
|
||||
}
|
||||
|
||||
func getReleaseCount(id int64) (int64, error) {
|
||||
return repo_model.GetReleaseCountByRepoID(
|
||||
db.DefaultContext,
|
||||
return models.GetReleaseCountByRepoID(
|
||||
id,
|
||||
repo_model.FindReleasesOptions{
|
||||
models.FindReleasesOptions{
|
||||
IncludeTags: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func runRegenerateHooks(_ *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
func runRegenerateHooks(c *cli.Context) error {
|
||||
if err := initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
|
||||
return repo_module.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
|
||||
}
|
||||
|
||||
func runRegenerateKeys(_ *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
func runRegenerateKeys(c *cli.Context) error {
|
||||
if err := initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
return asymkey_model.RewriteAllPublicKeys()
|
||||
return models.RewriteAllPublicKeys()
|
||||
}
|
||||
|
||||
func parseOAuth2Config(c *cli.Context) *oauth2.Source {
|
||||
func parseOAuth2Config(c *cli.Context) *models.OAuth2Config {
|
||||
var customURLMapping *oauth2.CustomURLMapping
|
||||
if c.IsSet("use-custom-urls") {
|
||||
customURLMapping = &oauth2.CustomURLMapping{
|
||||
@@ -437,43 +465,29 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source {
|
||||
AuthURL: c.String("custom-auth-url"),
|
||||
ProfileURL: c.String("custom-profile-url"),
|
||||
EmailURL: c.String("custom-email-url"),
|
||||
Tenant: c.String("custom-tenant-id"),
|
||||
}
|
||||
} else {
|
||||
customURLMapping = nil
|
||||
}
|
||||
return &oauth2.Source{
|
||||
return &models.OAuth2Config{
|
||||
Provider: c.String("provider"),
|
||||
ClientID: c.String("key"),
|
||||
ClientSecret: c.String("secret"),
|
||||
OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
|
||||
CustomURLMapping: customURLMapping,
|
||||
IconURL: c.String("icon-url"),
|
||||
SkipLocalTwoFA: c.Bool("skip-local-2fa"),
|
||||
Scopes: c.StringSlice("scopes"),
|
||||
RequiredClaimName: c.String("required-claim-name"),
|
||||
RequiredClaimValue: c.String("required-claim-value"),
|
||||
GroupClaimName: c.String("group-claim-name"),
|
||||
AdminGroup: c.String("admin-group"),
|
||||
RestrictedGroup: c.String("restricted-group"),
|
||||
GroupTeamMap: c.String("group-team-map"),
|
||||
GroupTeamMapRemoval: c.Bool("group-team-map-removal"),
|
||||
}
|
||||
}
|
||||
|
||||
func runAddOauth(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
if err := initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return auth_model.CreateSource(&auth_model.Source{
|
||||
Type: auth_model.OAuth2,
|
||||
Name: c.String("name"),
|
||||
IsActive: true,
|
||||
Cfg: parseOAuth2Config(c),
|
||||
return models.CreateLoginSource(&models.LoginSource{
|
||||
Type: models.LoginOAuth2,
|
||||
Name: c.String("name"),
|
||||
IsActived: true,
|
||||
Cfg: parseOAuth2Config(c),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -482,19 +496,16 @@ func runUpdateOauth(c *cli.Context) error {
|
||||
return fmt.Errorf("--id flag is missing")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
if err := initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source, err := auth_model.GetSourceByID(c.Int64("id"))
|
||||
source, err := models.GetLoginSourceByID(c.Int64("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oAuth2Config := source.Cfg.(*oauth2.Source)
|
||||
oAuth2Config := source.OAuth2()
|
||||
|
||||
if c.IsSet("name") {
|
||||
source.Name = c.String("name")
|
||||
@@ -516,46 +527,14 @@ func runUpdateOauth(c *cli.Context) error {
|
||||
oAuth2Config.OpenIDConnectAutoDiscoveryURL = c.String("auto-discover-url")
|
||||
}
|
||||
|
||||
if c.IsSet("icon-url") {
|
||||
oAuth2Config.IconURL = c.String("icon-url")
|
||||
}
|
||||
|
||||
if c.IsSet("scopes") {
|
||||
oAuth2Config.Scopes = c.StringSlice("scopes")
|
||||
}
|
||||
|
||||
if c.IsSet("required-claim-name") {
|
||||
oAuth2Config.RequiredClaimName = c.String("required-claim-name")
|
||||
}
|
||||
if c.IsSet("required-claim-value") {
|
||||
oAuth2Config.RequiredClaimValue = c.String("required-claim-value")
|
||||
}
|
||||
|
||||
if c.IsSet("group-claim-name") {
|
||||
oAuth2Config.GroupClaimName = c.String("group-claim-name")
|
||||
}
|
||||
if c.IsSet("admin-group") {
|
||||
oAuth2Config.AdminGroup = c.String("admin-group")
|
||||
}
|
||||
if c.IsSet("restricted-group") {
|
||||
oAuth2Config.RestrictedGroup = c.String("restricted-group")
|
||||
}
|
||||
if c.IsSet("group-team-map") {
|
||||
oAuth2Config.GroupTeamMap = c.String("group-team-map")
|
||||
}
|
||||
if c.IsSet("group-team-map-removal") {
|
||||
oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
|
||||
}
|
||||
|
||||
// update custom URL mapping
|
||||
customURLMapping := &oauth2.CustomURLMapping{}
|
||||
var customURLMapping = &oauth2.CustomURLMapping{}
|
||||
|
||||
if oAuth2Config.CustomURLMapping != nil {
|
||||
customURLMapping.TokenURL = oAuth2Config.CustomURLMapping.TokenURL
|
||||
customURLMapping.AuthURL = oAuth2Config.CustomURLMapping.AuthURL
|
||||
customURLMapping.ProfileURL = oAuth2Config.CustomURLMapping.ProfileURL
|
||||
customURLMapping.EmailURL = oAuth2Config.CustomURLMapping.EmailURL
|
||||
customURLMapping.Tenant = oAuth2Config.CustomURLMapping.Tenant
|
||||
}
|
||||
if c.IsSet("use-custom-urls") && c.IsSet("custom-token-url") {
|
||||
customURLMapping.TokenURL = c.String("custom-token-url")
|
||||
@@ -573,137 +552,19 @@ func runUpdateOauth(c *cli.Context) error {
|
||||
customURLMapping.EmailURL = c.String("custom-email-url")
|
||||
}
|
||||
|
||||
if c.IsSet("use-custom-urls") && c.IsSet("custom-tenant-id") {
|
||||
customURLMapping.Tenant = c.String("custom-tenant-id")
|
||||
}
|
||||
|
||||
oAuth2Config.CustomURLMapping = customURLMapping
|
||||
source.Cfg = oAuth2Config
|
||||
|
||||
return auth_model.UpdateSource(source)
|
||||
}
|
||||
|
||||
func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
|
||||
if c.IsSet("auth-type") {
|
||||
conf.Auth = c.String("auth-type")
|
||||
validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
|
||||
if !util.SliceContainsString(validAuthTypes, strings.ToUpper(c.String("auth-type"))) {
|
||||
return errors.New("Auth must be one of PLAIN/LOGIN/CRAM-MD5")
|
||||
}
|
||||
conf.Auth = c.String("auth-type")
|
||||
}
|
||||
if c.IsSet("host") {
|
||||
conf.Host = c.String("host")
|
||||
}
|
||||
if c.IsSet("port") {
|
||||
conf.Port = c.Int("port")
|
||||
}
|
||||
if c.IsSet("allowed-domains") {
|
||||
conf.AllowedDomains = c.String("allowed-domains")
|
||||
}
|
||||
if c.IsSet("force-smtps") {
|
||||
conf.ForceSMTPS = c.BoolT("force-smtps")
|
||||
}
|
||||
if c.IsSet("skip-verify") {
|
||||
conf.SkipVerify = c.BoolT("skip-verify")
|
||||
}
|
||||
if c.IsSet("helo-hostname") {
|
||||
conf.HeloHostname = c.String("helo-hostname")
|
||||
}
|
||||
if c.IsSet("disable-helo") {
|
||||
conf.DisableHelo = c.BoolT("disable-helo")
|
||||
}
|
||||
if c.IsSet("skip-local-2fa") {
|
||||
conf.SkipLocalTwoFA = c.BoolT("skip-local-2fa")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAddSMTP(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.IsSet("name") || len(c.String("name")) == 0 {
|
||||
return errors.New("name must be set")
|
||||
}
|
||||
if !c.IsSet("host") || len(c.String("host")) == 0 {
|
||||
return errors.New("host must be set")
|
||||
}
|
||||
if !c.IsSet("port") {
|
||||
return errors.New("port must be set")
|
||||
}
|
||||
active := true
|
||||
if c.IsSet("active") {
|
||||
active = c.BoolT("active")
|
||||
}
|
||||
|
||||
var smtpConfig smtp.Source
|
||||
if err := parseSMTPConfig(c, &smtpConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If not set default to PLAIN
|
||||
if len(smtpConfig.Auth) == 0 {
|
||||
smtpConfig.Auth = "PLAIN"
|
||||
}
|
||||
|
||||
return auth_model.CreateSource(&auth_model.Source{
|
||||
Type: auth_model.SMTP,
|
||||
Name: c.String("name"),
|
||||
IsActive: active,
|
||||
Cfg: &smtpConfig,
|
||||
})
|
||||
}
|
||||
|
||||
func runUpdateSMTP(c *cli.Context) error {
|
||||
if !c.IsSet("id") {
|
||||
return fmt.Errorf("--id flag is missing")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source, err := auth_model.GetSourceByID(c.Int64("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
smtpConfig := source.Cfg.(*smtp.Source)
|
||||
|
||||
if err := parseSMTPConfig(c, smtpConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.IsSet("name") {
|
||||
source.Name = c.String("name")
|
||||
}
|
||||
|
||||
if c.IsSet("active") {
|
||||
source.IsActive = c.BoolT("active")
|
||||
}
|
||||
|
||||
source.Cfg = smtpConfig
|
||||
|
||||
return auth_model.UpdateSource(source)
|
||||
return models.UpdateSource(source)
|
||||
}
|
||||
|
||||
func runListAuth(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
if err := initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authSources, err := auth_model.Sources()
|
||||
loginSources, err := models.LoginSources()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -721,8 +582,8 @@ func runListAuth(c *cli.Context) error {
|
||||
// loop through each source and print
|
||||
w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags)
|
||||
fmt.Fprintf(w, "ID\tName\tType\tEnabled\n")
|
||||
for _, source := range authSources {
|
||||
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive)
|
||||
for _, source := range loginSources {
|
||||
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, models.LoginNames[source.Type], source.IsActived)
|
||||
}
|
||||
w.Flush()
|
||||
|
||||
@@ -734,17 +595,14 @@ func runDeleteAuth(c *cli.Context) error {
|
||||
return fmt.Errorf("--id flag is missing")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
if err := initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source, err := auth_model.GetSourceByID(c.Int64("id"))
|
||||
source, err := models.GetLoginSourceByID(c.Int64("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return auth_service.DeleteSource(source)
|
||||
return models.DeleteSource(source)
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/services/auth/source/ldap"
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/auth/ldap"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type (
|
||||
authService struct {
|
||||
initDB func(ctx context.Context) error
|
||||
createAuthSource func(*auth.Source) error
|
||||
updateAuthSource func(*auth.Source) error
|
||||
getAuthSourceByID func(id int64) (*auth.Source, error)
|
||||
initDB func() error
|
||||
createLoginSource func(loginSource *models.LoginSource) error
|
||||
updateLoginSource func(loginSource *models.LoginSource) error
|
||||
getLoginSourceByID func(id int64) (*models.LoginSource, error)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -33,10 +33,6 @@ var (
|
||||
Name: "not-active",
|
||||
Usage: "Deactivate the authentication source.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "active",
|
||||
Usage: "Activate the authentication source.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "security-protocol",
|
||||
Usage: "Security protocol name.",
|
||||
@@ -93,14 +89,6 @@ var (
|
||||
Name: "public-ssh-key-attribute",
|
||||
Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-local-2fa",
|
||||
Usage: "Set to true to skip local 2fa for users authenticated by this source",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "avatar-attribute",
|
||||
Usage: "The attribute of the user’s LDAP record containing the user’s avatar.",
|
||||
},
|
||||
}
|
||||
|
||||
ldapBindDnCLIFlags = append(commonLdapCLIFlags,
|
||||
@@ -120,10 +108,6 @@ var (
|
||||
Name: "synchronize-users",
|
||||
Usage: "Enable user synchronization.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "disable-synchronize-users",
|
||||
Usage: "Disable user synchronization.",
|
||||
},
|
||||
cli.UintFlag{
|
||||
Name: "page-size",
|
||||
Usage: "Search page size.",
|
||||
@@ -175,103 +159,91 @@ var (
|
||||
// newAuthService creates a service with default functions.
|
||||
func newAuthService() *authService {
|
||||
return &authService{
|
||||
initDB: initDB,
|
||||
createAuthSource: auth.CreateSource,
|
||||
updateAuthSource: auth.UpdateSource,
|
||||
getAuthSourceByID: auth.GetSourceByID,
|
||||
initDB: initDB,
|
||||
createLoginSource: models.CreateLoginSource,
|
||||
updateLoginSource: models.UpdateSource,
|
||||
getLoginSourceByID: models.GetLoginSourceByID,
|
||||
}
|
||||
}
|
||||
|
||||
// parseAuthSource assigns values on authSource according to command line flags.
|
||||
func parseAuthSource(c *cli.Context, authSource *auth.Source) {
|
||||
// parseLoginSource assigns values on loginSource according to command line flags.
|
||||
func parseLoginSource(c *cli.Context, loginSource *models.LoginSource) {
|
||||
if c.IsSet("name") {
|
||||
authSource.Name = c.String("name")
|
||||
loginSource.Name = c.String("name")
|
||||
}
|
||||
if c.IsSet("not-active") {
|
||||
authSource.IsActive = !c.Bool("not-active")
|
||||
}
|
||||
if c.IsSet("active") {
|
||||
authSource.IsActive = c.Bool("active")
|
||||
loginSource.IsActived = !c.Bool("not-active")
|
||||
}
|
||||
if c.IsSet("synchronize-users") {
|
||||
authSource.IsSyncEnabled = c.Bool("synchronize-users")
|
||||
}
|
||||
if c.IsSet("disable-synchronize-users") {
|
||||
authSource.IsSyncEnabled = !c.Bool("disable-synchronize-users")
|
||||
loginSource.IsSyncEnabled = c.Bool("synchronize-users")
|
||||
}
|
||||
}
|
||||
|
||||
// parseLdapConfig assigns values on config according to command line flags.
|
||||
func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
|
||||
func parseLdapConfig(c *cli.Context, config *models.LDAPConfig) error {
|
||||
if c.IsSet("name") {
|
||||
config.Name = c.String("name")
|
||||
config.Source.Name = c.String("name")
|
||||
}
|
||||
if c.IsSet("host") {
|
||||
config.Host = c.String("host")
|
||||
config.Source.Host = c.String("host")
|
||||
}
|
||||
if c.IsSet("port") {
|
||||
config.Port = c.Int("port")
|
||||
config.Source.Port = c.Int("port")
|
||||
}
|
||||
if c.IsSet("security-protocol") {
|
||||
p, ok := findLdapSecurityProtocolByName(c.String("security-protocol"))
|
||||
if !ok {
|
||||
return fmt.Errorf("Unknown security protocol name: %s", c.String("security-protocol"))
|
||||
}
|
||||
config.SecurityProtocol = p
|
||||
config.Source.SecurityProtocol = p
|
||||
}
|
||||
if c.IsSet("skip-tls-verify") {
|
||||
config.SkipVerify = c.Bool("skip-tls-verify")
|
||||
config.Source.SkipVerify = c.Bool("skip-tls-verify")
|
||||
}
|
||||
if c.IsSet("bind-dn") {
|
||||
config.BindDN = c.String("bind-dn")
|
||||
config.Source.BindDN = c.String("bind-dn")
|
||||
}
|
||||
if c.IsSet("user-dn") {
|
||||
config.UserDN = c.String("user-dn")
|
||||
config.Source.UserDN = c.String("user-dn")
|
||||
}
|
||||
if c.IsSet("bind-password") {
|
||||
config.BindPassword = c.String("bind-password")
|
||||
config.Source.BindPassword = c.String("bind-password")
|
||||
}
|
||||
if c.IsSet("user-search-base") {
|
||||
config.UserBase = c.String("user-search-base")
|
||||
config.Source.UserBase = c.String("user-search-base")
|
||||
}
|
||||
if c.IsSet("username-attribute") {
|
||||
config.AttributeUsername = c.String("username-attribute")
|
||||
config.Source.AttributeUsername = c.String("username-attribute")
|
||||
}
|
||||
if c.IsSet("firstname-attribute") {
|
||||
config.AttributeName = c.String("firstname-attribute")
|
||||
config.Source.AttributeName = c.String("firstname-attribute")
|
||||
}
|
||||
if c.IsSet("surname-attribute") {
|
||||
config.AttributeSurname = c.String("surname-attribute")
|
||||
config.Source.AttributeSurname = c.String("surname-attribute")
|
||||
}
|
||||
if c.IsSet("email-attribute") {
|
||||
config.AttributeMail = c.String("email-attribute")
|
||||
config.Source.AttributeMail = c.String("email-attribute")
|
||||
}
|
||||
if c.IsSet("attributes-in-bind") {
|
||||
config.AttributesInBind = c.Bool("attributes-in-bind")
|
||||
config.Source.AttributesInBind = c.Bool("attributes-in-bind")
|
||||
}
|
||||
if c.IsSet("public-ssh-key-attribute") {
|
||||
config.AttributeSSHPublicKey = c.String("public-ssh-key-attribute")
|
||||
}
|
||||
if c.IsSet("avatar-attribute") {
|
||||
config.AttributeAvatar = c.String("avatar-attribute")
|
||||
config.Source.AttributeSSHPublicKey = c.String("public-ssh-key-attribute")
|
||||
}
|
||||
if c.IsSet("page-size") {
|
||||
config.SearchPageSize = uint32(c.Uint("page-size"))
|
||||
config.Source.SearchPageSize = uint32(c.Uint("page-size"))
|
||||
}
|
||||
if c.IsSet("user-filter") {
|
||||
config.Filter = c.String("user-filter")
|
||||
config.Source.Filter = c.String("user-filter")
|
||||
}
|
||||
if c.IsSet("admin-filter") {
|
||||
config.AdminFilter = c.String("admin-filter")
|
||||
config.Source.AdminFilter = c.String("admin-filter")
|
||||
}
|
||||
if c.IsSet("restricted-filter") {
|
||||
config.RestrictedFilter = c.String("restricted-filter")
|
||||
config.Source.RestrictedFilter = c.String("restricted-filter")
|
||||
}
|
||||
if c.IsSet("allow-deactivate-all") {
|
||||
config.AllowDeactivateAll = c.Bool("allow-deactivate-all")
|
||||
}
|
||||
if c.IsSet("skip-local-2fa") {
|
||||
config.SkipLocalTwoFA = c.Bool("skip-local-2fa")
|
||||
config.Source.AllowDeactivateAll = c.Bool("allow-deactivate-all")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -279,7 +251,7 @@ func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
|
||||
// findLdapSecurityProtocolByName finds security protocol by its name ignoring case.
|
||||
// It returns the value of the security protocol and if it was found.
|
||||
func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
|
||||
for i, n := range ldap.SecurityProtocolNames {
|
||||
for i, n := range models.SecurityProtocolNames {
|
||||
if strings.EqualFold(name, n) {
|
||||
return i, true
|
||||
}
|
||||
@@ -287,23 +259,23 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// getAuthSource gets the login source by its id defined in the command line flags.
|
||||
// getLoginSource gets the login source by its id defined in the command line flags.
|
||||
// It returns an error if the id is not set, does not match any source or if the source is not of expected type.
|
||||
func (a *authService) getAuthSource(c *cli.Context, authType auth.Type) (*auth.Source, error) {
|
||||
func (a *authService) getLoginSource(c *cli.Context, loginType models.LoginType) (*models.LoginSource, error) {
|
||||
if err := argsSet(c, "id"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authSource, err := a.getAuthSourceByID(c.Int64("id"))
|
||||
loginSource, err := a.getLoginSourceByID(c.Int64("id"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if authSource.Type != authType {
|
||||
return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String())
|
||||
if loginSource.Type != loginType {
|
||||
return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", models.LoginNames[loginType], models.LoginNames[loginSource.Type])
|
||||
}
|
||||
|
||||
return authSource, nil
|
||||
return loginSource, nil
|
||||
}
|
||||
|
||||
// addLdapBindDn adds a new LDAP via Bind DN authentication source.
|
||||
@@ -312,49 +284,45 @@ func (a *authService) addLdapBindDn(c *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
if err := a.initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authSource := &auth.Source{
|
||||
Type: auth.LDAP,
|
||||
IsActive: true, // active by default
|
||||
Cfg: &ldap.Source{
|
||||
Enabled: true, // always true
|
||||
loginSource := &models.LoginSource{
|
||||
Type: models.LoginLDAP,
|
||||
IsActived: true, // active by default
|
||||
Cfg: &models.LDAPConfig{
|
||||
Source: &ldap.Source{
|
||||
Enabled: true, // always true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
parseAuthSource(c, authSource)
|
||||
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
||||
parseLoginSource(c, loginSource)
|
||||
if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return a.createAuthSource(authSource)
|
||||
return a.createLoginSource(loginSource)
|
||||
}
|
||||
|
||||
// updateLdapBindDn updates a new LDAP via Bind DN authentication source.
|
||||
func (a *authService) updateLdapBindDn(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
if err := a.initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authSource, err := a.getAuthSource(c, auth.LDAP)
|
||||
loginSource, err := a.getLoginSource(c, models.LoginLDAP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parseAuthSource(c, authSource)
|
||||
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
||||
parseLoginSource(c, loginSource)
|
||||
if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return a.updateAuthSource(authSource)
|
||||
return a.updateLoginSource(loginSource)
|
||||
}
|
||||
|
||||
// addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.
|
||||
@@ -363,47 +331,43 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
if err := a.initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authSource := &auth.Source{
|
||||
Type: auth.DLDAP,
|
||||
IsActive: true, // active by default
|
||||
Cfg: &ldap.Source{
|
||||
Enabled: true, // always true
|
||||
loginSource := &models.LoginSource{
|
||||
Type: models.LoginDLDAP,
|
||||
IsActived: true, // active by default
|
||||
Cfg: &models.LDAPConfig{
|
||||
Source: &ldap.Source{
|
||||
Enabled: true, // always true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
parseAuthSource(c, authSource)
|
||||
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
||||
parseLoginSource(c, loginSource)
|
||||
if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return a.createAuthSource(authSource)
|
||||
return a.createLoginSource(loginSource)
|
||||
}
|
||||
|
||||
// updateLdapBindDn updates a new LDAP (simple auth) authentication source.
|
||||
func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
if err := a.initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authSource, err := a.getAuthSource(c, auth.DLDAP)
|
||||
loginSource, err := a.getLoginSource(c, models.LoginDLDAP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parseAuthSource(c, authSource)
|
||||
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
||||
parseLoginSource(c, loginSource)
|
||||
if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return a.updateAuthSource(authSource)
|
||||
return a.updateLoginSource(loginSource)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +0,0 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var subcmdUser = cli.Command{
|
||||
Name: "user",
|
||||
Usage: "Modify users",
|
||||
Subcommands: []cli.Command{
|
||||
microcmdUserCreate,
|
||||
microcmdUserList,
|
||||
microcmdUserChangePassword,
|
||||
microcmdUserDelete,
|
||||
microcmdUserGenerateAccessToken,
|
||||
microcmdUserMustChangePassword,
|
||||
},
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
pwd "code.gitea.io/gitea/modules/auth/password"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var microcmdUserChangePassword = cli.Command{
|
||||
Name: "change-password",
|
||||
Usage: "Change a user's password",
|
||||
Action: runChangePassword,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "username,u",
|
||||
Value: "",
|
||||
Usage: "The user to change password for",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "password,p",
|
||||
Value: "",
|
||||
Usage: "New password to set for user",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runChangePassword(c *cli.Context) error {
|
||||
if err := argsSet(c, "username", "password"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(c.String("password")) < setting.MinPasswordLength {
|
||||
return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
|
||||
}
|
||||
|
||||
if !pwd.IsComplexEnough(c.String("password")) {
|
||||
return errors.New("Password does not meet complexity requirements")
|
||||
}
|
||||
pwned, err := pwd.IsPwned(context.Background(), c.String("password"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pwned {
|
||||
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
|
||||
}
|
||||
uname := c.String("username")
|
||||
user, err := user_model.GetUserByName(ctx, uname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = user.SetPassword(c.String("password")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = user_model.UpdateUserCols(ctx, user, "passwd", "passwd_hash_algo", "salt"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s's password has been successfully updated!\n", user.Name)
|
||||
return nil
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
pwd "code.gitea.io/gitea/modules/auth/password"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var microcmdUserCreate = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create a new user in database",
|
||||
Action: runCreateUser,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "Username. DEPRECATED: use username instead",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "username",
|
||||
Usage: "Username",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "User password",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "email",
|
||||
Usage: "User email address",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "admin",
|
||||
Usage: "User is an admin",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "random-password",
|
||||
Usage: "Generate a random password for the user",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "must-change-password",
|
||||
Usage: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: true)",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "random-password-length",
|
||||
Usage: "Length of the random password to be generated",
|
||||
Value: 12,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "access-token",
|
||||
Usage: "Generate access token for the user",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "restricted",
|
||||
Usage: "Make a restricted user account",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runCreateUser(c *cli.Context) error {
|
||||
if err := argsSet(c, "email"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.IsSet("name") && c.IsSet("username") {
|
||||
return errors.New("Cannot set both --name and --username flags")
|
||||
}
|
||||
if !c.IsSet("name") && !c.IsSet("username") {
|
||||
return errors.New("One of --name or --username flags must be set")
|
||||
}
|
||||
|
||||
if c.IsSet("password") && c.IsSet("random-password") {
|
||||
return errors.New("cannot set both -random-password and -password flags")
|
||||
}
|
||||
|
||||
var username string
|
||||
if c.IsSet("username") {
|
||||
username = c.String("username")
|
||||
} else {
|
||||
username = c.String("name")
|
||||
fmt.Fprintf(os.Stderr, "--name flag is deprecated. Use --username instead.\n")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var password string
|
||||
if c.IsSet("password") {
|
||||
password = c.String("password")
|
||||
} else if c.IsSet("random-password") {
|
||||
var err error
|
||||
password, err = pwd.Generate(c.Int("random-password-length"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("generated random password is '%s'\n", password)
|
||||
} else {
|
||||
return errors.New("must set either password or random-password flag")
|
||||
}
|
||||
|
||||
// always default to true
|
||||
changePassword := true
|
||||
|
||||
// If this is the first user being created.
|
||||
// Take it as the admin and don't force a password update.
|
||||
if n := user_model.CountUsers(nil); n == 0 {
|
||||
changePassword = false
|
||||
}
|
||||
|
||||
if c.IsSet("must-change-password") {
|
||||
changePassword = c.Bool("must-change-password")
|
||||
}
|
||||
|
||||
restricted := util.OptionalBoolNone
|
||||
|
||||
if c.IsSet("restricted") {
|
||||
restricted = util.OptionalBoolOf(c.Bool("restricted"))
|
||||
}
|
||||
|
||||
// default user visibility in app.ini
|
||||
visibility := setting.Service.DefaultUserVisibilityMode
|
||||
|
||||
u := &user_model.User{
|
||||
Name: username,
|
||||
Email: c.String("email"),
|
||||
Passwd: password,
|
||||
IsAdmin: c.Bool("admin"),
|
||||
MustChangePassword: changePassword,
|
||||
Visibility: visibility,
|
||||
}
|
||||
|
||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
IsRestricted: restricted,
|
||||
}
|
||||
|
||||
if err := user_model.CreateUser(u, overwriteDefault); err != nil {
|
||||
return fmt.Errorf("CreateUser: %w", err)
|
||||
}
|
||||
|
||||
if c.Bool("access-token") {
|
||||
t := &auth_model.AccessToken{
|
||||
Name: "gitea-admin",
|
||||
UID: u.ID,
|
||||
}
|
||||
|
||||
if err := auth_model.NewAccessToken(t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Access token was successfully created... %s\n", t.Token)
|
||||
}
|
||||
|
||||
fmt.Printf("New user '%s' has been successfully created!\n", username)
|
||||
return nil
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var microcmdUserDelete = cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete specific user by id, name or email",
|
||||
Flags: []cli.Flag{
|
||||
cli.Int64Flag{
|
||||
Name: "id",
|
||||
Usage: "ID of user of the user to delete",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "username,u",
|
||||
Usage: "Username of the user to delete",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "email,e",
|
||||
Usage: "Email of the user to delete",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "purge",
|
||||
Usage: "Purge user, all their repositories, organizations and comments",
|
||||
},
|
||||
},
|
||||
Action: runDeleteUser,
|
||||
}
|
||||
|
||||
func runDeleteUser(c *cli.Context) error {
|
||||
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
|
||||
return fmt.Errorf("You must provide the id, username or email of a user to delete")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := storage.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
var user *user_model.User
|
||||
if c.IsSet("email") {
|
||||
user, err = user_model.GetUserByEmail(ctx, c.String("email"))
|
||||
} else if c.IsSet("username") {
|
||||
user, err = user_model.GetUserByName(ctx, c.String("username"))
|
||||
} else {
|
||||
user, err = user_model.GetUserByID(ctx, c.Int64("id"))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) {
|
||||
return fmt.Errorf("The user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username"))
|
||||
}
|
||||
|
||||
if c.IsSet("id") && user.ID != c.Int64("id") {
|
||||
return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id"))
|
||||
}
|
||||
|
||||
return user_service.DeleteUser(ctx, user, c.Bool("purge"))
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var microcmdUserGenerateAccessToken = cli.Command{
|
||||
Name: "generate-access-token",
|
||||
Usage: "Generate an access token for a specific user",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "username,u",
|
||||
Usage: "Username",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "token-name,t",
|
||||
Usage: "Token name",
|
||||
Value: "gitea-admin",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "raw",
|
||||
Usage: "Display only the token value",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "scopes",
|
||||
Value: "",
|
||||
Usage: "Comma separated list of scopes to apply to access token",
|
||||
},
|
||||
},
|
||||
Action: runGenerateAccessToken,
|
||||
}
|
||||
|
||||
func runGenerateAccessToken(c *cli.Context) error {
|
||||
if !c.IsSet("username") {
|
||||
return fmt.Errorf("You must provide a username to generate a token for")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := user_model.GetUserByName(ctx, c.String("username"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accessTokenScope, err := auth_model.AccessTokenScope(c.String("scopes")).Normalize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t := &auth_model.AccessToken{
|
||||
Name: c.String("token-name"),
|
||||
UID: user.ID,
|
||||
Scope: accessTokenScope,
|
||||
}
|
||||
|
||||
if err := auth_model.NewAccessToken(t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Bool("raw") {
|
||||
fmt.Printf("%s\n", t.Token)
|
||||
} else {
|
||||
fmt.Printf("Access token was successfully created: %s\n", t.Token)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var microcmdUserList = cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List users",
|
||||
Action: runListUsers,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "admin",
|
||||
Usage: "List only admin users",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runListUsers(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
users, err := user_model.GetAllUsers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0)
|
||||
|
||||
if c.IsSet("admin") {
|
||||
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\n")
|
||||
for _, u := range users {
|
||||
if u.IsAdmin {
|
||||
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
twofa := user_model.UserList(users).GetTwoFaStatus()
|
||||
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n")
|
||||
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])
|
||||
}
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var microcmdUserMustChangePassword = cli.Command{
|
||||
Name: "must-change-password",
|
||||
Usage: "Set the must change password flag for the provided users or all users",
|
||||
Action: runMustChangePassword,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "all,A",
|
||||
Usage: "All users must change password, except those explicitly excluded with --exclude",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "exclude,e",
|
||||
Usage: "Do not change the must-change-password flag for these users",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "unset",
|
||||
Usage: "Instead of setting the must-change-password flag, unset it",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runMustChangePassword(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if c.NArg() == 0 && !c.IsSet("all") {
|
||||
return errors.New("either usernames or --all must be provided")
|
||||
}
|
||||
|
||||
mustChangePassword := !c.Bool("unset")
|
||||
all := c.Bool("all")
|
||||
exclude := c.StringSlice("exclude")
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args(), exclude)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Updated %d users setting MustChangePassword to %t\n", n, mustChangePassword)
|
||||
return nil
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2016 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
@@ -179,7 +180,7 @@ func runCert(c *cli.Context) error {
|
||||
}
|
||||
log.Println("Written cert.pem")
|
||||
|
||||
keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||
keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open key.pem for writing: %v", err)
|
||||
}
|
||||
|
||||
72
cmd/cmd.go
72
cmd/cmd.go
@@ -1,21 +1,16 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package cmd provides subcommands to the gitea binary - such as "web" or
|
||||
// "admin".
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@@ -30,67 +25,24 @@ func argsSet(c *cli.Context, args ...string) error {
|
||||
return errors.New(a + " is not set")
|
||||
}
|
||||
|
||||
if util.IsEmptyString(c.String(a)) {
|
||||
if util.IsEmptyString(a) {
|
||||
return errors.New(a + " is required")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// confirm waits for user input which confirms an action
|
||||
func confirm() (bool, error) {
|
||||
var response string
|
||||
|
||||
_, err := fmt.Scanln(&response)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
switch strings.ToLower(response) {
|
||||
case "y", "yes":
|
||||
return true, nil
|
||||
case "n", "no":
|
||||
return false, nil
|
||||
default:
|
||||
return false, errors.New(response + " isn't a correct confirmation string")
|
||||
}
|
||||
func initDB() error {
|
||||
return initDBDisableConsole(false)
|
||||
}
|
||||
|
||||
func initDB(ctx context.Context) error {
|
||||
setting.InitProviderFromExistingFile()
|
||||
setting.LoadCommonSettings()
|
||||
setting.LoadDBSetting()
|
||||
setting.InitSQLLog(false)
|
||||
func initDBDisableConsole(disableConsole bool) error {
|
||||
setting.NewContext()
|
||||
setting.InitDBConfig()
|
||||
|
||||
if setting.Database.Type == "" {
|
||||
log.Fatal(`Database settings are missing from the configuration file: %q.
|
||||
Ensure you are running in the correct environment or set the correct configuration file with -c.
|
||||
If this is the intended configuration file complete the [database] section.`, setting.CustomConf)
|
||||
}
|
||||
if err := db.InitEngine(ctx); err != nil {
|
||||
return fmt.Errorf("unable to initialize the database using the configuration in %q. Error: %w", setting.CustomConf, err)
|
||||
setting.NewXORMLogService(disableConsole)
|
||||
if err := models.SetEngine(); err != nil {
|
||||
return fmt.Errorf("models.SetEngine: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func installSignals() (context.Context, context.CancelFunc) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
// install notify
|
||||
signalChannel := make(chan os.Signal, 1)
|
||||
|
||||
signal.Notify(
|
||||
signalChannel,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
)
|
||||
select {
|
||||
case <-signalChannel:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
cancel()
|
||||
signal.Reset()
|
||||
}()
|
||||
|
||||
return ctx, cancel
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
@@ -22,25 +23,22 @@ var CmdConvert = cli.Command{
|
||||
}
|
||||
|
||||
func runConvert(ctx *cli.Context) error {
|
||||
stdCtx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(stdCtx); err != nil {
|
||||
if err := initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("AppPath: %s", setting.AppPath)
|
||||
log.Info("AppWorkPath: %s", setting.AppWorkPath)
|
||||
log.Info("Custom path: %s", setting.CustomPath)
|
||||
log.Info("Log path: %s", setting.Log.RootPath)
|
||||
log.Info("Configuration file: %s", setting.CustomConf)
|
||||
log.Trace("AppPath: %s", setting.AppPath)
|
||||
log.Trace("AppWorkPath: %s", setting.AppWorkPath)
|
||||
log.Trace("Custom path: %s", setting.CustomPath)
|
||||
log.Trace("Log path: %s", setting.LogRootPath)
|
||||
setting.InitDBConfig()
|
||||
|
||||
if !setting.Database.UseMySQL {
|
||||
fmt.Println("This command can only be used with a MySQL database")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := db.ConvertUtf8ToUtf8mb4(); err != nil {
|
||||
if err := models.ConvertUtf8ToUtf8mb4(); err != nil {
|
||||
log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
64
cmd/docs.go
64
cmd/docs.go
@@ -1,64 +0,0 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// CmdDocs represents the available docs sub-command.
|
||||
var CmdDocs = cli.Command{
|
||||
Name: "docs",
|
||||
Usage: "Output CLI documentation",
|
||||
Description: "A command to output Gitea'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, 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
|
||||
}
|
||||
641
cmd/doctor.go
641
cmd/doctor.go
@@ -1,32 +1,41 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
golog "log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/migrations"
|
||||
migrate_base "code.gitea.io/gitea/models/migrations/base"
|
||||
"code.gitea.io/gitea/modules/doctor"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/options"
|
||||
"code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// CmdDoctor represents the available doctor sub-command.
|
||||
var CmdDoctor = cli.Command{
|
||||
Name: "doctor",
|
||||
Usage: "Diagnose and optionally fix problems",
|
||||
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
|
||||
Usage: "Diagnose problems",
|
||||
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration.",
|
||||
Action: runDoctor,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
@@ -53,10 +62,6 @@ var CmdDoctor = cli.Command{
|
||||
Name: "log-file",
|
||||
Usage: `Name of the log file (default: "doctor.log"). Set to "-" to output to stdout, set to "" to disable`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "color, H",
|
||||
Usage: "Use color for outputted information",
|
||||
},
|
||||
},
|
||||
Subcommands: []cli.Command{
|
||||
cmdRecreateTable,
|
||||
@@ -81,26 +86,93 @@ You should back-up your database before doing this and ensure that your database
|
||||
Action: runRecreateTable,
|
||||
}
|
||||
|
||||
type check struct {
|
||||
title string
|
||||
name string
|
||||
isDefault bool
|
||||
f func(ctx *cli.Context) ([]string, error)
|
||||
abortIfFailed bool
|
||||
skipDatabaseInit bool
|
||||
}
|
||||
|
||||
// checklist represents list for all checks
|
||||
var checklist = []check{
|
||||
{
|
||||
// NOTE: this check should be the first in the list
|
||||
title: "Check paths and basic configuration",
|
||||
name: "paths",
|
||||
isDefault: true,
|
||||
f: runDoctorPathInfo,
|
||||
abortIfFailed: true,
|
||||
skipDatabaseInit: true,
|
||||
},
|
||||
{
|
||||
title: "Check Database Version",
|
||||
name: "check-db-version",
|
||||
isDefault: true,
|
||||
f: runDoctorCheckDBVersion,
|
||||
abortIfFailed: false,
|
||||
},
|
||||
{
|
||||
title: "Check consistency of database",
|
||||
name: "check-db-consistency",
|
||||
isDefault: false,
|
||||
f: runDoctorCheckDBConsistency,
|
||||
},
|
||||
{
|
||||
title: "Check if OpenSSH authorized_keys file is up-to-date",
|
||||
name: "authorized_keys",
|
||||
isDefault: true,
|
||||
f: runDoctorAuthorizedKeys,
|
||||
},
|
||||
{
|
||||
title: "Check if SCRIPT_TYPE is available",
|
||||
name: "script-type",
|
||||
isDefault: false,
|
||||
f: runDoctorScriptType,
|
||||
},
|
||||
{
|
||||
title: "Check if hook files are up-to-date and executable",
|
||||
name: "hooks",
|
||||
isDefault: false,
|
||||
f: runDoctorHooks,
|
||||
},
|
||||
{
|
||||
title: "Recalculate merge bases",
|
||||
name: "recalculate_merge_bases",
|
||||
isDefault: false,
|
||||
f: runDoctorPRMergeBase,
|
||||
},
|
||||
{
|
||||
title: "Recalculate Stars number for all user",
|
||||
name: "recalculate_stars_number",
|
||||
isDefault: false,
|
||||
f: runDoctorUserStarNum,
|
||||
},
|
||||
{
|
||||
title: "Enable push options",
|
||||
name: "enable-push-options",
|
||||
isDefault: false,
|
||||
f: runDoctorEnablePushOptions,
|
||||
},
|
||||
// more checks please append here
|
||||
}
|
||||
|
||||
func runRecreateTable(ctx *cli.Context) error {
|
||||
// Redirect the default golog to here
|
||||
golog.SetFlags(0)
|
||||
golog.SetPrefix("")
|
||||
golog.SetOutput(log.NewLoggerAsWriter("INFO", log.GetLogger(log.DEFAULT)))
|
||||
|
||||
setting.InitProviderFromExistingFile()
|
||||
setting.LoadCommonSettings()
|
||||
setting.LoadDBSetting()
|
||||
setting.NewContext()
|
||||
setting.InitDBConfig()
|
||||
|
||||
setting.Log.EnableXORMLog = ctx.Bool("debug")
|
||||
setting.EnableXORMLog = ctx.Bool("debug")
|
||||
setting.Database.LogSQL = ctx.Bool("debug")
|
||||
// FIXME: don't use CfgProvider directly
|
||||
setting.CfgProvider.Section("log").Key("XORM").SetValue(",")
|
||||
setting.Cfg.Section("log").Key("XORM").SetValue(",")
|
||||
|
||||
setting.InitSQLLog(!ctx.Bool("debug"))
|
||||
stdCtx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := db.InitEngine(stdCtx); err != nil {
|
||||
setting.NewXORMLogService(!ctx.Bool("debug"))
|
||||
if err := models.SetEngine(); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.")
|
||||
return nil
|
||||
@@ -112,75 +184,39 @@ func runRecreateTable(ctx *cli.Context) error {
|
||||
names = append(names, args.Get(i))
|
||||
}
|
||||
|
||||
beans, err := db.NamesToBean(names...)
|
||||
beans, err := models.NamesToBean(names...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
recreateTables := migrate_base.RecreateTables(beans...)
|
||||
recreateTables := migrations.RecreateTables(beans...)
|
||||
|
||||
return db.InitEngineWithMigration(stdCtx, func(x *xorm.Engine) error {
|
||||
return models.NewEngine(context.Background(), func(x *xorm.Engine) error {
|
||||
if err := migrations.EnsureUpToDate(x); err != nil {
|
||||
return err
|
||||
}
|
||||
return recreateTables(x)
|
||||
})
|
||||
}
|
||||
|
||||
func setDoctorLogger(ctx *cli.Context) {
|
||||
logFile := ctx.String("log-file")
|
||||
if !ctx.IsSet("log-file") {
|
||||
logFile = "doctor.log"
|
||||
}
|
||||
colorize := log.CanColorStdout
|
||||
if ctx.IsSet("color") {
|
||||
colorize = ctx.Bool("color")
|
||||
}
|
||||
|
||||
if len(logFile) == 0 {
|
||||
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"NONE","stacktracelevel":"NONE","colorize":%t}`, colorize))
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
recovered := recover()
|
||||
if recovered == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err, ok := recovered.(error)
|
||||
if !ok {
|
||||
panic(recovered)
|
||||
}
|
||||
if errors.Is(err, os.ErrPermission) {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: Unable to write logs to provided file due to permissions error: %s\n %v\n", logFile, err)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: Unable to write logs to provided file: %s\n %v\n", logFile, err)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "WARN: Logging will be disabled\n Use `--log-file` to configure log file location\n")
|
||||
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"NONE","stacktracelevel":"NONE","colorize":%t}`, colorize))
|
||||
}()
|
||||
|
||||
if logFile == "-" {
|
||||
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"trace","stacktracelevel":"NONE","colorize":%t}`, colorize))
|
||||
} else {
|
||||
log.NewLogger(1000, "doctor", "file", fmt.Sprintf(`{"filename":%q,"level":"trace","stacktracelevel":"NONE"}`, logFile))
|
||||
}
|
||||
}
|
||||
|
||||
func runDoctor(ctx *cli.Context) error {
|
||||
stdCtx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
// Silence the default loggers
|
||||
log.DelNamedLogger("console")
|
||||
log.DelNamedLogger(log.DEFAULT)
|
||||
|
||||
// Now setup our own
|
||||
setDoctorLogger(ctx)
|
||||
logFile := ctx.String("log-file")
|
||||
if !ctx.IsSet("log-file") {
|
||||
logFile = "doctor.log"
|
||||
}
|
||||
|
||||
colorize := log.CanColorStdout
|
||||
if ctx.IsSet("color") {
|
||||
colorize = ctx.Bool("color")
|
||||
if len(logFile) == 0 {
|
||||
log.NewLogger(1000, "doctor", "console", `{"level":"NONE","stacktracelevel":"NONE","colorize":"%t"}`)
|
||||
} else if logFile == "-" {
|
||||
log.NewLogger(1000, "doctor", "console", `{"level":"trace","stacktracelevel":"NONE"}`)
|
||||
} else {
|
||||
log.NewLogger(1000, "doctor", "file", fmt.Sprintf(`{"filename":%q,"level":"trace","stacktracelevel":"NONE"}`, logFile))
|
||||
}
|
||||
|
||||
// Finally redirect the default golog to here
|
||||
@@ -189,24 +225,24 @@ func runDoctor(ctx *cli.Context) error {
|
||||
golog.SetOutput(log.NewLoggerAsWriter("INFO", log.GetLogger(log.DEFAULT)))
|
||||
|
||||
if ctx.IsSet("list") {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
|
||||
_, _ = w.Write([]byte("Default\tName\tTitle\n"))
|
||||
for _, check := range doctor.Checks {
|
||||
if check.IsDefault {
|
||||
for _, check := range checklist {
|
||||
if check.isDefault {
|
||||
_, _ = w.Write([]byte{'*'})
|
||||
}
|
||||
_, _ = w.Write([]byte{'\t'})
|
||||
_, _ = w.Write([]byte(check.Name))
|
||||
_, _ = w.Write([]byte(check.name))
|
||||
_, _ = w.Write([]byte{'\t'})
|
||||
_, _ = w.Write([]byte(check.Title))
|
||||
_, _ = w.Write([]byte(check.title))
|
||||
_, _ = w.Write([]byte{'\n'})
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
var checks []*doctor.Check
|
||||
var checks []check
|
||||
if ctx.Bool("all") {
|
||||
checks = doctor.Checks
|
||||
checks = checklist
|
||||
} else if ctx.IsSet("run") {
|
||||
addDefault := ctx.Bool("default")
|
||||
names := ctx.StringSlice("run")
|
||||
@@ -214,37 +250,452 @@ func runDoctor(ctx *cli.Context) error {
|
||||
names[i] = strings.ToLower(strings.TrimSpace(name))
|
||||
}
|
||||
|
||||
for _, check := range doctor.Checks {
|
||||
if addDefault && check.IsDefault {
|
||||
for _, check := range checklist {
|
||||
if addDefault && check.isDefault {
|
||||
checks = append(checks, check)
|
||||
continue
|
||||
}
|
||||
for _, name := range names {
|
||||
if name == check.Name {
|
||||
if name == check.name {
|
||||
checks = append(checks, check)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, check := range doctor.Checks {
|
||||
if check.IsDefault {
|
||||
for _, check := range checklist {
|
||||
if check.isDefault {
|
||||
checks = append(checks, check)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we can set up our own logger to return information about what the doctor is doing
|
||||
if err := log.NewNamedLogger("doctorouter",
|
||||
0,
|
||||
"console",
|
||||
"console",
|
||||
fmt.Sprintf(`{"level":"INFO","stacktracelevel":"NONE","colorize":%t,"flags":-1}`, colorize)); err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
dbIsInit := false
|
||||
|
||||
for i, check := range checks {
|
||||
if !dbIsInit && !check.skipDatabaseInit {
|
||||
// Only open database after the most basic configuration check
|
||||
setting.EnableXORMLog = false
|
||||
if err := initDBDisableConsole(true); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.")
|
||||
return nil
|
||||
}
|
||||
dbIsInit = true
|
||||
}
|
||||
fmt.Println("[", i+1, "]", check.title)
|
||||
messages, err := check.f(ctx)
|
||||
for _, message := range messages {
|
||||
fmt.Println("-", message)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
if check.abortIfFailed {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
fmt.Println("OK.")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runDoctorPathInfo(ctx *cli.Context) ([]string, error) {
|
||||
|
||||
res := make([]string, 0, 10)
|
||||
|
||||
if fi, err := os.Stat(setting.CustomConf); err != nil || !fi.Mode().IsRegular() {
|
||||
res = append(res, fmt.Sprintf("Failed to find configuration file at '%s'.", setting.CustomConf))
|
||||
res = append(res, fmt.Sprintf("If you've never ran Gitea yet, this is normal and '%s' will be created for you on first run.", setting.CustomConf))
|
||||
res = append(res, "Otherwise check that you are running this command from the correct path and/or provide a `--config` parameter.")
|
||||
return res, fmt.Errorf("can't proceed without a configuration file")
|
||||
}
|
||||
|
||||
logger := log.GetLogger("doctorouter")
|
||||
defer logger.Close()
|
||||
return doctor.RunChecks(stdCtx, logger, ctx.Bool("fix"), checks)
|
||||
setting.NewContext()
|
||||
|
||||
fail := false
|
||||
|
||||
check := func(name, path string, is_dir, required, is_write bool) {
|
||||
res = append(res, fmt.Sprintf("%-25s '%s'", name+":", path))
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) && ctx.Bool("fix") && is_dir {
|
||||
if err := os.MkdirAll(path, 0777); err != nil {
|
||||
res = append(res, fmt.Sprintf(" ERROR: %v", err))
|
||||
fail = true
|
||||
return
|
||||
}
|
||||
fi, err = os.Stat(path)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if required {
|
||||
res = append(res, fmt.Sprintf(" ERROR: %v", err))
|
||||
fail = true
|
||||
return
|
||||
}
|
||||
res = append(res, fmt.Sprintf(" NOTICE: not accessible (%v)", err))
|
||||
return
|
||||
}
|
||||
|
||||
if is_dir && !fi.IsDir() {
|
||||
res = append(res, " ERROR: not a directory")
|
||||
fail = true
|
||||
return
|
||||
} else if !is_dir && !fi.Mode().IsRegular() {
|
||||
res = append(res, " ERROR: not a regular file")
|
||||
fail = true
|
||||
} else if is_write {
|
||||
if err := runDoctorWritableDir(path); err != nil {
|
||||
res = append(res, fmt.Sprintf(" ERROR: not writable: %v", err))
|
||||
fail = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note print paths inside quotes to make any leading/trailing spaces evident
|
||||
check("Configuration File Path", setting.CustomConf, false, true, false)
|
||||
check("Repository Root Path", setting.RepoRootPath, true, true, true)
|
||||
check("Data Root Path", setting.AppDataPath, true, true, true)
|
||||
check("Custom File Root Path", setting.CustomPath, true, false, false)
|
||||
check("Work directory", setting.AppWorkPath, true, true, false)
|
||||
check("Log Root Path", setting.LogRootPath, true, true, true)
|
||||
|
||||
if options.IsDynamic() {
|
||||
// Do not check/report on StaticRootPath if data is embedded in Gitea (-tags bindata)
|
||||
check("Static File Root Path", setting.StaticRootPath, true, true, false)
|
||||
}
|
||||
|
||||
if fail {
|
||||
return res, fmt.Errorf("please check your configuration file and try again")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func runDoctorWritableDir(path string) error {
|
||||
// There's no platform-independent way of checking if a directory is writable
|
||||
// https://stackoverflow.com/questions/20026320/how-to-tell-if-folder-exists-and-is-writable
|
||||
|
||||
tmpFile, err := ioutil.TempFile(path, "doctors-order")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := util.Remove(tmpFile.Name()); err != nil {
|
||||
fmt.Printf("Warning: can't remove temporary file: '%s'\n", tmpFile.Name())
|
||||
}
|
||||
tmpFile.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
const tplCommentPrefix = `# gitea public key`
|
||||
|
||||
func runDoctorAuthorizedKeys(ctx *cli.Context) ([]string, error) {
|
||||
if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
|
||||
f, err := os.Open(fPath)
|
||||
if err != nil {
|
||||
if ctx.Bool("fix") {
|
||||
return []string{fmt.Sprintf("Error whilst opening authorized_keys: %v. Attempting regeneration", err)}, models.RewriteAllPublicKeys()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
linesInAuthorizedKeys := map[string]bool{}
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, tplCommentPrefix) {
|
||||
continue
|
||||
}
|
||||
linesInAuthorizedKeys[line] = true
|
||||
}
|
||||
f.Close()
|
||||
|
||||
// now we regenerate and check if there are any lines missing
|
||||
regenerated := &bytes.Buffer{}
|
||||
if err := models.RegeneratePublicKeys(regenerated); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scanner = bufio.NewScanner(regenerated)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, tplCommentPrefix) {
|
||||
continue
|
||||
}
|
||||
if ok := linesInAuthorizedKeys[line]; ok {
|
||||
continue
|
||||
}
|
||||
if ctx.Bool("fix") {
|
||||
return []string{"authorized_keys is out of date, attempting regeneration"}, models.RewriteAllPublicKeys()
|
||||
}
|
||||
return nil, fmt.Errorf(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized_keys --fix"`)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func runDoctorCheckDBVersion(ctx *cli.Context) ([]string, error) {
|
||||
if err := models.NewEngine(context.Background(), migrations.EnsureUpToDate); err != nil {
|
||||
if ctx.Bool("fix") {
|
||||
return []string{fmt.Sprintf("WARN: Got Error %v during ensure up to date", err), "Attempting to migrate to the latest DB version to fix this."}, models.NewEngine(context.Background(), migrations.Migrate)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func iterateRepositories(each func(*models.Repository) ([]string, error)) ([]string, error) {
|
||||
results := []string{}
|
||||
err := models.Iterate(
|
||||
models.DefaultDBContext(),
|
||||
new(models.Repository),
|
||||
builder.Gt{"id": 0},
|
||||
func(idx int, bean interface{}) error {
|
||||
res, err := each(bean.(*models.Repository))
|
||||
results = append(results, res...)
|
||||
return err
|
||||
},
|
||||
)
|
||||
return results, err
|
||||
}
|
||||
|
||||
func iteratePRs(repo *models.Repository, each func(*models.Repository, *models.PullRequest) ([]string, error)) ([]string, error) {
|
||||
results := []string{}
|
||||
err := models.Iterate(
|
||||
models.DefaultDBContext(),
|
||||
new(models.PullRequest),
|
||||
builder.Eq{"base_repo_id": repo.ID},
|
||||
func(idx int, bean interface{}) error {
|
||||
res, err := each(repo, bean.(*models.PullRequest))
|
||||
results = append(results, res...)
|
||||
return err
|
||||
},
|
||||
)
|
||||
return results, err
|
||||
}
|
||||
|
||||
func runDoctorHooks(ctx *cli.Context) ([]string, error) {
|
||||
// Need to iterate across all of the repositories
|
||||
return iterateRepositories(func(repo *models.Repository) ([]string, error) {
|
||||
results, err := repository.CheckDelegateHooks(repo.RepoPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(results) > 0 && ctx.Bool("fix") {
|
||||
return []string{fmt.Sprintf("regenerated hooks for %s", repo.FullName())}, repository.CreateDelegateHooks(repo.RepoPath())
|
||||
}
|
||||
|
||||
return results, nil
|
||||
})
|
||||
}
|
||||
|
||||
func runDoctorPRMergeBase(ctx *cli.Context) ([]string, error) {
|
||||
numRepos := 0
|
||||
numPRs := 0
|
||||
numPRsUpdated := 0
|
||||
results, err := iterateRepositories(func(repo *models.Repository) ([]string, error) {
|
||||
numRepos++
|
||||
return iteratePRs(repo, func(repo *models.Repository, pr *models.PullRequest) ([]string, error) {
|
||||
numPRs++
|
||||
results := []string{}
|
||||
pr.BaseRepo = repo
|
||||
repoPath := repo.RepoPath()
|
||||
|
||||
oldMergeBase := pr.MergeBase
|
||||
|
||||
if !pr.HasMerged {
|
||||
var err error
|
||||
pr.MergeBase, err = git.NewCommand("merge-base", "--", pr.BaseBranch, pr.GetGitRefName()).RunInDir(repoPath)
|
||||
if err != nil {
|
||||
var err2 error
|
||||
pr.MergeBase, err2 = git.NewCommand("rev-parse", git.BranchPrefix+pr.BaseBranch).RunInDir(repoPath)
|
||||
if err2 != nil {
|
||||
results = append(results, fmt.Sprintf("WARN: Unable to get merge base for PR ID %d, #%d onto %s in %s/%s", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name))
|
||||
log.Error("Unable to get merge base for PR ID %d, Index %d in %s/%s. Error: %v & %v", pr.ID, pr.Index, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err, err2)
|
||||
return results, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parentsString, err := git.NewCommand("rev-list", "--parents", "-n", "1", pr.MergedCommitID).RunInDir(repoPath)
|
||||
if err != nil {
|
||||
results = append(results, fmt.Sprintf("WARN: Unable to get parents for merged PR ID %d, #%d onto %s in %s/%s", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name))
|
||||
log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err)
|
||||
return results, nil
|
||||
}
|
||||
parents := strings.Split(strings.TrimSpace(parentsString), " ")
|
||||
if len(parents) < 2 {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
args := append([]string{"merge-base", "--"}, parents[1:]...)
|
||||
args = append(args, pr.GetGitRefName())
|
||||
|
||||
pr.MergeBase, err = git.NewCommand(args...).RunInDir(repoPath)
|
||||
if err != nil {
|
||||
results = append(results, fmt.Sprintf("WARN: Unable to get merge base for merged PR ID %d, #%d onto %s in %s/%s", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name))
|
||||
log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err)
|
||||
return results, nil
|
||||
}
|
||||
}
|
||||
pr.MergeBase = strings.TrimSpace(pr.MergeBase)
|
||||
if pr.MergeBase != oldMergeBase {
|
||||
if ctx.Bool("fix") {
|
||||
if err := pr.UpdateCols("merge_base"); err != nil {
|
||||
return results, err
|
||||
}
|
||||
} else {
|
||||
results = append(results, fmt.Sprintf("#%d onto %s in %s/%s: MergeBase should be %s but is %s", pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, oldMergeBase, pr.MergeBase))
|
||||
}
|
||||
numPRsUpdated++
|
||||
}
|
||||
return results, nil
|
||||
})
|
||||
})
|
||||
|
||||
if ctx.Bool("fix") {
|
||||
results = append(results, fmt.Sprintf("%d PR mergebases updated of %d PRs total in %d repos", numPRsUpdated, numPRs, numRepos))
|
||||
} else {
|
||||
if numPRsUpdated > 0 && err == nil {
|
||||
return results, fmt.Errorf("%d PRs with incorrect mergebases of %d PRs total in %d repos", numPRsUpdated, numPRs, numRepos)
|
||||
}
|
||||
results = append(results, fmt.Sprintf("%d PRs with incorrect mergebases of %d PRs total in %d repos", numPRsUpdated, numPRs, numRepos))
|
||||
}
|
||||
|
||||
return results, err
|
||||
}
|
||||
|
||||
func runDoctorUserStarNum(ctx *cli.Context) ([]string, error) {
|
||||
return nil, models.DoctorUserStarNum()
|
||||
}
|
||||
|
||||
func runDoctorScriptType(ctx *cli.Context) ([]string, error) {
|
||||
path, err := exec.LookPath(setting.ScriptType)
|
||||
if err != nil {
|
||||
return []string{fmt.Sprintf("ScriptType %s is not on the current PATH", setting.ScriptType)}, err
|
||||
}
|
||||
return []string{fmt.Sprintf("ScriptType %s is on the current PATH at %s", setting.ScriptType, path)}, nil
|
||||
}
|
||||
|
||||
func runDoctorCheckDBConsistency(ctx *cli.Context) ([]string, error) {
|
||||
var results []string
|
||||
|
||||
// make sure DB version is uptodate
|
||||
if err := models.NewEngine(context.Background(), migrations.EnsureUpToDate); err != nil {
|
||||
return nil, fmt.Errorf("model version on the database does not match the current Gitea version. Model consistency will not be checked until the database is upgraded")
|
||||
}
|
||||
|
||||
//find labels without existing repo or org
|
||||
count, err := models.CountOrphanedLabels()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if count > 0 {
|
||||
if ctx.Bool("fix") {
|
||||
if err = models.DeleteOrphanedLabels(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, fmt.Sprintf("%d labels without existing repository/organisation deleted", count))
|
||||
} else {
|
||||
results = append(results, fmt.Sprintf("%d labels without existing repository/organisation", count))
|
||||
}
|
||||
}
|
||||
|
||||
//find issues without existing repository
|
||||
count, err = models.CountOrphanedIssues()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if count > 0 {
|
||||
if ctx.Bool("fix") {
|
||||
if err = models.DeleteOrphanedIssues(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, fmt.Sprintf("%d issues without existing repository deleted", count))
|
||||
} else {
|
||||
results = append(results, fmt.Sprintf("%d issues without existing repository", count))
|
||||
}
|
||||
}
|
||||
|
||||
//find pulls without existing issues
|
||||
count, err = models.CountOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if count > 0 {
|
||||
if ctx.Bool("fix") {
|
||||
if err = models.DeleteOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, fmt.Sprintf("%d pull requests without existing issue deleted", count))
|
||||
} else {
|
||||
results = append(results, fmt.Sprintf("%d pull requests without existing issue", count))
|
||||
}
|
||||
}
|
||||
|
||||
//find tracked times without existing issues/pulls
|
||||
count, err = models.CountOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if count > 0 {
|
||||
if ctx.Bool("fix") {
|
||||
if err = models.DeleteOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, fmt.Sprintf("%d tracked times without existing issue deleted", count))
|
||||
} else {
|
||||
results = append(results, fmt.Sprintf("%d tracked times without existing issue", count))
|
||||
}
|
||||
}
|
||||
|
||||
count, err = models.CountNullArchivedRepository()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if count > 0 {
|
||||
if ctx.Bool("fix") {
|
||||
updatedCount, err := models.FixNullArchivedRepository()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, fmt.Sprintf("%d repositories with null is_archived updated", updatedCount))
|
||||
} else {
|
||||
results = append(results, fmt.Sprintf("%d repositories with null is_archived", count))
|
||||
}
|
||||
}
|
||||
|
||||
//ToDo: function to recalc all counters
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func runDoctorEnablePushOptions(ctx *cli.Context) ([]string, error) {
|
||||
numRepos := 0
|
||||
_, err := iterateRepositories(func(repo *models.Repository) ([]string, error) {
|
||||
numRepos++
|
||||
r, err := git.OpenRepository(repo.RepoPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
if ctx.Bool("fix") {
|
||||
_, err := git.NewCommand("config", "receive.advertisePushOptions", "true").RunInDir(r.Path)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
var prefix string
|
||||
if !ctx.Bool("fix") {
|
||||
prefix = "DRY RUN: "
|
||||
}
|
||||
return []string{fmt.Sprintf("%sEnabled push options for %d repositories.", prefix, numRepos)}, err
|
||||
}
|
||||
|
||||
248
cmd/dump.go
248
cmd/dump.go
@@ -1,45 +1,36 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2016 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"gitea.com/go-chi/session"
|
||||
"github.com/mholt/archiver/v3"
|
||||
"gitea.com/macaron/session"
|
||||
archiver "github.com/mholt/archiver/v3"
|
||||
"github.com/unknwon/com"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error {
|
||||
func addFile(w archiver.Writer, filePath string, absPath string, verbose bool) error {
|
||||
if verbose {
|
||||
log.Info("Adding file %s", customName)
|
||||
log.Info("Adding file %s\n", filePath)
|
||||
}
|
||||
|
||||
return w.Write(archiver.File{
|
||||
FileInfo: archiver.FileInfo{
|
||||
FileInfo: info,
|
||||
CustomName: customName,
|
||||
},
|
||||
ReadCloser: r,
|
||||
})
|
||||
}
|
||||
|
||||
func addFile(w archiver.Writer, filePath, absPath string, verbose bool) error {
|
||||
file, err := os.Open(absPath)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -50,10 +41,48 @@ func addFile(w archiver.Writer, filePath, absPath string, verbose bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return addReader(w, file, fileInfo, filePath, verbose)
|
||||
return w.Write(archiver.File{
|
||||
FileInfo: archiver.FileInfo{
|
||||
FileInfo: fileInfo,
|
||||
CustomName: filePath,
|
||||
},
|
||||
ReadCloser: file,
|
||||
})
|
||||
}
|
||||
|
||||
func isSubdir(upper, lower string) (bool, error) {
|
||||
func addRecursive(w archiver.Writer, dirPath string, absPath string, verbose bool) error {
|
||||
if verbose {
|
||||
log.Info("Adding dir %s\n", dirPath)
|
||||
}
|
||||
dir, err := os.Open(absPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not open directory %s: %s", absPath, err)
|
||||
}
|
||||
defer dir.Close()
|
||||
|
||||
files, err := dir.Readdir(0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to list files in %s: %s", absPath, err)
|
||||
}
|
||||
|
||||
if err := addFile(w, dirPath, absPath, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, fileInfo := range files {
|
||||
if fileInfo.IsDir() {
|
||||
err = addRecursive(w, filepath.Join(dirPath, fileInfo.Name()), filepath.Join(absPath, fileInfo.Name()), verbose)
|
||||
} else {
|
||||
err = addFile(w, filepath.Join(dirPath, fileInfo.Name()), filepath.Join(absPath, fileInfo.Name()), verbose)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSubdir(upper string, lower string) (bool, error) {
|
||||
if relPath, err := filepath.Rel(upper, lower); err != nil {
|
||||
return false, err
|
||||
} else if relPath == "." || !strings.HasPrefix(relPath, ".") {
|
||||
@@ -91,7 +120,7 @@ func (o outputType) String() string {
|
||||
}
|
||||
|
||||
var outputTypeEnum = &outputType{
|
||||
Enum: []string{"zip", "tar", "tar.sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4", "tar.zst"},
|
||||
Enum: []string{"zip", "tar", "tar.gz", "tar.xz", "tar.bz2"},
|
||||
Default: "zip",
|
||||
}
|
||||
|
||||
@@ -129,26 +158,6 @@ It can be used for backup and capture Gitea server image to send to maintainer`,
|
||||
Name: "skip-log, L",
|
||||
Usage: "Skip the log dumping",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-custom-dir",
|
||||
Usage: "Skip custom directory",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-lfs-data",
|
||||
Usage: "Skip LFS data",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-attachment-data",
|
||||
Usage: "Skip attachment data",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-package-data",
|
||||
Usage: "Skip package data",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-index",
|
||||
Usage: "Skip bleve index data",
|
||||
},
|
||||
cli.GenericFlag{
|
||||
Name: "type",
|
||||
Value: outputTypeEnum,
|
||||
@@ -165,43 +174,28 @@ func fatal(format string, args ...interface{}) {
|
||||
func runDump(ctx *cli.Context) error {
|
||||
var file *os.File
|
||||
fileName := ctx.String("file")
|
||||
outType := ctx.String("type")
|
||||
if fileName == "-" {
|
||||
file = os.Stdout
|
||||
err := log.DelLogger("console")
|
||||
if err != nil {
|
||||
fatal("Deleting default logger failed. Can not write to stdout: %v", err)
|
||||
}
|
||||
} else {
|
||||
for _, suffix := range outputTypeEnum.Enum {
|
||||
if strings.HasSuffix(fileName, "."+suffix) {
|
||||
fileName = strings.TrimSuffix(fileName, "."+suffix)
|
||||
break
|
||||
}
|
||||
}
|
||||
fileName += "." + outType
|
||||
}
|
||||
setting.InitProviderFromExistingFile()
|
||||
setting.LoadCommonSettings()
|
||||
|
||||
setting.NewContext()
|
||||
// make sure we are logging to the console no matter what the configuration tells us do to
|
||||
// FIXME: don't use CfgProvider directly
|
||||
if _, err := setting.CfgProvider.Section("log").NewKey("MODE", "console"); err != nil {
|
||||
if _, err := setting.Cfg.Section("log").NewKey("MODE", "console"); err != nil {
|
||||
fatal("Setting logging mode to console failed: %v", err)
|
||||
}
|
||||
if _, err := setting.CfgProvider.Section("log.console").NewKey("STDERR", "true"); err != nil {
|
||||
if _, err := setting.Cfg.Section("log.console").NewKey("STDERR", "true"); err != nil {
|
||||
fatal("Setting console logger to stderr failed: %v", err)
|
||||
}
|
||||
if !setting.InstallLock {
|
||||
log.Error("Is '%s' really the right config path?\n", setting.CustomConf)
|
||||
return fmt.Errorf("gitea is not initialized")
|
||||
}
|
||||
setting.LoadSettings() // cannot access session settings otherwise
|
||||
setting.NewServices() // cannot access session settings otherwise
|
||||
|
||||
stdCtx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
err := db.InitEngine(stdCtx)
|
||||
err := models.SetEngine()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -218,12 +212,8 @@ func runDump(ctx *cli.Context) error {
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
absFileName, err := filepath.Abs(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
verbose := ctx.Bool("verbose")
|
||||
outType := ctx.String("type")
|
||||
var iface interface{}
|
||||
if fileName == "-" {
|
||||
iface, err = archiver.ByExtension(fmt.Sprintf(".%s", outType))
|
||||
@@ -244,19 +234,23 @@ func runDump(ctx *cli.Context) error {
|
||||
log.Info("Skip dumping local repositories")
|
||||
} else {
|
||||
log.Info("Dumping local repositories... %s", setting.RepoRootPath)
|
||||
if err := addRecursiveExclude(w, "repos", setting.RepoRootPath, []string{absFileName}, verbose); err != nil {
|
||||
if err := addRecursive(w, "repos", setting.RepoRootPath, verbose); err != nil {
|
||||
fatal("Failed to include repositories: %v", err)
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") {
|
||||
log.Info("Skip dumping LFS data")
|
||||
} else if err := storage.LFS.IterateObjects(func(objPath string, object storage.Object) error {
|
||||
if err := storage.LFS.IterateObjects(func(objPath string, object storage.Object) error {
|
||||
info, err := object.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return addReader(w, object, info, path.Join("data", "lfs", objPath), verbose)
|
||||
return w.Write(archiver.File{
|
||||
FileInfo: archiver.FileInfo{
|
||||
FileInfo: info,
|
||||
CustomName: path.Join("data", "lfs", objPath),
|
||||
},
|
||||
ReadCloser: object,
|
||||
})
|
||||
}); err != nil {
|
||||
fatal("Failed to dump LFS objects: %v", err)
|
||||
}
|
||||
@@ -267,7 +261,7 @@ func runDump(ctx *cli.Context) error {
|
||||
fatal("Path does not exist: %s", tmpDir)
|
||||
}
|
||||
|
||||
dbDump, err := os.CreateTemp(tmpDir, "gitea-db.sql")
|
||||
dbDump, err := ioutil.TempFile(tmpDir, "gitea-db.sql")
|
||||
if err != nil {
|
||||
fatal("Failed to create tmp file: %v", err)
|
||||
}
|
||||
@@ -284,7 +278,7 @@ func runDump(ctx *cli.Context) error {
|
||||
log.Info("Dumping database...")
|
||||
}
|
||||
|
||||
if err := db.DumpDatabase(dbDump.Name(), targetDBType); err != nil {
|
||||
if err := models.DumpDatabase(dbDump.Name(), targetDBType); err != nil {
|
||||
fatal("Failed to dump database: %v", err)
|
||||
}
|
||||
|
||||
@@ -299,32 +293,24 @@ func runDump(ctx *cli.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
|
||||
log.Info("Skipping custom directory")
|
||||
} else {
|
||||
customDir, err := os.Stat(setting.CustomPath)
|
||||
if err == nil && customDir.IsDir() {
|
||||
if is, _ := isSubdir(setting.AppDataPath, setting.CustomPath); !is {
|
||||
if err := addRecursiveExclude(w, "custom", setting.CustomPath, []string{absFileName}, verbose); err != nil {
|
||||
fatal("Failed to include custom: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Info("Custom dir %s is inside data dir %s, skipped", setting.CustomPath, setting.AppDataPath)
|
||||
customDir, err := os.Stat(setting.CustomPath)
|
||||
if err == nil && customDir.IsDir() {
|
||||
if is, _ := isSubdir(setting.AppDataPath, setting.CustomPath); !is {
|
||||
if err := addRecursive(w, "custom", setting.CustomPath, verbose); err != nil {
|
||||
fatal("Failed to include custom: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Info("Custom dir %s doesn't exist, skipped", setting.CustomPath)
|
||||
log.Info("Custom dir %s is inside data dir %s, skipped", setting.CustomPath, setting.AppDataPath)
|
||||
}
|
||||
} else {
|
||||
log.Info("Custom dir %s doesn't exist, skipped", setting.CustomPath)
|
||||
}
|
||||
|
||||
isExist, err := util.IsExist(setting.AppDataPath)
|
||||
if err != nil {
|
||||
log.Error("Unable to check if %s exists. Error: %v", setting.AppDataPath, err)
|
||||
}
|
||||
if isExist {
|
||||
if com.IsExist(setting.AppDataPath) {
|
||||
log.Info("Packing data directory...%s", setting.AppDataPath)
|
||||
|
||||
var excludes []string
|
||||
if setting.SessionConfig.OriginalProvider == "file" {
|
||||
if setting.Cfg.Section("session").Key("PROVIDER").Value() == "file" {
|
||||
var opts session.Options
|
||||
if err = json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil {
|
||||
return err
|
||||
@@ -332,62 +318,40 @@ func runDump(ctx *cli.Context) error {
|
||||
excludes = append(excludes, opts.ProviderConfig)
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-index") && ctx.Bool("skip-index") {
|
||||
excludes = append(excludes, setting.Indexer.RepoPath)
|
||||
excludes = append(excludes, setting.Indexer.IssuePath)
|
||||
}
|
||||
|
||||
excludes = append(excludes, setting.RepoRootPath)
|
||||
excludes = append(excludes, setting.LFS.Path)
|
||||
excludes = append(excludes, setting.Attachment.Path)
|
||||
excludes = append(excludes, setting.Packages.Path)
|
||||
excludes = append(excludes, setting.Log.RootPath)
|
||||
excludes = append(excludes, absFileName)
|
||||
excludes = append(excludes, setting.LogRootPath)
|
||||
if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil {
|
||||
fatal("Failed to include data directory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
|
||||
log.Info("Skip dumping attachment data")
|
||||
} else if err := storage.Attachments.IterateObjects(func(objPath string, object storage.Object) error {
|
||||
if err := storage.Attachments.IterateObjects(func(objPath string, object storage.Object) error {
|
||||
info, err := object.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return addReader(w, object, info, path.Join("data", "attachments", objPath), verbose)
|
||||
return w.Write(archiver.File{
|
||||
FileInfo: archiver.FileInfo{
|
||||
FileInfo: info,
|
||||
CustomName: path.Join("data", "attachments", objPath),
|
||||
},
|
||||
ReadCloser: object,
|
||||
})
|
||||
}); err != nil {
|
||||
fatal("Failed to dump attachments: %v", err)
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
|
||||
log.Info("Skip dumping package data")
|
||||
} else if err := storage.Packages.IterateObjects(func(objPath string, object storage.Object) error {
|
||||
info, err := object.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return addReader(w, object, info, path.Join("data", "packages", objPath), verbose)
|
||||
}); err != nil {
|
||||
fatal("Failed to dump packages: %v", err)
|
||||
}
|
||||
|
||||
// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
|
||||
// ensuring that it's clear the dump is skipped whether the directory's initialized
|
||||
// yet or not.
|
||||
if ctx.IsSet("skip-log") && ctx.Bool("skip-log") {
|
||||
log.Info("Skip dumping log files")
|
||||
} else {
|
||||
isExist, err := util.IsExist(setting.Log.RootPath)
|
||||
if err != nil {
|
||||
log.Error("Unable to check if %s exists. Error: %v", setting.Log.RootPath, err)
|
||||
}
|
||||
if isExist {
|
||||
if err := addRecursiveExclude(w, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil {
|
||||
fatal("Failed to include log: %v", err)
|
||||
}
|
||||
} else if com.IsExist(setting.LogRootPath) {
|
||||
if err := addRecursive(w, "log", setting.LogRootPath, verbose); err != nil {
|
||||
fatal("Failed to include log: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,7 +361,7 @@ func runDump(ctx *cli.Context) error {
|
||||
fatal("Failed to save %s: %v", fileName, err)
|
||||
}
|
||||
|
||||
if err := os.Chmod(fileName, 0o600); err != nil {
|
||||
if err := os.Chmod(fileName, 0600); err != nil {
|
||||
log.Info("Can't change file access permissions mask to 0600: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -411,6 +375,15 @@ func runDump(ctx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func contains(slice []string, s string) bool {
|
||||
for _, v := range slice {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// addRecursiveExclude zips absPath to specified insidePath inside writer excluding excludeAbsPath
|
||||
func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeAbsPath []string, verbose bool) error {
|
||||
absPath, err := filepath.Abs(absPath)
|
||||
@@ -431,7 +404,7 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA
|
||||
currentAbsPath := path.Join(absPath, file.Name())
|
||||
currentInsidePath := path.Join(insidePath, file.Name())
|
||||
if file.IsDir() {
|
||||
if !util.SliceContainsString(excludeAbsPath, currentAbsPath) {
|
||||
if !contains(excludeAbsPath, currentAbsPath) {
|
||||
if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -440,23 +413,8 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// only copy regular files and symlink regular files, skip non-regular files like socket/pipe/...
|
||||
shouldAdd := file.Mode().IsRegular()
|
||||
if !shouldAdd && file.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
target, err := filepath.EvalSymlinks(currentAbsPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetStat, err := os.Stat(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
shouldAdd = targetStat.Mode().IsRegular()
|
||||
}
|
||||
if shouldAdd {
|
||||
if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
191
cmd/dump_repo.go
191
cmd/dump_repo.go
@@ -1,191 +0,0 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
base "code.gitea.io/gitea/modules/migration"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/migrations"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// CmdDumpRepository represents the available dump repository sub-command.
|
||||
var CmdDumpRepository = cli.Command{
|
||||
Name: "dump-repo",
|
||||
Usage: "Dump the repository from git/github/gitea/gitlab",
|
||||
Description: "This is a command for dumping the repository data.",
|
||||
Action: runDumpRepository,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "git_service",
|
||||
Value: "",
|
||||
Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo_dir, r",
|
||||
Value: "./data",
|
||||
Usage: "Repository dir path to store the data",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "clone_addr",
|
||||
Value: "",
|
||||
Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "auth_username",
|
||||
Value: "",
|
||||
Usage: "The username to visit the clone_addr",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "auth_password",
|
||||
Value: "",
|
||||
Usage: "The password to visit the clone_addr",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "auth_token",
|
||||
Value: "",
|
||||
Usage: "The personal token to visit the clone_addr",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "owner_name",
|
||||
Value: "",
|
||||
Usage: "The data will be stored on a directory with owner name if not empty",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo_name",
|
||||
Value: "",
|
||||
Usage: "The data will be stored on a directory with repository name if not empty",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "units",
|
||||
Value: "",
|
||||
Usage: `Which items will be migrated, one or more units should be separated as comma.
|
||||
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runDumpRepository(ctx *cli.Context) error {
|
||||
stdCtx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(stdCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// migrations.GiteaLocalUploader depends on git module
|
||||
if err := git.InitSimple(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("AppPath: %s", setting.AppPath)
|
||||
log.Info("AppWorkPath: %s", setting.AppWorkPath)
|
||||
log.Info("Custom path: %s", setting.CustomPath)
|
||||
log.Info("Log path: %s", setting.Log.RootPath)
|
||||
log.Info("Configuration file: %s", setting.CustomConf)
|
||||
|
||||
var (
|
||||
serviceType structs.GitServiceType
|
||||
cloneAddr = ctx.String("clone_addr")
|
||||
serviceStr = ctx.String("git_service")
|
||||
)
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(cloneAddr), "https://github.com/") {
|
||||
serviceStr = "github"
|
||||
} else if strings.HasPrefix(strings.ToLower(cloneAddr), "https://gitlab.com/") {
|
||||
serviceStr = "gitlab"
|
||||
} else if strings.HasPrefix(strings.ToLower(cloneAddr), "https://gitea.com/") {
|
||||
serviceStr = "gitea"
|
||||
}
|
||||
if serviceStr == "" {
|
||||
return errors.New("git_service missed or clone_addr cannot be recognized")
|
||||
}
|
||||
serviceType = convert.ToGitServiceType(serviceStr)
|
||||
|
||||
opts := base.MigrateOptions{
|
||||
GitServiceType: serviceType,
|
||||
CloneAddr: cloneAddr,
|
||||
AuthUsername: ctx.String("auth_username"),
|
||||
AuthPassword: ctx.String("auth_password"),
|
||||
AuthToken: ctx.String("auth_token"),
|
||||
RepoName: ctx.String("repo_name"),
|
||||
}
|
||||
|
||||
if len(ctx.String("units")) == 0 {
|
||||
opts.Wiki = true
|
||||
opts.Issues = true
|
||||
opts.Milestones = true
|
||||
opts.Labels = true
|
||||
opts.Releases = true
|
||||
opts.Comments = true
|
||||
opts.PullRequests = true
|
||||
opts.ReleaseAssets = true
|
||||
} else {
|
||||
units := strings.Split(ctx.String("units"), ",")
|
||||
for _, unit := range units {
|
||||
switch strings.ToLower(strings.TrimSpace(unit)) {
|
||||
case "":
|
||||
continue
|
||||
case "wiki":
|
||||
opts.Wiki = true
|
||||
case "issues":
|
||||
opts.Issues = true
|
||||
case "milestones":
|
||||
opts.Milestones = true
|
||||
case "labels":
|
||||
opts.Labels = true
|
||||
case "releases":
|
||||
opts.Releases = true
|
||||
case "release_assets":
|
||||
opts.ReleaseAssets = true
|
||||
case "comments":
|
||||
opts.Comments = true
|
||||
case "pull_requests":
|
||||
opts.PullRequests = true
|
||||
default:
|
||||
return errors.New("invalid unit: " + unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the repo_dir will be removed if error occurs in DumpRepository
|
||||
// make sure the directory doesn't exist or is empty, prevent from deleting user files
|
||||
repoDir := ctx.String("repo_dir")
|
||||
if exists, err := util.IsExist(repoDir); err != nil {
|
||||
return fmt.Errorf("unable to stat repo_dir %q: %w", repoDir, err)
|
||||
} else if exists {
|
||||
if isDir, _ := util.IsDir(repoDir); !isDir {
|
||||
return fmt.Errorf("repo_dir %q already exists but it's not a directory", repoDir)
|
||||
}
|
||||
if dir, _ := os.ReadDir(repoDir); len(dir) > 0 {
|
||||
return fmt.Errorf("repo_dir %q is not empty", repoDir)
|
||||
}
|
||||
}
|
||||
|
||||
if err := migrations.DumpRepository(
|
||||
context.Background(),
|
||||
repoDir,
|
||||
ctx.String("owner_name"),
|
||||
opts,
|
||||
); err != nil {
|
||||
log.Fatal("Failed to dump repository: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Trace("Dump finished!!!")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build bindata
|
||||
// +build bindata
|
||||
|
||||
package cmd
|
||||
|
||||
@@ -18,7 +19,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/public"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/urfave/cli"
|
||||
@@ -107,13 +107,13 @@ type asset struct {
|
||||
}
|
||||
|
||||
func initEmbeddedExtractor(c *cli.Context) error {
|
||||
|
||||
// Silence the console logger
|
||||
log.DelNamedLogger("console")
|
||||
log.DelNamedLogger(log.DEFAULT)
|
||||
|
||||
// Read configuration file
|
||||
setting.InitProviderAllowEmpty()
|
||||
setting.LoadCommonSettings()
|
||||
setting.NewContext()
|
||||
|
||||
pats, err := getPatterns(c.Args())
|
||||
if err != nil {
|
||||
@@ -123,7 +123,7 @@ func initEmbeddedExtractor(c *cli.Context) error {
|
||||
|
||||
sections["public"] = §ion{Path: "public", Names: public.AssetNames, IsDir: public.AssetIsDir, Asset: public.Asset}
|
||||
sections["options"] = §ion{Path: "options", Names: options.AssetNames, IsDir: options.AssetIsDir, Asset: options.Asset}
|
||||
sections["templates"] = §ion{Path: "templates", Names: templates.BuiltinAssetNames, IsDir: templates.BuiltinAssetIsDir, Asset: templates.BuiltinAsset}
|
||||
sections["templates"] = §ion{Path: "templates", Names: templates.AssetNames, IsDir: templates.AssetIsDir, Asset: templates.Asset}
|
||||
|
||||
for _, sec := range sections {
|
||||
assets = append(assets, buildAssetList(sec, pats, c)...)
|
||||
@@ -186,11 +186,11 @@ func runViewDo(c *cli.Context) error {
|
||||
|
||||
data, err := assets[0].Section.Asset(assets[0].Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", assets[0].Path, err)
|
||||
return fmt.Errorf("%s: %v", assets[0].Path, err)
|
||||
}
|
||||
|
||||
if _, err = os.Stdout.Write(data); err != nil {
|
||||
return fmt.Errorf("%s: %w", assets[0].Path, err)
|
||||
return fmt.Errorf("%s: %v", assets[0].Path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -251,19 +251,19 @@ func extractAsset(d string, a asset, overwrite, rename bool) error {
|
||||
|
||||
data, err := a.Section.Asset(a.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", a.Path, err)
|
||||
return fmt.Errorf("%s: %v", a.Path, err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("%s: %w", dir, err)
|
||||
return fmt.Errorf("%s: %v", dir, err)
|
||||
}
|
||||
|
||||
perms := os.ModePerm & 0o666
|
||||
perms := os.ModePerm & 0666
|
||||
|
||||
fi, err := os.Lstat(dest)
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("%s: %w", dest, err)
|
||||
return fmt.Errorf("%s: %v", dest, err)
|
||||
}
|
||||
} else if !overwrite && !rename {
|
||||
fmt.Printf("%s already exists; skipped.\n", dest)
|
||||
@@ -271,8 +271,8 @@ func extractAsset(d string, a asset, overwrite, rename bool) error {
|
||||
} else if !fi.Mode().IsRegular() {
|
||||
return fmt.Errorf("%s already exists, but it's not a regular file", dest)
|
||||
} else if rename {
|
||||
if err := util.Rename(dest, dest+".bak"); err != nil {
|
||||
return fmt.Errorf("Error creating backup for %s: %w", dest, err)
|
||||
if err := os.Rename(dest, dest+".bak"); err != nil {
|
||||
return fmt.Errorf("Error creating backup for %s: %v", dest, err)
|
||||
}
|
||||
// Attempt to respect file permissions mask (even if user:group will be set anew)
|
||||
perms = fi.Mode()
|
||||
@@ -280,12 +280,12 @@ func extractAsset(d string, a asset, overwrite, rename bool) error {
|
||||
|
||||
file, err := os.OpenFile(dest, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perms)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", dest, err)
|
||||
return fmt.Errorf("%s: %v", dest, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err = file.Write(data); err != nil {
|
||||
return fmt.Errorf("%s: %w", dest, err)
|
||||
return fmt.Errorf("%s: %v", dest, err)
|
||||
}
|
||||
|
||||
fmt.Println(dest)
|
||||
@@ -294,7 +294,7 @@ func extractAsset(d string, a asset, overwrite, rename bool) error {
|
||||
}
|
||||
|
||||
func buildAssetList(sec *section, globs []glob.Glob, c *cli.Context) []asset {
|
||||
results := make([]asset, 0, 64)
|
||||
var results = make([]asset, 0, 64)
|
||||
for _, name := range sec.Names() {
|
||||
if isdir, err := sec.IsDir(name); !isdir && err == nil {
|
||||
if sec.Path == "public" &&
|
||||
@@ -305,11 +305,9 @@ func buildAssetList(sec *section, globs []glob.Glob, c *cli.Context) []asset {
|
||||
matchName := sec.Path + "/" + name
|
||||
for _, g := range globs {
|
||||
if g.Match(matchName) {
|
||||
results = append(results, asset{
|
||||
Section: sec,
|
||||
Name: name,
|
||||
Path: sec.Path + "/" + name,
|
||||
})
|
||||
results = append(results, asset{Section: sec,
|
||||
Name: name,
|
||||
Path: sec.Path + "/" + name})
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -325,7 +323,7 @@ func getPatterns(args []string) ([]glob.Glob, error) {
|
||||
pat := make([]glob.Glob, len(args))
|
||||
for i := range args {
|
||||
if g, err := glob.Compile(args[i], '/'); err != nil {
|
||||
return nil, fmt.Errorf("'%s': Invalid glob pattern: %w", args[i], err)
|
||||
return nil, fmt.Errorf("'%s': Invalid glob pattern: %v", args[i], err)
|
||||
} else {
|
||||
pat[i] = g
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !bindata
|
||||
// +build !bindata
|
||||
|
||||
package cmd
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2016 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
@@ -70,7 +71,7 @@ func runGenerateInternalToken(c *cli.Context) error {
|
||||
}
|
||||
|
||||
func runGenerateLfsJwtSecret(c *cli.Context) error {
|
||||
JWTSecretBase64, err := generate.NewJwtSecretBase64()
|
||||
JWTSecretBase64, err := generate.NewJwtSecret()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
445
cmd/hook.go
445
cmd/hook.go
@@ -1,5 +1,6 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
@@ -14,9 +15,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@@ -37,7 +38,6 @@ var (
|
||||
subcmdHookPreReceive,
|
||||
subcmdHookUpdate,
|
||||
subcmdHookPostReceive,
|
||||
subcmdHookProcReceive,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -74,18 +74,6 @@ var (
|
||||
},
|
||||
},
|
||||
}
|
||||
// Note: new hook since git 2.29
|
||||
subcmdHookProcReceive = cli.Command{
|
||||
Name: "proc-receive",
|
||||
Usage: "Delegate proc-receive Git hook",
|
||||
Description: "This command should only be called by Git",
|
||||
Action: runHookProcReceive,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type delayWriter struct {
|
||||
@@ -161,31 +149,29 @@ func (n *nilWriter) WriteString(s string) (int, error) {
|
||||
}
|
||||
|
||||
func runHookPreReceive(c *cli.Context) error {
|
||||
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
||||
if os.Getenv(models.EnvIsInternal) == "true" {
|
||||
return nil
|
||||
}
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setup("hooks/pre-receive.log", c.Bool("debug"))
|
||||
|
||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
||||
return fail(`Rejecting changes as Gitea environment not set.
|
||||
fail(`Rejecting changes as Gitea environment not set.
|
||||
If you are pushing over SSH you must push with a key managed by
|
||||
Gitea or set your environment appropriately.`, "")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// the environment is set by serv command
|
||||
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
||||
username := os.Getenv(repo_module.EnvRepoUsername)
|
||||
reponame := os.Getenv(repo_module.EnvRepoName)
|
||||
userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
||||
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
|
||||
deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64)
|
||||
actionPerm, _ := strconv.ParseInt(os.Getenv(repo_module.EnvActionPerm), 10, 64)
|
||||
// the environment setted on serv command
|
||||
isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
|
||||
username := os.Getenv(models.EnvRepoUsername)
|
||||
reponame := os.Getenv(models.EnvRepoName)
|
||||
userID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
|
||||
prID, _ := strconv.ParseInt(os.Getenv(models.EnvPRID), 10, 64)
|
||||
isDeployKey, _ := strconv.ParseBool(os.Getenv(models.EnvIsDeployKey))
|
||||
|
||||
hookOptions := private.HookOptions{
|
||||
UserID: userID,
|
||||
@@ -193,9 +179,8 @@ Gitea or set your environment appropriately.`, "")
|
||||
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
|
||||
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
|
||||
GitPushOptions: pushOptions(),
|
||||
PullRequestID: prID,
|
||||
DeployKeyID: deployKeyID,
|
||||
ActionPerm: int(actionPerm),
|
||||
ProtectedBranchID: prID,
|
||||
IsDeployKey: isDeployKey,
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
@@ -219,11 +204,6 @@ Gitea or set your environment appropriately.`, "")
|
||||
}
|
||||
}
|
||||
|
||||
supportProcReceive := false
|
||||
if git.CheckGitVersionAtLeast("2.29") == nil {
|
||||
supportProcReceive = true
|
||||
}
|
||||
|
||||
for scanner.Scan() {
|
||||
// TODO: support news feeds for wiki
|
||||
if isWiki {
|
||||
@@ -241,10 +221,8 @@ Gitea or set your environment appropriately.`, "")
|
||||
total++
|
||||
lastline++
|
||||
|
||||
// If the ref is a branch or tag, check if it's protected
|
||||
// if supportProcReceive all ref should be checked because
|
||||
// permission check was delayed
|
||||
if supportProcReceive || strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
|
||||
// If the ref is a branch, check if it's protected
|
||||
if strings.HasPrefix(refFullName, git.BranchPrefix) {
|
||||
oldCommitIDs[count] = oldCommitID
|
||||
newCommitIDs[count] = newCommitID
|
||||
refFullNames[count] = refFullName
|
||||
@@ -252,19 +230,19 @@ Gitea or set your environment appropriately.`, "")
|
||||
fmt.Fprintf(out, "*")
|
||||
|
||||
if count >= hookBatchSize {
|
||||
fmt.Fprintf(out, " Checking %d references\n", count)
|
||||
fmt.Fprintf(out, " Checking %d branches\n", count)
|
||||
|
||||
hookOptions.OldCommitIDs = oldCommitIDs
|
||||
hookOptions.NewCommitIDs = newCommitIDs
|
||||
hookOptions.RefFullNames = refFullNames
|
||||
statusCode, msg := private.HookPreReceive(ctx, username, reponame, hookOptions)
|
||||
statusCode, msg := private.HookPreReceive(username, reponame, hookOptions)
|
||||
switch statusCode {
|
||||
case http.StatusOK:
|
||||
// no-op
|
||||
case http.StatusInternalServerError:
|
||||
return fail("Internal Server Error", msg)
|
||||
fail("Internal Server Error", msg)
|
||||
default:
|
||||
return fail(msg, "")
|
||||
fail(msg, "")
|
||||
}
|
||||
count = 0
|
||||
lastline = 0
|
||||
@@ -283,17 +261,18 @@ Gitea or set your environment appropriately.`, "")
|
||||
hookOptions.NewCommitIDs = newCommitIDs[:count]
|
||||
hookOptions.RefFullNames = refFullNames[:count]
|
||||
|
||||
fmt.Fprintf(out, " Checking %d references\n", count)
|
||||
fmt.Fprintf(out, " Checking %d branches\n", count)
|
||||
|
||||
statusCode, msg := private.HookPreReceive(ctx, username, reponame, hookOptions)
|
||||
statusCode, msg := private.HookPreReceive(username, reponame, hookOptions)
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
return fail("Internal Server Error", msg)
|
||||
fail("Internal Server Error", msg)
|
||||
case http.StatusForbidden:
|
||||
return fail(msg, "")
|
||||
fail(msg, "")
|
||||
}
|
||||
} else if lastline > 0 {
|
||||
fmt.Fprintf(out, "\n")
|
||||
lastline = 0
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "Checked %d references in total\n", total)
|
||||
@@ -306,28 +285,26 @@ func runHookUpdate(c *cli.Context) error {
|
||||
}
|
||||
|
||||
func runHookPostReceive(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setup("hooks/post-receive.log", c.Bool("debug"))
|
||||
|
||||
// First of all run update-server-info no matter what
|
||||
if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil {
|
||||
return fmt.Errorf("Failed to call 'git update-server-info': %w", err)
|
||||
if _, err := git.NewCommand("update-server-info").Run(); err != nil {
|
||||
return fmt.Errorf("Failed to call 'git update-server-info': %v", err)
|
||||
}
|
||||
|
||||
// Now if we're an internal don't do anything else
|
||||
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
||||
if os.Getenv(models.EnvIsInternal) == "true" {
|
||||
return nil
|
||||
}
|
||||
|
||||
setup("hooks/post-receive.log", c.Bool("debug"))
|
||||
|
||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
||||
return fail(`Rejecting changes as Gitea environment not set.
|
||||
fail(`Rejecting changes as Gitea environment not set.
|
||||
If you are pushing over SSH you must push with a key managed by
|
||||
Gitea or set your environment appropriately.`, "")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var out io.Writer
|
||||
@@ -343,12 +320,12 @@ Gitea or set your environment appropriately.`, "")
|
||||
}
|
||||
}
|
||||
|
||||
// the environment is set by serv command
|
||||
repoUser := os.Getenv(repo_module.EnvRepoUsername)
|
||||
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
||||
repoName := os.Getenv(repo_module.EnvRepoName)
|
||||
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
||||
pusherName := os.Getenv(repo_module.EnvPusherName)
|
||||
// the environment setted on serv command
|
||||
repoUser := os.Getenv(models.EnvRepoUsername)
|
||||
isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
|
||||
repoName := os.Getenv(models.EnvRepoName)
|
||||
pusherID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
|
||||
pusherName := os.Getenv(models.EnvPusherName)
|
||||
|
||||
hookOptions := private.HookOptions{
|
||||
UserName: pusherName,
|
||||
@@ -394,11 +371,11 @@ Gitea or set your environment appropriately.`, "")
|
||||
hookOptions.OldCommitIDs = oldCommitIDs
|
||||
hookOptions.NewCommitIDs = newCommitIDs
|
||||
hookOptions.RefFullNames = refFullNames
|
||||
resp, err := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
|
||||
resp, err := private.HookPostReceive(repoUser, repoName, hookOptions)
|
||||
if resp == nil {
|
||||
_ = dWriter.Close()
|
||||
hookPrintResults(results)
|
||||
return fail("Internal Server Error", err)
|
||||
fail("Internal Server Error", err)
|
||||
}
|
||||
wasEmpty = wasEmpty || resp.RepoWasEmpty
|
||||
results = append(results, resp.Results...)
|
||||
@@ -409,9 +386,9 @@ Gitea or set your environment appropriately.`, "")
|
||||
if count == 0 {
|
||||
if wasEmpty && masterPushed {
|
||||
// We need to tell the repo to reset the default branch to master
|
||||
err := private.SetDefaultBranch(ctx, repoUser, repoName, "master")
|
||||
err := private.SetDefaultBranch(repoUser, repoName, "master")
|
||||
if err != nil {
|
||||
return fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
|
||||
fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(out, "Processed %d references in total\n", total)
|
||||
@@ -427,11 +404,11 @@ Gitea or set your environment appropriately.`, "")
|
||||
|
||||
fmt.Fprintf(out, " Processing %d references\n", count)
|
||||
|
||||
resp, err := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
|
||||
resp, err := private.HookPostReceive(repoUser, repoName, hookOptions)
|
||||
if resp == nil {
|
||||
_ = dWriter.Close()
|
||||
hookPrintResults(results)
|
||||
return fail("Internal Server Error", err)
|
||||
fail("Internal Server Error", err)
|
||||
}
|
||||
wasEmpty = wasEmpty || resp.RepoWasEmpty
|
||||
results = append(results, resp.Results...)
|
||||
@@ -440,9 +417,9 @@ Gitea or set your environment appropriately.`, "")
|
||||
|
||||
if wasEmpty && masterPushed {
|
||||
// We need to tell the repo to reset the default branch to master
|
||||
err := private.SetDefaultBranch(ctx, repoUser, repoName, "master")
|
||||
err := private.SetDefaultBranch(repoUser, repoName, "master")
|
||||
if err != nil {
|
||||
return fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
|
||||
fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
|
||||
}
|
||||
}
|
||||
_ = dWriter.Close()
|
||||
@@ -483,327 +460,3 @@ func pushOptions() map[string]string {
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func runHookProcReceive(c *cli.Context) error {
|
||||
setup("hooks/proc-receive.log", c.Bool("debug"))
|
||||
|
||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
||||
return fail(`Rejecting changes as Gitea environment not set.
|
||||
If you are pushing over SSH you must push with a key managed by
|
||||
Gitea or set your environment appropriately.`, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if git.CheckGitVersionAtLeast("2.29") != nil {
|
||||
return fail("Internal Server Error", "git not support proc-receive.")
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
repoUser := os.Getenv(repo_module.EnvRepoUsername)
|
||||
repoName := os.Getenv(repo_module.EnvRepoName)
|
||||
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
||||
pusherName := os.Getenv(repo_module.EnvPusherName)
|
||||
|
||||
// 1. Version and features negotiation.
|
||||
// S: PKT-LINE(version=1\0push-options atomic...) / PKT-LINE(version=1\n)
|
||||
// S: flush-pkt
|
||||
// H: PKT-LINE(version=1\0push-options...)
|
||||
// H: flush-pkt
|
||||
|
||||
rs, err := readPktLine(reader, pktLineTypeData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const VersionHead string = "version=1"
|
||||
|
||||
var (
|
||||
hasPushOptions bool
|
||||
response = []byte(VersionHead)
|
||||
requestOptions []string
|
||||
)
|
||||
|
||||
index := bytes.IndexByte(rs.Data, byte(0))
|
||||
if index >= len(rs.Data) {
|
||||
return fail("Internal Server Error", "pkt-line: format error "+fmt.Sprint(rs.Data))
|
||||
}
|
||||
|
||||
if index < 0 {
|
||||
if len(rs.Data) == 10 && rs.Data[9] == '\n' {
|
||||
index = 9
|
||||
} else {
|
||||
return fail("Internal Server Error", "pkt-line: format error "+fmt.Sprint(rs.Data))
|
||||
}
|
||||
}
|
||||
|
||||
if string(rs.Data[0:index]) != VersionHead {
|
||||
return fail("Internal Server Error", "Received unsupported version: %s", string(rs.Data[0:index]))
|
||||
}
|
||||
requestOptions = strings.Split(string(rs.Data[index+1:]), " ")
|
||||
|
||||
for _, option := range requestOptions {
|
||||
if strings.HasPrefix(option, "push-options") {
|
||||
response = append(response, byte(0))
|
||||
response = append(response, []byte("push-options")...)
|
||||
hasPushOptions = true
|
||||
}
|
||||
}
|
||||
response = append(response, '\n')
|
||||
|
||||
_, err = readPktLine(reader, pktLineTypeFlush)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = writeDataPktLine(os.Stdout, response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = writeFlushPktLine(os.Stdout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. receive commands from server.
|
||||
// S: PKT-LINE(<old-oid> <new-oid> <ref>)
|
||||
// S: ... ...
|
||||
// S: flush-pkt
|
||||
// # [receive push-options]
|
||||
// S: PKT-LINE(push-option)
|
||||
// S: ... ...
|
||||
// S: flush-pkt
|
||||
hookOptions := private.HookOptions{
|
||||
UserName: pusherName,
|
||||
UserID: pusherID,
|
||||
}
|
||||
hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize)
|
||||
hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize)
|
||||
hookOptions.RefFullNames = make([]string, 0, hookBatchSize)
|
||||
|
||||
for {
|
||||
// note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed
|
||||
rs, err = readPktLine(reader, pktLineTypeUnknow)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rs.Type == pktLineTypeFlush {
|
||||
break
|
||||
}
|
||||
t := strings.SplitN(string(rs.Data), " ", 3)
|
||||
if len(t) != 3 {
|
||||
continue
|
||||
}
|
||||
hookOptions.OldCommitIDs = append(hookOptions.OldCommitIDs, t[0])
|
||||
hookOptions.NewCommitIDs = append(hookOptions.NewCommitIDs, t[1])
|
||||
hookOptions.RefFullNames = append(hookOptions.RefFullNames, t[2])
|
||||
}
|
||||
|
||||
hookOptions.GitPushOptions = make(map[string]string)
|
||||
|
||||
if hasPushOptions {
|
||||
for {
|
||||
rs, err = readPktLine(reader, pktLineTypeUnknow)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rs.Type == pktLineTypeFlush {
|
||||
break
|
||||
}
|
||||
|
||||
kv := strings.SplitN(string(rs.Data), "=", 2)
|
||||
if len(kv) == 2 {
|
||||
hookOptions.GitPushOptions[kv[0]] = kv[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. run hook
|
||||
resp, err := private.HookProcReceive(ctx, repoUser, repoName, hookOptions)
|
||||
if err != nil {
|
||||
return fail("Internal Server Error", "run proc-receive hook failed :%v", err)
|
||||
}
|
||||
|
||||
// 4. response result to service
|
||||
// # a. OK, but has an alternate reference. The alternate reference name
|
||||
// # and other status can be given in option directives.
|
||||
// H: PKT-LINE(ok <ref>)
|
||||
// H: PKT-LINE(option refname <refname>)
|
||||
// H: PKT-LINE(option old-oid <old-oid>)
|
||||
// H: PKT-LINE(option new-oid <new-oid>)
|
||||
// H: PKT-LINE(option forced-update)
|
||||
// H: ... ...
|
||||
// H: flush-pkt
|
||||
// # b. NO, I reject it.
|
||||
// H: PKT-LINE(ng <ref> <reason>)
|
||||
// # c. Fall through, let 'receive-pack' to execute it.
|
||||
// H: PKT-LINE(ok <ref>)
|
||||
// H: PKT-LINE(option fall-through)
|
||||
|
||||
for _, rs := range resp.Results {
|
||||
if len(rs.Err) > 0 {
|
||||
err = writeDataPktLine(os.Stdout, []byte("ng "+rs.OriginalRef+" "+rs.Err))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if rs.IsNotMatched {
|
||||
err = writeDataPktLine(os.Stdout, []byte("ok "+rs.OriginalRef))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writeDataPktLine(os.Stdout, []byte("option fall-through"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
err = writeDataPktLine(os.Stdout, []byte("ok "+rs.OriginalRef))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writeDataPktLine(os.Stdout, []byte("option refname "+rs.Ref))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rs.OldOID != git.EmptySHA {
|
||||
err = writeDataPktLine(os.Stdout, []byte("option old-oid "+rs.OldOID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = writeDataPktLine(os.Stdout, []byte("option new-oid "+rs.NewOID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rs.IsForcePush {
|
||||
err = writeDataPktLine(os.Stdout, []byte("option forced-update"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
err = writeFlushPktLine(os.Stdout)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// git PKT-Line api
|
||||
// pktLineType message type of pkt-line
|
||||
type pktLineType int64
|
||||
|
||||
const (
|
||||
// UnKnow type
|
||||
pktLineTypeUnknow pktLineType = 0
|
||||
// flush-pkt "0000"
|
||||
pktLineTypeFlush pktLineType = iota
|
||||
// data line
|
||||
pktLineTypeData
|
||||
)
|
||||
|
||||
// gitPktLine pkt-line api
|
||||
type gitPktLine struct {
|
||||
Type pktLineType
|
||||
Length uint64
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func readPktLine(in *bufio.Reader, requestType pktLineType) (*gitPktLine, error) {
|
||||
var (
|
||||
err error
|
||||
r *gitPktLine
|
||||
)
|
||||
|
||||
// read prefix
|
||||
lengthBytes := make([]byte, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
lengthBytes[i], err = in.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fail("Internal Server Error", "Pkt-Line: read stdin failed : %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
r = new(gitPktLine)
|
||||
r.Length, err = strconv.ParseUint(string(lengthBytes), 16, 32)
|
||||
if err != nil {
|
||||
return nil, fail("Internal Server Error", "Pkt-Line format is wrong :%v", err)
|
||||
}
|
||||
|
||||
if r.Length == 0 {
|
||||
if requestType == pktLineTypeData {
|
||||
return nil, fail("Internal Server Error", "Pkt-Line format is wrong")
|
||||
}
|
||||
r.Type = pktLineTypeFlush
|
||||
return r, nil
|
||||
}
|
||||
|
||||
if r.Length <= 4 || r.Length > 65520 || requestType == pktLineTypeFlush {
|
||||
return nil, fail("Internal Server Error", "Pkt-Line format is wrong")
|
||||
}
|
||||
|
||||
r.Data = make([]byte, r.Length-4)
|
||||
for i := range r.Data {
|
||||
r.Data[i], err = in.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fail("Internal Server Error", "Pkt-Line: read stdin failed : %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
r.Type = pktLineTypeData
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func writeFlushPktLine(out io.Writer) error {
|
||||
l, err := out.Write([]byte("0000"))
|
||||
if err != nil {
|
||||
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
|
||||
}
|
||||
if l != 4 {
|
||||
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeDataPktLine(out io.Writer, data []byte) error {
|
||||
hexchar := []byte("0123456789abcdef")
|
||||
hex := func(n uint64) byte {
|
||||
return hexchar[(n)&15]
|
||||
}
|
||||
|
||||
length := uint64(len(data) + 4)
|
||||
tmp := make([]byte, 4)
|
||||
tmp[0] = hex(length >> 12)
|
||||
tmp[1] = hex(length >> 8)
|
||||
tmp[2] = hex(length >> 4)
|
||||
tmp[3] = hex(length)
|
||||
|
||||
lr, err := out.Write(tmp)
|
||||
if err != nil {
|
||||
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
|
||||
}
|
||||
if lr != 4 {
|
||||
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
|
||||
}
|
||||
|
||||
lr, err = out.Write(data)
|
||||
if err != nil {
|
||||
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
|
||||
}
|
||||
if int(length-4) != lr {
|
||||
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPktLine(t *testing.T) {
|
||||
// test read
|
||||
s := strings.NewReader("0000")
|
||||
r := bufio.NewReader(s)
|
||||
result, err := readPktLine(r, pktLineTypeFlush)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, pktLineTypeFlush, result.Type)
|
||||
|
||||
s = strings.NewReader("0006a\n")
|
||||
r = bufio.NewReader(s)
|
||||
result, err = readPktLine(r, pktLineTypeData)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, pktLineTypeData, result.Type)
|
||||
assert.Equal(t, []byte("a\n"), result.Data)
|
||||
|
||||
// test write
|
||||
w := bytes.NewBuffer([]byte{})
|
||||
err = writeFlushPktLine(w)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("0000"), w.Bytes())
|
||||
|
||||
w.Reset()
|
||||
err = writeDataPktLine(w, []byte("a\nb"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("0007a\nb"), w.Bytes())
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
@@ -61,12 +62,9 @@ func runKeys(c *cli.Context) error {
|
||||
return errors.New("No key type and content provided")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setup("keys.log", false)
|
||||
|
||||
authorizedString, err := private.AuthorizedPublicKeyByContent(ctx, content)
|
||||
authorizedString, err := private.AuthorizedPublicKeyByContent(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func runSendMail(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setting.InitProviderFromExistingFile()
|
||||
setting.LoadCommonSettings()
|
||||
|
||||
if err := argsSet(c, "title"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subject := c.String("title")
|
||||
confirmSkiped := c.Bool("force")
|
||||
body := c.String("content")
|
||||
|
||||
if !confirmSkiped {
|
||||
if len(body) == 0 {
|
||||
fmt.Print("warning: Content is empty")
|
||||
}
|
||||
|
||||
fmt.Print("Proceed with sending email? [Y/n] ")
|
||||
isConfirmed, err := confirm()
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !isConfirmed {
|
||||
fmt.Println("The mail was not sent")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
status, message := private.SendEmail(ctx, subject, body, nil)
|
||||
if status != http.StatusOK {
|
||||
fmt.Printf("error: %s\n", message)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Success: %s\n", message)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
func init() {
|
||||
setting.SetCustomPathAndConf("", "", "")
|
||||
setting.InitProviderAndLoadCommonSettingsForTest()
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m, &unittest.TestOptions{
|
||||
GiteaRootPath: "..",
|
||||
})
|
||||
}
|
||||
401
cmd/manager.go
401
cmd/manager.go
@@ -1,5 +1,6 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
@@ -25,7 +27,6 @@ var (
|
||||
subcmdRestart,
|
||||
subcmdFlushQueues,
|
||||
subcmdLogging,
|
||||
subCmdProcesses,
|
||||
},
|
||||
}
|
||||
subcmdShutdown = cli.Command{
|
||||
@@ -57,8 +58,7 @@ var (
|
||||
Name: "timeout",
|
||||
Value: 60 * time.Second,
|
||||
Usage: "Timeout for the flushing process",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
}, cli.BoolFlag{
|
||||
Name: "non-blocking",
|
||||
Usage: "Set to true to not wait for flush to complete before returning",
|
||||
},
|
||||
@@ -67,47 +67,326 @@ var (
|
||||
},
|
||||
},
|
||||
}
|
||||
subCmdProcesses = cli.Command{
|
||||
Name: "processes",
|
||||
Usage: "Display running processes within the current process",
|
||||
Action: runProcesses,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "flat",
|
||||
Usage: "Show processes as flat table rather than as tree",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-system",
|
||||
Usage: "Do not show system processes",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "stacktraces",
|
||||
Usage: "Show stacktraces",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "json",
|
||||
Usage: "Output as json",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cancel",
|
||||
Usage: "Process PID to cancel. (Only available for non-system processes.)",
|
||||
defaultLoggingFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "group, g",
|
||||
Usage: "Group to add logger to - will default to \"default\"",
|
||||
}, cli.StringFlag{
|
||||
Name: "name, n",
|
||||
Usage: "Name of the new logger - will default to mode",
|
||||
}, cli.StringFlag{
|
||||
Name: "level, l",
|
||||
Usage: "Logging level for the new logger",
|
||||
}, cli.StringFlag{
|
||||
Name: "stacktrace-level, L",
|
||||
Usage: "Stacktrace logging level",
|
||||
}, cli.StringFlag{
|
||||
Name: "flags, F",
|
||||
Usage: "Flags for the logger",
|
||||
}, cli.StringFlag{
|
||||
Name: "expression, e",
|
||||
Usage: "Matching expression for the logger",
|
||||
}, cli.StringFlag{
|
||||
Name: "prefix, p",
|
||||
Usage: "Prefix for the logger",
|
||||
}, cli.BoolFlag{
|
||||
Name: "color",
|
||||
Usage: "Use color in the logs",
|
||||
}, cli.BoolFlag{
|
||||
Name: "debug",
|
||||
},
|
||||
}
|
||||
subcmdLogging = cli.Command{
|
||||
Name: "logging",
|
||||
Usage: "Adjust logging commands",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "pause",
|
||||
Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Action: runPauseLogging,
|
||||
}, {
|
||||
Name: "resume",
|
||||
Usage: "Resume logging",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Action: runResumeLogging,
|
||||
}, {
|
||||
Name: "release-and-reopen",
|
||||
Usage: "Cause Gitea to release and re-open files used for logging",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Action: runReleaseReopenLogging,
|
||||
}, {
|
||||
Name: "remove",
|
||||
Usage: "Remove a logger",
|
||||
ArgsUsage: "[name] Name of logger to remove",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
}, cli.StringFlag{
|
||||
Name: "group, g",
|
||||
Usage: "Group to add logger to - will default to \"default\"",
|
||||
},
|
||||
},
|
||||
Action: runRemoveLogger,
|
||||
}, {
|
||||
Name: "add",
|
||||
Usage: "Add a logger",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "console",
|
||||
Usage: "Add a console logger",
|
||||
Flags: append(defaultLoggingFlags,
|
||||
cli.BoolFlag{
|
||||
Name: "stderr",
|
||||
Usage: "Output console logs to stderr - only relevant for console",
|
||||
}),
|
||||
Action: runAddConsoleLogger,
|
||||
}, {
|
||||
Name: "file",
|
||||
Usage: "Add a file logger",
|
||||
Flags: append(defaultLoggingFlags, []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "filename, f",
|
||||
Usage: "Filename for the logger - this must be set.",
|
||||
}, cli.BoolTFlag{
|
||||
Name: "rotate, r",
|
||||
Usage: "Rotate logs",
|
||||
}, cli.Int64Flag{
|
||||
Name: "max-size, s",
|
||||
Usage: "Maximum size in bytes before rotation",
|
||||
}, cli.BoolTFlag{
|
||||
Name: "daily, d",
|
||||
Usage: "Rotate logs daily",
|
||||
}, cli.IntFlag{
|
||||
Name: "max-days, D",
|
||||
Usage: "Maximum number of daily logs to keep",
|
||||
}, cli.BoolTFlag{
|
||||
Name: "compress, z",
|
||||
Usage: "Compress rotated logs",
|
||||
}, cli.IntFlag{
|
||||
Name: "compression-level, Z",
|
||||
Usage: "Compression level to use",
|
||||
},
|
||||
}...),
|
||||
Action: runAddFileLogger,
|
||||
}, {
|
||||
Name: "conn",
|
||||
Usage: "Add a net conn logger",
|
||||
Flags: append(defaultLoggingFlags, []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "reconnect-on-message, R",
|
||||
Usage: "Reconnect to host for every message",
|
||||
}, cli.BoolFlag{
|
||||
Name: "reconnect, r",
|
||||
Usage: "Reconnect to host when connection is dropped",
|
||||
}, cli.StringFlag{
|
||||
Name: "protocol, P",
|
||||
Usage: "Set protocol to use: tcp, unix, or udp (defaults to tcp)",
|
||||
}, cli.StringFlag{
|
||||
Name: "address, a",
|
||||
Usage: "Host address and port to connect to (defaults to :7020)",
|
||||
},
|
||||
}...),
|
||||
Action: runAddConnLogger,
|
||||
}, {
|
||||
Name: "smtp",
|
||||
Usage: "Add an SMTP logger",
|
||||
Flags: append(defaultLoggingFlags, []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "username, u",
|
||||
Usage: "Mail server username",
|
||||
}, cli.StringFlag{
|
||||
Name: "password, P",
|
||||
Usage: "Mail server password",
|
||||
}, cli.StringFlag{
|
||||
Name: "host, H",
|
||||
Usage: "Mail server host (defaults to: 127.0.0.1:25)",
|
||||
}, cli.StringSliceFlag{
|
||||
Name: "send-to, s",
|
||||
Usage: "Email address(es) to send to",
|
||||
}, cli.StringFlag{
|
||||
Name: "subject, S",
|
||||
Usage: "Subject header of sent emails",
|
||||
},
|
||||
}...),
|
||||
Action: runAddSMTPLogger,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func runShutdown(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
func runRemoveLogger(c *cli.Context) error {
|
||||
setup("manager", c.Bool("debug"))
|
||||
statusCode, msg := private.Shutdown(ctx)
|
||||
group := c.String("group")
|
||||
if len(group) == 0 {
|
||||
group = log.DEFAULT
|
||||
}
|
||||
name := c.Args().First()
|
||||
statusCode, msg := private.RemoveLogger(group, name)
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
return fail("InternalServerError", msg)
|
||||
fail("InternalServerError", msg)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAddSMTPLogger(c *cli.Context) error {
|
||||
setup("manager", c.Bool("debug"))
|
||||
vals := map[string]interface{}{}
|
||||
mode := "smtp"
|
||||
if c.IsSet("host") {
|
||||
vals["host"] = c.String("host")
|
||||
} else {
|
||||
vals["host"] = "127.0.0.1:25"
|
||||
}
|
||||
|
||||
if c.IsSet("username") {
|
||||
vals["username"] = c.String("username")
|
||||
}
|
||||
if c.IsSet("password") {
|
||||
vals["password"] = c.String("password")
|
||||
}
|
||||
|
||||
if !c.IsSet("send-to") {
|
||||
return fmt.Errorf("Some recipients must be provided")
|
||||
}
|
||||
vals["sendTos"] = c.StringSlice("send-to")
|
||||
|
||||
if c.IsSet("subject") {
|
||||
vals["subject"] = c.String("subject")
|
||||
} else {
|
||||
vals["subject"] = "Diagnostic message from Gitea"
|
||||
}
|
||||
|
||||
return commonAddLogger(c, mode, vals)
|
||||
}
|
||||
|
||||
func runAddConnLogger(c *cli.Context) error {
|
||||
setup("manager", c.Bool("debug"))
|
||||
vals := map[string]interface{}{}
|
||||
mode := "conn"
|
||||
vals["net"] = "tcp"
|
||||
if c.IsSet("protocol") {
|
||||
switch c.String("protocol") {
|
||||
case "udp":
|
||||
vals["net"] = "udp"
|
||||
case "unix":
|
||||
vals["net"] = "unix"
|
||||
}
|
||||
}
|
||||
if c.IsSet("address") {
|
||||
vals["address"] = c.String("address")
|
||||
} else {
|
||||
vals["address"] = ":7020"
|
||||
}
|
||||
if c.IsSet("reconnect") {
|
||||
vals["reconnect"] = c.Bool("reconnect")
|
||||
}
|
||||
if c.IsSet("reconnect-on-message") {
|
||||
vals["reconnectOnMsg"] = c.Bool("reconnect-on-message")
|
||||
}
|
||||
return commonAddLogger(c, mode, vals)
|
||||
}
|
||||
|
||||
func runAddFileLogger(c *cli.Context) error {
|
||||
setup("manager", c.Bool("debug"))
|
||||
vals := map[string]interface{}{}
|
||||
mode := "file"
|
||||
if c.IsSet("filename") {
|
||||
vals["filename"] = c.String("filename")
|
||||
} else {
|
||||
return fmt.Errorf("filename must be set when creating a file logger")
|
||||
}
|
||||
if c.IsSet("rotate") {
|
||||
vals["rotate"] = c.Bool("rotate")
|
||||
}
|
||||
if c.IsSet("max-size") {
|
||||
vals["maxsize"] = c.Int64("max-size")
|
||||
}
|
||||
if c.IsSet("daily") {
|
||||
vals["daily"] = c.Bool("daily")
|
||||
}
|
||||
if c.IsSet("max-days") {
|
||||
vals["maxdays"] = c.Int("max-days")
|
||||
}
|
||||
if c.IsSet("compress") {
|
||||
vals["compress"] = c.Bool("compress")
|
||||
}
|
||||
if c.IsSet("compression-level") {
|
||||
vals["compressionLevel"] = c.Int("compression-level")
|
||||
}
|
||||
return commonAddLogger(c, mode, vals)
|
||||
}
|
||||
|
||||
func runAddConsoleLogger(c *cli.Context) error {
|
||||
setup("manager", c.Bool("debug"))
|
||||
vals := map[string]interface{}{}
|
||||
mode := "console"
|
||||
if c.IsSet("stderr") && c.Bool("stderr") {
|
||||
vals["stderr"] = c.Bool("stderr")
|
||||
}
|
||||
return commonAddLogger(c, mode, vals)
|
||||
}
|
||||
|
||||
func commonAddLogger(c *cli.Context, mode string, vals map[string]interface{}) error {
|
||||
if len(c.String("level")) > 0 {
|
||||
vals["level"] = log.FromString(c.String("level")).String()
|
||||
}
|
||||
if len(c.String("stacktrace-level")) > 0 {
|
||||
vals["stacktraceLevel"] = log.FromString(c.String("stacktrace-level")).String()
|
||||
}
|
||||
if len(c.String("expression")) > 0 {
|
||||
vals["expression"] = c.String("expression")
|
||||
}
|
||||
if len(c.String("prefix")) > 0 {
|
||||
vals["prefix"] = c.String("prefix")
|
||||
}
|
||||
if len(c.String("flags")) > 0 {
|
||||
vals["flags"] = log.FlagsFromString(c.String("flags"))
|
||||
}
|
||||
if c.IsSet("color") {
|
||||
vals["colorize"] = c.Bool("color")
|
||||
}
|
||||
group := "default"
|
||||
if c.IsSet("group") {
|
||||
group = c.String("group")
|
||||
}
|
||||
name := mode
|
||||
if c.IsSet("name") {
|
||||
name = c.String("name")
|
||||
}
|
||||
statusCode, msg := private.AddLogger(group, name, mode, vals)
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
fail("InternalServerError", msg)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runShutdown(c *cli.Context) error {
|
||||
setup("manager", c.Bool("debug"))
|
||||
statusCode, msg := private.Shutdown()
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
fail("InternalServerError", msg)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, msg)
|
||||
@@ -115,14 +394,11 @@ func runShutdown(c *cli.Context) error {
|
||||
}
|
||||
|
||||
func runRestart(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setup("manager", c.Bool("debug"))
|
||||
statusCode, msg := private.Restart(ctx)
|
||||
statusCode, msg := private.Restart()
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
return fail("InternalServerError", msg)
|
||||
fail("InternalServerError", msg)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, msg)
|
||||
@@ -130,30 +406,49 @@ func runRestart(c *cli.Context) error {
|
||||
}
|
||||
|
||||
func runFlushQueues(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setup("manager", c.Bool("debug"))
|
||||
statusCode, msg := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking"))
|
||||
statusCode, msg := private.FlushQueues(c.Duration("timeout"), c.Bool("non-blocking"))
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
return fail("InternalServerError", msg)
|
||||
fail("InternalServerError", msg)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runProcesses(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
func runPauseLogging(c *cli.Context) error {
|
||||
setup("manager", c.Bool("debug"))
|
||||
statusCode, msg := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel"))
|
||||
statusCode, msg := private.PauseLogging()
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
return fail("InternalServerError", msg)
|
||||
fail("InternalServerError", msg)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runResumeLogging(c *cli.Context) error {
|
||||
setup("manager", c.Bool("debug"))
|
||||
statusCode, msg := private.ResumeLogging()
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
fail("InternalServerError", msg)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runReleaseReopenLogging(c *cli.Context) error {
|
||||
setup("manager", c.Bool("debug"))
|
||||
statusCode, msg := private.ReleaseReopenLogging()
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
fail("InternalServerError", msg)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,409 +0,0 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultLoggingFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "group, g",
|
||||
Usage: "Group to add logger to - will default to \"default\"",
|
||||
}, cli.StringFlag{
|
||||
Name: "name, n",
|
||||
Usage: "Name of the new logger - will default to mode",
|
||||
}, cli.StringFlag{
|
||||
Name: "level, l",
|
||||
Usage: "Logging level for the new logger",
|
||||
}, cli.StringFlag{
|
||||
Name: "stacktrace-level, L",
|
||||
Usage: "Stacktrace logging level",
|
||||
}, cli.StringFlag{
|
||||
Name: "flags, F",
|
||||
Usage: "Flags for the logger",
|
||||
}, cli.StringFlag{
|
||||
Name: "expression, e",
|
||||
Usage: "Matching expression for the logger",
|
||||
}, cli.StringFlag{
|
||||
Name: "prefix, p",
|
||||
Usage: "Prefix for the logger",
|
||||
}, cli.BoolFlag{
|
||||
Name: "color",
|
||||
Usage: "Use color in the logs",
|
||||
}, cli.BoolFlag{
|
||||
Name: "debug",
|
||||
},
|
||||
}
|
||||
|
||||
subcmdLogging = cli.Command{
|
||||
Name: "logging",
|
||||
Usage: "Adjust logging commands",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "pause",
|
||||
Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Action: runPauseLogging,
|
||||
}, {
|
||||
Name: "resume",
|
||||
Usage: "Resume logging",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Action: runResumeLogging,
|
||||
}, {
|
||||
Name: "release-and-reopen",
|
||||
Usage: "Cause Gitea to release and re-open files used for logging",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Action: runReleaseReopenLogging,
|
||||
}, {
|
||||
Name: "remove",
|
||||
Usage: "Remove a logger",
|
||||
ArgsUsage: "[name] Name of logger to remove",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
}, cli.StringFlag{
|
||||
Name: "group, g",
|
||||
Usage: "Group to add logger to - will default to \"default\"",
|
||||
},
|
||||
},
|
||||
Action: runRemoveLogger,
|
||||
}, {
|
||||
Name: "add",
|
||||
Usage: "Add a logger",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "console",
|
||||
Usage: "Add a console logger",
|
||||
Flags: append(defaultLoggingFlags,
|
||||
cli.BoolFlag{
|
||||
Name: "stderr",
|
||||
Usage: "Output console logs to stderr - only relevant for console",
|
||||
}),
|
||||
Action: runAddConsoleLogger,
|
||||
}, {
|
||||
Name: "file",
|
||||
Usage: "Add a file logger",
|
||||
Flags: append(defaultLoggingFlags, []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "filename, f",
|
||||
Usage: "Filename for the logger - this must be set.",
|
||||
}, cli.BoolTFlag{
|
||||
Name: "rotate, r",
|
||||
Usage: "Rotate logs",
|
||||
}, cli.Int64Flag{
|
||||
Name: "max-size, s",
|
||||
Usage: "Maximum size in bytes before rotation",
|
||||
}, cli.BoolTFlag{
|
||||
Name: "daily, d",
|
||||
Usage: "Rotate logs daily",
|
||||
}, cli.IntFlag{
|
||||
Name: "max-days, D",
|
||||
Usage: "Maximum number of daily logs to keep",
|
||||
}, cli.BoolTFlag{
|
||||
Name: "compress, z",
|
||||
Usage: "Compress rotated logs",
|
||||
}, cli.IntFlag{
|
||||
Name: "compression-level, Z",
|
||||
Usage: "Compression level to use",
|
||||
},
|
||||
}...),
|
||||
Action: runAddFileLogger,
|
||||
}, {
|
||||
Name: "conn",
|
||||
Usage: "Add a net conn logger",
|
||||
Flags: append(defaultLoggingFlags, []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "reconnect-on-message, R",
|
||||
Usage: "Reconnect to host for every message",
|
||||
}, cli.BoolFlag{
|
||||
Name: "reconnect, r",
|
||||
Usage: "Reconnect to host when connection is dropped",
|
||||
}, cli.StringFlag{
|
||||
Name: "protocol, P",
|
||||
Usage: "Set protocol to use: tcp, unix, or udp (defaults to tcp)",
|
||||
}, cli.StringFlag{
|
||||
Name: "address, a",
|
||||
Usage: "Host address and port to connect to (defaults to :7020)",
|
||||
},
|
||||
}...),
|
||||
Action: runAddConnLogger,
|
||||
}, {
|
||||
Name: "smtp",
|
||||
Usage: "Add an SMTP logger",
|
||||
Flags: append(defaultLoggingFlags, []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "username, u",
|
||||
Usage: "Mail server username",
|
||||
}, cli.StringFlag{
|
||||
Name: "password, P",
|
||||
Usage: "Mail server password",
|
||||
}, cli.StringFlag{
|
||||
Name: "host, H",
|
||||
Usage: "Mail server host (defaults to: 127.0.0.1:25)",
|
||||
}, cli.StringSliceFlag{
|
||||
Name: "send-to, s",
|
||||
Usage: "Email address(es) to send to",
|
||||
}, cli.StringFlag{
|
||||
Name: "subject, S",
|
||||
Usage: "Subject header of sent emails",
|
||||
},
|
||||
}...),
|
||||
Action: runAddSMTPLogger,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: "log-sql",
|
||||
Usage: "Set LogSQL",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
}, cli.BoolFlag{
|
||||
Name: "off",
|
||||
Usage: "Switch off SQL logging",
|
||||
},
|
||||
},
|
||||
Action: runSetLogSQL,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func runRemoveLogger(c *cli.Context) error {
|
||||
setup("manager", c.Bool("debug"))
|
||||
group := c.String("group")
|
||||
if len(group) == 0 {
|
||||
group = log.DEFAULT
|
||||
}
|
||||
name := c.Args().First()
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
statusCode, msg := private.RemoveLogger(ctx, group, name)
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
return fail("InternalServerError", msg)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAddSMTPLogger(c *cli.Context) error {
|
||||
setup("manager", c.Bool("debug"))
|
||||
vals := map[string]interface{}{}
|
||||
mode := "smtp"
|
||||
if c.IsSet("host") {
|
||||
vals["host"] = c.String("host")
|
||||
} else {
|
||||
vals["host"] = "127.0.0.1:25"
|
||||
}
|
||||
|
||||
if c.IsSet("username") {
|
||||
vals["username"] = c.String("username")
|
||||
}
|
||||
if c.IsSet("password") {
|
||||
vals["password"] = c.String("password")
|
||||
}
|
||||
|
||||
if !c.IsSet("send-to") {
|
||||
return fmt.Errorf("Some recipients must be provided")
|
||||
}
|
||||
vals["sendTos"] = c.StringSlice("send-to")
|
||||
|
||||
if c.IsSet("subject") {
|
||||
vals["subject"] = c.String("subject")
|
||||
} else {
|
||||
vals["subject"] = "Diagnostic message from Gitea"
|
||||
}
|
||||
|
||||
return commonAddLogger(c, mode, vals)
|
||||
}
|
||||
|
||||
func runAddConnLogger(c *cli.Context) error {
|
||||
setup("manager", c.Bool("debug"))
|
||||
vals := map[string]interface{}{}
|
||||
mode := "conn"
|
||||
vals["net"] = "tcp"
|
||||
if c.IsSet("protocol") {
|
||||
switch c.String("protocol") {
|
||||
case "udp":
|
||||
vals["net"] = "udp"
|
||||
case "unix":
|
||||
vals["net"] = "unix"
|
||||
}
|
||||
}
|
||||
if c.IsSet("address") {
|
||||
vals["address"] = c.String("address")
|
||||
} else {
|
||||
vals["address"] = ":7020"
|
||||
}
|
||||
if c.IsSet("reconnect") {
|
||||
vals["reconnect"] = c.Bool("reconnect")
|
||||
}
|
||||
if c.IsSet("reconnect-on-message") {
|
||||
vals["reconnectOnMsg"] = c.Bool("reconnect-on-message")
|
||||
}
|
||||
return commonAddLogger(c, mode, vals)
|
||||
}
|
||||
|
||||
func runAddFileLogger(c *cli.Context) error {
|
||||
setup("manager", c.Bool("debug"))
|
||||
vals := map[string]interface{}{}
|
||||
mode := "file"
|
||||
if c.IsSet("filename") {
|
||||
vals["filename"] = c.String("filename")
|
||||
} else {
|
||||
return fmt.Errorf("filename must be set when creating a file logger")
|
||||
}
|
||||
if c.IsSet("rotate") {
|
||||
vals["rotate"] = c.Bool("rotate")
|
||||
}
|
||||
if c.IsSet("max-size") {
|
||||
vals["maxsize"] = c.Int64("max-size")
|
||||
}
|
||||
if c.IsSet("daily") {
|
||||
vals["daily"] = c.Bool("daily")
|
||||
}
|
||||
if c.IsSet("max-days") {
|
||||
vals["maxdays"] = c.Int("max-days")
|
||||
}
|
||||
if c.IsSet("compress") {
|
||||
vals["compress"] = c.Bool("compress")
|
||||
}
|
||||
if c.IsSet("compression-level") {
|
||||
vals["compressionLevel"] = c.Int("compression-level")
|
||||
}
|
||||
return commonAddLogger(c, mode, vals)
|
||||
}
|
||||
|
||||
func runAddConsoleLogger(c *cli.Context) error {
|
||||
setup("manager", c.Bool("debug"))
|
||||
vals := map[string]interface{}{}
|
||||
mode := "console"
|
||||
if c.IsSet("stderr") && c.Bool("stderr") {
|
||||
vals["stderr"] = c.Bool("stderr")
|
||||
}
|
||||
return commonAddLogger(c, mode, vals)
|
||||
}
|
||||
|
||||
func commonAddLogger(c *cli.Context, mode string, vals map[string]interface{}) error {
|
||||
if len(c.String("level")) > 0 {
|
||||
vals["level"] = log.FromString(c.String("level")).String()
|
||||
}
|
||||
if len(c.String("stacktrace-level")) > 0 {
|
||||
vals["stacktraceLevel"] = log.FromString(c.String("stacktrace-level")).String()
|
||||
}
|
||||
if len(c.String("expression")) > 0 {
|
||||
vals["expression"] = c.String("expression")
|
||||
}
|
||||
if len(c.String("prefix")) > 0 {
|
||||
vals["prefix"] = c.String("prefix")
|
||||
}
|
||||
if len(c.String("flags")) > 0 {
|
||||
vals["flags"] = log.FlagsFromString(c.String("flags"))
|
||||
}
|
||||
if c.IsSet("color") {
|
||||
vals["colorize"] = c.Bool("color")
|
||||
}
|
||||
group := "default"
|
||||
if c.IsSet("group") {
|
||||
group = c.String("group")
|
||||
}
|
||||
name := mode
|
||||
if c.IsSet("name") {
|
||||
name = c.String("name")
|
||||
}
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
statusCode, msg := private.AddLogger(ctx, group, name, mode, vals)
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
return fail("InternalServerError", msg)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runPauseLogging(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setup("manager", c.Bool("debug"))
|
||||
statusCode, msg := private.PauseLogging(ctx)
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
return fail("InternalServerError", msg)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runResumeLogging(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setup("manager", c.Bool("debug"))
|
||||
statusCode, msg := private.ResumeLogging(ctx)
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
return fail("InternalServerError", msg)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runReleaseReopenLogging(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setup("manager", c.Bool("debug"))
|
||||
statusCode, msg := private.ReleaseReopenLogging(ctx)
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
return fail("InternalServerError", msg)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSetLogSQL(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
setup("manager", c.Bool("debug"))
|
||||
|
||||
statusCode, msg := private.SetLogSQL(ctx, !c.Bool("off"))
|
||||
switch statusCode {
|
||||
case http.StatusInternalServerError:
|
||||
return fail("InternalServerError", msg)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, msg)
|
||||
return nil
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/migrations"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -23,20 +24,17 @@ var CmdMigrate = cli.Command{
|
||||
}
|
||||
|
||||
func runMigrate(ctx *cli.Context) error {
|
||||
stdCtx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(stdCtx); err != nil {
|
||||
if err := initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("AppPath: %s", setting.AppPath)
|
||||
log.Info("AppWorkPath: %s", setting.AppWorkPath)
|
||||
log.Info("Custom path: %s", setting.CustomPath)
|
||||
log.Info("Log path: %s", setting.Log.RootPath)
|
||||
log.Info("Configuration file: %s", setting.CustomConf)
|
||||
log.Trace("AppPath: %s", setting.AppPath)
|
||||
log.Trace("AppWorkPath: %s", setting.AppWorkPath)
|
||||
log.Trace("Custom path: %s", setting.CustomPath)
|
||||
log.Trace("Log path: %s", setting.LogRootPath)
|
||||
setting.InitDBConfig()
|
||||
|
||||
if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
|
||||
if err := models.NewEngine(context.Background(), migrations.Migrate); err != nil {
|
||||
log.Fatal("Failed to initialize ORM engine: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
@@ -8,14 +9,9 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/migrations"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
|
||||
@@ -26,13 +22,13 @@ import (
|
||||
var CmdMigrateStorage = cli.Command{
|
||||
Name: "migrate-storage",
|
||||
Usage: "Migrate the storage",
|
||||
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
|
||||
Description: "This is a command for migrating storage.",
|
||||
Action: runMigrateStorage,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "type, t",
|
||||
Value: "",
|
||||
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages'",
|
||||
Usage: "Kinds of files to migrate, currently only 'attachments' is supported",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "storage, s",
|
||||
@@ -81,69 +77,52 @@ var CmdMigrateStorage = cli.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||
return db.Iterate(ctx, nil, func(ctx context.Context, attach *repo_model.Attachment) error {
|
||||
func migrateAttachments(dstStorage storage.ObjectStorage) error {
|
||||
return models.IterateAttachment(func(attach *models.Attachment) error {
|
||||
_, err := storage.Copy(dstStorage, attach.RelativePath(), storage.Attachments, attach.RelativePath())
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func migrateLFS(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||
return db.Iterate(ctx, nil, func(ctx context.Context, mo *git_model.LFSMetaObject) error {
|
||||
func migrateLFS(dstStorage storage.ObjectStorage) error {
|
||||
return models.IterateLFS(func(mo *models.LFSMetaObject) error {
|
||||
_, err := storage.Copy(dstStorage, mo.RelativePath(), storage.LFS, mo.RelativePath())
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func migrateAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||
return db.Iterate(ctx, nil, func(ctx context.Context, user *user_model.User) error {
|
||||
func migrateAvatars(dstStorage storage.ObjectStorage) error {
|
||||
return models.IterateUser(func(user *models.User) error {
|
||||
_, err := storage.Copy(dstStorage, user.CustomAvatarRelativePath(), storage.Avatars, user.CustomAvatarRelativePath())
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func migrateRepoAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||
return db.Iterate(ctx, nil, func(ctx context.Context, repo *repo_model.Repository) error {
|
||||
func migrateRepoAvatars(dstStorage storage.ObjectStorage) error {
|
||||
return models.IterateRepository(func(repo *models.Repository) error {
|
||||
_, err := storage.Copy(dstStorage, repo.CustomAvatarRelativePath(), storage.RepoAvatars, repo.CustomAvatarRelativePath())
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func migrateRepoArchivers(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||
return db.Iterate(ctx, nil, func(ctx context.Context, archiver *repo_model.RepoArchiver) error {
|
||||
p := archiver.RelativePath()
|
||||
_, err := storage.Copy(dstStorage, p, storage.RepoArchives, p)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func migratePackages(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||
return db.Iterate(ctx, nil, func(ctx context.Context, pb *packages_model.PackageBlob) error {
|
||||
p := packages_module.KeyToRelativePath(packages_module.BlobHash256Key(pb.HashSHA256))
|
||||
_, err := storage.Copy(dstStorage, p, storage.Packages, p)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func runMigrateStorage(ctx *cli.Context) error {
|
||||
stdCtx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(stdCtx); err != nil {
|
||||
if err := initDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("AppPath: %s", setting.AppPath)
|
||||
log.Info("AppWorkPath: %s", setting.AppWorkPath)
|
||||
log.Info("Custom path: %s", setting.CustomPath)
|
||||
log.Info("Log path: %s", setting.Log.RootPath)
|
||||
log.Info("Configuration file: %s", setting.CustomConf)
|
||||
log.Trace("AppPath: %s", setting.AppPath)
|
||||
log.Trace("AppWorkPath: %s", setting.AppWorkPath)
|
||||
log.Trace("Custom path: %s", setting.CustomPath)
|
||||
log.Trace("Log path: %s", setting.LogRootPath)
|
||||
setting.InitDBConfig()
|
||||
|
||||
if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
|
||||
if err := models.NewEngine(context.Background(), migrations.Migrate); err != nil {
|
||||
log.Fatal("Failed to initialize ORM engine: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
goCtx := context.Background()
|
||||
|
||||
if err := storage.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -160,13 +139,13 @@ func runMigrateStorage(ctx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
dstStorage, err = storage.NewLocalStorage(
|
||||
stdCtx,
|
||||
goCtx,
|
||||
storage.LocalStorageConfig{
|
||||
Path: p,
|
||||
})
|
||||
case string(storage.MinioStorageType):
|
||||
dstStorage, err = storage.NewMinioStorage(
|
||||
stdCtx,
|
||||
goCtx,
|
||||
storage.MinioStorageConfig{
|
||||
Endpoint: ctx.String("minio-endpoint"),
|
||||
AccessKeyID: ctx.String("minio-access-key-id"),
|
||||
@@ -177,29 +156,35 @@ func runMigrateStorage(ctx *cli.Context) error {
|
||||
UseSSL: ctx.Bool("minio-use-ssl"),
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("unsupported storage type: %s", ctx.String("storage"))
|
||||
return fmt.Errorf("Unsupported storage type: %s", ctx.String("storage"))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migratedMethods := map[string]func(context.Context, storage.ObjectStorage) error{
|
||||
"attachments": migrateAttachments,
|
||||
"lfs": migrateLFS,
|
||||
"avatars": migrateAvatars,
|
||||
"repo-avatars": migrateRepoAvatars,
|
||||
"repo-archivers": migrateRepoArchivers,
|
||||
"packages": migratePackages,
|
||||
}
|
||||
|
||||
tp := strings.ToLower(ctx.String("type"))
|
||||
if m, ok := migratedMethods[tp]; ok {
|
||||
if err := m(stdCtx, dstStorage); err != nil {
|
||||
switch tp {
|
||||
case "attachments":
|
||||
if err := migrateAttachments(dstStorage); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("%s files have successfully been copied to the new storage.", tp)
|
||||
return nil
|
||||
case "lfs":
|
||||
if err := migrateLFS(dstStorage); err != nil {
|
||||
return err
|
||||
}
|
||||
case "avatars":
|
||||
if err := migrateAvatars(dstStorage); err != nil {
|
||||
return err
|
||||
}
|
||||
case "repo-avatars":
|
||||
if err := migrateRepoAvatars(dstStorage); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("Unsupported storage: %s", ctx.String("type"))
|
||||
}
|
||||
|
||||
return fmt.Errorf("unsupported storage: %s", ctx.String("type"))
|
||||
log.Warn("All files have been copied to the new placement but old files are still on the orignial placement.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMigratePackages(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
creator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
content := "package main\n\nfunc main() {\nfmt.Println(\"hi\")\n}\n"
|
||||
buf, err := packages_module.CreateHashedBufferFromReader(strings.NewReader(content), 1024)
|
||||
assert.NoError(t, err)
|
||||
defer buf.Close()
|
||||
|
||||
v, f, err := packages_service.CreatePackageAndAddFile(&packages_service.PackageCreationInfo{
|
||||
PackageInfo: packages_service.PackageInfo{
|
||||
Owner: creator,
|
||||
PackageType: packages.TypeGeneric,
|
||||
Name: "test",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Creator: creator,
|
||||
SemverCompatible: true,
|
||||
VersionProperties: map[string]string{},
|
||||
}, &packages_service.PackageFileCreationInfo{
|
||||
PackageFileInfo: packages_service.PackageFileInfo{
|
||||
Filename: "a.go",
|
||||
},
|
||||
Creator: creator,
|
||||
Data: buf,
|
||||
IsLead: true,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, v)
|
||||
assert.NotNil(t, f)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
p := t.TempDir()
|
||||
|
||||
dstStorage, err := storage.NewLocalStorage(
|
||||
ctx,
|
||||
storage.LocalStorageConfig{
|
||||
Path: p,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = migratePackages(ctx, dstStorage)
|
||||
assert.NoError(t, err)
|
||||
|
||||
entries, err := os.ReadDir(p)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 2, len(entries))
|
||||
assert.EqualValues(t, "01", entries[0].Name())
|
||||
assert.EqualValues(t, "tmp", entries[1].Name())
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// CmdRestoreRepository represents the available restore a repository sub-command.
|
||||
var CmdRestoreRepository = cli.Command{
|
||||
Name: "restore-repo",
|
||||
Usage: "Restore the repository from disk",
|
||||
Description: "This is a command for restoring the repository data.",
|
||||
Action: runRestoreRepository,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "repo_dir, r",
|
||||
Value: "./data",
|
||||
Usage: "Repository dir path to restore from",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "owner_name",
|
||||
Value: "",
|
||||
Usage: "Restore destination owner name",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo_name",
|
||||
Value: "",
|
||||
Usage: "Restore destination repository name",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "units",
|
||||
Value: "",
|
||||
Usage: `Which items will be restored, one or more units should be separated as comma.
|
||||
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "validation",
|
||||
Usage: "Sanity check the content of the files before trying to load them",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runRestoreRepository(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setting.InitProviderFromExistingFile()
|
||||
setting.LoadCommonSettings()
|
||||
var units []string
|
||||
if s := c.String("units"); s != "" {
|
||||
units = strings.Split(s, ",")
|
||||
}
|
||||
statusCode, errStr := private.RestoreRepo(
|
||||
ctx,
|
||||
c.String("repo_dir"),
|
||||
c.String("owner_name"),
|
||||
c.String("repo_name"),
|
||||
units,
|
||||
c.Bool("validation"),
|
||||
)
|
||||
if statusCode == http.StatusOK {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Fatal("Failed to restore repository: %v", errStr)
|
||||
return errors.New(errStr)
|
||||
}
|
||||
182
cmd/serv.go
182
cmd/serv.go
@@ -1,11 +1,12 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2016 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -16,21 +17,16 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/pprof"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/lfs"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/kballard/go-shellquote"
|
||||
"github.com/unknwon/com"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -42,7 +38,7 @@ const (
|
||||
var CmdServ = cli.Command{
|
||||
Name: "serv",
|
||||
Usage: "This command should only be called by SSH shell",
|
||||
Description: "Serv provides access auth for repositories",
|
||||
Description: `Serv provide access auth for repositories`,
|
||||
Action: runServ,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
@@ -61,62 +57,35 @@ func setup(logPath string, debug bool) {
|
||||
} else {
|
||||
_ = log.NewLogger(1000, "console", "console", `{"level":"fatal","stacktracelevel":"NONE","stderr":true}`)
|
||||
}
|
||||
setting.InitProviderFromExistingFile()
|
||||
setting.LoadCommonSettings()
|
||||
setting.NewContext()
|
||||
if debug {
|
||||
setting.RunMode = "dev"
|
||||
}
|
||||
|
||||
// Check if setting.RepoRootPath exists. It could be the case that it doesn't exist, this can happen when
|
||||
// `[repository]` `ROOT` is a relative path and $GITEA_WORK_DIR isn't passed to the SSH connection.
|
||||
if _, err := os.Stat(setting.RepoRootPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
_ = fail("Incorrect configuration, no repository directory.", "Directory `[repository].ROOT` %q was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository].ROOT` an absolute value.", setting.RepoRootPath)
|
||||
} else {
|
||||
_ = fail("Incorrect configuration, repository directory is inaccessible", "Directory `[repository].ROOT` %q is inaccessible. err: %v", setting.RepoRootPath, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := git.InitSimple(context.Background()); err != nil {
|
||||
_ = fail("Failed to init git", "Failed to init git, err: %v", err)
|
||||
setting.ProdMode = false
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
allowedCommands = map[string]perm.AccessMode{
|
||||
"git-upload-pack": perm.AccessModeRead,
|
||||
"git-upload-archive": perm.AccessModeRead,
|
||||
"git-receive-pack": perm.AccessModeWrite,
|
||||
lfsAuthenticateVerb: perm.AccessModeNone,
|
||||
allowedCommands = map[string]models.AccessMode{
|
||||
"git-upload-pack": models.AccessModeRead,
|
||||
"git-upload-archive": models.AccessModeRead,
|
||||
"git-receive-pack": models.AccessModeWrite,
|
||||
lfsAuthenticateVerb: models.AccessModeNone,
|
||||
}
|
||||
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
|
||||
)
|
||||
|
||||
func fail(userMessage, logMessage string, args ...interface{}) error {
|
||||
// There appears to be a chance to cause a zombie process and failure to read the Exit status
|
||||
// if nothing is outputted on stdout.
|
||||
_, _ = fmt.Fprintln(os.Stdout, "")
|
||||
_, _ = fmt.Fprintln(os.Stderr, "Gitea:", userMessage)
|
||||
func fail(userMessage, logMessage string, args ...interface{}) {
|
||||
fmt.Fprintln(os.Stderr, "Gitea:", userMessage)
|
||||
|
||||
if len(logMessage) > 0 {
|
||||
if !setting.IsProd {
|
||||
_, _ = fmt.Fprintf(os.Stderr, logMessage+"\n", args...)
|
||||
if !setting.ProdMode {
|
||||
fmt.Fprintf(os.Stderr, logMessage+"\n", args...)
|
||||
}
|
||||
}
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if len(logMessage) > 0 {
|
||||
_ = private.SSHLog(ctx, true, fmt.Sprintf(logMessage+": ", args...))
|
||||
}
|
||||
return cli.NewExitError("", 1)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func runServ(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
// FIXME: This needs to internationalised
|
||||
setup("serv.log", c.Bool("debug"))
|
||||
|
||||
@@ -134,23 +103,20 @@ func runServ(c *cli.Context) error {
|
||||
|
||||
keys := strings.Split(c.Args()[0], "-")
|
||||
if len(keys) != 2 || keys[0] != "key" {
|
||||
return fail("Key ID format error", "Invalid key argument: %s", c.Args()[0])
|
||||
}
|
||||
keyID, err := strconv.ParseInt(keys[1], 10, 64)
|
||||
if err != nil {
|
||||
return fail("Key ID format error", "Invalid key argument: %s", c.Args()[1])
|
||||
fail("Key ID format error", "Invalid key argument: %s", c.Args()[0])
|
||||
}
|
||||
keyID := com.StrTo(keys[1]).MustInt64()
|
||||
|
||||
cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
|
||||
if len(cmd) == 0 {
|
||||
key, user, err := private.ServNoCommand(ctx, keyID)
|
||||
key, user, err := private.ServNoCommand(keyID)
|
||||
if err != nil {
|
||||
return fail("Internal error", "Failed to check provided key: %v", err)
|
||||
fail("Internal error", "Failed to check provided key: %v", err)
|
||||
}
|
||||
switch key.Type {
|
||||
case asymkey_model.KeyTypeDeploy:
|
||||
case models.KeyTypeDeploy:
|
||||
println("Hi there! You've successfully authenticated with the deploy key named " + key.Name + ", but Gitea does not provide shell access.")
|
||||
case asymkey_model.KeyTypePrincipal:
|
||||
case models.KeyTypePrincipal:
|
||||
println("Hi there! You've successfully authenticated with the principal " + key.Content + ", but Gitea does not provide shell access.")
|
||||
default:
|
||||
println("Hi there, " + user.Name + "! You've successfully authenticated with the key named " + key.Name + ", but Gitea does not provide shell access.")
|
||||
@@ -163,18 +129,11 @@ func runServ(c *cli.Context) error {
|
||||
|
||||
words, err := shellquote.Split(cmd)
|
||||
if err != nil {
|
||||
return fail("Error parsing arguments", "Failed to parse arguments: %v", err)
|
||||
fail("Error parsing arguments", "Failed to parse arguments: %v", err)
|
||||
}
|
||||
|
||||
if len(words) < 2 {
|
||||
if git.CheckGitVersionAtLeast("2.29") == nil {
|
||||
// for AGit Flow
|
||||
if cmd == "ssh_info" {
|
||||
fmt.Print(`{"type":"gitea","version":1}`)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fail("Too few arguments", "Too few arguments in cmd: %s", cmd)
|
||||
fail("Too few arguments", "Too few arguments in cmd: %s", cmd)
|
||||
}
|
||||
|
||||
verb := words[0]
|
||||
@@ -186,7 +145,7 @@ func runServ(c *cli.Context) error {
|
||||
var lfsVerb string
|
||||
if verb == lfsAuthenticateVerb {
|
||||
if !setting.LFS.StartServer {
|
||||
return fail("Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled")
|
||||
fail("Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled")
|
||||
}
|
||||
|
||||
if len(words) > 2 {
|
||||
@@ -199,70 +158,82 @@ func runServ(c *cli.Context) error {
|
||||
|
||||
rr := strings.SplitN(repoPath, "/", 2)
|
||||
if len(rr) != 2 {
|
||||
return fail("Invalid repository path", "Invalid repository path: %v", repoPath)
|
||||
fail("Invalid repository path", "Invalid repository path: %v", repoPath)
|
||||
}
|
||||
|
||||
username := strings.ToLower(rr[0])
|
||||
reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git"))
|
||||
|
||||
if alphaDashDotPattern.MatchString(reponame) {
|
||||
return fail("Invalid repo name", "Invalid repo name: %s", reponame)
|
||||
fail("Invalid repo name", "Invalid repo name: %s", reponame)
|
||||
}
|
||||
|
||||
if c.Bool("enable-pprof") {
|
||||
if setting.EnablePprof || c.Bool("enable-pprof") {
|
||||
if err := os.MkdirAll(setting.PprofDataPath, os.ModePerm); err != nil {
|
||||
return fail("Error while trying to create PPROF_DATA_PATH", "Error while trying to create PPROF_DATA_PATH: %v", err)
|
||||
fail("Error while trying to create PPROF_DATA_PATH", "Error while trying to create PPROF_DATA_PATH: %v", err)
|
||||
}
|
||||
|
||||
stopCPUProfiler, err := pprof.DumpCPUProfileForUsername(setting.PprofDataPath, username)
|
||||
if err != nil {
|
||||
return fail("Internal Server Error", "Unable to start CPU profile: %v", err)
|
||||
fail("Internal Server Error", "Unable to start CPU profile: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
stopCPUProfiler()
|
||||
err := pprof.DumpMemProfileForUsername(setting.PprofDataPath, username)
|
||||
if err != nil {
|
||||
_ = fail("Internal Server Error", "Unable to dump Mem Profile: %v", err)
|
||||
fail("Internal Server Error", "Unable to dump Mem Profile: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
requestedMode, has := allowedCommands[verb]
|
||||
if !has {
|
||||
return fail("Unknown git command", "Unknown git command %s", verb)
|
||||
fail("Unknown git command", "Unknown git command %s", verb)
|
||||
}
|
||||
|
||||
if verb == lfsAuthenticateVerb {
|
||||
if lfsVerb == "upload" {
|
||||
requestedMode = perm.AccessModeWrite
|
||||
requestedMode = models.AccessModeWrite
|
||||
} else if lfsVerb == "download" {
|
||||
requestedMode = perm.AccessModeRead
|
||||
requestedMode = models.AccessModeRead
|
||||
} else {
|
||||
return fail("Unknown LFS verb", "Unknown lfs verb %s", lfsVerb)
|
||||
fail("Unknown LFS verb", "Unknown lfs verb %s", lfsVerb)
|
||||
}
|
||||
}
|
||||
|
||||
results, err := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb)
|
||||
results, err := private.ServCommand(keyID, username, reponame, requestedMode, verb, lfsVerb)
|
||||
if err != nil {
|
||||
if private.IsErrServCommand(err) {
|
||||
errServCommand := err.(private.ErrServCommand)
|
||||
if errServCommand.StatusCode != http.StatusInternalServerError {
|
||||
return fail("Unauthorized", "%s", errServCommand.Error())
|
||||
fail("Unauthorized", "%s", errServCommand.Error())
|
||||
} else {
|
||||
fail("Internal Server Error", "%s", errServCommand.Error())
|
||||
}
|
||||
return fail("Internal Server Error", "%s", errServCommand.Error())
|
||||
}
|
||||
return fail("Internal Server Error", "%s", err.Error())
|
||||
fail("Internal Server Error", "%s", err.Error())
|
||||
}
|
||||
os.Setenv(models.EnvRepoIsWiki, strconv.FormatBool(results.IsWiki))
|
||||
os.Setenv(models.EnvRepoName, results.RepoName)
|
||||
os.Setenv(models.EnvRepoUsername, results.OwnerName)
|
||||
os.Setenv(models.EnvPusherName, results.UserName)
|
||||
os.Setenv(models.EnvPusherEmail, results.UserEmail)
|
||||
os.Setenv(models.EnvPusherID, strconv.FormatInt(results.UserID, 10))
|
||||
os.Setenv(models.EnvRepoID, strconv.FormatInt(results.RepoID, 10))
|
||||
os.Setenv(models.EnvPRID, fmt.Sprintf("%d", 0))
|
||||
os.Setenv(models.EnvIsDeployKey, fmt.Sprintf("%t", results.IsDeployKey))
|
||||
os.Setenv(models.EnvKeyID, fmt.Sprintf("%d", results.KeyID))
|
||||
os.Setenv(models.EnvAppURL, setting.AppURL)
|
||||
|
||||
// LFS token authentication
|
||||
//LFS token authentication
|
||||
if verb == lfsAuthenticateVerb {
|
||||
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
|
||||
|
||||
now := time.Now()
|
||||
claims := lfs.Claims{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)),
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: now.Add(setting.LFS.HTTPAuthExpiry).Unix(),
|
||||
NotBefore: now.Unix(),
|
||||
},
|
||||
RepoID: results.RepoID,
|
||||
Op: lfsVerb,
|
||||
@@ -273,10 +244,10 @@ func runServ(c *cli.Context) error {
|
||||
// Sign and get the complete encoded token as a string using the secret
|
||||
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
|
||||
if err != nil {
|
||||
return fail("Internal error", "Failed to sign JWT token: %v", err)
|
||||
fail("Internal error", "Failed to sign JWT token: %v", err)
|
||||
}
|
||||
|
||||
tokenAuthentication := &git_model.LFSTokenResponse{
|
||||
tokenAuthentication := &models.LFSTokenResponse{
|
||||
Header: make(map[string]string),
|
||||
Href: url,
|
||||
}
|
||||
@@ -285,7 +256,7 @@ func runServ(c *cli.Context) error {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
err = enc.Encode(tokenAuthentication)
|
||||
if err != nil {
|
||||
return fail("Internal error", "Failed to encode LFS json response: %v", err)
|
||||
fail("Internal error", "Failed to encode LFS json response: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -298,42 +269,23 @@ func runServ(c *cli.Context) error {
|
||||
var gitcmd *exec.Cmd
|
||||
verbs := strings.Split(verb, " ")
|
||||
if len(verbs) == 2 {
|
||||
gitcmd = exec.CommandContext(ctx, verbs[0], verbs[1], repoPath)
|
||||
gitcmd = exec.Command(verbs[0], verbs[1], repoPath)
|
||||
} else {
|
||||
gitcmd = exec.CommandContext(ctx, verb, repoPath)
|
||||
gitcmd = exec.Command(verb, repoPath)
|
||||
}
|
||||
|
||||
process.SetSysProcAttribute(gitcmd)
|
||||
gitcmd.Dir = setting.RepoRootPath
|
||||
gitcmd.Stdout = os.Stdout
|
||||
gitcmd.Stdin = os.Stdin
|
||||
gitcmd.Stderr = os.Stderr
|
||||
gitcmd.Env = append(gitcmd.Env, os.Environ()...)
|
||||
gitcmd.Env = append(gitcmd.Env,
|
||||
repo_module.EnvRepoIsWiki+"="+strconv.FormatBool(results.IsWiki),
|
||||
repo_module.EnvRepoName+"="+results.RepoName,
|
||||
repo_module.EnvRepoUsername+"="+results.OwnerName,
|
||||
repo_module.EnvPusherName+"="+results.UserName,
|
||||
repo_module.EnvPusherEmail+"="+results.UserEmail,
|
||||
repo_module.EnvPusherID+"="+strconv.FormatInt(results.UserID, 10),
|
||||
repo_module.EnvRepoID+"="+strconv.FormatInt(results.RepoID, 10),
|
||||
repo_module.EnvPRID+"="+fmt.Sprintf("%d", 0),
|
||||
repo_module.EnvDeployKeyID+"="+fmt.Sprintf("%d", results.DeployKeyID),
|
||||
repo_module.EnvKeyID+"="+fmt.Sprintf("%d", results.KeyID),
|
||||
repo_module.EnvAppURL+"="+setting.AppURL,
|
||||
)
|
||||
// to avoid breaking, here only use the minimal environment variables for the "gitea serv" command.
|
||||
// it could be re-considered whether to use the same git.CommonGitCmdEnvs() as "git" command later.
|
||||
gitcmd.Env = append(gitcmd.Env, git.CommonCmdServEnvs()...)
|
||||
|
||||
if err = gitcmd.Run(); err != nil {
|
||||
return fail("Internal error", "Failed to execute git command: %v", err)
|
||||
fail("Internal error", "Failed to execute git command: %v", err)
|
||||
}
|
||||
|
||||
// Update user key activity.
|
||||
if results.KeyID > 0 {
|
||||
if err = private.UpdatePublicKeyInRepo(ctx, results.KeyID, results.RepoID); err != nil {
|
||||
return fail("Internal error", "UpdatePublicKeyInRepo: %v", err)
|
||||
if err = private.UpdatePublicKeyInRepo(results.KeyID, results.RepoID); err != nil {
|
||||
fail("Internal error", "UpdatePublicKeyInRepo: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
256
cmd/web.go
256
cmd/web.go
@@ -1,5 +1,6 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
@@ -8,20 +9,20 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
|
||||
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/routers"
|
||||
"code.gitea.io/gitea/routers/install"
|
||||
"code.gitea.io/gitea/routers/routes"
|
||||
|
||||
"github.com/felixge/fgprof"
|
||||
context2 "github.com/gorilla/context"
|
||||
"github.com/unknwon/com"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
ini "gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
@@ -38,31 +39,15 @@ and it takes care of all the other things for you`,
|
||||
Value: "3000",
|
||||
Usage: "Temporary port number to prevent conflict",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "install-port",
|
||||
Value: "3000",
|
||||
Usage: "Temporary port number to run the install page on to prevent conflict",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pid, P",
|
||||
Value: setting.PIDFile,
|
||||
Usage: "Custom pid file path",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "Only display Fatal logging errors until logging is set-up",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "verbose",
|
||||
Usage: "Set initial logging to TRACE level until logging is properly set-up",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runHTTPRedirector() {
|
||||
_, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: HTTP Redirector", process.SystemProcessType, true)
|
||||
defer finished()
|
||||
|
||||
source := fmt.Sprintf("%s:%s", setting.HTTPAddr, setting.PortToRedirect)
|
||||
dest := strings.TrimSuffix(setting.AppURL, "/")
|
||||
log.Info("Redirecting: %s to %s", source, dest)
|
||||
@@ -75,26 +60,44 @@ func runHTTPRedirector() {
|
||||
http.Redirect(w, r, target, http.StatusTemporaryRedirect)
|
||||
})
|
||||
|
||||
err := runHTTP("tcp", source, "HTTP Redirector", handler, setting.RedirectorUseProxyProtocol)
|
||||
var err = runHTTP("tcp", source, context2.ClearHandler(handler))
|
||||
|
||||
if err != nil {
|
||||
log.Fatal("Failed to start port redirection: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func runWeb(ctx *cli.Context) error {
|
||||
if ctx.Bool("verbose") {
|
||||
_ = log.DelLogger("console")
|
||||
log.NewLogger(0, "console", "console", fmt.Sprintf(`{"level": "trace", "colorize": %t, "stacktraceLevel": "none"}`, log.CanColorStdout))
|
||||
} else if ctx.Bool("quiet") {
|
||||
_ = log.DelLogger("console")
|
||||
log.NewLogger(0, "console", "console", fmt.Sprintf(`{"level": "fatal", "colorize": %t, "stacktraceLevel": "none"}`, log.CanColorStdout))
|
||||
func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler) error {
|
||||
certManager := autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
HostPolicy: autocert.HostWhitelist(domain),
|
||||
Cache: autocert.DirCache(directory),
|
||||
Email: email,
|
||||
}
|
||||
defer func() {
|
||||
if panicked := recover(); panicked != nil {
|
||||
log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2))
|
||||
go func() {
|
||||
log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect)
|
||||
// all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here)
|
||||
var err = runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)))
|
||||
if err != nil {
|
||||
log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err)
|
||||
}
|
||||
}()
|
||||
return runHTTPSWithTLSConfig("tcp", listenAddr, certManager.TLSConfig(), context2.ClearHandler(m))
|
||||
}
|
||||
|
||||
func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" && r.Method != "HEAD" {
|
||||
http.Error(w, "Use HTTPS", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// Remove the trailing slash at the end of setting.AppURL, the request
|
||||
// URI always contains a leading slash, which would result in a double
|
||||
// slash
|
||||
target := strings.TrimSuffix(setting.AppURL, "/") + r.URL.RequestURI()
|
||||
http.Redirect(w, r, target, http.StatusFound)
|
||||
}
|
||||
|
||||
func runWeb(ctx *cli.Context) error {
|
||||
managerCtx, cancel := context.WithCancel(context.Background())
|
||||
graceful.InitManager(managerCtx)
|
||||
defer cancel()
|
||||
@@ -111,163 +114,100 @@ func runWeb(ctx *cli.Context) error {
|
||||
setting.WritePIDFile = true
|
||||
}
|
||||
|
||||
// Perform pre-initialization
|
||||
needsInstall := install.PreloadSettings(graceful.GetManager().HammerContext())
|
||||
if needsInstall {
|
||||
// Flag for port number in case first time run conflict
|
||||
if ctx.IsSet("port") {
|
||||
if err := setPort(ctx.String("port")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if ctx.IsSet("install-port") {
|
||||
if err := setPort(ctx.String("install-port")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
installCtx, cancel := context.WithCancel(graceful.GetManager().HammerContext())
|
||||
c := install.Routes(installCtx)
|
||||
err := listen(c, false)
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Critical("Unable to open listener for installer. Is Gitea already running?")
|
||||
graceful.GetManager().DoGracefulShutdown()
|
||||
}
|
||||
select {
|
||||
case <-graceful.GetManager().IsShutdown():
|
||||
<-graceful.GetManager().Done()
|
||||
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
||||
log.Close()
|
||||
return err
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
NoInstallListener()
|
||||
}
|
||||
|
||||
if setting.EnablePprof {
|
||||
go func() {
|
||||
http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler())
|
||||
_, _, finished := process.GetManager().AddTypedContext(context.Background(), "Web: PProf Server", process.SystemProcessType, true)
|
||||
// The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment it's not worth to introduce a configurable option for it.
|
||||
log.Info("Starting pprof server on localhost:6060")
|
||||
log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", nil))
|
||||
finished()
|
||||
}()
|
||||
}
|
||||
|
||||
log.Info("Global init")
|
||||
// Perform global initialization
|
||||
setting.InitProviderFromExistingFile()
|
||||
setting.LoadCommonSettings()
|
||||
routers.GlobalInitInstalled(graceful.GetManager().HammerContext())
|
||||
routers.GlobalInit(graceful.GetManager().HammerContext())
|
||||
|
||||
// We check that AppDataPath exists here (it should have been created during installation)
|
||||
// We can't check it in `GlobalInitInstalled`, because some integration tests
|
||||
// use cmd -> GlobalInitInstalled, but the AppDataPath doesn't exist during those tests.
|
||||
if _, err := os.Stat(setting.AppDataPath); err != nil {
|
||||
log.Fatal("Can not find APP_DATA_PATH '%s'", setting.AppDataPath)
|
||||
}
|
||||
// Set up Macaron
|
||||
m := routes.NewMacaron()
|
||||
routes.RegisterRoutes(m)
|
||||
|
||||
// Override the provided port number within the configuration
|
||||
// Flag for port number in case first time run conflict.
|
||||
if ctx.IsSet("port") {
|
||||
if err := setPort(ctx.String("port")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, ctx.String("port"), 1)
|
||||
setting.HTTPPort = ctx.String("port")
|
||||
|
||||
// Set up Chi routes
|
||||
c := routers.NormalRoutes(graceful.GetManager().HammerContext())
|
||||
err := listen(c, true)
|
||||
<-graceful.GetManager().Done()
|
||||
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
||||
log.Close()
|
||||
return err
|
||||
}
|
||||
switch setting.Protocol {
|
||||
case setting.UnixSocket:
|
||||
case setting.FCGI:
|
||||
case setting.FCGIUnix:
|
||||
default:
|
||||
// Save LOCAL_ROOT_URL if port changed
|
||||
cfg := ini.Empty()
|
||||
if com.IsFile(setting.CustomConf) {
|
||||
// Keeps custom settings if there is already something.
|
||||
if err := cfg.Append(setting.CustomConf); err != nil {
|
||||
return fmt.Errorf("Failed to load custom conf '%s': %v", setting.CustomConf, err)
|
||||
}
|
||||
}
|
||||
|
||||
func setPort(port string) error {
|
||||
setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, port, 1)
|
||||
setting.HTTPPort = port
|
||||
defaultLocalURL := string(setting.Protocol) + "://"
|
||||
if setting.HTTPAddr == "0.0.0.0" {
|
||||
defaultLocalURL += "localhost"
|
||||
} else {
|
||||
defaultLocalURL += setting.HTTPAddr
|
||||
}
|
||||
defaultLocalURL += ":" + setting.HTTPPort + "/"
|
||||
|
||||
switch setting.Protocol {
|
||||
case setting.HTTPUnix:
|
||||
case setting.FCGI:
|
||||
case setting.FCGIUnix:
|
||||
default:
|
||||
defaultLocalURL := string(setting.Protocol) + "://"
|
||||
if setting.HTTPAddr == "0.0.0.0" {
|
||||
defaultLocalURL += "localhost"
|
||||
} else {
|
||||
defaultLocalURL += setting.HTTPAddr
|
||||
}
|
||||
defaultLocalURL += ":" + setting.HTTPPort + "/"
|
||||
|
||||
// Save LOCAL_ROOT_URL if port changed
|
||||
setting.CreateOrAppendToCustomConf("server.LOCAL_ROOT_URL", func(cfg *ini.File) {
|
||||
cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func listen(m http.Handler, handleRedirector bool) error {
|
||||
if err := cfg.SaveTo(setting.CustomConf); err != nil {
|
||||
return fmt.Errorf("Error saving generated JWT Secret to custom config: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listenAddr := setting.HTTPAddr
|
||||
if setting.Protocol != setting.HTTPUnix && setting.Protocol != setting.FCGIUnix {
|
||||
if setting.Protocol != setting.UnixSocket && setting.Protocol != setting.FCGIUnix {
|
||||
listenAddr = net.JoinHostPort(listenAddr, setting.HTTPPort)
|
||||
}
|
||||
_, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: Gitea Server", process.SystemProcessType, true)
|
||||
defer finished()
|
||||
log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubURL)
|
||||
// This can be useful for users, many users do wrong to their config and get strange behaviors behind a reverse-proxy.
|
||||
// A user may fix the configuration mistake when he sees this log.
|
||||
// And this is also very helpful to maintainers to provide help to users to resolve their configuration problems.
|
||||
log.Info("AppURL(ROOT_URL): %s", setting.AppURL)
|
||||
|
||||
if setting.LFS.StartServer {
|
||||
log.Info("LFS server enabled")
|
||||
}
|
||||
|
||||
if setting.EnablePprof {
|
||||
go func() {
|
||||
log.Info("Starting pprof server on localhost:6060")
|
||||
log.Info("%v", http.ListenAndServe("localhost:6060", nil))
|
||||
}()
|
||||
}
|
||||
|
||||
var err error
|
||||
switch setting.Protocol {
|
||||
case setting.HTTP:
|
||||
if handleRedirector {
|
||||
NoHTTPRedirector()
|
||||
}
|
||||
err = runHTTP("tcp", listenAddr, "Web", m, setting.UseProxyProtocol)
|
||||
NoHTTPRedirector()
|
||||
err = runHTTP("tcp", listenAddr, context2.ClearHandler(m))
|
||||
case setting.HTTPS:
|
||||
if setting.EnableAcme {
|
||||
err = runACME(listenAddr, m)
|
||||
if setting.EnableLetsEncrypt {
|
||||
err = runLetsEncrypt(listenAddr, setting.Domain, setting.LetsEncryptDirectory, setting.LetsEncryptEmail, context2.ClearHandler(m))
|
||||
break
|
||||
}
|
||||
if handleRedirector {
|
||||
if setting.RedirectOtherPort {
|
||||
go runHTTPRedirector()
|
||||
} else {
|
||||
NoHTTPRedirector()
|
||||
}
|
||||
if setting.RedirectOtherPort {
|
||||
go runHTTPRedirector()
|
||||
} else {
|
||||
NoHTTPRedirector()
|
||||
}
|
||||
err = runHTTPS("tcp", listenAddr, "Web", setting.CertFile, setting.KeyFile, m, setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging)
|
||||
err = runHTTPS("tcp", listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m))
|
||||
case setting.FCGI:
|
||||
if handleRedirector {
|
||||
NoHTTPRedirector()
|
||||
}
|
||||
err = runFCGI("tcp", listenAddr, "FCGI Web", m, setting.UseProxyProtocol)
|
||||
case setting.HTTPUnix:
|
||||
if handleRedirector {
|
||||
NoHTTPRedirector()
|
||||
}
|
||||
err = runHTTP("unix", listenAddr, "Web", m, setting.UseProxyProtocol)
|
||||
NoHTTPRedirector()
|
||||
err = runFCGI("tcp", listenAddr, context2.ClearHandler(m))
|
||||
case setting.UnixSocket:
|
||||
NoHTTPRedirector()
|
||||
err = runHTTP("unix", listenAddr, context2.ClearHandler(m))
|
||||
case setting.FCGIUnix:
|
||||
if handleRedirector {
|
||||
NoHTTPRedirector()
|
||||
}
|
||||
err = runFCGI("unix", listenAddr, "Web", m, setting.UseProxyProtocol)
|
||||
NoHTTPRedirector()
|
||||
err = runFCGI("unix", listenAddr, context2.ClearHandler(m))
|
||||
default:
|
||||
log.Fatal("Invalid protocol: %s", setting.Protocol)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Critical("Failed to start server: %v", err)
|
||||
}
|
||||
log.Info("HTTP Listener: %s Closed", listenAddr)
|
||||
return err
|
||||
<-graceful.GetManager().Done()
|
||||
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
||||
log.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
135
cmd/web_acme.go
135
cmd/web_acme.go
@@ -1,135 +0,0 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
)
|
||||
|
||||
func getCARoot(path string) (*x509.CertPool, error) {
|
||||
r, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block, _ := pem.Decode(r)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("no PEM found in the file %s", path)
|
||||
}
|
||||
caRoot, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certPool := x509.NewCertPool()
|
||||
certPool.AddCert(caRoot)
|
||||
return certPool, nil
|
||||
}
|
||||
|
||||
func runACME(listenAddr string, m http.Handler) error {
|
||||
// If HTTP Challenge enabled, needs to be serving on port 80. For TLSALPN needs 443.
|
||||
// Due to docker port mapping this can't be checked programmatically
|
||||
// TODO: these are placeholders until we add options for each in settings with appropriate warning
|
||||
enableHTTPChallenge := true
|
||||
enableTLSALPNChallenge := true
|
||||
altHTTPPort := 0
|
||||
altTLSALPNPort := 0
|
||||
|
||||
if p, err := strconv.Atoi(setting.PortToRedirect); err == nil {
|
||||
altHTTPPort = p
|
||||
}
|
||||
if p, err := strconv.Atoi(setting.HTTPPort); err == nil {
|
||||
altTLSALPNPort = p
|
||||
}
|
||||
|
||||
magic := certmagic.NewDefault()
|
||||
magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
|
||||
// Try to use private CA root if provided, otherwise defaults to system's trust
|
||||
var certPool *x509.CertPool
|
||||
if setting.AcmeCARoot != "" {
|
||||
var err error
|
||||
certPool, err = getCARoot(setting.AcmeCARoot)
|
||||
if err != nil {
|
||||
log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
|
||||
}
|
||||
}
|
||||
myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
|
||||
CA: setting.AcmeURL,
|
||||
TrustedRoots: certPool,
|
||||
Email: setting.AcmeEmail,
|
||||
Agreed: setting.AcmeTOS,
|
||||
DisableHTTPChallenge: !enableHTTPChallenge,
|
||||
DisableTLSALPNChallenge: !enableTLSALPNChallenge,
|
||||
ListenHost: setting.HTTPAddr,
|
||||
AltTLSALPNPort: altTLSALPNPort,
|
||||
AltHTTPPort: altHTTPPort,
|
||||
})
|
||||
|
||||
magic.Issuers = []certmagic.Issuer{myACME}
|
||||
|
||||
// this obtains certificates or renews them if necessary
|
||||
err := magic.ManageSync(graceful.GetManager().HammerContext(), []string{setting.Domain})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tlsConfig := magic.TLSConfig()
|
||||
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")
|
||||
|
||||
if version := toTLSVersion(setting.SSLMinimumVersion); version != 0 {
|
||||
tlsConfig.MinVersion = version
|
||||
}
|
||||
if version := toTLSVersion(setting.SSLMaximumVersion); version != 0 {
|
||||
tlsConfig.MaxVersion = version
|
||||
}
|
||||
|
||||
// Set curve preferences
|
||||
if curves := toCurvePreferences(setting.SSLCurvePreferences); len(curves) > 0 {
|
||||
tlsConfig.CurvePreferences = curves
|
||||
}
|
||||
|
||||
// Set cipher suites
|
||||
if ciphers := toTLSCiphers(setting.SSLCipherSuites); len(ciphers) > 0 {
|
||||
tlsConfig.CipherSuites = ciphers
|
||||
}
|
||||
|
||||
if enableHTTPChallenge {
|
||||
go func() {
|
||||
_, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: ACME HTTP challenge server", process.SystemProcessType, true)
|
||||
defer finished()
|
||||
|
||||
log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect)
|
||||
// all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here)
|
||||
err := runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)), setting.RedirectorUseProxyProtocol)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return runHTTPSWithTLSConfig("tcp", listenAddr, "Web", tlsConfig, m, setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging)
|
||||
}
|
||||
|
||||
func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" && r.Method != "HEAD" {
|
||||
http.Error(w, "Use HTTPS", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// Remove the trailing slash at the end of setting.AppURL, the request
|
||||
// URI always contains a leading slash, which would result in a double
|
||||
// slash
|
||||
target := strings.TrimSuffix(setting.AppURL, "/") + r.URL.RequestURI()
|
||||
http.Redirect(w, r, target, http.StatusTemporaryRedirect)
|
||||
}
|
||||
@@ -1,21 +1,29 @@
|
||||
// Copyright 2016 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/fcgi"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
func runHTTP(network, listenAddr, name string, m http.Handler, useProxyProtocol bool) error {
|
||||
return graceful.HTTPListenAndServe(network, listenAddr, name, m, useProxyProtocol)
|
||||
func runHTTP(network, listenAddr string, m http.Handler) error {
|
||||
return graceful.HTTPListenAndServe(network, listenAddr, m)
|
||||
}
|
||||
|
||||
func runHTTPS(network, listenAddr, certFile, keyFile string, m http.Handler) error {
|
||||
return graceful.HTTPListenAndServeTLS(network, listenAddr, certFile, keyFile, m)
|
||||
}
|
||||
|
||||
func runHTTPSWithTLSConfig(network, listenAddr string, tlsConfig *tls.Config, m http.Handler) error {
|
||||
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, tlsConfig, m)
|
||||
}
|
||||
|
||||
// NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector
|
||||
@@ -29,24 +37,13 @@ func NoMainListener() {
|
||||
graceful.GetManager().InformCleanup()
|
||||
}
|
||||
|
||||
// NoInstallListener tells our cleanup routine that we will not be using a possibly provided listener
|
||||
// for our install HTTP/HTTPS service
|
||||
func NoInstallListener() {
|
||||
graceful.GetManager().InformCleanup()
|
||||
}
|
||||
|
||||
func runFCGI(network, listenAddr, name string, m http.Handler, useProxyProtocol bool) error {
|
||||
func runFCGI(network, listenAddr string, m http.Handler) error {
|
||||
// This needs to handle stdin as fcgi point
|
||||
fcgiServer := graceful.NewServer(network, listenAddr, name)
|
||||
fcgiServer := graceful.NewServer(network, listenAddr)
|
||||
|
||||
err := fcgiServer.ListenAndServe(func(listener net.Listener) error {
|
||||
return fcgi.Serve(listener, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
if setting.AppSubURL != "" {
|
||||
req.URL.Path = strings.TrimPrefix(req.URL.Path, setting.AppSubURL)
|
||||
}
|
||||
m.ServeHTTP(resp, req)
|
||||
}))
|
||||
}, useProxyProtocol)
|
||||
return fcgi.Serve(listener, m)
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal("Failed to start FCGI main server: %v", err)
|
||||
}
|
||||
|
||||
191
cmd/web_https.go
191
cmd/web_https.go
@@ -1,191 +0,0 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/klauspost/cpuid/v2"
|
||||
)
|
||||
|
||||
var tlsVersionStringMap = map[string]uint16{
|
||||
"": tls.VersionTLS12, // Default to tls.VersionTLS12
|
||||
"tlsv1.0": tls.VersionTLS10,
|
||||
"tlsv1.1": tls.VersionTLS11,
|
||||
"tlsv1.2": tls.VersionTLS12,
|
||||
"tlsv1.3": tls.VersionTLS13,
|
||||
}
|
||||
|
||||
func toTLSVersion(version string) uint16 {
|
||||
tlsVersion, ok := tlsVersionStringMap[strings.TrimSpace(strings.ToLower(version))]
|
||||
if !ok {
|
||||
log.Warn("Unknown tls version: %s", version)
|
||||
return 0
|
||||
}
|
||||
return tlsVersion
|
||||
}
|
||||
|
||||
var curveStringMap = map[string]tls.CurveID{
|
||||
"x25519": tls.X25519,
|
||||
"p256": tls.CurveP256,
|
||||
"p384": tls.CurveP384,
|
||||
"p521": tls.CurveP521,
|
||||
}
|
||||
|
||||
func toCurvePreferences(preferences []string) []tls.CurveID {
|
||||
ids := make([]tls.CurveID, 0, len(preferences))
|
||||
for _, pref := range preferences {
|
||||
id, ok := curveStringMap[strings.TrimSpace(strings.ToLower(pref))]
|
||||
if !ok {
|
||||
log.Warn("Unknown curve: %s", pref)
|
||||
}
|
||||
if id != 0 {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
var cipherStringMap = map[string]uint16{
|
||||
"rsa_with_rc4_128_sha": tls.TLS_RSA_WITH_RC4_128_SHA,
|
||||
"rsa_with_3des_ede_cbc_sha": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
"rsa_with_aes_128_cbc_sha": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
"rsa_with_aes_256_cbc_sha": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
"rsa_with_aes_128_cbc_sha256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
"rsa_with_aes_128_gcm_sha256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
"rsa_with_aes_256_gcm_sha384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
"ecdhe_ecdsa_with_rc4_128_sha": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||
"ecdhe_ecdsa_with_aes_128_cbc_sha": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
"ecdhe_ecdsa_with_aes_256_cbc_sha": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
"ecdhe_rsa_with_rc4_128_sha": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
"ecdhe_rsa_with_3des_ede_cbc_sha": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
"ecdhe_rsa_with_aes_128_cbc_sha": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
"ecdhe_rsa_with_aes_256_cbc_sha": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
"ecdhe_ecdsa_with_aes_128_cbc_sha256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
"ecdhe_rsa_with_aes_128_cbc_sha256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
"ecdhe_rsa_with_aes_128_gcm_sha256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
"ecdhe_ecdsa_with_aes_128_gcm_sha256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
"ecdhe_rsa_with_aes_256_gcm_sha384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
"ecdhe_ecdsa_with_aes_256_gcm_sha384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
"ecdhe_rsa_with_chacha20_poly1305_sha256": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
"ecdhe_ecdsa_with_chacha20_poly1305_sha256": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
"ecdhe_rsa_with_chacha20_poly1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
"ecdhe_ecdsa_with_chacha20_poly1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
"aes_128_gcm_sha256": tls.TLS_AES_128_GCM_SHA256,
|
||||
"aes_256_gcm_sha384": tls.TLS_AES_256_GCM_SHA384,
|
||||
"chacha20_poly1305_sha256": tls.TLS_CHACHA20_POLY1305_SHA256,
|
||||
}
|
||||
|
||||
func toTLSCiphers(cipherStrings []string) []uint16 {
|
||||
ciphers := make([]uint16, 0, len(cipherStrings))
|
||||
for _, cipherString := range cipherStrings {
|
||||
cipher, ok := cipherStringMap[strings.TrimSpace(strings.ToLower(cipherString))]
|
||||
if !ok {
|
||||
log.Warn("Unknown cipher: %s", cipherString)
|
||||
}
|
||||
if cipher != 0 {
|
||||
ciphers = append(ciphers, cipher)
|
||||
}
|
||||
}
|
||||
|
||||
return ciphers
|
||||
}
|
||||
|
||||
// defaultCiphers uses hardware support to check if AES is specifically
|
||||
// supported by the CPU.
|
||||
//
|
||||
// If AES is supported AES ciphers will be preferred over ChaCha based ciphers
|
||||
// (This code is directly inspired by the certmagic code.)
|
||||
func defaultCiphers() []uint16 {
|
||||
if cpuid.CPU.Supports(cpuid.AESNI) {
|
||||
return defaultCiphersAESfirst
|
||||
}
|
||||
return defaultCiphersChaChaFirst
|
||||
}
|
||||
|
||||
var (
|
||||
defaultCiphersAES = []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
}
|
||||
|
||||
defaultCiphersChaCha = []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
}
|
||||
|
||||
defaultCiphersAESfirst = append(defaultCiphersAES, defaultCiphersChaCha...)
|
||||
defaultCiphersChaChaFirst = append(defaultCiphersChaCha, defaultCiphersAES...)
|
||||
)
|
||||
|
||||
// runHTTPS listens on the provided network address and then calls
|
||||
// Serve to handle requests on incoming TLS connections.
|
||||
//
|
||||
// Filenames containing a certificate and matching private key for the server must
|
||||
// be provided. If the certificate is signed by a certificate authority, the
|
||||
// certFile should be the concatenation of the server's certificate followed by the
|
||||
// CA's certificate.
|
||||
func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error {
|
||||
tlsConfig := &tls.Config{}
|
||||
if tlsConfig.NextProtos == nil {
|
||||
tlsConfig.NextProtos = []string{"h2", "http/1.1"}
|
||||
}
|
||||
|
||||
if version := toTLSVersion(setting.SSLMinimumVersion); version != 0 {
|
||||
tlsConfig.MinVersion = version
|
||||
}
|
||||
if version := toTLSVersion(setting.SSLMaximumVersion); version != 0 {
|
||||
tlsConfig.MaxVersion = version
|
||||
}
|
||||
|
||||
// Set curve preferences
|
||||
tlsConfig.CurvePreferences = []tls.CurveID{
|
||||
tls.X25519,
|
||||
tls.CurveP256,
|
||||
}
|
||||
if curves := toCurvePreferences(setting.SSLCurvePreferences); len(curves) > 0 {
|
||||
tlsConfig.CurvePreferences = curves
|
||||
}
|
||||
|
||||
// Set cipher suites
|
||||
tlsConfig.CipherSuites = defaultCiphers()
|
||||
if ciphers := toTLSCiphers(setting.SSLCipherSuites); len(ciphers) > 0 {
|
||||
tlsConfig.CipherSuites = ciphers
|
||||
}
|
||||
|
||||
tlsConfig.Certificates = make([]tls.Certificate, 1)
|
||||
|
||||
certPEMBlock, err := os.ReadFile(certFile)
|
||||
if err != nil {
|
||||
log.Error("Failed to load https cert file %s for %s:%s: %v", certFile, network, listenAddr, err)
|
||||
return err
|
||||
}
|
||||
|
||||
keyPEMBlock, err := os.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
log.Error("Failed to load https key file %s for %s:%s: %v", keyFile, network, listenAddr, err)
|
||||
return err
|
||||
}
|
||||
|
||||
tlsConfig.Certificates[0], err = tls.X509KeyPair(certPEMBlock, keyPEMBlock)
|
||||
if err != nil {
|
||||
log.Error("Failed to create certificate from cert file %s and key file %s for %s:%s: %v", certFile, keyFile, network, listenAddr, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m, useProxyProtocol, proxyProtocolTLSBridging)
|
||||
}
|
||||
|
||||
func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error {
|
||||
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m, useProxyProtocol, proxyProtocolTLSBridging)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
Bash and Zsh completion
|
||||
=======================
|
||||
|
||||
From within the gitea root run:
|
||||
|
||||
```bash
|
||||
source contrib/autocompletion/bash_autocomplete
|
||||
```
|
||||
|
||||
or for zsh run:
|
||||
|
||||
```bash
|
||||
source contrib/autocompletion/zsh_autocomplete
|
||||
```
|
||||
|
||||
These scripts will check if gitea is on the path and if so add autocompletion for `gitea`. Or if not autocompletion will work for `./gitea`.
|
||||
If gitea has been installed as a different program pass in the `PROG` environment variable to set the correct program name.
|
||||
@@ -1,30 +0,0 @@
|
||||
#! /bin/bash
|
||||
# Heavily inspired by https://github.com/urfave/cli
|
||||
|
||||
_cli_bash_autocomplete() {
|
||||
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
|
||||
local cur opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
|
||||
else
|
||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
||||
fi
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -z "$PROG" ] && [ ! "$(command -v gitea &> /dev/null)" ] ; then
|
||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete gitea
|
||||
elif [ -z "$PROG" ]; then
|
||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete ./gitea
|
||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete "$PWD/gitea"
|
||||
else
|
||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete "$PROG"
|
||||
unset PROG
|
||||
fi
|
||||
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
#compdef ${PROG:=gitea}
|
||||
|
||||
|
||||
# Heavily inspired by https://github.com/urfave/cli
|
||||
|
||||
_cli_zsh_autocomplete() {
|
||||
|
||||
local -a opts
|
||||
local cur
|
||||
cur=${words[-1]}
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
|
||||
else
|
||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
||||
fi
|
||||
|
||||
if [[ "${opts[1]}" != "" ]]; then
|
||||
_describe 'values' opts
|
||||
else
|
||||
_files
|
||||
fi
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if [ -z $PROG ] ; then
|
||||
compdef _cli_zsh_autocomplete gitea
|
||||
else
|
||||
compdef _cli_zsh_autocomplete $(basename $PROG)
|
||||
fi
|
||||
@@ -1,41 +0,0 @@
|
||||
`backport`
|
||||
==========
|
||||
|
||||
`backport` is a command to help create backports of PRs. It backports a
|
||||
provided PR from main on to a released version.
|
||||
|
||||
It will create a backport branch, cherry-pick the PR's merge commit, adjust
|
||||
the commit message and then push this back up to your fork's remote.
|
||||
|
||||
The default version will read from `docs/config.yml`. You can override this
|
||||
using the option `--version`.
|
||||
|
||||
The upstream branches will be fetched, using the remote `origin`. This can
|
||||
be overrided using `--upstream`, and fetching can be avoided using
|
||||
`--no-fetch`.
|
||||
|
||||
By default the branch created will be called `backport-$PR-$VERSION`. You
|
||||
can override this using the option `--backport-branch`. This branch will
|
||||
be created from `--release-branch` which is `release/$(VERSION)`
|
||||
by default and will be pulled from `$(UPSTREAM)`.
|
||||
|
||||
The merge-commit as determined by the github API will be used as the SHA to
|
||||
cherry-pick. You can override this using `--cherry-pick`.
|
||||
|
||||
The commit message will be amended to add the `Backport` header.
|
||||
`--no-amend-message` can be set to stop this from happening.
|
||||
|
||||
If cherry-pick is successful the backported branch will be pushed up to your
|
||||
fork using your remote. These will be determined using `git remote -v`. You
|
||||
can set your fork name using `--fork-user` and your remote name using
|
||||
`--remote`. You can avoid pushing using `--no-push`.
|
||||
|
||||
If the push is successful, `xdg-open` will be called to open a backport url.
|
||||
You can stop this using `--no-xdg-open`.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
```bash
|
||||
go install contrib/backport/backport.go
|
||||
```
|
||||
@@ -1,473 +0,0 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/google/go-github/v45/github"
|
||||
"github.com/urfave/cli"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const defaultVersion = "v1.18" // to backport to
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "backport"
|
||||
app.Usage = "Backport provided PR-number on to the current or previous released version"
|
||||
app.Description = `Backport will look-up the PR in Gitea's git log and attempt to cherry-pick it on the current version`
|
||||
app.ArgsUsage = "<PR-to-backport>"
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "version",
|
||||
Usage: "Version branch to backport on to",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "upstream",
|
||||
Value: "origin",
|
||||
Usage: "Upstream remote for the Gitea upstream",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "release-branch",
|
||||
Value: "",
|
||||
Usage: "Release branch to backport on. Will default to release/<version>",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cherry-pick",
|
||||
Usage: "SHA to cherry-pick as backport",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "backport-branch",
|
||||
Usage: "Backport branch to backport on to (default: backport-<pr>-<version>",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "remote",
|
||||
Value: "",
|
||||
Usage: "Remote for your fork of the Gitea upstream",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "fork-user",
|
||||
Value: "",
|
||||
Usage: "Forked user name on Github",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-fetch",
|
||||
Usage: "Set this flag to prevent fetch of remote branches",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-amend-message",
|
||||
Usage: "Set this flag to prevent automatic amendment of the commit message",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-push",
|
||||
Usage: "Set this flag to prevent pushing the backport up to your fork",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-xdg-open",
|
||||
Usage: "Set this flag to not use xdg-open to open the PR URL",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "continue",
|
||||
Usage: "Set this flag to continue from a git cherry-pick that has broken",
|
||||
},
|
||||
}
|
||||
cli.AppHelpTemplate = `NAME:
|
||||
{{.Name}} - {{.Usage}}
|
||||
USAGE:
|
||||
{{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||
{{if len .Authors}}
|
||||
AUTHOR:
|
||||
{{range .Authors}}{{ . }}{{end}}
|
||||
{{end}}{{if .Commands}}
|
||||
OPTIONS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
`
|
||||
|
||||
app.Action = runBackport
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func runBackport(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
continuing := c.Bool("continue")
|
||||
|
||||
var pr string
|
||||
|
||||
version := c.String("version")
|
||||
if version == "" && continuing {
|
||||
// determine version from current branch name
|
||||
var err error
|
||||
pr, version, err = readCurrentBranch(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if version == "" {
|
||||
version = readVersion()
|
||||
}
|
||||
if version == "" {
|
||||
version = defaultVersion
|
||||
}
|
||||
|
||||
upstream := c.String("upstream")
|
||||
if upstream == "" {
|
||||
upstream = "origin"
|
||||
}
|
||||
|
||||
forkUser := c.String("fork-user")
|
||||
remote := c.String("remote")
|
||||
if remote == "" && !c.Bool("--no-push") {
|
||||
var err error
|
||||
remote, forkUser, err = determineRemote(ctx, forkUser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
upstreamReleaseBranch := c.String("release-branch")
|
||||
if upstreamReleaseBranch == "" {
|
||||
upstreamReleaseBranch = path.Join("release", version)
|
||||
}
|
||||
|
||||
localReleaseBranch := path.Join(upstream, upstreamReleaseBranch)
|
||||
|
||||
args := c.Args()
|
||||
if len(args) == 0 && pr == "" {
|
||||
return fmt.Errorf("no PR number provided\nProvide a PR number to backport")
|
||||
} else if len(args) != 1 && pr == "" {
|
||||
return fmt.Errorf("multiple PRs provided %v\nOnly a single PR can be backported at a time", args)
|
||||
}
|
||||
if pr == "" {
|
||||
pr = args[0]
|
||||
}
|
||||
|
||||
backportBranch := c.String("backport-branch")
|
||||
if backportBranch == "" {
|
||||
backportBranch = "backport-" + pr + "-" + version
|
||||
}
|
||||
|
||||
fmt.Printf("* Backporting %s to %s as %s\n", pr, localReleaseBranch, backportBranch)
|
||||
|
||||
sha := c.String("cherry-pick")
|
||||
if sha == "" {
|
||||
var err error
|
||||
sha, err = determineSHAforPR(ctx, pr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if sha == "" {
|
||||
return fmt.Errorf("unable to determine sha for cherry-pick of %s", pr)
|
||||
}
|
||||
|
||||
if !c.Bool("no-fetch") {
|
||||
if err := fetchRemoteAndMain(ctx, upstream, upstreamReleaseBranch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !continuing {
|
||||
if err := checkoutBackportBranch(ctx, backportBranch, localReleaseBranch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := cherrypick(ctx, sha); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.Bool("no-amend-message") {
|
||||
if err := amendCommit(ctx, pr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !c.Bool("no-push") {
|
||||
url := "https://github.com/go-gitea/gitea/compare/" + upstreamReleaseBranch + "..." + forkUser + ":" + backportBranch
|
||||
|
||||
if err := gitPushUp(ctx, remote, backportBranch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.Bool("no-xdg-open") {
|
||||
if err := xdgOpen(ctx, url); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("* Navigate to %s to open PR\n", url)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func xdgOpen(ctx context.Context, url string) error {
|
||||
fmt.Printf("* `xdg-open %s`\n", url)
|
||||
out, err := exec.CommandContext(ctx, "xdg-open", url).Output()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s", string(out))
|
||||
return fmt.Errorf("unable to xdg-open to %s: %w", url, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func gitPushUp(ctx context.Context, remote, backportBranch string) error {
|
||||
fmt.Printf("* `git push -u %s %s`\n", remote, backportBranch)
|
||||
out, err := exec.CommandContext(ctx, "git", "push", "-u", remote, backportBranch).Output()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s", string(out))
|
||||
return fmt.Errorf("unable to push up to %s: %w", remote, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func amendCommit(ctx context.Context, pr string) error {
|
||||
fmt.Printf("* Amending commit to prepend `Backport #%s` to body\n", pr)
|
||||
out, err := exec.CommandContext(ctx, "git", "log", "-1", "--pretty=format:%B").Output()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s", string(out))
|
||||
return fmt.Errorf("unable to get last log message: %w", err)
|
||||
}
|
||||
|
||||
parts := strings.SplitN(string(out), "\n", 2)
|
||||
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("unable to interpret log message:\n%s", string(out))
|
||||
}
|
||||
subject, body := parts[0], parts[1]
|
||||
if !strings.HasSuffix(subject, " (#"+pr+")") {
|
||||
subject = subject + " (#" + pr + ")"
|
||||
}
|
||||
|
||||
out, err = exec.CommandContext(ctx, "git", "commit", "--amend", "-m", subject+"\n\nBackport #"+pr+"\n"+body).Output()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s", string(out))
|
||||
return fmt.Errorf("unable to amend last log message: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cherrypick(ctx context.Context, sha string) error {
|
||||
// Check if a CHERRY_PICK_HEAD exists
|
||||
if _, err := os.Stat(".git/CHERRY_PICK_HEAD"); err == nil {
|
||||
// Assume that we are in the middle of cherry-pick - continue it
|
||||
fmt.Println("* Attempting git cherry-pick --continue")
|
||||
out, err := exec.CommandContext(ctx, "git", "cherry-pick", "--continue").Output()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "git cherry-pick --continue failed:\n%s\n", string(out))
|
||||
return fmt.Errorf("unable to continue cherry-pick: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("* Attempting git cherry-pick %s\n", sha)
|
||||
out, err := exec.CommandContext(ctx, "git", "cherry-pick", sha).Output()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "git cherry-pick %s failed:\n%s\n", sha, string(out))
|
||||
return fmt.Errorf("git cherry-pick %s failed: %w", sha, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkoutBackportBranch(ctx context.Context, backportBranch, releaseBranch string) error {
|
||||
out, err := exec.CommandContext(ctx, "git", "branch", "--show-current").Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to check current branch %w", err)
|
||||
}
|
||||
|
||||
currentBranch := strings.TrimSpace(string(out))
|
||||
fmt.Printf("* Current branch is %s\n", currentBranch)
|
||||
if currentBranch == backportBranch {
|
||||
fmt.Printf("* Current branch is %s - not checking out\n", currentBranch)
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := exec.CommandContext(ctx, "git", "rev-list", "-1", backportBranch).Output(); err == nil {
|
||||
fmt.Printf("* Branch %s already exists. Checking it out...\n", backportBranch)
|
||||
return exec.CommandContext(ctx, "git", "checkout", "-f", backportBranch).Run()
|
||||
}
|
||||
|
||||
fmt.Printf("* `git checkout -b %s %s`\n", backportBranch, releaseBranch)
|
||||
return exec.CommandContext(ctx, "git", "checkout", "-b", backportBranch, releaseBranch).Run()
|
||||
}
|
||||
|
||||
func fetchRemoteAndMain(ctx context.Context, remote, releaseBranch string) error {
|
||||
fmt.Printf("* `git fetch %s main`\n", remote)
|
||||
out, err := exec.CommandContext(ctx, "git", "fetch", remote, "main").Output()
|
||||
if err != nil {
|
||||
fmt.Println(string(out))
|
||||
return fmt.Errorf("unable to fetch %s from %s: %w", "main", remote, err)
|
||||
}
|
||||
fmt.Println(string(out))
|
||||
|
||||
fmt.Printf("* `git fetch %s %s`\n", remote, releaseBranch)
|
||||
out, err = exec.CommandContext(ctx, "git", "fetch", remote, releaseBranch).Output()
|
||||
if err != nil {
|
||||
fmt.Println(string(out))
|
||||
return fmt.Errorf("unable to fetch %s from %s: %w", releaseBranch, remote, err)
|
||||
}
|
||||
fmt.Println(string(out))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func determineRemote(ctx context.Context, forkUser string) (string, string, error) {
|
||||
out, err := exec.CommandContext(ctx, "git", "remote", "-v").Output()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to list git remotes:\n%s\n", string(out))
|
||||
return "", "", fmt.Errorf("unable to determine forked remote: %w", err)
|
||||
}
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Split(line, "\t")
|
||||
name, remote := fields[0], fields[1]
|
||||
// only look at pushers
|
||||
if !strings.HasSuffix(remote, " (push)") {
|
||||
continue
|
||||
}
|
||||
// only look at github.com pushes
|
||||
if !strings.Contains(remote, "github.com") {
|
||||
continue
|
||||
}
|
||||
// ignore go-gitea/gitea
|
||||
if strings.Contains(remote, "go-gitea/gitea") {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(remote, forkUser) {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(remote, "git@github.com:") {
|
||||
forkUser = strings.TrimPrefix(remote, "git@github.com:")
|
||||
} else if strings.HasPrefix(remote, "https://github.com/") {
|
||||
forkUser = strings.TrimPrefix(remote, "https://github.com/")
|
||||
} else if strings.HasPrefix(remote, "https://www.github.com/") {
|
||||
forkUser = strings.TrimPrefix(remote, "https://www.github.com/")
|
||||
} else if forkUser == "" {
|
||||
return "", "", fmt.Errorf("unable to extract forkUser from remote %s: %s", name, remote)
|
||||
}
|
||||
idx := strings.Index(forkUser, "/")
|
||||
if idx >= 0 {
|
||||
forkUser = forkUser[:idx]
|
||||
}
|
||||
return name, forkUser, nil
|
||||
}
|
||||
return "", "", fmt.Errorf("unable to find appropriate remote in:\n%s", string(out))
|
||||
}
|
||||
|
||||
func readCurrentBranch(ctx context.Context) (pr, version string, err error) {
|
||||
out, err := exec.CommandContext(ctx, "git", "branch", "--show-current").Output()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to read current git branch:\n%s\n", string(out))
|
||||
return "", "", fmt.Errorf("unable to read current git branch: %w", err)
|
||||
}
|
||||
parts := strings.Split(strings.TrimSpace(string(out)), "-")
|
||||
|
||||
if len(parts) != 3 || parts[0] != "backport" {
|
||||
fmt.Fprintf(os.Stderr, "Unable to continue from git branch:\n%s\n", string(out))
|
||||
return "", "", fmt.Errorf("unable to continue from git branch:\n%s", string(out))
|
||||
}
|
||||
|
||||
return parts[1], parts[2], nil
|
||||
}
|
||||
|
||||
func readVersion() string {
|
||||
bs, err := os.ReadFile("docs/config.yaml")
|
||||
if err != nil {
|
||||
if err == os.ErrNotExist {
|
||||
log.Println("`docs/config.yaml` not present")
|
||||
return ""
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Unable to read `docs/config.yaml`: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
type params struct {
|
||||
Version string
|
||||
}
|
||||
type docConfig struct {
|
||||
Params params
|
||||
}
|
||||
dc := &docConfig{}
|
||||
if err := yaml.Unmarshal(bs, dc); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to read `docs/config.yaml`: %v\n", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
if dc.Params.Version == "" {
|
||||
fmt.Fprintf(os.Stderr, "No version in `docs/config.yaml`")
|
||||
return ""
|
||||
}
|
||||
|
||||
version := dc.Params.Version
|
||||
if version[0] != 'v' {
|
||||
version = "v" + version
|
||||
}
|
||||
|
||||
split := strings.SplitN(version, ".", 3)
|
||||
|
||||
return strings.Join(split[:2], ".")
|
||||
}
|
||||
|
||||
func determineSHAforPR(ctx context.Context, prStr string) (string, error) {
|
||||
prNum, err := strconv.Atoi(prStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
client := github.NewClient(http.DefaultClient)
|
||||
|
||||
pr, _, err := client.PullRequests.Get(ctx, "go-gitea", "gitea", prNum)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if pr.Merged == nil || !*pr.Merged {
|
||||
return "", fmt.Errorf("PR #%d is not yet merged - cannot determine sha to backport", prNum)
|
||||
}
|
||||
|
||||
if pr.MergeCommitSHA != nil {
|
||||
return *pr.MergeCommitSHA, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func installSignals() (context.Context, context.CancelFunc) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
// install notify
|
||||
signalChannel := make(chan os.Signal, 1)
|
||||
|
||||
signal.Notify(
|
||||
signalChannel,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
)
|
||||
select {
|
||||
case <-signalChannel:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
cancel()
|
||||
signal.Reset()
|
||||
}()
|
||||
|
||||
return ctx, cancel
|
||||
}
|
||||
@@ -22,13 +22,11 @@ The environment variables should be of the form:
|
||||
|
||||
GITEA__SECTION_NAME__KEY_NAME
|
||||
|
||||
Note, SECTION_NAME in the notation above is case-insensitive.
|
||||
|
||||
Environment variables are usually restricted to a reduced character
|
||||
set "0-9A-Z_" - in order to allow the setting of sections with
|
||||
characters outside of that set, they should be escaped as following:
|
||||
"_0X2E_" for "." and "_0X2D_" for "-". The entire section and key names
|
||||
can be escaped as a UTF8 byte string if necessary. E.g. to configure:
|
||||
"_0X2E_" for ".". The entire section and key names can be escaped as
|
||||
a UTF8 byte string if necessary. E.g. to configure:
|
||||
|
||||
"""
|
||||
...
|
||||
@@ -42,6 +40,27 @@ You would set the environment variables: "GITEA__LOG_0x2E_CONSOLE__COLORIZE=fals
|
||||
and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found
|
||||
on the configuration cheat sheet.
|
||||
|
||||
To build locally, run:
|
||||
To plug this command in to the docker, you simply compile the provided go file using:
|
||||
|
||||
go build environment-to-ini.go
|
||||
|
||||
And copy the resulting `environment-to-ini` command to /app/gitea in the docker.
|
||||
|
||||
Apply the below patch to /etc/s6/gitea.setup to wire this in.
|
||||
|
||||
If you find this useful please comment on #7287
|
||||
|
||||
|
||||
diff --git a/docker/root/etc/s6/gitea/setup b/docker/root/etc/s6/gitea/setup
|
||||
index f87ce9115..565bfcba9 100755
|
||||
--- a/docker/root/etc/s6/gitea/setup
|
||||
+++ b/docker/root/etc/s6/gitea/setup
|
||||
@@ -44,6 +44,8 @@ if [ ! -f ${GITEA_CUSTOM}/conf/app.ini ]; then
|
||||
SECRET_KEY=${SECRET_KEY:-""} \
|
||||
envsubst < /etc/templates/app.ini > ${GITEA_CUSTOM}/conf/app.ini
|
||||
|
||||
+ /app/gitea/environment-to-ini -c ${GITEA_CUSTOM}/conf/app.ini
|
||||
+
|
||||
chown ${USER}:git ${GITEA_CUSTOM}/conf/app.ini
|
||||
fi
|
||||
|
||||
go build contrib/environment-to-ini/environment-to-ini.go
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user