Compare commits

..

8 Commits

Author SHA1 Message Date
Lunny Xiao 7a4bb23150
fix comment content 2019-11-14 09:39:37 +08:00
Lunny Xiao 58bc380724
fix typo 2019-11-14 09:33:19 +08:00
Lunny Xiao ac7c78d004
remove duplicated actions 2019-11-14 08:46:40 +08:00
Lunny Xiao 0e063de211
remove duplicated actions 2019-11-14 08:46:39 +08:00
Lunny Xiao 5067294f32
fix lint 2019-11-14 08:46:39 +08:00
Lunny Xiao da074675c6
remove unused comment 2019-11-14 08:46:39 +08:00
Lunny Xiao 7735d70d66
remove unnecessary code 2019-11-14 08:46:39 +08:00
Lunny Xiao 0f5de85f9a
refactor submit review 2019-11-14 08:46:36 +08:00
10342 changed files with 497039 additions and 1646334 deletions

View File

@ -1,9 +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"]
include_dir = ["cmd", "models", "modules", "options", "routers", "services", "templates"]

View File

@ -1,61 +1,44 @@
# The full repository name
repo: go-gitea/gitea
# Service type (gitea or github)
service: github
# Base URL for Gitea instance if using gitea service type (optional)
# Default: https://gitea.com
base-url:
# Changelog groups and which labeled PRs to add to each group
groups:
-
-
name: BREAKING
labels:
- kind/breaking
-
name: SECURITY
labels:
- kind/security
-
name: FEATURES
-
name: FEATURE
labels:
- kind/feature
-
name: API
name: BUGFIXES
labels:
- kind/api
-
name: ENHANCEMENTS
- kind/bug
-
name: ENHANCEMENT
labels:
- kind/enhancement
- kind/refactor
- kind/ui
-
name: BUGFIXES
name: SECURITY
labels:
- kind/bug
-
- kind/security
-
name: TESTING
labels:
- kind/testing
-
-
name: TRANSLATION
labels:
- kind/translation
-
-
name: BUILD
labels:
- kind/build
- kind/lint
-
-
name: DOCS
labels:
- kind/docs
-
-
name: MISC
default: true
# regex indicating which labels to skip for the changelog
skip-labels: skip-changelog|backport\/.+
default: true

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,30 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
[*.go]
indent_style = tab
indent_size = 8
[*.{tmpl,html}]
indent_style = tab
indent_size = 4
[*.less]
indent_style = space
indent_size = 4
[*.{yml,json}]
indent_style = space
indent_size = 2
tab_width = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
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]
[*.js]
indent_style = space
indent_size = 4
[Makefile]
indent_style = tab
[*.svg]
insert_final_newline = false
[*.md]
trim_trailing_whitespace = false

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
/public/js/semantic.dropdown.custom.js

452
.eslintrc
View File

@ -1,452 +1,28 @@
root: true
reportUnusedDisableDirectives: true
ignorePatterns:
- /web_src/js/vendor
- /templates/base/head.tmpl
- /templates/repo/activity.tmpl
- /templates/repo/view_file.tmpl
parserOptions:
sourceType: module
ecmaVersion: 2021
plugins:
- eslint-plugin-unicorn
- eslint-plugin-import
- eslint-plugin-vue
- eslint-plugin-html
extends:
- plugin:vue/recommended
- eslint:recommended
parserOptions:
ecmaVersion: 2015
env:
es2021: true
browser: true
jquery: true
es6: true
node: true
globals:
__webpack_public_path__: true
Clipboard: false
CodeMirror: false
Dropzone: false
emojify: false
SimpleMDE: false
Vue: false
Dropzone: false
u2fApi: false
settings:
html/html-extensions: [".tmpl"]
overrides:
- files: ["web_src/**/*.js", "web_src/**/*.vue", "templates/**/*.tmpl"]
env:
browser: true
jquery: true
node: false
- files: ["templates/**/*.tmpl"]
rules:
no-tabs: [0]
indent: [2, tab, {SwitchCase: 1}]
- 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]
- files: ["*.test.js"]
env:
jest: true
- files: ["*.config.js"]
rules:
import/no-unused-modules: [0]
hljs: false
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: [2, {ignoreExternal: true}]
import/no-default-export: [0]
import/no-deprecated: [0]
import/no-dynamic-require: [0]
import/no-extraneous-dependencies: [2]
import/no-import-module-exports: [0]
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-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}]
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]
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}}]
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-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]
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}]
no-unused-vars: [error, {args: all, argsIgnorePattern: ^_, varsIgnorePattern: ^_, ignoreRestSiblings: 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-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: [0]
unicorn/no-array-instanceof: [0]
unicorn/no-array-method-this-argument: [2]
unicorn/no-array-push-push: [2]
unicorn/no-console-spaces: [0]
unicorn/no-document-cookie: [2]
unicorn/no-fn-reference-in-iterator: [0]
unicorn/no-for-loop: [0]
unicorn/no-hex-escape: [0]
unicorn/no-keyword-prefix: [0]
unicorn/no-lonely-if: [2]
unicorn/no-nested-ternary: [0]
unicorn/no-new-array: [0]
unicorn/no-new-buffer: [0]
unicorn/no-null: [0]
unicorn/no-object-as-default-parameter: [2]
unicorn/no-process-exit: [0]
unicorn/no-reduce: [2]
unicorn/no-static-only-class: [2]
unicorn/no-this-assignment: [2]
unicorn/no-unreadable-array-destructuring: [0]
unicorn/no-unsafe-regex: [0]
unicorn/no-unused-properties: [2]
unicorn/no-useless-length-check: [2]
unicorn/no-useless-spread: [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-dataset: [2]
unicorn/prefer-date-now: [2]
unicorn/prefer-default-parameters: [0]
unicorn/prefer-event-key: [2]
unicorn/prefer-includes: [2]
unicorn/prefer-math-trunc: [2]
unicorn/prefer-modern-dom-apis: [0]
unicorn/prefer-module: [2]
unicorn/prefer-negative-index: [2]
unicorn/prefer-node-append: [0]
unicorn/prefer-node-protocol: [0]
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-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/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/throw-new-error: [2]
use-isnan: [2]
valid-typeof: [2, {requireStringLiterals: true}]
vars-on-top: [0]
vue/attributes-order: [0]
vue/component-definition-name-casing: [0]
vue/html-closing-bracket-spacing: [0]
vue/max-attributes-per-line: [0]
vue/one-component-per-file: [0]
wrap-iife: [2, inside]
wrap-regex: [0]
yield-star-spacing: [2, after]
yoda: [2, never]
no-var: [2]

13
.gitattributes vendored
View File

@ -1,7 +1,6 @@
* text=auto eol=lf
/vendor/** -text -eol linguist-vendored
/public/vendor/** -text -eol linguist-vendored
/templates/**/*.tmpl linguist-language=Handlebars
/.eslintrc linguist-language=YAML
/.stylelintrc linguist-language=YAML
/web_src/fomantic/build/** linguist-generated
conf/* linguist-vendored
docker/* linguist-vendored
options/* linguist-vendored
public/* linguist-vendored
scripts/* linguist-vendored
templates/* linguist-vendored

1
.github/FUNDING.yml vendored
View File

@ -1,2 +1 @@
open_collective: gitea
custom: https://www.bountysource.com/teams/gitea

View File

@ -2,20 +2,16 @@
<!--
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
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
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:
<!-- 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
@ -24,15 +20,10 @@
- Can you reproduce the bug at https://try.gitea.io:
- [ ] Yes (provide example URL)
- [ ] No
- [ ] Not relevant
- 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. -->
...

23
.github/lock.yml vendored
View File

@ -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

View File

@ -1,6 +1,6 @@
Please check the following:
1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for bug fixes.
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)

9
.github/stale.yml vendored
View File

@ -27,11 +27,9 @@ staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had recent activity.
I am here to help clear issues left open even if solved or waiting for more insight.
This issue will be closed if no further activity occurs during the next 2 weeks.
If the issue is still valid just add a comment to keep it alive.
Thank you for your contributions.
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs during the next 2 weeks. Thank you
for your contributions.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
@ -52,3 +50,4 @@ pulls:
closeComment: >
This pull request has been automatically closed because of inactivity.
You can re-open it if needed.

42
.gitignore vendored
View File

@ -32,29 +32,21 @@ _testmain.go
*coverage.out
coverage.all
cpu.out
/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
/custom
/data
/indexers
/log
@ -69,34 +61,14 @@ cpu.out
/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
/modules/indexer/issues/indexers
routers/repo/authorized_keys
/yarn.lock
/yarn-error.log
/npm-debug.log*
/public/js
/public/serviceworker.js
/public/css
/public/fonts
/public/img/webpack
/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
# Snapcraft
snap/.snapcraft/
@ -106,10 +78,4 @@ prime/
*.snap
*.snap-build
*_source.tar.bz2
.DS_Store
# Make evidence files
/.make_evidence
# Manpage
/man
.DS_Store

View File

@ -26,7 +26,7 @@ linters-settings:
gocritic:
disabled-checks:
- ifElseChain
- singleCaseSwitch # Every time this occurred in the code, there was no other way.
- singleCaseSwitch # Every time this occured in the code, there was no other way.
issues:
exclude-rules:
@ -70,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
@ -92,23 +95,3 @@ issues:
- 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."

View File

@ -1,5 +0,0 @@
/vendor
/public/vendor/plugins
/modules/options/bindata.go
/modules/public/bindata.go
/modules/templates/bindata.go

4
.npmrc
View File

@ -1,5 +1 @@
audit=false
fund=false
update-notifier=false
package-lock=true
save-exact=true

View File

@ -1,15 +1,11 @@
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
indentation: 4
no-descending-specificity: null
number-leading-zero: never
rule-empty-line-before: null
selector-pseudo-element-colon-notation: double
shorthand-property-no-redundant-values: true
selector-pseudo-element-colon-notation: null

File diff suppressed because it is too large Load Diff

View File

@ -3,18 +3,15 @@
## 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)
- [API v1](#api-v1)
- [Developer Certificate of Origin (DCO)](#developer-certificate-of-origin-dco)
- [Release Cycle](#release-cycle)
- [Maintainers](#maintainers)
@ -73,21 +70,23 @@ to make sure your changes don't cause regression elsewhere.
Here's how to run the test suite:
- code lint
- Install the correct version of the drone-cli package. As of this
writing, the correct drone-cli version is
[1.2.0](https://docs.drone.io/cli/install/).
- Ensure you have enough free disk space. You will need at least
15-20 Gb of free disk space to hold all of the containers drone
creates (a default AWS or GCE disk size won't work -- see
[#6243](https://github.com/go-gitea/gitea/issues/6243)).
- Change into the base directory of your copy of the gitea repository,
and run `drone exec --event pull_request`.
- At the moment `drone exec` doesn't support the Docker Toolbox on Windows 10
(see [drone-cli#135](https://github.com/drone/drone-cli/issues/135))
| | |
| :-------------------- | :---------------------------------------------------------------- |
|``make lint`` | lint everything (not suggest if you only change one type code) |
|``make lint-frontend`` | lint frontend files |
|``make lint-backend`` | lint backend files |
- run test code (Suggest run in linux)
| | |
| :------------------------------------- | :----------------------------------------------- |
|``make test[\#TestSpecificName]`` | run unit test |
|``make test-sqlite[\#TestSpecificName]``| run [integration](integrations) test for sqlite |
|[More detail message about integrations](integrations/README.md) |
The drone version, command line, and disk requirements do change over
time (see [#4053](https://github.com/go-gitea/gitea/issues/4053) and
[#6243](https://github.com/go-gitea/gitea/issues/6243)); if you
discover any issues, please feel free to send us a pull request to
update these instructions.
## Vendoring
@ -114,7 +113,15 @@ included in the next released version.
## Building Gitea
See the [hacking instructions](https://docs.gitea.io/en-us/hacking-on-gitea/).
Generally, the go build tools are installed as-needed in the `Makefile`.
An exception are the tools to build the CSS and images.
- To build CSS: Install [Node.js](https://nodejs.org/en/download/package-manager) at version 8.0 or above
with `npm` and then run `npm install` and `make css`.
- To build Images: ImageMagick, inkscape and zopflipng binaries must be
available in your `PATH` to run `make generate-images`.
For more details on how to generate files, build and test Gitea, see the [hacking instructions](https://docs.gitea.io/en-us/hacking-on-gitea/)
## Code review
@ -159,8 +166,8 @@ import (
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:
- **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. 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.
@ -170,47 +177,6 @@ To maintain understandable code and avoid circular dependencies it is important
- **templates:** Golang templates for generating the html output.
- **vendor:** External code that Gitea depends on.
## API v1
The API is documented by [swagger](http://try.gitea.io/api/swagger) and is based on [GitHub API v3](https://developer.github.com/v3/).
Thus, Gitea´s API should use the same endpoints and fields as GitHub´s API as far as possible, unless there are good reasons to deviate.
If Gitea provides functionality that GitHub does not, a new endpoint can be created.
If information is provided by Gitea that is not provided by the GitHub API, a new field can be used that doesn't collide with any GitHub fields.
Updating an existing API should not remove existing fields unless there is a really good reason to do so.
The same applies to status responses. If you notice a problem, feel free to leave a comment in the code for future refactoring to APIv2 (which is currently not planned).
All expected results (errors, success, fail messages) should be documented
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L319-L327)).
All JSON input types must be defined as a struct in [modules/structs/](modules/structs/)
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L76-L91))
and referenced in
[routers/api/v1/swagger/options.go](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/options.go).
They can then be used like the following:
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L318)).
All JSON responses must be defined as a struct in [modules/structs/](modules/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))
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 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))
## Developer Certificate of Origin (DCO)
@ -232,18 +198,18 @@ 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
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
@ -299,31 +265,21 @@ and lead the development of Gitea.
To honor the past owners, here's the history of the owners and the time
they served:
* 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>
* 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>
* 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>
* 2018-01-01 ~ 2018-12-31
* [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
* [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) <matti@mdranta.net>
## Versions
Gitea has the `master` branch as a tip branch and has version branches
@ -355,7 +311,7 @@ be reviewed by two maintainers and must pass the automatic tests.
Code that you contribute should use the standard copyright header:
```
// Copyright 2020 The Gitea Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
```

View File

@ -1,18 +1,17 @@
###################################
#Build stage
FROM golang:1.17-alpine3.13 AS build-env
FROM golang:1.13-alpine3.10 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
ENV TAGS "bindata $TAGS"
#Build deps
RUN apk --no-cache add build-base git nodejs npm
RUN apk --no-cache add build-base git
#Setup repo
COPY . ${GOPATH}/src/code.gitea.io/gitea
@ -20,12 +19,9 @@ 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
&& make clean generate build
# Begin env-to-ini build
RUN go build contrib/environment-to-ini/environment-to-ini.go
FROM alpine:3.13
FROM alpine:3.10
LABEL maintainer="maintainers@gitea.io"
EXPOSE 22 3000
@ -41,7 +37,7 @@ RUN apk --no-cache add \
s6 \
sqlite \
su-exec \
gnupg
tzdata
RUN addgroup \
-S -g 1000 \
@ -53,7 +49,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
@ -65,5 +61,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
RUN ln -s /app/gitea/gitea /usr/local/bin/gitea

View File

@ -1,74 +0,0 @@
###################################
#Build stage
FROM golang:1.17-alpine3.13 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.13
LABEL maintainer="maintainers@gitea.io"
EXPOSE 2222 3000
RUN apk --no-cache add \
bash \
ca-certificates \
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 /usr/local/bin/gitea
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
#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 (usefull 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/local/bin/docker-entrypoint.sh"]
CMD []

View File

@ -19,7 +19,7 @@ Lauris Bukšis-Haberkorns <lauris@nix.lv> (@lafriks)
Jonas Östanbäck <jonas.ostanback@gmail.com> (@cez81)
David Schneiderbauer <dschneiderbauer@gmail.com> (@daviian)
Peter Žeby <morlinest@gmail.com> (@morlinest)
Matti Ranta <techknowlogick@gitea.io> (@techknowlogick)
Matti Ranta <matti@mdranta.net> (@techknowlogick)
Jonas Franz <info@jonasfranz.software> (@jonasfranz)
Alexey Terentyev <axifnx@gmail.com> (@axifive)
Lanre Adelowo <yo@lanre.wtf> (@adelowo)
@ -35,12 +35,3 @@ Guillermo Prandi <gitea.maint@mailfilter.com.ar> (@guillep2k)
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)

686
Makefile
View File

@ -1,75 +1,31 @@
ifeq ($(USE_REPO_TEST_DIR),1)
# This rule replaces the whole Makefile when we're trying to use /tmp repository temporary files
location = $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
self := $(location)
%:
@tmpdir=`mktemp --tmpdir -d` ; \
echo Using temporary directory $$tmpdir for test repositories ; \
USE_REPO_TEST_DIR= $(MAKE) -f $(self) --no-print-directory REPO_TEST_DIR=$$tmpdir/ $@ ; \
STATUS=$$? ; rm -r "$$tmpdir" ; exit $$STATUS
else
# This is the "normal" part of the Makefile
DIST := dist
DIST_DIRS := $(DIST)/binaries $(DIST)/release
IMPORT := code.gitea.io/gitea
export GO111MODULE=on
export GO111MODULE=off
GO ?= go
SED_INPLACE := sed -i
SHASUM ?= shasum -a 256
HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
COMMA := ,
XGO_VERSION := go-1.17.x
MIN_GO_VERSION := 001016000
MIN_NODE_VERSION := 012017000
DOCKER_IMAGE ?= gitea/gitea
DOCKER_TAG ?= latest
DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)
ifeq ($(HAS_GO), GO)
GOPATH ?= $(shell $(GO) env GOPATH)
export PATH := $(GOPATH)/bin:$(PATH)
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
endif
export PATH := $($(GO) env GOPATH)/bin:$(PATH)
ifeq ($(OS), Windows_NT)
GOFLAGS := -v -buildmode=exe
EXECUTABLE ?= gitea.exe
else ifeq ($(OS), Windows)
GOFLAGS := -v -buildmode=exe
EXECUTABLE ?= gitea.exe
else
GOFLAGS := -v
EXECUTABLE ?= gitea
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
SED_INPLACE := sed -i ''
endif
endif
ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu)
SED_INPLACE := sed -i
else
SED_INPLACE := sed -i ''
endif
BINDATA := modules/{options,public,templates}/bindata.go
GOFILES := $(shell find . -name "*.go" -type f ! -path "./vendor/*" ! -path "*/bindata.go")
GOFMT ?= gofmt -s
GOFLAGS := -v
EXTRA_GOFLAGS ?=
MAKE_VERSION := $(shell $(MAKE) -v | head -n 1)
MAKE_EVIDENCE_DIR := .make_evidence
ifeq ($(RACE_ENABLED),true)
GOFLAGS += -race
GOTESTFLAGS += -race
endif
STORED_VERSION_FILE := VERSION
MAKE_VERSION := $(shell make -v | head -n 1)
ifneq ($(DRONE_TAG),)
VERSION ?= $(subst v,,$(DRONE_TAG))
@ -78,62 +34,25 @@ else
ifneq ($(DRONE_BRANCH),)
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
else
VERSION ?= main
endif
STORED_VERSION=$(shell cat $(STORED_VERSION_FILE) 2>/dev/null)
ifneq ($(STORED_VERSION),)
GITEA_VERSION ?= $(STORED_VERSION)
else
GITEA_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
VERSION ?= master
endif
GITEA_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
endif
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/models/migrations code.gitea.io/gitea/integrations/migration-test code.gitea.io/gitea/integrations,$(shell $(GO) list -mod=vendor ./... | grep -v /vendor/))
FOMANTIC_WORK_DIR := web_src/fomantic
WEBPACK_SOURCES := $(shell find web_src/js web_src/less -type f)
WEBPACK_CONFIGS := webpack.config.js
WEBPACK_DEST := public/js/index.js public/css/index.css
WEBPACK_DEST_ENTRIES := public/js public/css public/fonts public/img/webpack public/serviceworker.js
BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
SVG_DEST_DIR := public/img/svg
AIR_TMP_DIR := .air
PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations/migration-test,$(filter-out code.gitea.io/gitea/integrations,$(shell GO111MODULE=on $(GO) list -mod=vendor ./... | grep -v /vendor/)))
SOURCES ?= $(shell find . -name "*.go" -type f)
TAGS ?=
TAGS_SPLIT := $(subst $(COMMA), ,$(TAGS))
TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
TEST_TAGS ?= sqlite sqlite_unlock_notify
TMPDIR := $(shell mktemp -d 2>/dev/null || mktemp -d -t 'gitea-temp')
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMANTIC_WORK_DIR)/node_modules $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR)
GO_DIRS := cmd integrations models modules routers build services vendor tools
GO_SOURCES := $(wildcard *.go)
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" -not -path modules/options/bindata.go -not -path modules/public/bindata.go -not -path modules/templates/bindata.go)
ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
GO_SOURCES += $(BINDATA_DEST)
endif
GO_SOURCES_OWN := $(filter-out vendor/% %/bindata.go, $(GO_SOURCES))
#To update swagger use: GO111MODULE=on go get -u github.com/go-swagger/go-swagger/cmd/swagger
SWAGGER := $(GO) run -mod=vendor github.com/go-swagger/go-swagger/cmd/swagger
#To update swagger use: GO111MODULE=on go get -u github.com/go-swagger/go-swagger/cmd/swagger@v0.20.1
SWAGGER := GO111MODULE=on $(GO) run -mod=vendor github.com/go-swagger/go-swagger/cmd/swagger
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|g
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|"basePath": "/api/v1"|g
SWAGGER_EXCLUDE := code.gitea.io/sdk
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl}}/api/v1"|g
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl}}/api/v1"|"basePath": "/api/v1"|g
SWAGGER_NEWLINE_COMMAND := -e '$$a\'
TEST_MYSQL_HOST ?= mysql:3306
@ -148,116 +67,43 @@ TEST_PGSQL_HOST ?= pgsql:5432
TEST_PGSQL_DBNAME ?= testgitea
TEST_PGSQL_USERNAME ?= postgres
TEST_PGSQL_PASSWORD ?= postgres
TEST_PGSQL_SCHEMA ?= gtestschema
TEST_MSSQL_HOST ?= mssql:1433
TEST_MSSQL_DBNAME ?= gitea
TEST_MSSQL_USERNAME ?= sa
TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1
# $(call strip-suffix,filename)
strip-suffix = $(firstword $(subst ., ,$(1)))
.PHONY: all
all: build
.PHONY: help
help:
@echo "Make Routines:"
@echo " - \"\" equivalent to \"build\""
@echo " - build build everything"
@echo " - frontend build frontend files"
@echo " - backend build backend files"
@echo " - watch watch everything and continuously rebuild"
@echo " - watch-frontend watch frontend files and continuously rebuild"
@echo " - watch-backend watch backend files and continuously rebuild"
@echo " - clean delete backend and integration files"
@echo " - clean-all delete backend, frontend and integration files"
@echo " - lint lint everything"
@echo " - lint-frontend lint frontend files"
@echo " - lint-backend lint backend files"
@echo " - checks run various consistency checks"
@echo " - checks-frontend check frontend files"
@echo " - checks-backend check backend files"
@echo " - test test everything"
@echo " - test-frontend test frontend files"
@echo " - test-backend test backend files"
@echo " - webpack build webpack files"
@echo " - svg build svg files"
@echo " - fomantic build fomantic files"
@echo " - generate run \"go generate\""
@echo " - fmt format the Go code"
@echo " - generate-license update license files"
@echo " - generate-gitignore update gitignore files"
@echo " - generate-manpage generate manpage"
@echo " - generate-swagger generate the swagger spec from code comments"
@echo " - swagger-validate check if the swagger spec is valid"
@echo " - golangci-lint run golangci-lint linter"
@echo " - revive run revive linter"
@echo " - misspell check for misspellings"
@echo " - vet examines Go source code and reports suspicious constructs"
@echo " - test[\#TestSpecificName] run unit test"
@echo " - test-sqlite[\#TestSpecificName] run integration test for sqlite"
@echo " - pr#<index> build and start gitea from a PR with integration test data loaded"
.PHONY: go-check
go-check:
$(eval GO_VERSION := $(shell printf "%03d%03d%03d" $(shell $(GO) version | grep -Eo '[0-9]+\.[0-9.]+' | tr '.' ' ');))
@if [ "$(GO_VERSION)" -lt "$(MIN_GO_VERSION)" ]; then \
echo "Gitea requires Go 1.16 or greater to build. You can get it at https://golang.org/dl/"; \
exit 1; \
fi
.PHONY: git-check
git-check:
@if git lfs >/dev/null 2>&1 ; then : ; else \
echo "Gitea requires git with lfs support to run tests." ; \
exit 1; \
fi
.PHONY: node-check
node-check:
$(eval NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v | cut -c2- | tr '.' ' ');))
$(eval MIN_NODE_VER_FMT := $(shell printf "%g.%g.%g" $(shell echo $(MIN_NODE_VERSION) | grep -o ...)))
$(eval NPM_MISSING := $(shell hash npm > /dev/null 2>&1 || echo 1))
@if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" -o "$(NPM_MISSING)" = "1" ]; then \
echo "Gitea requires Node.js $(MIN_NODE_VER_FMT) or greater and npm to build. You can get it at https://nodejs.org/en/download/"; \
exit 1; \
fi
.PHONY: clean-all
clean-all: clean
rm -rf $(WEBPACK_DEST_ENTRIES) node_modules
include docker/Makefile
.PHONY: clean
clean:
$(GO) clean -i ./...
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) $(BINDATA_HASH) \
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA) \
integrations*.test \
integrations/gitea-integration-pgsql/ integrations/gitea-integration-mysql/ integrations/gitea-integration-mysql8/ integrations/gitea-integration-sqlite/ \
integrations/gitea-integration-mssql/ integrations/indexers-mysql/ integrations/indexers-mysql8/ integrations/indexers-pgsql integrations/indexers-sqlite \
integrations/indexers-mssql integrations/mysql.ini integrations/mysql8.ini integrations/pgsql.ini integrations/mssql.ini man/
integrations/indexers-mssql integrations/mysql.ini integrations/mysql8.ini integrations/pgsql.ini integrations/mssql.ini
.PHONY: fmt
fmt:
@echo "Running go fmt..."
@$(GOFMT) -w $(GO_SOURCES_OWN)
$(GOFMT) -w $(GOFILES)
.PHONY: vet
vet:
@echo "Running go vet..."
@$(GO) vet $(GO_PACKAGES)
@GOOS= GOARCH= $(GO) build -mod=vendor code.gitea.io/gitea-vet
@$(GO) vet -vettool=gitea-vet $(GO_PACKAGES)
$(GO) vet $(PACKAGES)
.PHONY: $(TAGS_EVIDENCE)
$(TAGS_EVIDENCE):
@mkdir -p $(MAKE_EVIDENCE_DIR)
@echo "$(TAGS)" > $(TAGS_EVIDENCE)
ifneq "$(TAGS)" "$(shell cat $(TAGS_EVIDENCE) 2>/dev/null)"
TAGS_PREREQ := $(TAGS_EVIDENCE)
endif
.PHONY: generate
generate:
GO111MODULE=on $(GO) generate -mod=vendor $(PACKAGES)
.PHONY: generate-swagger
generate-swagger:
$(SWAGGER) generate spec -x "$(SWAGGER_EXCLUDE)" -o './$(SWAGGER_SPEC)'
$(SWAGGER) generate spec -o './$(SWAGGER_SPEC)'
$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
$(SED_INPLACE) $(SWAGGER_NEWLINE_COMMAND) './$(SWAGGER_SPEC)'
@ -268,7 +114,7 @@ swagger-check: generate-swagger
echo "Please run 'make generate-swagger' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi
fi;
.PHONY: swagger-validate
swagger-validate:
@ -279,124 +125,67 @@ swagger-validate:
.PHONY: errcheck
errcheck:
@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
GO111MODULE=off $(GO) get -u github.com/kisielk/errcheck; \
$(GO) get -u github.com/kisielk/errcheck; \
fi
@echo "Running errcheck..."
@errcheck $(GO_PACKAGES)
errcheck $(PACKAGES)
.PHONY: lint
lint:
@echo 'make lint is depricated. Use "make revive" if you want to use the old lint tool, or "make golangci-lint" to run a complete code check.'
.PHONY: revive
revive:
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
GO111MODULE=off $(GO) get -u github.com/mgechev/revive; \
$(GO) get -u github.com/mgechev/revive; \
fi
@revive -config .revive.toml -exclude=./vendor/... ./...
revive -config .revive.toml -exclude=./vendor/... ./... || exit 1
.PHONY: misspell-check
misspell-check:
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
GO111MODULE=off $(GO) get -u github.com/client9/misspell/cmd/misspell; \
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
fi
@echo "Running misspell-check..."
@misspell -error -i unknwon $(GO_SOURCES_OWN)
misspell -error -i unknwon,destory $(GOFILES)
.PHONY: misspell
misspell:
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
GO111MODULE=off $(GO) get -u github.com/client9/misspell/cmd/misspell; \
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
fi
@echo "Running go misspell..."
@misspell -w -i unknwon $(GO_SOURCES_OWN)
misspell -w -i unknwon $(GOFILES)
.PHONY: fmt-check
fmt-check:
# get all go files and run go fmt on them
@diff=$$($(GOFMT) -d $(GO_SOURCES_OWN)); \
@diff=$$($(GOFMT) -d $(GOFILES)); \
if [ -n "$$diff" ]; then \
echo "Please run 'make fmt' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi
.PHONY: checks
checks: checks-frontend checks-backend
.PHONY: checks-frontend
checks-frontend: svg-check
.PHONY: checks-backend
checks-backend: misspell-check test-vendor swagger-check swagger-validate
.PHONY: lint
lint: lint-frontend lint-backend
.PHONY: lint-frontend
lint-frontend: node_modules
npx eslint --color --max-warnings=0 web_src/js build templates *.config.js
npx stylelint --color --max-warnings=0 web_src/less
npx editorconfig-checker templates
.PHONY: lint-backend
lint-backend: golangci-lint revive vet
.PHONY: watch
watch:
bash tools/watch.sh
.PHONY: watch-frontend
watch-frontend: node-check node_modules
rm -rf $(WEBPACK_DEST_ENTRIES)
NODE_ENV=development npx webpack --watch --progress
.PHONY: watch-backend
watch-backend: go-check
@hash air > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
GO111MODULE=off $(GO) get -u github.com/cosmtrek/air; \
fi
air -c .air.conf
fi;
.PHONY: test
test: test-frontend test-backend
.PHONY: test-backend
test-backend:
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
@$(GO) test $(GOTESTFLAGS) -mod=vendor -tags='$(TEST_TAGS)' $(GO_PACKAGES)
.PHONY: test-frontend
test-frontend: node_modules
@NODE_OPTIONS="--experimental-vm-modules --no-warnings" npx jest --color
.PHONY: test-check
test-check:
@echo "Running test-check...";
@diff=$$(git status -s); \
if [ -n "$$diff" ]; then \
echo "make test-backend has changed files in the source tree:"; \
echo "$${diff}"; \
echo "You should change the tests to create these files in a temporary directory."; \
echo "Do not simply add these files to .gitignore"; \
exit 1; \
fi
test:
GO111MODULE=on $(GO) test -mod=vendor -tags='sqlite sqlite_unlock_notify' $(PACKAGES)
.PHONY: test\#%
test\#%:
@echo "Running go test with -tags '$(TEST_TAGS)'..."
@$(GO) test -mod=vendor $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_PACKAGES)
GO111MODULE=on $(GO) test -mod=vendor -tags='sqlite sqlite_unlock_notify' -run $* $(PACKAGES)
.PHONY: coverage
coverage:
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out
GO111MODULE=on $(GO) run -mod=vendor build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all || (echo "gocovmerge failed"; echo "integration.coverage.out"; cat integration.coverage.out; echo "coverage.out"; cat coverage.out; exit 1)
@hash gocovmerge > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u github.com/wadey/gocovmerge; \
fi
gocovmerge integration.coverage.out $(shell find . -type f -name "coverage.out") > coverage.all;\
.PHONY: unit-test-coverage
unit-test-coverage:
@echo "Running unit-test-coverage $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
@$(GO) test $(GOTESTFLAGS) -mod=vendor -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
$(GO) test -tags='sqlite sqlite_unlock_notify' -cover -coverprofile coverage.out $(PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
.PHONY: vendor
vendor:
$(GO) mod tidy && $(GO) mod vendor
GO111MODULE=on $(GO) mod tidy && GO111MODULE=on $(GO) mod vendor
.PHONY: test-vendor
test-vendor: vendor
@ -405,329 +194,285 @@ test-vendor: vendor
echo "Please run 'make vendor' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi
generate-ini-sqlite:
sed -e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
integrations/sqlite.ini.tmpl > integrations/sqlite.ini
fi;
.PHONY: test-sqlite
test-sqlite: integrations.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test
test-sqlite: integrations.sqlite.test
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test
.PHONY: test-sqlite\#%
test-sqlite\#%: integrations.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test -test.run $(subst .,/,$*)
test-sqlite\#%: integrations.sqlite.test
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test -test.run $*
.PHONY: test-sqlite-migration
test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.sqlite.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test
test-sqlite-migration: migrations.sqlite.test
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/sqlite.ini ./migrations.sqlite.test
generate-ini-mysql:
sed -e 's|{{TEST_MYSQL_HOST}}|${TEST_MYSQL_HOST}|g' \
-e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \
-e 's|{{TEST_MYSQL_USERNAME}}|${TEST_MYSQL_USERNAME}|g' \
-e 's|{{TEST_MYSQL_PASSWORD}}|${TEST_MYSQL_PASSWORD}|g' \
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
integrations/mysql.ini.tmpl > integrations/mysql.ini
.PHONY: test-mysql
test-mysql: integrations.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./integrations.mysql.test
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.mysql.test
.PHONY: test-mysql\#%
test-mysql\#%: integrations.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./integrations.mysql.test -test.run $(subst .,/,$*)
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.mysql.test -test.run $*
.PHONY: test-mysql-migration
test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./migrations.mysql.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./migrations.individual.mysql.test
test-mysql-migration: migrations.mysql.test generate-ini-mysql
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./migrations.mysql.test
generate-ini-mysql8:
sed -e 's|{{TEST_MYSQL8_HOST}}|${TEST_MYSQL8_HOST}|g' \
-e 's|{{TEST_MYSQL8_DBNAME}}|${TEST_MYSQL8_DBNAME}|g' \
-e 's|{{TEST_MYSQL8_USERNAME}}|${TEST_MYSQL8_USERNAME}|g' \
-e 's|{{TEST_MYSQL8_PASSWORD}}|${TEST_MYSQL8_PASSWORD}|g' \
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
integrations/mysql8.ini.tmpl > integrations/mysql8.ini
.PHONY: test-mysql8
test-mysql8: integrations.mysql8.test generate-ini-mysql8
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql8.ini ./integrations.mysql8.test
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql8.ini ./integrations.mysql8.test
.PHONY: test-mysql8\#%
test-mysql8\#%: integrations.mysql8.test generate-ini-mysql8
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql8.ini ./integrations.mysql8.test -test.run $(subst .,/,$*)
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql8.ini ./integrations.mysql8.test -test.run $*
.PHONY: test-mysql8-migration
test-mysql8-migration: migrations.mysql8.test migrations.individual.mysql8.test generate-ini-mysql8
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql8.ini ./migrations.mysql8.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql8.ini ./migrations.individual.mysql8.test
test-mysql8-migration: migrations.mysql8.test generate-ini-mysql8
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql8.ini ./migrations.mysql8.test
generate-ini-pgsql:
sed -e 's|{{TEST_PGSQL_HOST}}|${TEST_PGSQL_HOST}|g' \
-e 's|{{TEST_PGSQL_DBNAME}}|${TEST_PGSQL_DBNAME}|g' \
-e 's|{{TEST_PGSQL_USERNAME}}|${TEST_PGSQL_USERNAME}|g' \
-e 's|{{TEST_PGSQL_PASSWORD}}|${TEST_PGSQL_PASSWORD}|g' \
-e 's|{{TEST_PGSQL_SCHEMA}}|${TEST_PGSQL_SCHEMA}|g' \
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
integrations/pgsql.ini.tmpl > integrations/pgsql.ini
.PHONY: test-pgsql
test-pgsql: integrations.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/pgsql.ini ./integrations.pgsql.test
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.pgsql.test
.PHONY: test-pgsql\#%
test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/pgsql.ini ./integrations.pgsql.test -test.run $(subst .,/,$*)
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.pgsql.test -test.run $*
.PHONY: test-pgsql-migration
test-pgsql-migration: migrations.pgsql.test migrations.individual.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/pgsql.ini ./migrations.pgsql.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/pgsql.ini ./migrations.individual.pgsql.test
test-pgsql-migration: migrations.pgsql.test generate-ini-pgsql
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./migrations.pgsql.test
generate-ini-mssql:
sed -e 's|{{TEST_MSSQL_HOST}}|${TEST_MSSQL_HOST}|g' \
-e 's|{{TEST_MSSQL_DBNAME}}|${TEST_MSSQL_DBNAME}|g' \
-e 's|{{TEST_MSSQL_USERNAME}}|${TEST_MSSQL_USERNAME}|g' \
-e 's|{{TEST_MSSQL_PASSWORD}}|${TEST_MSSQL_PASSWORD}|g' \
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
integrations/mssql.ini.tmpl > integrations/mssql.ini
.PHONY: test-mssql
test-mssql: integrations.mssql.test generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mssql.ini ./integrations.mssql.test
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mssql.ini ./integrations.mssql.test
.PHONY: test-mssql\#%
test-mssql\#%: integrations.mssql.test generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mssql.ini ./integrations.mssql.test -test.run $(subst .,/,$*)
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mssql.ini ./integrations.mssql.test -test.run $*
.PHONY: test-mssql-migration
test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mssql.ini ./migrations.mssql.test -test.failfast
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mssql.ini ./migrations.individual.mssql.test -test.failfast
test-mssql-migration: migrations.mssql.test generate-ini-mssql
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mssql.ini ./migrations.mssql.test
.PHONY: bench-sqlite
bench-sqlite: integrations.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
bench-sqlite: integrations.sqlite.test
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
.PHONY: bench-mysql
bench-mysql: integrations.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./integrations.mysql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.mysql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
.PHONY: bench-mssql
bench-mssql: integrations.mssql.test generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mssql.ini ./integrations.mssql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mssql.ini ./integrations.mssql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
.PHONY: bench-pgsql
bench-pgsql: integrations.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/pgsql.ini ./integrations.pgsql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.pgsql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
.PHONY: integration-test-coverage
integration-test-coverage: integrations.cover.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out
GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out
integrations.mysql.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.mysql.test
integrations.mysql.test: $(SOURCES)
GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.mysql.test
integrations.mysql8.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.mysql8.test
integrations.mysql8.test: $(SOURCES)
GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.mysql8.test
integrations.pgsql.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.pgsql.test
integrations.pgsql.test: $(SOURCES)
GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.pgsql.test
integrations.mssql.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.mssql.test
integrations.mssql.test: $(SOURCES)
GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.mssql.test
integrations.sqlite.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.sqlite.test -tags '$(TEST_TAGS)'
integrations.sqlite.test: $(SOURCES)
GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.sqlite.test -tags 'sqlite sqlite_unlock_notify'
integrations.cover.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -coverpkg $(shell echo $(GO_PACKAGES) | tr ' ' ',') -o integrations.cover.test
integrations.cover.test: $(SOURCES)
GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -coverpkg $(shell echo $(PACKAGES) | tr ' ' ',') -o integrations.cover.test
.PHONY: migrations.mysql.test
migrations.mysql.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations/migration-test -o migrations.mysql.test
migrations.mysql.test: $(SOURCES)
$(GO) test -c code.gitea.io/gitea/integrations/migration-test -o migrations.mysql.test
.PHONY: migrations.mysql8.test
migrations.mysql8.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations/migration-test -o migrations.mysql8.test
migrations.mysql8.test: $(SOURCES)
$(GO) test -c code.gitea.io/gitea/integrations/migration-test -o migrations.mysql8.test
.PHONY: migrations.pgsql.test
migrations.pgsql.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations/migration-test -o migrations.pgsql.test
migrations.pgsql.test: $(SOURCES)
$(GO) test -c code.gitea.io/gitea/integrations/migration-test -o migrations.pgsql.test
.PHONY: migrations.mssql.test
migrations.mssql.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations/migration-test -o migrations.mssql.test
migrations.mssql.test: $(SOURCES)
$(GO) test -c code.gitea.io/gitea/integrations/migration-test -o migrations.mssql.test
.PHONY: migrations.sqlite.test
migrations.sqlite.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)'
.PHONY: migrations.individual.mysql.test
migrations.individual.mysql.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.mysql.test
.PHONY: migrations.individual.mysql8.test
migrations.individual.mysql8.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.mysql8.test
.PHONY: migrations.individual.pgsql.test
migrations.individual.pgsql.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.pgsql.test
.PHONY: migrations.individual.mssql.test
migrations.individual.mssql.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.mssql.test
.PHONY: migrations.individual.sqlite.test
migrations.individual.sqlite.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.sqlite.test -tags '$(TEST_TAGS)'
migrations.sqlite.test: $(SOURCES)
$(GO) test -c code.gitea.io/gitea/integrations/migration-test -o migrations.sqlite.test -tags 'sqlite sqlite_unlock_notify'
.PHONY: check
check: test
.PHONY: install $(TAGS_PREREQ)
.PHONY: install
install: $(wildcard *.go)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
$(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
.PHONY: build
build: frontend backend
build: $(EXECUTABLE)
.PHONY: frontend
frontend: $(WEBPACK_DEST)
.PHONY: backend
backend: go-check generate $(EXECUTABLE)
.PHONY: generate
generate: $(TAGS_PREREQ)
@echo "Running go generate..."
@CC= GOOS= GOARCH= $(GO) generate -mod=vendor -tags '$(TAGS)' $(GO_PACKAGES)
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build -mod=vendor $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
$(EXECUTABLE): $(SOURCES)
GO111MODULE=on $(GO) build -mod=vendor $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
.PHONY: release
release: frontend generate release-windows release-linux release-darwin release-copy release-compress release-sources release-docs release-check
release: release-dirs release-windows release-linux release-darwin release-copy release-compress release-check
$(DIST_DIRS):
mkdir -p $(DIST_DIRS)
.PHONY: release-dirs
release-dirs:
mkdir -p $(DIST)/binaries $(DIST)/release
.PHONY: release-windows
release-windows: | $(DIST_DIRS)
release-windows:
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) install src.techknowlogick.com/xgo@latest; \
$(GO) get -u src.techknowlogick.com/xgo; \
fi
CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
ifeq (,$(findstring gogit,$(TAGS)))
CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
endif
xgo -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
ifeq ($(CI),drone)
cp /build/* $(DIST)/binaries
endif
.PHONY: release-linux
release-linux: | $(DIST_DIRS)
release-linux:
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) install src.techknowlogick.com/xgo@latest; \
$(GO) get -u src.techknowlogick.com/xgo; \
fi
CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
xgo -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/mips64le,linux/mips,linux/mipsle' -out gitea-$(VERSION) .
ifeq ($(CI),drone)
cp /build/* $(DIST)/binaries
endif
.PHONY: release-darwin
release-darwin: | $(DIST_DIRS)
release-darwin:
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) install src.techknowlogick.com/xgo@latest; \
$(GO) get -u src.techknowlogick.com/xgo; \
fi
CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
xgo -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out gitea-$(VERSION) .
ifeq ($(CI),drone)
cp /build/* $(DIST)/binaries
endif
.PHONY: release-copy
release-copy: | $(DIST_DIRS)
release-copy:
cd $(DIST); for file in `find /build -type f -name "*"`; do cp $${file} ./release/; done;
.PHONY: release-check
release-check: | $(DIST_DIRS)
release-check:
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "checksumming $${file}" && $(SHASUM) `echo $${file} | sed 's/^..//'` > $${file}.sha256; done;
.PHONY: release-compress
release-compress: | $(DIST_DIRS)
release-compress:
@hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
GO111MODULE=off $(GO) get -u github.com/ulikunitz/xz/cmd/gxz; \
$(GO) get -u github.com/ulikunitz/xz/cmd/gxz; \
fi
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done;
.PHONY: release-sources
release-sources: | $(DIST_DIRS)
echo $(VERSION) > $(STORED_VERSION_FILE)
# bsdtar needs a ^ to prevent matching subdirectories
$(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./)
tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) -czf $(DIST)/release/gitea-src-$(VERSION).tar.gz .
rm -f $(STORED_VERSION_FILE)
npm-check:
@hash npm > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
echo "Please install Node.js 8.x or greater with npm"; \
exit 1; \
fi;
@hash npx > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
echo "Please install Node.js 8.x or greater with npm"; \
exit 1; \
fi;
.PHONY: release-docs
release-docs: | $(DIST_DIRS) docs
tar -czf $(DIST)/release/gitea-docs-$(VERSION).tar.gz -C ./docs/public .
.PHONY: docs
docs:
@hash hugo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
curl -sL https://github.com/gohugoio/hugo/releases/download/v0.74.3/hugo_0.74.3_Linux-64bit.tar.gz | tar zxf - -C /tmp && mv /tmp/hugo /usr/bin/hugo && chmod +x /usr/bin/hugo; \
fi
cd docs; make trans-copy clean build-offline;
node_modules: package-lock.json
.PHONY: npm
npm: npm-check
npm install --no-save
@touch node_modules
.PHONY: npm-update
npm-update: node-check | node_modules
npm-update: npm-check
npx updates -cu
rm -rf node_modules package-lock.json
npm install --package-lock
@touch node_modules
.PHONY: fomantic
fomantic:
rm -rf $(FOMANTIC_WORK_DIR)/build
cd $(FOMANTIC_WORK_DIR) && npm install --no-save
cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
cp -f web_src/js/vendor/dropdown.js $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/definitions/modules
cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build
rm -f $(FOMANTIC_WORK_DIR)/build/*.min.*
.PHONY: webpack
webpack: $(WEBPACK_DEST)
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json
@$(MAKE) -s node-check node_modules
rm -rf $(WEBPACK_DEST_ENTRIES)
.PHONY: js
js: npm
npx eslint web_src/js webpack.config.js
npx webpack
@touch $(WEBPACK_DEST)
.PHONY: svg
svg: node-check | node_modules
rm -rf $(SVG_DEST_DIR)
node build/generate-svg.js
.PHONY: css
css: npm
npx stylelint web_src/less
npx lessc --clean-css="--s0 -b" web_src/less/index.less public/css/index.css
$(foreach file, $(filter-out web_src/less/themes/_base.less, $(wildcard web_src/less/themes/*)),npx lessc --clean-css="--s0 -b" web_src/less/themes/$(notdir $(file)) > public/css/theme-$(notdir $(call strip-suffix,$(file))).css;)
npx postcss --use autoprefixer --no-map --replace public/css/*
.PHONY: svg-check
svg-check: svg
@git add $(SVG_DEST_DIR)
@diff=$$(git diff --cached $(SVG_DEST_DIR)); \
if [ -n "$$diff" ]; then \
echo "Please run 'make svg' and 'git add $(SVG_DEST_DIR)' and commit the result:"; \
@diff=$$(git diff public/css/*); \
if ([ -n "$$CI" ] && [ -n "$$diff" ]); then \
echo "Generated files in public/css have changed, please commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi
fi;
.PHONY: javascripts
javascripts:
echo "'make javascripts' is deprecated, please use 'make js'"
$(MAKE) js
.PHONY: stylesheets-check
stylesheets-check:
echo "'make stylesheets-check' is deprecated, please use 'make css'"
$(MAKE) css
.PHONY: generate-stylesheets
generate-stylesheets:
echo "'make generate-stylesheets' is deprecated, please use 'make css'"
$(MAKE) css
.PHONY: swagger-ui
swagger-ui:
rm -Rf public/vendor/assets/swagger-ui
git clone --depth=10 -b v3.13.4 --single-branch https://github.com/swagger-api/swagger-ui.git $(TMPDIR)/swagger-ui
mv $(TMPDIR)/swagger-ui/dist public/vendor/assets/swagger-ui
rm -Rf $(TMPDIR)/swagger-ui
$(SED_INPLACE) "s;http://petstore.swagger.io/v2/swagger.json;../../../swagger.v1.json;g" public/vendor/assets/swagger-ui/index.html
.PHONY: update-translations
update-translations:
@ -739,47 +484,42 @@ update-translations:
mv ./translations/*.ini ./options/locale/
rmdir ./translations
.PHONY: generate-license
generate-license:
GO111MODULE=on $(GO) run build/generate-licenses.go
.PHONY: generate-gitignore
generate-gitignore:
GO111MODULE=on $(GO) run build/generate-gitignores.go
.PHONY: generate-images
generate-images: | node_modules
npm install --no-save --no-package-lock fabric@4 imagemin-zopfli@7
node build/generate-images.js $(TAGS)
generate-images:
mkdir -p $(TMPDIR)/images
inkscape -f $(PWD)/assets/logo.svg -w 880 -h 880 -e $(PWD)/public/img/gitea-lg.png
inkscape -f $(PWD)/assets/logo.svg -w 512 -h 512 -e $(PWD)/public/img/gitea-512.png
inkscape -f $(PWD)/assets/logo.svg -w 192 -h 192 -e $(PWD)/public/img/gitea-192.png
inkscape -f $(PWD)/assets/logo.svg -w 120 -h 120 -jC -i layer1 -e $(TMPDIR)/images/sm-1.png
inkscape -f $(PWD)/assets/logo.svg -w 120 -h 120 -jC -i layer2 -e $(TMPDIR)/images/sm-2.png
composite -compose atop $(TMPDIR)/images/sm-2.png $(TMPDIR)/images/sm-1.png $(PWD)/public/img/gitea-sm.png
inkscape -f $(PWD)/assets/logo.svg -w 200 -h 200 -e $(PWD)/public/img/avatar_default.png
inkscape -f $(PWD)/assets/logo.svg -w 180 -h 180 -e $(PWD)/public/img/favicon.png
inkscape -f $(PWD)/assets/logo.svg -w 128 -h 128 -e $(TMPDIR)/images/128-raw.png
inkscape -f $(PWD)/assets/logo.svg -w 64 -h 64 -e $(TMPDIR)/images/64-raw.png
inkscape -f $(PWD)/assets/logo.svg -w 32 -h 32 -jC -i layer1 -e $(TMPDIR)/images/32-1.png
inkscape -f $(PWD)/assets/logo.svg -w 32 -h 32 -jC -i layer2 -e $(TMPDIR)/images/32-2.png
composite -compose atop $(TMPDIR)/images/32-2.png $(TMPDIR)/images/32-1.png $(TMPDIR)/images/32-raw.png
inkscape -f $(PWD)/assets/logo.svg -w 16 -h 16 -jC -i layer1 -e $(TMPDIR)/images/16-raw.png
zopflipng -m -y $(TMPDIR)/images/128-raw.png $(TMPDIR)/images/128.png
zopflipng -m -y $(TMPDIR)/images/64-raw.png $(TMPDIR)/images/64.png
zopflipng -m -y $(TMPDIR)/images/32-raw.png $(TMPDIR)/images/32.png
zopflipng -m -y $(TMPDIR)/images/16-raw.png $(TMPDIR)/images/16.png
rm -f $(TMPDIR)/images/*-*.png
convert $(TMPDIR)/images/16.png $(TMPDIR)/images/32.png \
$(TMPDIR)/images/64.png $(TMPDIR)/images/128.png \
$(PWD)/public/img/favicon.ico
rm -rf $(TMPDIR)/images
$(foreach file, $(shell find public/img -type f -name '*.png'),zopflipng -m -y $(file) $(file);)
.PHONY: generate-manpage
generate-manpage:
@[ -f gitea ] || make backend
@mkdir -p man/man1/ man/man5
@./gitea docs --man > man/man1/gitea.1
@gzip -9 man/man1/gitea.1 && echo man/man1/gitea.1.gz created
@#TODO A smal script witch format config-cheat-sheet.en-us.md nicely to suit as config man page
.PHONY: pr\#%
pr\#%: clean-all
$(GO) run contrib/pr/checkout.go $*
.PHONY: pr
pr:
$(GO) run contrib/pr/checkout.go $(PR)
.PHONY: golangci-lint
golangci-lint:
@hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
export BINARY="golangci-lint"; \
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.37.0; \
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.20.0; \
fi
golangci-lint run --timeout 10m
.PHONY: docker
docker:
docker build --disable-content-trust=false -t $(DOCKER_REF) .
# support also build args docker build --build-arg GITEA_VERSION=v1.2.3 --build-arg TAGS="bindata sqlite sqlite_unlock_notify" .
.PHONY: docker-build
docker-build:
docker run -ti --rm -v "$(CURDIR):/srv/app/src/code.gitea.io/gitea" -w /srv/app/src/code.gitea.io/gitea -e TAGS="bindata $(TAGS)" LDFLAGS="$(LDFLAGS)" CGO_EXTRA_CFLAGS="$(CGO_EXTRA_CFLAGS)" webhippie/golang:edge make clean build
# This endif closes the if at the top of the file
endif
golangci-lint run --timeout 5m

View File

@ -1,52 +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>
[简体中文](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://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://godoc.org/code.gitea.io/gitea" title="GoDoc">
<img src="https://godoc.org/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://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" title="TODOs">
<img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea">
</a>
<a href="https://www.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 the chinese version of this document</a>
</p>
[![Build Status](https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg)](https://drone.gitea.io/go-gitea/gitea)
[![Join the Discord chat at https://discord.gg/NsatcWJ](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/NsatcWJ)
[![](https://images.microbadger.com/badges/image/gitea/gitea.svg)](https://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com")
[![codecov](https://codecov.io/gh/go-gitea/gitea/branch/master/graph/badge.svg)](https://codecov.io/gh/go-gitea/gitea)
[![Go Report Card](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea)
[![GoDoc](https://godoc.org/code.gitea.io/gitea?status.svg)](https://godoc.org/code.gitea.io/gitea)
[![GitHub release](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest)
[![Help Contribute to Open Source](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea)
[![Become a backer/sponsor of gitea](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://crowdin.com/project/gitea)
## Purpose
@ -65,20 +31,7 @@ This project has been
From the root of the source tree, run:
TAGS="bindata" make build
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 1.16](https://golang.org/dl/) or greater.
- `make frontend` which requires [Node.js 12.17](https://nodejs.org/en/download/) or greater and Internet connectivity to download npm dependencies.
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.
Parallelism (`make -j <num>`) is not supported.
TAGS="bindata" make generate all
More info: https://docs.gitea.io/en-us/install-from-source/
@ -98,24 +51,13 @@ 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 fo fill it as questions pop up.
https://docs.gitea.io/en-us/translation-guidelines/
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](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/NsatcWJ),
or [forum](https://discourse.gitea.io/)!
## Authors
@ -157,7 +99,7 @@ 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

View File

@ -1,52 +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://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://godoc.org/code.gitea.io/gitea" title="GoDoc">
<img src="https://godoc.org/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://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" title="TODOs">
<img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea">
</a>
<a href="https://img.shields.io/bountysource/team/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 the english version of this document</a>
</p>
[![Build Status](https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg)](https://drone.gitea.io/go-gitea/gitea)
[![Join the Discord chat at https://discord.gg/NsatcWJ](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/NsatcWJ)
[![](https://images.microbadger.com/badges/image/gitea/gitea.svg)](https://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com")
[![codecov](https://codecov.io/gh/go-gitea/gitea/branch/master/graph/badge.svg)](https://codecov.io/gh/go-gitea/gitea)
[![Go Report Card](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea)
[![GoDoc](https://godoc.org/code.gitea.io/gitea?status.svg)](https://godoc.org/code.gitea.io/gitea)
[![GitHub release](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest)
[![Help Contribute to Open Source](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea)
[![Become a backer/sponsor of gitea](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://crowdin.com/project/gitea)
## 目标
@ -68,11 +34,6 @@ Gitea 的首要目标是创建一个极易安装,运行非常快速,安装
Fork -> Patch -> Push -> Pull Request
## 翻译
多语言翻译是基于Crowdin进行的.
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://crowdin.com/project/gitea)
## 作者
* [Maintainers](https://github.com/orgs/go-gitea/people)
@ -81,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) 文件中。
## 截图

View File

@ -1,10 +0,0 @@
# Reporting security issues
The Gitea maintainers take security seriously.
If you discover a security issue, please bring it to their attention right away!
### Reporting a Vulnerability
Please **DO NOT** file a public issue, instead send your report privately to `security@gitea.io`.
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

View File

@ -1,31 +1,160 @@
<?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>
<?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"
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

View File

@ -1,25 +0,0 @@
// 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.
//go:build vendor
// +build vendor
package main
// Libraries that are included to vendor utilities used during build.
// These libraries will not be included in a normal compilation.
import (
// for embed
_ "github.com/shurcooL/vfsgen"
// for cover merge
_ "golang.org/x/tools/cover"
// for vet
_ "code.gitea.io/gitea-vet"
// for swagger
_ "github.com/go-swagger/go-swagger/cmd/swagger"
)

View File

@ -1,87 +0,0 @@
// 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.
//go:build ignore
// +build ignore
package main
import (
"bytes"
"crypto/sha1"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"github.com/shurcooL/vfsgen"
)
func needsUpdate(dir string, filename string) (bool, []byte) {
needRegen := false
_, err := os.Stat(filename)
if err != nil {
needRegen = true
}
oldHash, err := ioutil.ReadFile(filename + ".hash")
if err != nil {
oldHash = []byte{}
}
hasher := sha1.New()
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
_, _ = hasher.Write([]byte(info.Name()))
_, _ = hasher.Write([]byte(info.ModTime().String()))
_, _ = hasher.Write([]byte(strconv.FormatInt(info.Size(), 16)))
return nil
})
if err != nil {
return true, oldHash
}
newHash := hasher.Sum([]byte{})
if bytes.Compare(oldHash, newHash) != 0 {
return true, newHash
}
return needRegen, newHash
}
func main() {
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]
update, newHash := needsUpdate(dir, filename)
if !update {
fmt.Printf("bindata for %s already up-to-date\n", packageName)
return
}
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,
})
if err != nil {
log.Fatalf("%v\n", err)
}
_ = ioutil.WriteFile(filename+".hash", newHash, 0666)
}

View File

@ -1,223 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Copyright 2015 Kenneth Shaw
// 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 (
"flag"
"fmt"
"go/format"
"io/ioutil"
"log"
"net/http"
"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 = 12
)
var (
flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")
)
// Gemoji is a set of emoji data.
type Gemoji []Emoji
// Emoji represents a single emoji and associated data.
type Emoji struct {
Emoji string `json:"emoji"`
Description string `json:"description,omitempty"`
Aliases []string `json:"aliases"`
UnicodeVersion string `json:"unicode_version,omitempty"`
SkinTones bool `json:"skin_tones,omitempty"`
}
// Don't include some fields in JSON
func (e Emoji) MarshalJSON() ([]byte, error) {
type emoji Emoji
x := emoji(e)
x.UnicodeVersion = ""
x.Description = ""
x.SkinTones = false
return json.Marshal(x)
}
func main() {
var err error
flag.Parse()
// generate data
buf, err := generate()
if err != nil {
log.Fatal(err)
}
// write
err = ioutil.WriteFile(*flagOut, buf, 0644)
if err != nil {
log.Fatal(err)
}
}
var replacer = strings.NewReplacer(
"main.Gemoji", "Gemoji",
"main.Emoji", "\n",
"}}", "},\n}",
", Description:", ", ",
", Aliases:", ", ",
", UnicodeVersion:", ", ",
", SkinTones:", ", ",
)
var emojiRE = regexp.MustCompile(`\{Emoji:"([^"]*)"`)
func generate() ([]byte, error) {
var err error
// load gemoji data
res, err := http.Get(gemojiURL)
if err != nil {
return nil, err
}
defer res.Body.Close()
// read all
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
// unmarshal
var data Gemoji
err = json.Unmarshal(body, &data)
if err != nil {
return nil, err
}
var skinTones = make(map[string]string)
skinTones["\U0001f3fb"] = "Light Skin Tone"
skinTones["\U0001f3fc"] = "Medium-Light Skin Tone"
skinTones["\U0001f3fd"] = "Medium Skin Tone"
skinTones["\U0001f3fe"] = "Medium-Dark Skin Tone"
skinTones["\U0001f3ff"] = "Dark Skin Tone"
var tmp Gemoji
//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 {
tmp = append(tmp, data[i])
}
}
data = tmp
sort.Slice(data, func(i, j int) bool {
return data[i].Aliases[0] < data[j].Aliases[0]
})
aliasMap := make(map[string]int, len(data))
for i, e := range data {
if e.Emoji == "" || len(e.Aliases) == 0 {
continue
}
for _, a := range e.Aliases {
if a == "" {
continue
}
aliasMap[a] = i
}
}
// gitea customizations
i, ok := aliasMap["tada"]
if ok {
data[i].Aliases = append(data[i].Aliases, "hooray")
}
i, ok = aliasMap["laughing"]
if ok {
data[i].Aliases = append(data[i].Aliases, "laugh")
}
// 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)
_ = ioutil.WriteFile("assets/emoji.json", file, 0644)
// Add skin tones to emoji that support it
var (
s []string
newEmoji string
newDescription string
newData Emoji
)
for i := range data {
if data[i].SkinTones {
for k, v := range skinTones {
s = strings.Split(data[i].Emoji, "")
if utf8.RuneCountInString(data[i].Emoji) == 1 {
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
s = append(s, "")
copy(s[2:], s[1:])
s[1] = k
}
newEmoji = strings.Join(s, "")
newDescription = data[i].Description + ": " + v
newAlias := data[i].Aliases[0] + "_" + strings.ReplaceAll(v, " ", "_")
newData = Emoji{newEmoji, newDescription, []string{newAlias}, "12.0", false}
data = append(data, newData)
}
}
}
// add header
str := replacer.Replace(fmt.Sprintf(hdr, gemojiURL, data))
// change the format of the unicode string
str = emojiRE.ReplaceAllStringFunc(str, func(s string) string {
var err error
s, err = strconv.Unquote(s[len("{Emoji:"):])
if err != nil {
panic(err)
}
return "{" + strconv.QuoteToASCII(s)
})
// format
return format.Source([]byte(str))
}
const hdr = `
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package emoji
// Code generated by gen.go. DO NOT EDIT.
// Sourced from %s
//
var GemojiData = %#v
`

View File

@ -1,83 +0,0 @@
import imageminZopfli from 'imagemin-zopfli';
import {optimize, extendDefaultPlugins} from 'svgo';
import {fabric} from 'fabric';
import fs from 'fs';
import {resolve, dirname} from 'path';
import {fileURLToPath} from 'url';
const {readFile, writeFile} = fs.promises;
const __dirname = dirname(fileURLToPath(import.meta.url));
const logoFile = resolve(__dirname, '../assets/logo.svg');
function exit(err) {
if (err) console.error(err);
process.exit(err ? 1 : 0);
}
function loadSvg(svg) {
return new Promise((resolve) => {
fabric.loadSVGFromString(svg, (objects, options) => {
resolve({objects, options});
});
});
}
async function generate(svg, outputFile, {size, bg}) {
if (outputFile.endsWith('.svg')) {
const {data} = optimize(svg, {
plugins: extendDefaultPlugins([
'removeDimensions',
{
name: 'addAttributesToSVGElement',
params: {attributes: [{width: size}, {height: size}]}
},
]),
});
await writeFile(outputFile, data);
return;
}
const {objects, options} = await loadSvg(svg);
const canvas = new fabric.Canvas();
canvas.setDimensions({width: size, height: size});
const ctx = canvas.getContext('2d');
ctx.scale(options.width ? (size / options.width) : 1, options.height ? (size / options.height) : 1);
if (bg) {
canvas.add(new fabric.Rect({
left: 0,
top: 0,
height: size * (1 / (size / options.height)),
width: size * (1 / (size / options.width)),
fill: 'white',
}));
}
canvas.add(fabric.util.groupSVGElements(objects, options));
canvas.renderAll();
let png = Buffer.from([]);
for await (const chunk of canvas.createPNGStream()) {
png = Buffer.concat([png, chunk]);
}
png = await imageminZopfli({more: true})(png);
await writeFile(outputFile, png);
}
async function main() {
const gitea = process.argv.slice(2).includes('gitea');
const svg = await readFile(logoFile, 'utf8');
await Promise.all([
generate(svg, resolve(__dirname, '../public/img/logo.svg'), {size: 32}),
generate(svg, resolve(__dirname, '../public/img/logo.png'), {size: 512}),
generate(svg, resolve(__dirname, '../public/img/favicon.png'), {size: 180}),
generate(svg, resolve(__dirname, '../public/img/avatar_default.png'), {size: 200}),
generate(svg, resolve(__dirname, '../public/img/apple-touch-icon.png'), {size: 180, bg: true}),
gitea && generate(svg, resolve(__dirname, '../public/img/gitea.svg'), {size: 32}),
]);
}
main().then(exit).catch(exit);

View File

@ -1,119 +0,0 @@
//go:build ignore
// +build ignore
package main
import (
"archive/tar"
"compress/gzip"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/util"
)
func main() {
var (
prefix = "gitea-licenses"
url = "https://api.github.com/repos/spdx/license-list-data/tarball"
githubApiToken = ""
githubUsername = ""
destination = ""
)
flag.StringVar(&destination, "dest", "options/license/", "destination for the licenses")
flag.StringVar(&githubUsername, "username", "", "github username")
flag.StringVar(&githubApiToken, "token", "", "github api token")
flag.Parse()
file, err := ioutil.TempFile(os.TempDir(), prefix)
if err != nil {
log.Fatalf("Failed to create temp file. %s", err)
}
defer util.Remove(file.Name())
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Fatalf("Failed to download archive. %s", err)
}
if len(githubApiToken) > 0 && len(githubUsername) > 0 {
req.SetBasicAuth(githubUsername, githubApiToken)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("Failed to download archive. %s", err)
}
defer resp.Body.Close()
if _, err := io.Copy(file, resp.Body); err != nil {
log.Fatalf("Failed to copy archive to file. %s", err)
}
if _, err := file.Seek(0, 0); err != nil {
log.Fatalf("Failed to reset seek on archive. %s", err)
}
gz, err := gzip.NewReader(file)
if err != nil {
log.Fatalf("Failed to gunzip the archive. %s", err)
}
tr := tar.NewReader(gz)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("Failed to iterate archive. %s", err)
}
if !strings.Contains(hdr.Name, "/text/") {
continue
}
if filepath.Ext(hdr.Name) != ".txt" {
continue
}
if strings.HasPrefix(filepath.Base(hdr.Name), "README") {
continue
}
if strings.HasPrefix(filepath.Base(hdr.Name), "deprecated_") {
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)
}
defer out.Close()
if _, err := io.Copy(out, tr); err != nil {
log.Fatalf("Failed to write new file. %s", err)
} else {
fmt.Printf("Written %s\n", out.Name())
}
}
fmt.Println("Done")
}

View File

@ -1,57 +0,0 @@
import fastGlob from 'fast-glob';
import {optimize} from 'svgo';
import {resolve, parse, dirname} from 'path';
import fs from 'fs';
import {fileURLToPath} from 'url';
const {readFile, writeFile, mkdir} = fs.promises;
const __dirname = dirname(fileURLToPath(import.meta.url));
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
}
const {data} = optimize(await readFile(file, 'utf8'), {
plugins: [
{name: 'preset-default'},
{name: 'removeXMLNS'},
{name: 'removeDimensions'},
{name: 'addClassesToSVGElement', params: {classNames: ['svg', name]}},
{name: 'addAttributesToSVGElement', params: {attributes: [{'width': '16'}, {'height': '16'}, {'aria-hidden': 'true'}]}},
],
});
await writeFile(resolve(outputDir, `${name}.svg`), data);
}
function processFiles(pattern, opts) {
return glob(pattern).map((file) => processFile(file, opts));
}
async function main() {
try {
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'}),
]);
}
main().then(exit).catch(exit);

View File

@ -1,120 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Copyright (c) 2015, Wade Simmons
// 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
import (
"flag"
"fmt"
"io"
"log"
"os"
"sort"
"golang.org/x/tools/cover"
)
func mergeProfiles(p *cover.Profile, merge *cover.Profile) {
if p.Mode != merge.Mode {
log.Fatalf("cannot merge profiles with different modes")
}
// Since the blocks are sorted, we can keep track of where the last block
// was inserted and only look at the blocks after that as targets for merge
startIndex := 0
for _, b := range merge.Blocks {
startIndex = mergeProfileBlock(p, b, startIndex)
}
}
func mergeProfileBlock(p *cover.Profile, pb cover.ProfileBlock, startIndex int) int {
sortFunc := func(i int) bool {
pi := p.Blocks[i+startIndex]
return pi.StartLine >= pb.StartLine && (pi.StartLine != pb.StartLine || pi.StartCol >= pb.StartCol)
}
i := 0
if sortFunc(i) != true {
i = sort.Search(len(p.Blocks)-startIndex, sortFunc)
}
i += startIndex
if i < len(p.Blocks) && p.Blocks[i].StartLine == pb.StartLine && p.Blocks[i].StartCol == pb.StartCol {
if p.Blocks[i].EndLine != pb.EndLine || p.Blocks[i].EndCol != pb.EndCol {
log.Fatalf("OVERLAP MERGE: %v %v %v", p.FileName, p.Blocks[i], pb)
}
switch p.Mode {
case "set":
p.Blocks[i].Count |= pb.Count
case "count", "atomic":
p.Blocks[i].Count += pb.Count
default:
log.Fatalf("unsupported covermode: '%s'", p.Mode)
}
} else {
if i > 0 {
pa := p.Blocks[i-1]
if pa.EndLine >= pb.EndLine && (pa.EndLine != pb.EndLine || pa.EndCol > pb.EndCol) {
log.Fatalf("OVERLAP BEFORE: %v %v %v", p.FileName, pa, pb)
}
}
if i < len(p.Blocks)-1 {
pa := p.Blocks[i+1]
if pa.StartLine <= pb.StartLine && (pa.StartLine != pb.StartLine || pa.StartCol < pb.StartCol) {
log.Fatalf("OVERLAP AFTER: %v %v %v", p.FileName, pa, pb)
}
}
p.Blocks = append(p.Blocks, cover.ProfileBlock{})
copy(p.Blocks[i+1:], p.Blocks[i:])
p.Blocks[i] = pb
}
return i + 1
}
func addProfile(profiles []*cover.Profile, p *cover.Profile) []*cover.Profile {
i := sort.Search(len(profiles), func(i int) bool { return profiles[i].FileName >= p.FileName })
if i < len(profiles) && profiles[i].FileName == p.FileName {
mergeProfiles(profiles[i], p)
} else {
profiles = append(profiles, nil)
copy(profiles[i+1:], profiles[i:])
profiles[i] = p
}
return profiles
}
func dumpProfiles(profiles []*cover.Profile, out io.Writer) {
if len(profiles) == 0 {
return
}
fmt.Fprintf(out, "mode: %s\n", profiles[0].Mode)
for _, p := range profiles {
for _, b := range p.Blocks {
fmt.Fprintf(out, "%s:%d.%d,%d.%d %d %d\n", p.FileName, b.StartLine, b.StartCol, b.EndLine, b.EndCol, b.NumStmt, b.Count)
}
}
}
func main() {
flag.Parse()
var merged []*cover.Profile
for _, file := range flag.Args() {
profiles, err := cover.ParseProfiles(file)
if err != nil {
log.Fatalf("failed to parse profile '%s': %v", file, err)
}
for _, p := range profiles {
merged = addProfile(merged, p)
}
}
dumpProfiles(merged, os.Stdout)
}

View File

@ -1,23 +0,0 @@
#!/bin/sh
mv ./options/locale/locale_en-US.ini ./options/
# Make sure to only change lines that have the translation enclosed between quotes
sed -i -r -e '/^[a-zA-Z0-9_.-]+[ ]*=[ ]*".*"$/ {
s/^([a-zA-Z0-9_.-]+)[ ]*="/\1=/
s/\\"/"/g
s/"$//
}' ./options/locale/*.ini
# Remove translation under 25% of en_us
baselines=$(wc -l "./options/locale_en-US.ini" | cut -d" " -f1)
baselines=$((baselines / 4))
for filename in ./options/locale/*.ini; do
lines=$(wc -l "$filename" | cut -d" " -f1)
if [ $lines -lt $baselines ]; then
echo "Removing $filename: $lines/$baselines"
rm "$filename"
fi
done
mv ./options/locale_en-US.ini ./options/locale/

View File

@ -6,22 +6,17 @@
package cmd
import (
"context"
"errors"
"fmt"
"os"
"strings"
"text/tabwriter"
"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/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/services/auth/source/oauth2"
"github.com/urfave/cli"
)
@ -32,39 +27,16 @@ var (
Name: "admin",
Usage: "Command line interface to perform common administrative operations",
Subcommands: []cli.Command{
subcmdUser,
subcmdCreateUser,
subcmdChangePassword,
subcmdRepoSyncReleases,
subcmdRegenerate,
subcmdAuth,
subcmdSendMail,
},
}
subcmdUser = cli.Command{
Name: "user",
Usage: "Modify users",
Subcommands: []cli.Command{
microcmdUserCreate,
microcmdUserList,
microcmdUserChangePassword,
microcmdUserDelete,
},
}
microcmdUserList = cli.Command{
Name: "list",
Usage: "List users",
Action: runListUsers,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "admin",
Usage: "List only admin users",
},
},
}
microcmdUserCreate = cli.Command{
Name: "create",
subcmdCreateUser = cli.Command{
Name: "create-user",
Usage: "Create a new user in database",
Action: runCreateUser,
Flags: []cli.Flag{
@ -108,7 +80,7 @@ var (
},
}
microcmdUserChangePassword = cli.Command{
subcmdChangePassword = cli.Command{
Name: "change-password",
Usage: "Change a user's password",
Action: runChangePassword,
@ -126,26 +98,6 @@ 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",
},
},
Action: runDeleteUser,
}
subcmdRepoSyncReleases = cli.Command{
Name: "repo-sync-releases",
Usage: "Synchronize repository releases with tags",
@ -192,32 +144,6 @@ var (
Name: "list",
Usage: "List auth sources",
Action: runListAuth,
Flags: []cli.Flag{
cli.IntFlag{
Name: "min-width",
Usage: "Minimal cell width including any padding for the formatted table",
Value: 0,
},
cli.IntFlag{
Name: "tab-width",
Usage: "width of tab characters in formatted table (equivalent number of spaces)",
Value: 8,
},
cli.IntFlag{
Name: "padding",
Usage: "padding added to a cell before computing its width",
Value: 1,
},
cli.StringFlag{
Name: "pad-char",
Usage: `ASCII char used for padding if padchar == '\\t', the Writer will assume that the width of a '\\t' in the formatted output is tabwidth, and cells are left-aligned independent of align_left (for correct-looking results, tabwidth must correspond to the tab width in the viewer displaying the result)`,
Value: "\t",
},
cli.BoolFlag{
Name: "vertical-bars",
Usage: "Set to true to print vertical bars between columns",
},
},
}
idFlag = cli.Int64Flag{
@ -228,7 +154,6 @@ var (
microcmdAuthDelete = cli.Command{
Name: "delete",
Usage: "Delete specific auth source",
Flags: []cli.Flag{idFlag},
Action: runDeleteAuth,
}
@ -283,11 +208,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",
},
}
microcmdAuthUpdateOauth = cli.Command{
@ -303,28 +223,6 @@ 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",
},
},
}
)
func runChangePassword(c *cli.Context) error {
@ -338,23 +236,17 @@ func runChangePassword(c *cli.Context) error {
if !pwd.IsComplexEnough(c.String("password")) {
return errors.New("Password does not meet complexity requirements")
}
pwned, err := pwd.IsPwned(context.Background(), c.String("password"))
if err != nil {
return err
}
if pwned {
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
}
uname := c.String("username")
user, err := models.GetUserByName(uname)
if err != nil {
return err
}
if err = user.SetPassword(c.String("password")); err != nil {
if user.Salt, err = models.GetUserSalt(); err != nil {
return err
}
user.HashPassword(c.String("password"))
if err = models.UpdateUserCols(user, "passwd", "passwd_hash_algo", "salt"); err != nil {
if err := models.UpdateUserCols(user, "passwd", "salt"); err != nil {
return err
}
@ -448,76 +340,7 @@ func runCreateUser(c *cli.Context) error {
return nil
}
func runListUsers(c *cli.Context) error {
if err := initDB(); err != nil {
return err
}
users, err := models.GetAllUsers()
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0)
if c.IsSet("admin") {
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\n")
for _, u := range users {
if u.IsAdmin {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive)
}
}
} else {
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\n")
for _, u := range users {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin)
}
}
w.Flush()
return nil
}
func runDeleteUser(c *cli.Context) error {
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
return fmt.Errorf("You must provide the id, username or email of a user to delete")
}
if err := initDB(); err != nil {
return err
}
if err := storage.Init(); err != nil {
return err
}
var err error
var user *models.User
if c.IsSet("email") {
user, err = models.GetUserByEmail(c.String("email"))
} else if c.IsSet("username") {
user, err = models.GetUserByName(c.String("username"))
} else {
user, err = models.GetUserByID(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 models.DeleteUser(user)
}
func runRepoSyncReleases(_ *cli.Context) error {
func runRepoSyncReleases(c *cli.Context) error {
if err := initDB(); err != nil {
return err
}
@ -525,11 +348,9 @@ func runRepoSyncReleases(_ *cli.Context) error {
log.Trace("Synchronizing repository releases (this may take a while)")
for page := 1; ; page++ {
repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{
ListOptions: models.ListOptions{
PageSize: models.RepositoryListDefaultPageSize,
Page: page,
},
Private: true,
Page: page,
PageSize: models.RepositoryListDefaultPageSize,
Private: true,
})
if err != nil {
return fmt.Errorf("SearchRepositoryByName: %v", err)
@ -552,7 +373,7 @@ func runRepoSyncReleases(_ *cli.Context) error {
}
log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum)
if err = repo_module.SyncReleasesWithTags(repo, gitRepo); err != nil {
if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil {
log.Warn(" SyncReleasesWithTags: %v", err)
gitRepo.Close()
continue
@ -583,21 +404,21 @@ func getReleaseCount(id int64) (int64, error) {
)
}
func runRegenerateHooks(_ *cli.Context) error {
func runRegenerateHooks(c *cli.Context) error {
if err := initDB(); err != nil {
return err
}
return repo_module.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
return models.SyncRepositoryHooks()
}
func runRegenerateKeys(_ *cli.Context) error {
func runRegenerateKeys(c *cli.Context) error {
if err := initDB(); err != nil {
return err
}
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{
@ -609,13 +430,12 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source {
} 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"),
}
}
@ -625,10 +445,10 @@ func runAddOauth(c *cli.Context) error {
}
return models.CreateLoginSource(&models.LoginSource{
Type: models.LoginOAuth2,
Name: c.String("name"),
IsActive: true,
Cfg: parseOAuth2Config(c),
Type: models.LoginOAuth2,
Name: c.String("name"),
IsActived: true,
Cfg: parseOAuth2Config(c),
})
}
@ -646,7 +466,7 @@ func runUpdateOauth(c *cli.Context) error {
return err
}
oAuth2Config := source.Cfg.(*oauth2.Source)
oAuth2Config := source.OAuth2()
if c.IsSet("name") {
source.Name = c.String("name")
@ -668,10 +488,6 @@ func runUpdateOauth(c *cli.Context) error {
oAuth2Config.OpenIDConnectAutoDiscoveryURL = c.String("auto-discover-url")
}
if c.IsSet("icon-url") {
oAuth2Config.IconURL = c.String("icon-url")
}
// update custom URL mapping
var customURLMapping = &oauth2.CustomURLMapping{}
@ -714,21 +530,11 @@ func runListAuth(c *cli.Context) error {
return err
}
flags := tabwriter.AlignRight
if c.Bool("vertical-bars") {
flags |= tabwriter.Debug
}
padChar := byte('\t')
if len(c.String("pad-char")) > 0 {
padChar = c.String("pad-char")[0]
}
// 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")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.AlignRight)
fmt.Fprintf(w, "ID\tName\tType\tEnabled")
for _, source := range loginSources {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, models.LoginNames[source.Type], source.IsActive)
fmt.Fprintf(w, "%d\t%s\t%s\t%t", source.ID, source.Name, models.LoginNames[source.Type], source.IsActived)
}
w.Flush()

View File

@ -9,7 +9,7 @@ import (
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/services/auth/source/ldap"
"code.gitea.io/gitea/modules/auth/ldap"
"github.com/urfave/cli"
)
@ -61,14 +61,6 @@ var (
Name: "admin-filter",
Usage: "An LDAP filter specifying if a user should be given administrator privileges.",
},
cli.StringFlag{
Name: "restricted-filter",
Usage: "An LDAP filter specifying if a user should be given restricted status.",
},
cli.BoolFlag{
Name: "allow-deactivate-all",
Usage: "Allow empty search results to deactivate all users.",
},
cli.StringFlag{
Name: "username-attribute",
Usage: "The attribute of the users LDAP record containing the user name.",
@ -172,7 +164,7 @@ func parseLoginSource(c *cli.Context, loginSource *models.LoginSource) {
loginSource.Name = c.String("name")
}
if c.IsSet("not-active") {
loginSource.IsActive = !c.Bool("not-active")
loginSource.IsActived = !c.Bool("not-active")
}
if c.IsSet("synchronize-users") {
loginSource.IsSyncEnabled = c.Bool("synchronize-users")
@ -180,70 +172,64 @@ func parseLoginSource(c *cli.Context, loginSource *models.LoginSource) {
}
// 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")
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")
}
if c.IsSet("restricted-filter") {
config.RestrictedFilter = c.String("restricted-filter")
}
if c.IsSet("allow-deactivate-all") {
config.AllowDeactivateAll = c.Bool("allow-deactivate-all")
config.Source.AdminFilter = c.String("admin-filter")
}
return nil
}
@ -251,7 +237,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
}
@ -289,15 +275,17 @@ func (a *authService) addLdapBindDn(c *cli.Context) error {
}
loginSource := &models.LoginSource{
Type: models.LoginLDAP,
IsActive: true, // active by default
Cfg: &ldap.Source{
Enabled: true, // always true
Type: models.LoginLDAP,
IsActived: true, // active by default
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Enabled: true, // always true
},
},
}
parseLoginSource(c, loginSource)
if err := parseLdapConfig(c, loginSource.Cfg.(*ldap.Source)); err != nil {
if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
return err
}
@ -316,7 +304,7 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error {
}
parseLoginSource(c, loginSource)
if err := parseLdapConfig(c, loginSource.Cfg.(*ldap.Source)); err != nil {
if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
return err
}
@ -334,15 +322,17 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
}
loginSource := &models.LoginSource{
Type: models.LoginDLDAP,
IsActive: true, // active by default
Cfg: &ldap.Source{
Enabled: true, // always true
Type: models.LoginDLDAP,
IsActived: true, // active by default
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Enabled: true, // always true
},
},
}
parseLoginSource(c, loginSource)
if err := parseLdapConfig(c, loginSource.Cfg.(*ldap.Source)); err != nil {
if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
return err
}
@ -361,7 +351,7 @@ func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
}
parseLoginSource(c, loginSource)
if err := parseLdapConfig(c, loginSource.Cfg.(*ldap.Source)); err != nil {
if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
return err
}

View File

@ -8,7 +8,7 @@ import (
"testing"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/services/auth/source/ldap"
"code.gitea.io/gitea/modules/auth/ldap"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli"
@ -39,7 +39,6 @@ func TestAddLdapBindDn(t *testing.T) {
"--user-search-base", "ou=Users,dc=full-domain-bind,dc=org",
"--user-filter", "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)",
"--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
"--restricted-filter", "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
"--username-attribute", "uid-bind full",
"--firstname-attribute", "givenName-bind full",
"--surname-attribute", "sn-bind full",
@ -54,28 +53,29 @@ func TestAddLdapBindDn(t *testing.T) {
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Name: "ldap (via Bind DN) source full",
IsActive: false,
IsActived: false,
IsSyncEnabled: true,
Cfg: &ldap.Source{
Name: "ldap (via Bind DN) source full",
Host: "ldap-bind-server full",
Port: 9876,
SecurityProtocol: ldap.SecurityProtocol(1),
SkipVerify: true,
BindDN: "cn=readonly,dc=full-domain-bind,dc=org",
BindPassword: "secret-bind-full",
UserBase: "ou=Users,dc=full-domain-bind,dc=org",
AttributeUsername: "uid-bind full",
AttributeName: "givenName-bind full",
AttributeSurname: "sn-bind full",
AttributeMail: "mail-bind full",
AttributesInBind: true,
AttributeSSHPublicKey: "publickey-bind full",
SearchPageSize: 99,
Filter: "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)",
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
Enabled: true,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Name: "ldap (via Bind DN) source full",
Host: "ldap-bind-server full",
Port: 9876,
SecurityProtocol: ldap.SecurityProtocol(1),
SkipVerify: true,
BindDN: "cn=readonly,dc=full-domain-bind,dc=org",
BindPassword: "secret-bind-full",
UserBase: "ou=Users,dc=full-domain-bind,dc=org",
AttributeUsername: "uid-bind full",
AttributeName: "givenName-bind full",
AttributeSurname: "sn-bind full",
AttributeMail: "mail-bind full",
AttributesInBind: true,
AttributeSSHPublicKey: "publickey-bind full",
SearchPageSize: 99,
Filter: "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)",
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
Enabled: true,
},
},
},
},
@ -92,18 +92,20 @@ func TestAddLdapBindDn(t *testing.T) {
"--email-attribute", "mail-bind min",
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Name: "ldap (via Bind DN) source min",
IsActive: true,
Cfg: &ldap.Source{
Name: "ldap (via Bind DN) source min",
Host: "ldap-bind-server min",
Port: 1234,
SecurityProtocol: ldap.SecurityProtocol(0),
UserBase: "ou=Users,dc=min-domain-bind,dc=org",
AttributeMail: "mail-bind min",
Filter: "(memberOf=cn=user-group,ou=example,dc=min-domain-bind,dc=org)",
Enabled: true,
Type: models.LoginLDAP,
Name: "ldap (via Bind DN) source min",
IsActived: true,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Name: "ldap (via Bind DN) source min",
Host: "ldap-bind-server min",
Port: 1234,
SecurityProtocol: ldap.SecurityProtocol(0),
UserBase: "ou=Users,dc=min-domain-bind,dc=org",
AttributeMail: "mail-bind min",
Filter: "(memberOf=cn=user-group,ou=example,dc=min-domain-bind,dc=org)",
Enabled: true,
},
},
},
},
@ -263,7 +265,6 @@ func TestAddLdapSimpleAuth(t *testing.T) {
"--user-search-base", "ou=Users,dc=full-domain-simple,dc=org",
"--user-filter", "(&(objectClass=posixAccount)(full-simple-cn=%s))",
"--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)",
"--restricted-filter", "(memberOf=cn=restricted-group,ou=example,dc=full-domain-simple,dc=org)",
"--username-attribute", "uid-simple full",
"--firstname-attribute", "givenName-simple full",
"--surname-attribute", "sn-simple full",
@ -272,26 +273,27 @@ func TestAddLdapSimpleAuth(t *testing.T) {
"--user-dn", "cn=%s,ou=Users,dc=full-domain-simple,dc=org",
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Name: "ldap (simple auth) source full",
IsActive: false,
Cfg: &ldap.Source{
Name: "ldap (simple auth) source full",
Host: "ldap-simple-server full",
Port: 987,
SecurityProtocol: ldap.SecurityProtocol(2),
SkipVerify: true,
UserDN: "cn=%s,ou=Users,dc=full-domain-simple,dc=org",
UserBase: "ou=Users,dc=full-domain-simple,dc=org",
AttributeUsername: "uid-simple full",
AttributeName: "givenName-simple full",
AttributeSurname: "sn-simple full",
AttributeMail: "mail-simple full",
AttributeSSHPublicKey: "publickey-simple full",
Filter: "(&(objectClass=posixAccount)(full-simple-cn=%s))",
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)",
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-simple,dc=org)",
Enabled: true,
Type: models.LoginDLDAP,
Name: "ldap (simple auth) source full",
IsActived: false,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Name: "ldap (simple auth) source full",
Host: "ldap-simple-server full",
Port: 987,
SecurityProtocol: ldap.SecurityProtocol(2),
SkipVerify: true,
UserDN: "cn=%s,ou=Users,dc=full-domain-simple,dc=org",
UserBase: "ou=Users,dc=full-domain-simple,dc=org",
AttributeUsername: "uid-simple full",
AttributeName: "givenName-simple full",
AttributeSurname: "sn-simple full",
AttributeMail: "mail-simple full",
AttributeSSHPublicKey: "publickey-simple full",
Filter: "(&(objectClass=posixAccount)(full-simple-cn=%s))",
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)",
Enabled: true,
},
},
},
},
@ -308,18 +310,20 @@ func TestAddLdapSimpleAuth(t *testing.T) {
"--user-dn", "cn=%s,ou=Users,dc=min-domain-simple,dc=org",
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Name: "ldap (simple auth) source min",
IsActive: true,
Cfg: &ldap.Source{
Name: "ldap (simple auth) source min",
Host: "ldap-simple-server min",
Port: 123,
SecurityProtocol: ldap.SecurityProtocol(0),
UserDN: "cn=%s,ou=Users,dc=min-domain-simple,dc=org",
AttributeMail: "mail-simple min",
Filter: "(&(objectClass=posixAccount)(min-simple-cn=%s))",
Enabled: true,
Type: models.LoginDLDAP,
Name: "ldap (simple auth) source min",
IsActived: true,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Name: "ldap (simple auth) source min",
Host: "ldap-simple-server min",
Port: 123,
SecurityProtocol: ldap.SecurityProtocol(0),
UserDN: "cn=%s,ou=Users,dc=min-domain-simple,dc=org",
AttributeMail: "mail-simple min",
Filter: "(&(objectClass=posixAccount)(min-simple-cn=%s))",
Enabled: true,
},
},
},
},
@ -495,7 +499,6 @@ func TestUpdateLdapBindDn(t *testing.T) {
"--user-search-base", "ou=Users,dc=full-domain-bind,dc=org",
"--user-filter", "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)",
"--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
"--restricted-filter", "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
"--username-attribute", "uid-bind full",
"--firstname-attribute", "givenName-bind full",
"--surname-attribute", "sn-bind full",
@ -508,37 +511,40 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
id: 23,
existingLoginSource: &models.LoginSource{
Type: models.LoginLDAP,
IsActive: true,
Cfg: &ldap.Source{
Enabled: true,
Type: models.LoginLDAP,
IsActived: true,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Enabled: true,
},
},
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Name: "ldap (via Bind DN) source full",
IsActive: false,
IsActived: false,
IsSyncEnabled: true,
Cfg: &ldap.Source{
Name: "ldap (via Bind DN) source full",
Host: "ldap-bind-server full",
Port: 9876,
SecurityProtocol: ldap.SecurityProtocol(1),
SkipVerify: true,
BindDN: "cn=readonly,dc=full-domain-bind,dc=org",
BindPassword: "secret-bind-full",
UserBase: "ou=Users,dc=full-domain-bind,dc=org",
AttributeUsername: "uid-bind full",
AttributeName: "givenName-bind full",
AttributeSurname: "sn-bind full",
AttributeMail: "mail-bind full",
AttributesInBind: false,
AttributeSSHPublicKey: "publickey-bind full",
SearchPageSize: 99,
Filter: "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)",
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
Enabled: true,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Name: "ldap (via Bind DN) source full",
Host: "ldap-bind-server full",
Port: 9876,
SecurityProtocol: ldap.SecurityProtocol(1),
SkipVerify: true,
BindDN: "cn=readonly,dc=full-domain-bind,dc=org",
BindPassword: "secret-bind-full",
UserBase: "ou=Users,dc=full-domain-bind,dc=org",
AttributeUsername: "uid-bind full",
AttributeName: "givenName-bind full",
AttributeSurname: "sn-bind full",
AttributeMail: "mail-bind full",
AttributesInBind: false,
AttributeSSHPublicKey: "publickey-bind full",
SearchPageSize: 99,
Filter: "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)",
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
Enabled: true,
},
},
},
},
@ -550,7 +556,9 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{},
Cfg: &models.LDAPConfig{
Source: &ldap.Source{},
},
},
},
// case 2
@ -563,8 +571,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Name: "ldap (via Bind DN) source",
Cfg: &ldap.Source{
Name: "ldap (via Bind DN) source",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Name: "ldap (via Bind DN) source",
},
},
},
},
@ -576,14 +586,18 @@ func TestUpdateLdapBindDn(t *testing.T) {
"--not-active",
},
existingLoginSource: &models.LoginSource{
Type: models.LoginLDAP,
IsActive: true,
Cfg: &ldap.Source{},
Type: models.LoginLDAP,
IsActived: true,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{},
},
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
IsActive: false,
Cfg: &ldap.Source{},
Type: models.LoginLDAP,
IsActived: false,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{},
},
},
},
// case 4
@ -595,8 +609,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
SecurityProtocol: ldap.SecurityProtocol(1),
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
SecurityProtocol: ldap.SecurityProtocol(1),
},
},
},
},
@ -609,8 +625,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
SkipVerify: true,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
SkipVerify: true,
},
},
},
},
@ -623,8 +641,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
Host: "ldap-server",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Host: "ldap-server",
},
},
},
},
@ -637,8 +657,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
Port: 389,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Port: 389,
},
},
},
},
@ -651,8 +673,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
UserBase: "ou=Users,dc=domain,dc=org",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
UserBase: "ou=Users,dc=domain,dc=org",
},
},
},
},
@ -665,8 +689,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
Filter: "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Filter: "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
},
},
},
},
@ -679,8 +705,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
},
},
},
},
@ -693,8 +721,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
AttributeUsername: "uid",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
AttributeUsername: "uid",
},
},
},
},
@ -707,8 +737,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
AttributeName: "givenName",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
AttributeName: "givenName",
},
},
},
},
@ -721,8 +753,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
AttributeSurname: "sn",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
AttributeSurname: "sn",
},
},
},
},
@ -735,8 +769,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
AttributeMail: "mail",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
AttributeMail: "mail",
},
},
},
},
@ -749,8 +785,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
AttributesInBind: true,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
AttributesInBind: true,
},
},
},
},
@ -763,8 +801,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
AttributeSSHPublicKey: "publickey",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
AttributeSSHPublicKey: "publickey",
},
},
},
},
@ -777,8 +817,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
BindDN: "cn=readonly,dc=domain,dc=org",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
BindDN: "cn=readonly,dc=domain,dc=org",
},
},
},
},
@ -791,8 +833,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
BindPassword: "secret",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
BindPassword: "secret",
},
},
},
},
@ -806,7 +850,9 @@ func TestUpdateLdapBindDn(t *testing.T) {
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
IsSyncEnabled: true,
Cfg: &ldap.Source{},
Cfg: &models.LDAPConfig{
Source: &ldap.Source{},
},
},
},
// case 20
@ -818,8 +864,10 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{
SearchPageSize: 12,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
SearchPageSize: 12,
},
},
},
},
@ -847,7 +895,9 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
existingLoginSource: &models.LoginSource{
Type: models.LoginOAuth2,
Cfg: &ldap.Source{},
Cfg: &models.LDAPConfig{
Source: &ldap.Source{},
},
},
errMsg: "Invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2",
},
@ -877,7 +927,9 @@ func TestUpdateLdapBindDn(t *testing.T) {
}
return &models.LoginSource{
Type: models.LoginLDAP,
Cfg: &ldap.Source{},
Cfg: &models.LDAPConfig{
Source: &ldap.Source{},
},
}, nil
},
}
@ -926,7 +978,6 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
"--user-search-base", "ou=Users,dc=full-domain-simple,dc=org",
"--user-filter", "(&(objectClass=posixAccount)(full-simple-cn=%s))",
"--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)",
"--restricted-filter", "(memberOf=cn=restricted-group,ou=example,dc=full-domain-simple,dc=org)",
"--username-attribute", "uid-simple full",
"--firstname-attribute", "givenName-simple full",
"--surname-attribute", "sn-simple full",
@ -936,25 +987,26 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
id: 7,
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Name: "ldap (simple auth) source full",
IsActive: false,
Cfg: &ldap.Source{
Name: "ldap (simple auth) source full",
Host: "ldap-simple-server full",
Port: 987,
SecurityProtocol: ldap.SecurityProtocol(2),
SkipVerify: true,
UserDN: "cn=%s,ou=Users,dc=full-domain-simple,dc=org",
UserBase: "ou=Users,dc=full-domain-simple,dc=org",
AttributeUsername: "uid-simple full",
AttributeName: "givenName-simple full",
AttributeSurname: "sn-simple full",
AttributeMail: "mail-simple full",
AttributeSSHPublicKey: "publickey-simple full",
Filter: "(&(objectClass=posixAccount)(full-simple-cn=%s))",
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)",
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-simple,dc=org)",
Type: models.LoginDLDAP,
Name: "ldap (simple auth) source full",
IsActived: false,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Name: "ldap (simple auth) source full",
Host: "ldap-simple-server full",
Port: 987,
SecurityProtocol: ldap.SecurityProtocol(2),
SkipVerify: true,
UserDN: "cn=%s,ou=Users,dc=full-domain-simple,dc=org",
UserBase: "ou=Users,dc=full-domain-simple,dc=org",
AttributeUsername: "uid-simple full",
AttributeName: "givenName-simple full",
AttributeSurname: "sn-simple full",
AttributeMail: "mail-simple full",
AttributeSSHPublicKey: "publickey-simple full",
Filter: "(&(objectClass=posixAccount)(full-simple-cn=%s))",
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)",
},
},
},
},
@ -966,7 +1018,9 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{},
Cfg: &models.LDAPConfig{
Source: &ldap.Source{},
},
},
},
// case 2
@ -979,8 +1033,10 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Name: "ldap (simple auth) source",
Cfg: &ldap.Source{
Name: "ldap (simple auth) source",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Name: "ldap (simple auth) source",
},
},
},
},
@ -992,14 +1048,18 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
"--not-active",
},
existingLoginSource: &models.LoginSource{
Type: models.LoginDLDAP,
IsActive: true,
Cfg: &ldap.Source{},
Type: models.LoginDLDAP,
IsActived: true,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{},
},
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
IsActive: false,
Cfg: &ldap.Source{},
Type: models.LoginDLDAP,
IsActived: false,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{},
},
},
},
// case 4
@ -1011,8 +1071,10 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{
SecurityProtocol: ldap.SecurityProtocol(2),
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
SecurityProtocol: ldap.SecurityProtocol(2),
},
},
},
},
@ -1025,8 +1087,10 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{
SkipVerify: true,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
SkipVerify: true,
},
},
},
},
@ -1039,8 +1103,10 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{
Host: "ldap-server",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Host: "ldap-server",
},
},
},
},
@ -1053,8 +1119,10 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{
Port: 987,
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Port: 987,
},
},
},
},
@ -1067,8 +1135,10 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{
UserBase: "ou=Users,dc=domain,dc=org",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
UserBase: "ou=Users,dc=domain,dc=org",
},
},
},
},
@ -1081,8 +1151,10 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{
Filter: "(&(objectClass=posixAccount)(cn=%s))",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
Filter: "(&(objectClass=posixAccount)(cn=%s))",
},
},
},
},
@ -1095,8 +1167,10 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
},
},
},
},
@ -1109,8 +1183,10 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{
AttributeUsername: "uid",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
AttributeUsername: "uid",
},
},
},
},
@ -1123,8 +1199,10 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{
AttributeName: "givenName",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
AttributeName: "givenName",
},
},
},
},
@ -1137,8 +1215,10 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{
AttributeSurname: "sn",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
AttributeSurname: "sn",
},
},
},
},
@ -1151,9 +1231,10 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{
AttributeMail: "mail",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
AttributeMail: "mail",
},
},
},
},
@ -1166,8 +1247,10 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{
AttributeSSHPublicKey: "publickey",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
AttributeSSHPublicKey: "publickey",
},
},
},
},
@ -1180,8 +1263,10 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
loginSource: &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{
UserDN: "cn=%s,ou=Users,dc=domain,dc=org",
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
UserDN: "cn=%s,ou=Users,dc=domain,dc=org",
},
},
},
},
@ -1209,7 +1294,9 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
existingLoginSource: &models.LoginSource{
Type: models.LoginPAM,
Cfg: &ldap.Source{},
Cfg: &models.LDAPConfig{
Source: &ldap.Source{},
},
},
errMsg: "Invalid authentication type. expected: LDAP (simple auth), actual: PAM",
},
@ -1239,7 +1326,9 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
}
return &models.LoginSource{
Type: models.LoginDLDAP,
Cfg: &ldap.Source{},
Cfg: &models.LDAPConfig{
Source: &ldap.Source{},
},
}, nil
},
}

View File

@ -7,13 +7,8 @@
package cmd
import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/setting"
@ -37,25 +32,6 @@ func argsSet(c *cli.Context, args ...string) error {
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)
}
@ -70,25 +46,3 @@ func initDBDisableConsole(disableConsole bool) error {
}
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
}

View File

@ -27,10 +27,10 @@ func runConvert(ctx *cli.Context) error {
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.LogRootPath)
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 {

View File

@ -1,61 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package 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
docs = docs[strings.Index(docs, "#"):]
}
out := os.Stdout
if ctx.String("output") != "" {
fi, err := os.Create(ctx.String("output"))
if err != nil {
return err
}
defer fi.Close()
out = fi
}
_, err = fmt.Fprintln(out, docs)
return err
}

View File

@ -1,215 +0,0 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"context"
"fmt"
golog "log"
"os"
"strings"
"text/tabwriter"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/migrations"
"code.gitea.io/gitea/modules/doctor"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"xorm.io/xorm"
"github.com/urfave/cli"
)
// CmdDoctor represents the available doctor sub-command.
var CmdDoctor = cli.Command{
Name: "doctor",
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{
Name: "list",
Usage: "List the available checks",
},
cli.BoolFlag{
Name: "default",
Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)",
},
cli.StringSliceFlag{
Name: "run",
Usage: "Run the provided checks - (if --default is set, the default checks will also run)",
},
cli.BoolFlag{
Name: "all",
Usage: "Run all the available checks",
},
cli.BoolFlag{
Name: "fix",
Usage: "Automatically fix what we can",
},
cli.StringFlag{
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,
},
}
var cmdRecreateTable = cli.Command{
Name: "recreate-table",
Usage: "Recreate tables from XORM definitions and copy the data.",
ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
Usage: "Print SQL commands sent",
},
},
Description: `The database definitions Gitea uses change across versions, sometimes changing default values and leaving old unused columns.
This command will cause Xorm to recreate tables, copying over the data and deleting the old table.
You should back-up your database before doing this and ensure that your database is up-to-date first.`,
Action: runRecreateTable,
}
func runRecreateTable(ctx *cli.Context) error {
// Redirect the default golog to here
golog.SetFlags(0)
golog.SetPrefix("")
golog.SetOutput(log.NewLoggerAsWriter("INFO", log.GetLogger(log.DEFAULT)))
setting.NewContext()
setting.InitDBConfig()
setting.EnableXORMLog = ctx.Bool("debug")
setting.Database.LogSQL = ctx.Bool("debug")
setting.Cfg.Section("log").Key("XORM").SetValue(",")
setting.NewXORMLogService(!ctx.Bool("debug"))
if err := models.SetEngine(); err != nil {
fmt.Println(err)
fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.")
return nil
}
args := ctx.Args()
names := make([]string, 0, ctx.NArg())
for i := 0; i < ctx.NArg(); i++ {
names = append(names, args.Get(i))
}
beans, err := models.NamesToBean(names...)
if err != nil {
return err
}
recreateTables := migrations.RecreateTables(beans...)
return models.NewEngine(context.Background(), func(x *xorm.Engine) error {
if err := migrations.EnsureUpToDate(x); err != nil {
return err
}
return recreateTables(x)
})
}
func runDoctor(ctx *cli.Context) error {
// Silence the default loggers
log.DelNamedLogger("console")
log.DelNamedLogger(log.DEFAULT)
// Now setup our own
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))
} else 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))
}
// Finally redirect the default golog to here
golog.SetFlags(0)
golog.SetPrefix("")
golog.SetOutput(log.NewLoggerAsWriter("INFO", log.GetLogger(log.DEFAULT)))
if ctx.IsSet("list") {
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
_, _ = w.Write([]byte("Default\tName\tTitle\n"))
for _, check := range doctor.Checks {
if check.IsDefault {
_, _ = w.Write([]byte{'*'})
}
_, _ = w.Write([]byte{'\t'})
_, _ = w.Write([]byte(check.Name))
_, _ = w.Write([]byte{'\t'})
_, _ = w.Write([]byte(check.Title))
_, _ = w.Write([]byte{'\n'})
}
return w.Flush()
}
var checks []*doctor.Check
if ctx.Bool("all") {
checks = doctor.Checks
} else if ctx.IsSet("run") {
addDefault := ctx.Bool("default")
names := ctx.StringSlice("run")
for i, name := range names {
names[i] = strings.ToLower(strings.TrimSpace(name))
}
for _, check := range doctor.Checks {
if addDefault && check.IsDefault {
checks = append(checks, check)
continue
}
for _, name := range names {
if name == check.Name {
checks = append(checks, check)
break
}
}
}
} else {
for _, check := range doctor.Checks {
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",
1000,
"console",
"console",
fmt.Sprintf(`{"level":"INFO","stacktracelevel":"NONE","colorize":%t,"flags":-1}`, colorize)); err != nil {
fmt.Println(err)
return err
}
logger := log.GetLogger("doctorouter")
defer logger.Close()
return doctor.RunChecks(logger, ctx.Bool("fix"), checks)
}

View File

@ -8,89 +8,20 @@ package cmd
import (
"fmt"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/json"
"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"
archiver "github.com/mholt/archiver/v3"
"github.com/unknwon/cae/zip"
"github.com/unknwon/com"
"github.com/urfave/cli"
)
func addFile(w archiver.Writer, filePath string, absPath string, verbose bool) error {
if verbose {
log.Info("Adding file %s\n", filePath)
}
file, err := os.Open(absPath)
if err != nil {
return err
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
return err
}
return w.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: fileInfo,
CustomName: filePath,
},
ReadCloser: file,
})
}
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, ".") {
return true, nil
}
return false, nil
}
type outputType struct {
Enum []string
Default string
selected string
}
func (o outputType) Join() string {
return strings.Join(o.Enum, ", ")
}
func (o *outputType) Set(value string) error {
for _, enum := range o.Enum {
if enum == value {
o.selected = value
return nil
}
}
return fmt.Errorf("allowed values are %s", o.Join())
}
func (o outputType) String() string {
if o.selected == "" {
return o.Default
}
return o.selected
}
var outputTypeEnum = &outputType{
Enum: []string{"zip", "tar", "tar.gz", "tar.xz", "tar.bz2"},
Default: "zip",
}
// CmdDump represents the available dump sub-command.
var CmdDump = cli.Command{
Name: "dump",
@ -102,7 +33,7 @@ It can be used for backup and capture Gitea server image to send to maintainer`,
cli.StringFlag{
Name: "file, f",
Value: fmt.Sprintf("gitea-dump-%d.zip", time.Now().Unix()),
Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.",
Usage: "Name of the dump file which will be created.",
},
cli.BoolFlag{
Name: "verbose, V",
@ -121,57 +52,11 @@ It can be used for backup and capture Gitea server image to send to maintainer`,
Name: "skip-repository, R",
Usage: "Skip the repository dumping",
},
cli.BoolFlag{
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.GenericFlag{
Name: "type",
Value: outputTypeEnum,
Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),
},
},
}
func fatal(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
log.Fatal(format, args...)
}
func runDump(ctx *cli.Context) error {
var file *os.File
fileName := ctx.String("file")
if fileName == "-" {
file = os.Stdout
err := log.DelLogger("console")
if err != nil {
fatal("Deleting default logger failed. Can not write to stdout: %v", err)
}
}
setting.NewContext()
// make sure we are logging to the console no matter what the configuration tells us do to
if _, err := setting.Cfg.Section("log").NewKey("MODE", "console"); err != nil {
fatal("Setting logging mode to console failed: %v", err)
}
if _, err := setting.Cfg.Section("log.console").NewKey("STDERR", "true"); err != nil {
fatal("Setting console logger to stderr failed: %v", err)
}
if !setting.InstallLock {
log.Error("Is '%s' really the right config path?\n", setting.CustomConf)
return fmt.Errorf("gitea is not initialized")
}
setting.NewServices() // cannot access session settings otherwise
err := models.SetEngine()
@ -179,216 +64,112 @@ func runDump(ctx *cli.Context) error {
return err
}
if err := storage.Init(); err != nil {
return err
}
if file == nil {
file, err = os.Create(fileName)
if err != nil {
fatal("Unable to open %s: %v", fileName, err)
}
}
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))
} else {
iface, err = archiver.ByExtension(fileName)
}
if err != nil {
fatal("Unable to get archiver for extension: %v", err)
}
w, _ := iface.(archiver.Writer)
if err := w.Create(file); err != nil {
fatal("Creating archiver.Writer failed: %v", err)
}
defer w.Close()
if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
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 {
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 {
info, err := object.Stat()
if err != nil {
return err
}
return w.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
CustomName: path.Join("data", "lfs", objPath),
},
ReadCloser: object,
})
}); err != nil {
fatal("Failed to dump LFS objects: %v", err)
}
}
tmpDir := ctx.String("tempdir")
if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
fatal("Path does not exist: %s", tmpDir)
log.Fatalf("Path does not exist: %s", tmpDir)
}
tmpWorkDir, err := ioutil.TempDir(tmpDir, "gitea-dump-")
if err != nil {
log.Fatalf("Failed to create tmp work directory: %v", err)
}
log.Printf("Creating tmp work dir: %s", tmpWorkDir)
// work-around #1103
if os.Getenv("TMPDIR") == "" {
os.Setenv("TMPDIR", tmpWorkDir)
}
dbDump, err := ioutil.TempFile(tmpDir, "gitea-db.sql")
dbDump := path.Join(tmpWorkDir, "gitea-db.sql")
fileName := ctx.String("file")
log.Printf("Packing dump files...")
z, err := zip.Create(fileName)
if err != nil {
fatal("Failed to create tmp file: %v", err)
log.Fatalf("Failed to create %s: %v", fileName, err)
}
defer func() {
if err := util.Remove(dbDump.Name()); err != nil {
log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err)
zip.Verbose = ctx.Bool("verbose")
if ctx.IsSet("skip-repository") {
log.Printf("Skip dumping local repositories")
} else {
log.Printf("Dumping local repositories...%s", setting.RepoRootPath)
reposDump := path.Join(tmpWorkDir, "gitea-repo.zip")
if err := zip.PackTo(setting.RepoRootPath, reposDump, true); err != nil {
log.Fatalf("Failed to dump local repositories: %v", err)
}
}()
if err := z.AddFile("gitea-repo.zip", reposDump); err != nil {
log.Fatalf("Failed to include gitea-repo.zip: %v", err)
}
}
targetDBType := ctx.String("database")
if len(targetDBType) > 0 && targetDBType != setting.Database.Type {
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
log.Printf("Dumping database %s => %s...", setting.Database.Type, targetDBType)
} else {
log.Info("Dumping database...")
log.Printf("Dumping database...")
}
if err := models.DumpDatabase(dbDump.Name(), targetDBType); err != nil {
fatal("Failed to dump database: %v", err)
if err := models.DumpDatabase(dbDump, targetDBType); err != nil {
log.Fatalf("Failed to dump database: %v", err)
}
if err := addFile(w, "gitea-db.sql", dbDump.Name(), verbose); err != nil {
fatal("Failed to include gitea-db.sql: %v", err)
if err := z.AddFile("gitea-db.sql", dbDump); err != nil {
log.Fatalf("Failed to include gitea-db.sql: %v", err)
}
if len(setting.CustomConf) > 0 {
log.Info("Adding custom configuration file from %s", setting.CustomConf)
if err := addFile(w, "app.ini", setting.CustomConf, verbose); err != nil {
fatal("Failed to include specified app.ini: %v", err)
log.Printf("Adding custom configuration file from %s", setting.CustomConf)
if err := z.AddFile("app.ini", setting.CustomConf); err != nil {
log.Fatalf("Failed to include specified app.ini: %v", err)
}
}
if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
log.Info("Skipping custom directory")
customDir, err := os.Stat(setting.CustomPath)
if err == nil && customDir.IsDir() {
if err := z.AddDir("custom", setting.CustomPath); err != nil {
log.Fatalf("Failed to include custom: %v", err)
}
} 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)
}
} else {
log.Info("Custom dir %s doesn't exist, skipped", setting.CustomPath)
log.Printf("Custom dir %s doesn't exist, skipped", setting.CustomPath)
}
if com.IsExist(setting.AppDataPath) {
log.Printf("Packing data directory...%s", setting.AppDataPath)
var sessionAbsPath string
if setting.SessionConfig.Provider == "file" {
sessionAbsPath = setting.SessionConfig.ProviderConfig
}
if err := zipAddDirectoryExclude(z, "data", setting.AppDataPath, sessionAbsPath); err != nil {
log.Fatalf("Failed to include data directory: %v", err)
}
}
isExist, err := util.IsExist(setting.AppDataPath)
if err != nil {
log.Error("Unable to check if %s exists. Error: %v", setting.AppDataPath, err)
}
if isExist {
log.Info("Packing data directory...%s", setting.AppDataPath)
var excludes []string
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
}
excludes = append(excludes, opts.ProviderConfig)
}
excludes = append(excludes, setting.RepoRootPath)
excludes = append(excludes, setting.LFS.Path)
excludes = append(excludes, setting.Attachment.Path)
excludes = append(excludes, setting.LogRootPath)
excludes = append(excludes, absFileName)
if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil {
fatal("Failed to include data directory: %v", err)
}
if err := z.AddDir("log", setting.LogRootPath); err != nil {
log.Fatalf("Failed to include log: %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 {
info, err := object.Stat()
if err != nil {
return err
}
return w.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
CustomName: path.Join("data", "attachments", objPath),
},
ReadCloser: object,
})
}); err != nil {
fatal("Failed to dump attachments: %v", err)
if err = z.Close(); err != nil {
_ = os.Remove(fileName)
log.Fatalf("Failed to save %s: %v", fileName, 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.LogRootPath)
if err != nil {
log.Error("Unable to check if %s exists. Error: %v", setting.LogRootPath, err)
}
if isExist {
if err := addRecursiveExclude(w, "log", setting.LogRootPath, []string{absFileName}, verbose); err != nil {
fatal("Failed to include log: %v", err)
}
}
if err := os.Chmod(fileName, 0600); err != nil {
log.Printf("Can't change file access permissions mask to 0600: %v", err)
}
if fileName != "-" {
if err = w.Close(); err != nil {
_ = util.Remove(fileName)
fatal("Failed to save %s: %v", fileName, err)
}
log.Printf("Removing tmp work dir: %s", tmpWorkDir)
if err := os.Chmod(fileName, 0600); err != nil {
log.Info("Can't change file access permissions mask to 0600: %v", err)
}
}
if fileName != "-" {
log.Info("Finish dumping in file %s", fileName)
} else {
log.Info("Finish dumping to stdout")
if err := os.RemoveAll(tmpWorkDir); err != nil {
log.Fatalf("Failed to remove %s: %v", tmpWorkDir, err)
}
log.Printf("Finish dumping in file %s", fileName)
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 {
// zipAddDirectoryExclude zips absPath to specified zipPath inside z excluding excludeAbsPath
func zipAddDirectoryExclude(zip *zip.ZipArchive, zipPath, absPath string, excludeAbsPath string) error {
absPath, err := filepath.Abs(absPath)
if err != nil {
return err
@ -399,24 +180,24 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA
}
defer dir.Close()
zip.AddEmptyDir(zipPath)
files, err := dir.Readdir(0)
if err != nil {
return err
}
for _, file := range files {
currentAbsPath := path.Join(absPath, file.Name())
currentInsidePath := path.Join(insidePath, file.Name())
currentZipPath := path.Join(zipPath, file.Name())
if file.IsDir() {
if !contains(excludeAbsPath, currentAbsPath) {
if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
return err
}
if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
if currentAbsPath != excludeAbsPath {
if err = zipAddDirectoryExclude(zip, currentZipPath, currentAbsPath, excludeAbsPath); err != nil {
return err
}
}
} else {
if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil {
if err = zip.AddFile(currentZipPath, currentAbsPath); err != nil {
return err
}
}

View File

@ -1,162 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"context"
"errors"
"strings"
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/migrations"
"code.gitea.io/gitea/modules/migrations/base"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"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 {
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.LogRootPath)
setting.InitDBConfig()
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)
var 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(unit) {
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
}
}
}
if err := migrations.DumpRepository(
context.Background(),
ctx.String("repo_dir"),
ctx.String("owner_name"),
opts,
); err != nil {
log.Fatal("Failed to dump repository: %v", err)
return err
}
log.Trace("Dump finished!!!")
return nil
}

View File

@ -1,334 +0,0 @@
// 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.
//go:build bindata
// +build bindata
package cmd
import (
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options"
"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"
)
// Cmdembedded represents the available extract sub-command.
var (
Cmdembedded = cli.Command{
Name: "embedded",
Usage: "Extract embedded resources",
Description: "A command for extracting embedded resources, like templates and images",
Subcommands: []cli.Command{
subcmdList,
subcmdView,
subcmdExtract,
},
}
subcmdList = cli.Command{
Name: "list",
Usage: "List files matching the given pattern",
Action: runList,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "include-vendored,vendor",
Usage: "Include files under public/vendor as well",
},
},
}
subcmdView = cli.Command{
Name: "view",
Usage: "View a file matching the given pattern",
Action: runView,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "include-vendored,vendor",
Usage: "Include files under public/vendor as well",
},
},
}
subcmdExtract = cli.Command{
Name: "extract",
Usage: "Extract resources",
Action: runExtract,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "include-vendored,vendor",
Usage: "Include files under public/vendor as well",
},
cli.BoolFlag{
Name: "overwrite",
Usage: "Overwrite files if they already exist",
},
cli.BoolFlag{
Name: "rename",
Usage: "Rename files as {name}.bak if they already exist (overwrites previous .bak)",
},
cli.BoolFlag{
Name: "custom",
Usage: "Extract to the 'custom' directory as per app.ini",
},
cli.StringFlag{
Name: "destination,dest-dir",
Usage: "Extract to the specified directory",
},
},
}
sections map[string]*section
assets []asset
)
type section struct {
Path string
Names func() []string
IsDir func(string) (bool, error)
Asset func(string) ([]byte, error)
}
type asset struct {
Section *section
Name string
Path string
}
func initEmbeddedExtractor(c *cli.Context) error {
// Silence the console logger
log.DelNamedLogger("console")
log.DelNamedLogger(log.DEFAULT)
// Read configuration file
setting.NewContext()
pats, err := getPatterns(c.Args())
if err != nil {
return err
}
sections := make(map[string]*section, 3)
sections["public"] = &section{Path: "public", Names: public.AssetNames, IsDir: public.AssetIsDir, Asset: public.Asset}
sections["options"] = &section{Path: "options", Names: options.AssetNames, IsDir: options.AssetIsDir, Asset: options.Asset}
sections["templates"] = &section{Path: "templates", Names: templates.AssetNames, IsDir: templates.AssetIsDir, Asset: templates.Asset}
for _, sec := range sections {
assets = append(assets, buildAssetList(sec, pats, c)...)
}
// Sort assets
sort.SliceStable(assets, func(i, j int) bool {
return assets[i].Path < assets[j].Path
})
return nil
}
func runList(c *cli.Context) error {
if err := runListDo(c); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
func runView(c *cli.Context) error {
if err := runViewDo(c); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
func runExtract(c *cli.Context) error {
if err := runExtractDo(c); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
func runListDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil {
return err
}
for _, a := range assets {
fmt.Println(a.Path)
}
return nil
}
func runViewDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil {
return err
}
if len(assets) == 0 {
return fmt.Errorf("No files matched the given pattern")
} else if len(assets) > 1 {
return fmt.Errorf("Too many files matched the given pattern; try to be more specific")
}
data, err := assets[0].Section.Asset(assets[0].Name)
if err != nil {
return fmt.Errorf("%s: %v", assets[0].Path, err)
}
if _, err = os.Stdout.Write(data); err != nil {
return fmt.Errorf("%s: %v", assets[0].Path, err)
}
return nil
}
func runExtractDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil {
return err
}
if len(c.Args()) == 0 {
return fmt.Errorf("A list of pattern of files to extract is mandatory (e.g. '**' for all)")
}
destdir := "."
if c.IsSet("destination") {
destdir = c.String("destination")
} else if c.Bool("custom") {
destdir = setting.CustomPath
fmt.Println("Using app.ini at", setting.CustomConf)
}
fi, err := os.Stat(destdir)
if errors.Is(err, os.ErrNotExist) {
// In case Windows users attempt to provide a forward-slash path
wdestdir := filepath.FromSlash(destdir)
if wfi, werr := os.Stat(wdestdir); werr == nil {
destdir = wdestdir
fi = wfi
err = nil
}
}
if err != nil {
return fmt.Errorf("%s: %s", destdir, err)
} else if !fi.IsDir() {
return fmt.Errorf("%s is not a directory.", destdir)
}
fmt.Printf("Extracting to %s:\n", destdir)
overwrite := c.Bool("overwrite")
rename := c.Bool("rename")
for _, a := range assets {
if err := extractAsset(destdir, a, overwrite, rename); err != nil {
// Non-fatal error
fmt.Fprintf(os.Stderr, "%s: %v", a.Path, err)
}
}
return nil
}
func extractAsset(d string, a asset, overwrite, rename bool) error {
dest := filepath.Join(d, filepath.FromSlash(a.Path))
dir := filepath.Dir(dest)
data, err := a.Section.Asset(a.Name)
if err != nil {
return fmt.Errorf("%s: %v", a.Path, err)
}
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return fmt.Errorf("%s: %v", dir, err)
}
perms := os.ModePerm & 0666
fi, err := os.Lstat(dest)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("%s: %v", dest, err)
}
} else if !overwrite && !rename {
fmt.Printf("%s already exists; skipped.\n", dest)
return nil
} 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: %v", dest, err)
}
// Attempt to respect file permissions mask (even if user:group will be set anew)
perms = fi.Mode()
}
file, err := os.OpenFile(dest, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perms)
if err != nil {
return fmt.Errorf("%s: %v", dest, err)
}
defer file.Close()
if _, err = file.Write(data); err != nil {
return fmt.Errorf("%s: %v", dest, err)
}
fmt.Println(dest)
return nil
}
func buildAssetList(sec *section, globs []glob.Glob, c *cli.Context) []asset {
var results = make([]asset, 0, 64)
for _, name := range sec.Names() {
if isdir, err := sec.IsDir(name); !isdir && err == nil {
if sec.Path == "public" &&
strings.HasPrefix(name, "vendor/") &&
!c.Bool("include-vendored") {
continue
}
matchName := sec.Path + "/" + name
for _, g := range globs {
if g.Match(matchName) {
results = append(results, asset{Section: sec,
Name: name,
Path: sec.Path + "/" + name})
break
}
}
}
}
return results
}
func getPatterns(args []string) ([]glob.Glob, error) {
if len(args) == 0 {
args = []string{"**"}
}
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: %v", args[i], err)
} else {
pat[i] = g
}
}
return pat, nil
}

View File

@ -1,31 +0,0 @@
// 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.
//go:build !bindata
// +build !bindata
package cmd
import (
"fmt"
"os"
"github.com/urfave/cli"
)
// Cmdembedded represents the available extract sub-command.
var (
Cmdembedded = cli.Command{
Name: "embedded",
Usage: "Extract embedded resources",
Description: "A command for extracting embedded resources, like templates and images",
Action: extractorNotImplemented,
}
)
func extractorNotImplemented(c *cli.Context) error {
err := fmt.Errorf("Sorry: the 'embedded' subcommand is not available in builds without bindata")
fmt.Fprintf(os.Stderr, "%s\n", err)
return err
}

View File

@ -7,11 +7,9 @@ package cmd
import (
"fmt"
"os"
"code.gitea.io/gitea/modules/generate"
"github.com/mattn/go-isatty"
"github.com/urfave/cli"
)
@ -61,27 +59,17 @@ func runGenerateInternalToken(c *cli.Context) error {
return err
}
fmt.Printf("%s", internalToken)
if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Printf("\n")
}
fmt.Printf("%s\n", internalToken)
return nil
}
func runGenerateLfsJwtSecret(c *cli.Context) error {
JWTSecretBase64, err := generate.NewJwtSecretBase64()
JWTSecretBase64, err := generate.NewJwtSecret()
if err != nil {
return err
}
fmt.Printf("%s", JWTSecretBase64)
if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Printf("\n")
}
fmt.Printf("%s\n", JWTSecretBase64)
return nil
}
@ -91,11 +79,6 @@ func runGenerateSecretKey(c *cli.Context) error {
return err
}
fmt.Printf("%s", secretKey)
if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Printf("\n")
}
fmt.Printf("%s\n", secretKey)
return nil
}

View File

@ -8,26 +8,18 @@ import (
"bufio"
"bytes"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/urfave/cli"
)
const (
hookBatchSize = 30
)
var (
// CmdHook represents the available hooks sub-command.
CmdHook = cli.Command{
@ -38,7 +30,6 @@ var (
subcmdHookPreReceive,
subcmdHookUpdate,
subcmdHookPostReceive,
subcmdHookProcReceive,
},
}
@ -47,183 +38,42 @@ var (
Usage: "Delegate pre-receive Git hook",
Description: "This command should only be called by Git",
Action: runHookPreReceive,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
},
},
}
subcmdHookUpdate = cli.Command{
Name: "update",
Usage: "Delegate update Git hook",
Description: "This command should only be called by Git",
Action: runHookUpdate,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
},
},
}
subcmdHookPostReceive = cli.Command{
Name: "post-receive",
Usage: "Delegate post-receive Git hook",
Description: "This command should only be called by Git",
Action: runHookPostReceive,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
},
},
}
// 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 {
internal io.Writer
buf *bytes.Buffer
timer *time.Timer
}
func newDelayWriter(internal io.Writer, delay time.Duration) *delayWriter {
timer := time.NewTimer(delay)
return &delayWriter{
internal: internal,
buf: &bytes.Buffer{},
timer: timer,
}
}
func (d *delayWriter) Write(p []byte) (n int, err error) {
if d.buf != nil {
select {
case <-d.timer.C:
_, err := d.internal.Write(d.buf.Bytes())
if err != nil {
return 0, err
}
d.buf = nil
return d.internal.Write(p)
default:
return d.buf.Write(p)
}
}
return d.internal.Write(p)
}
func (d *delayWriter) WriteString(s string) (n int, err error) {
if d.buf != nil {
select {
case <-d.timer.C:
_, err := d.internal.Write(d.buf.Bytes())
if err != nil {
return 0, err
}
d.buf = nil
return d.internal.Write([]byte(s))
default:
return d.buf.WriteString(s)
}
}
return d.internal.Write([]byte(s))
}
func (d *delayWriter) Close() error {
if d == nil {
return nil
}
stopped := util.StopTimer(d.timer)
if stopped || d.buf == nil {
return nil
}
_, err := d.internal.Write(d.buf.Bytes())
d.buf = nil
return err
}
type nilWriter struct{}
func (n *nilWriter) Write(p []byte) (int, error) {
return len(p), nil
}
func (n *nilWriter) WriteString(s string) (int, error) {
return len(s), nil
}
func runHookPreReceive(c *cli.Context) error {
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.
If you are pushing over SSH you must push with a key managed by
Gitea or set your environment appropriately.`, "")
}
return nil
}
// the environment is set by serv command
isWiki := os.Getenv(models.EnvRepoIsWiki) == "true"
setup("hooks/pre-receive.log")
// 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)
prID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchPRID), 10, 64)
isDeployKey, _ := strconv.ParseBool(os.Getenv(models.EnvIsDeployKey))
hookOptions := private.HookOptions{
UserID: userID,
GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories),
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
GitPushOptions: pushOptions(),
PullRequestID: prID,
IsDeployKey: isDeployKey,
}
buf := bytes.NewBuffer(nil)
scanner := bufio.NewScanner(os.Stdin)
oldCommitIDs := make([]string, hookBatchSize)
newCommitIDs := make([]string, hookBatchSize)
refFullNames := make([]string, hookBatchSize)
count := 0
total := 0
lastline := 0
var out io.Writer
out = &nilWriter{}
if setting.Git.VerbosePush {
if setting.Git.VerbosePushDelay > 0 {
dWriter := newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
defer dWriter.Close()
out = dWriter
} else {
out = os.Stdout
}
}
supportProcRecive := false
if git.CheckGitVersionAtLeast("2.29") == nil {
supportProcRecive = true
}
for scanner.Scan() {
buf.Write(scanner.Bytes())
buf.WriteByte('\n')
// TODO: support news feeds for wiki
if isWiki {
continue
@ -237,138 +87,62 @@ Gitea or set your environment appropriately.`, "")
oldCommitID := string(fields[0])
newCommitID := string(fields[1])
refFullName := string(fields[2])
total++
lastline++
// If the ref is a branch or tag, check if it's protected
// if supportProcRecive all ref should be checked because
// permission check was delayed
if supportProcRecive || strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
oldCommitIDs[count] = oldCommitID
newCommitIDs[count] = newCommitID
refFullNames[count] = refFullName
count++
fmt.Fprintf(out, "*")
if count >= hookBatchSize {
fmt.Fprintf(out, " Checking %d references\n", count)
hookOptions.OldCommitIDs = oldCommitIDs
hookOptions.NewCommitIDs = newCommitIDs
hookOptions.RefFullNames = refFullNames
statusCode, msg := private.HookPreReceive(ctx, username, reponame, hookOptions)
switch statusCode {
case http.StatusOK:
// no-op
case http.StatusInternalServerError:
return fail("Internal Server Error", msg)
default:
return fail(msg, "")
}
count = 0
lastline = 0
// If the ref is a branch, check if it's protected
if strings.HasPrefix(refFullName, git.BranchPrefix) {
statusCode, msg := private.HookPreReceive(username, reponame, private.HookOptions{
OldCommitID: oldCommitID,
NewCommitID: newCommitID,
RefFullName: refFullName,
UserID: userID,
GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories),
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
ProtectedBranchID: prID,
IsDeployKey: isDeployKey,
})
switch statusCode {
case http.StatusInternalServerError:
fail("Internal Server Error", msg)
case http.StatusForbidden:
fail(msg, "")
}
} else {
fmt.Fprintf(out, ".")
}
if lastline >= hookBatchSize {
fmt.Fprintf(out, "\n")
lastline = 0
}
}
if count > 0 {
hookOptions.OldCommitIDs = oldCommitIDs[:count]
hookOptions.NewCommitIDs = newCommitIDs[:count]
hookOptions.RefFullNames = refFullNames[:count]
fmt.Fprintf(out, " Checking %d references\n", count)
statusCode, msg := private.HookPreReceive(ctx, username, reponame, hookOptions)
switch statusCode {
case http.StatusInternalServerError:
return fail("Internal Server Error", msg)
case http.StatusForbidden:
return fail(msg, "")
}
} else if lastline > 0 {
fmt.Fprintf(out, "\n")
lastline = 0
}
fmt.Fprintf(out, "Checked %d references in total\n", total)
return nil
}
func runHookUpdate(c *cli.Context) error {
// Update is empty and is kept only for backwards compatibility
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
return nil
}
setup("hooks/update.log")
return nil
}
func runHookPostReceive(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
// First of all run update-server-info no matter what
if _, err := git.NewCommand("update-server-info").SetParentContext(ctx).Run(); err != nil {
return fmt.Errorf("Failed to call 'git update-server-info': %v", err)
}
// Now if we're an internal don't do anything else
if os.Getenv(models.EnvIsInternal) == "true" {
return nil
}
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.
If you are pushing over SSH you must push with a key managed by
Gitea or set your environment appropriately.`, "")
}
return nil
}
var out io.Writer
var dWriter *delayWriter
out = &nilWriter{}
if setting.Git.VerbosePush {
if setting.Git.VerbosePushDelay > 0 {
dWriter = newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
defer dWriter.Close()
out = dWriter
} else {
out = os.Stdout
}
}
setup("hooks/post-receive.log")
// the environment is set by serv command
// the environment setted on serv command
repoUser := os.Getenv(models.EnvRepoUsername)
isWiki := os.Getenv(models.EnvRepoIsWiki) == "true"
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,
UserID: pusherID,
GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories),
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
GitPushOptions: pushOptions(),
}
oldCommitIDs := make([]string, hookBatchSize)
newCommitIDs := make([]string, hookBatchSize)
refFullNames := make([]string, hookBatchSize)
count := 0
total := 0
wasEmpty := false
masterPushed := false
results := make([]private.HookPostReceiveBranchResult, 0)
buf := bytes.NewBuffer(nil)
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
buf.Write(scanner.Bytes())
buf.WriteByte('\n')
// TODO: support news feeds for wiki
if isWiki {
continue
@ -379,430 +153,35 @@ Gitea or set your environment appropriately.`, "")
continue
}
fmt.Fprintf(out, ".")
oldCommitIDs[count] = string(fields[0])
newCommitIDs[count] = string(fields[1])
refFullNames[count] = string(fields[2])
if refFullNames[count] == git.BranchPrefix+"master" && newCommitIDs[count] != git.EmptySHA && count == total {
masterPushed = true
oldCommitID := string(fields[0])
newCommitID := string(fields[1])
refFullName := string(fields[2])
res, err := private.HookPostReceive(repoUser, repoName, private.HookOptions{
OldCommitID: oldCommitID,
NewCommitID: newCommitID,
RefFullName: refFullName,
UserID: pusherID,
UserName: pusherName,
})
if res == nil {
fail("Internal Server Error", err)
}
count++
total++
if count >= hookBatchSize {
fmt.Fprintf(out, " Processing %d references\n", count)
hookOptions.OldCommitIDs = oldCommitIDs
hookOptions.NewCommitIDs = newCommitIDs
hookOptions.RefFullNames = refFullNames
resp, err := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
if resp == nil {
_ = dWriter.Close()
hookPrintResults(results)
return fail("Internal Server Error", err)
}
wasEmpty = wasEmpty || resp.RepoWasEmpty
results = append(results, resp.Results...)
count = 0
}
}
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")
if err != nil {
return fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
}
}
fmt.Fprintf(out, "Processed %d references in total\n", total)
_ = dWriter.Close()
hookPrintResults(results)
return nil
}
hookOptions.OldCommitIDs = oldCommitIDs[:count]
hookOptions.NewCommitIDs = newCommitIDs[:count]
hookOptions.RefFullNames = refFullNames[:count]
fmt.Fprintf(out, " Processing %d references\n", count)
resp, err := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
if resp == nil {
_ = dWriter.Close()
hookPrintResults(results)
return fail("Internal Server Error", err)
}
wasEmpty = wasEmpty || resp.RepoWasEmpty
results = append(results, resp.Results...)
fmt.Fprintf(out, "Processed %d references in total\n", total)
if wasEmpty && masterPushed {
// We need to tell the repo to reset the default branch to master
err := private.SetDefaultBranch(ctx, repoUser, repoName, "master")
if err != nil {
return fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
}
}
_ = dWriter.Close()
hookPrintResults(results)
return nil
}
func hookPrintResults(results []private.HookPostReceiveBranchResult) {
for _, res := range results {
if !res.Message {
if res["message"] == false {
continue
}
fmt.Fprintln(os.Stderr, "")
if res.Create {
fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", res.Branch)
fmt.Fprintf(os.Stderr, " %s\n", res.URL)
if res["create"] == true {
fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", res["branch"])
fmt.Fprintf(os.Stderr, " %s\n", res["url"])
} else {
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
fmt.Fprintf(os.Stderr, " %s\n", res.URL)
fmt.Fprintf(os.Stderr, " %s\n", res["url"])
}
fmt.Fprintln(os.Stderr, "")
os.Stderr.Sync()
}
}
func pushOptions() map[string]string {
opts := make(map[string]string)
if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil {
for idx := 0; idx < pushCount; idx++ {
opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", idx))
kv := strings.SplitN(opt, "=", 2)
if len(kv) == 2 {
opts[kv[0]] = kv[1]
}
}
}
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(models.EnvRepoUsername)
repoName := os.Getenv(models.EnvRepoName)
pusherID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
pusherName := os.Getenv(models.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 4 != lr {
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

View File

@ -1,41 +0,0 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package 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())
}

View File

@ -9,7 +9,7 @@ import (
"fmt"
"strings"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/models"
"github.com/urfave/cli"
)
@ -62,15 +62,14 @@ func runKeys(c *cli.Context) error {
return errors.New("No key type and content provided")
}
ctx, cancel := installSignals()
defer cancel()
if err := initDBDisableConsole(true); err != nil {
return err
}
setup("keys.log", false)
authorizedString, err := private.AuthorizedPublicKeyByContent(ctx, content)
publicKey, err := models.SearchPublicKeyByContent(content)
if err != nil {
return err
}
fmt.Println(strings.TrimSpace(authorizedString))
fmt.Println(publicKey.AuthorizedString())
return nil
}

View File

@ -1,54 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package 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.NewContext()
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
}

View File

@ -1,478 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"fmt"
"net/http"
"os"
"time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"github.com/urfave/cli"
)
var (
// CmdManager represents the manager command
CmdManager = cli.Command{
Name: "manager",
Usage: "Manage the running gitea process",
Description: "This is a command for managing the running gitea process",
Subcommands: []cli.Command{
subcmdShutdown,
subcmdRestart,
subcmdFlushQueues,
subcmdLogging,
},
}
subcmdShutdown = cli.Command{
Name: "shutdown",
Usage: "Gracefully shutdown the running process",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
},
},
Action: runShutdown,
}
subcmdRestart = cli.Command{
Name: "restart",
Usage: "Gracefully restart the running process - (not implemented for windows servers)",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
},
},
Action: runRestart,
}
subcmdFlushQueues = cli.Command{
Name: "flush-queues",
Usage: "Flush queues in the running process",
Action: runFlushQueues,
Flags: []cli.Flag{
cli.DurationFlag{
Name: "timeout",
Value: 60 * time.Second,
Usage: "Timeout for the flushing process",
}, cli.BoolFlag{
Name: "non-blocking",
Usage: "Set to true to not wait for flush to complete before returning",
},
cli.BoolFlag{
Name: "debug",
},
},
}
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 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 runShutdown(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup("manager", c.Bool("debug"))
statusCode, msg := private.Shutdown(ctx)
switch statusCode {
case http.StatusInternalServerError:
return fail("InternalServerError", msg)
}
fmt.Fprintln(os.Stdout, msg)
return nil
}
func runRestart(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup("manager", c.Bool("debug"))
statusCode, msg := private.Restart(ctx)
switch statusCode {
case http.StatusInternalServerError:
return fail("InternalServerError", msg)
}
fmt.Fprintln(os.Stdout, msg)
return nil
}
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"))
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
}

View File

@ -5,8 +5,6 @@
package cmd
import (
"context"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/migrations"
"code.gitea.io/gitea/modules/log"
@ -28,13 +26,13 @@ func runMigrate(ctx *cli.Context) error {
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.LogRootPath)
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 := models.NewEngine(context.Background(), migrations.Migrate); err != nil {
if err := models.NewEngine(migrations.Migrate); err != nil {
log.Fatal("Failed to initialize ORM engine: %v", err)
return err
}

View File

@ -1,190 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"context"
"fmt"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/migrations"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"github.com/urfave/cli"
)
// CmdMigrateStorage represents the available migrate storage sub-command.
var CmdMigrateStorage = cli.Command{
Name: "migrate-storage",
Usage: "Migrate the storage",
Description: "This is a command for migrating storage.",
Action: runMigrateStorage,
Flags: []cli.Flag{
cli.StringFlag{
Name: "type, t",
Value: "",
Usage: "Kinds of files to migrate, currently only 'attachments' is supported",
},
cli.StringFlag{
Name: "storage, s",
Value: "",
Usage: "New storage type: local (default) or minio",
},
cli.StringFlag{
Name: "path, p",
Value: "",
Usage: "New storage placement if store is local (leave blank for default)",
},
cli.StringFlag{
Name: "minio-endpoint",
Value: "",
Usage: "Minio storage endpoint",
},
cli.StringFlag{
Name: "minio-access-key-id",
Value: "",
Usage: "Minio storage accessKeyID",
},
cli.StringFlag{
Name: "minio-secret-access-key",
Value: "",
Usage: "Minio storage secretAccessKey",
},
cli.StringFlag{
Name: "minio-bucket",
Value: "",
Usage: "Minio storage bucket",
},
cli.StringFlag{
Name: "minio-location",
Value: "",
Usage: "Minio storage location to create bucket",
},
cli.StringFlag{
Name: "minio-base-path",
Value: "",
Usage: "Minio storage basepath on the bucket",
},
cli.BoolFlag{
Name: "minio-use-ssl",
Usage: "Enable SSL for minio",
},
},
}
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(dstStorage storage.ObjectStorage) error {
return models.IterateLFS(func(mo *models.LFSMetaObject) error {
_, err := storage.Copy(dstStorage, mo.RelativePath(), storage.LFS, mo.RelativePath())
return err
})
}
func migrateAvatars(dstStorage storage.ObjectStorage) error {
return models.IterateUser(func(user *models.User) error {
_, err := storage.Copy(dstStorage, user.CustomAvatarRelativePath(), storage.Avatars, user.CustomAvatarRelativePath())
return err
})
}
func migrateRepoAvatars(dstStorage storage.ObjectStorage) error {
return models.IterateRepository(func(repo *models.Repository) error {
_, err := storage.Copy(dstStorage, repo.CustomAvatarRelativePath(), storage.RepoAvatars, repo.CustomAvatarRelativePath())
return err
})
}
func runMigrateStorage(ctx *cli.Context) error {
if err := initDB(); err != nil {
return err
}
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.LogRootPath)
setting.InitDBConfig()
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
}
var dstStorage storage.ObjectStorage
var err error
switch strings.ToLower(ctx.String("storage")) {
case "":
fallthrough
case string(storage.LocalStorageType):
p := ctx.String("path")
if p == "" {
log.Fatal("Path must be given when storage is loal")
return nil
}
dstStorage, err = storage.NewLocalStorage(
goCtx,
storage.LocalStorageConfig{
Path: p,
})
case string(storage.MinioStorageType):
dstStorage, err = storage.NewMinioStorage(
goCtx,
storage.MinioStorageConfig{
Endpoint: ctx.String("minio-endpoint"),
AccessKeyID: ctx.String("minio-access-key-id"),
SecretAccessKey: ctx.String("minio-secret-access-key"),
Bucket: ctx.String("minio-bucket"),
Location: ctx.String("minio-location"),
BasePath: ctx.String("minio-base-path"),
UseSSL: ctx.Bool("minio-use-ssl"),
})
default:
return fmt.Errorf("Unsupported storage type: %s", ctx.String("storage"))
}
if err != nil {
return err
}
tp := strings.ToLower(ctx.String("type"))
switch tp {
case "attachments":
if err := migrateAttachments(dstStorage); err != nil {
return err
}
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"))
}
log.Warn("All files have been copied to the new placement but old files are still on the original placement.")
return nil
}

View File

@ -1,68 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"errors"
"net/http"
"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.`,
},
},
}
func runRestoreRepository(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setting.NewContext()
statusCode, errStr := private.RestoreRepo(
ctx,
c.String("repo_dir"),
c.String("owner_name"),
c.String("repo_name"),
c.StringSlice("units"),
)
if statusCode == http.StatusOK {
return nil
}
log.Fatal("Failed to restore repository: %v", errStr)
return errors.New(errStr)
}

View File

@ -6,27 +6,24 @@
package cmd
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/pprof"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/lfs"
"github.com/golang-jwt/jwt"
"github.com/kballard/go-shellquote"
"github.com/dgrijalva/jwt-go"
"github.com/unknwon/com"
"github.com/urfave/cli"
)
@ -44,23 +41,20 @@ var CmdServ = cli.Command{
cli.BoolFlag{
Name: "enable-pprof",
},
cli.BoolFlag{
Name: "debug",
},
},
}
func setup(logPath string, debug bool) {
func setup(logPath string) {
_ = log.DelLogger("console")
if debug {
_ = log.NewLogger(1000, "console", "console", `{"level":"trace","stacktracelevel":"NONE","stderr":true}`)
} else {
_ = log.NewLogger(1000, "console", "console", `{"level":"fatal","stacktracelevel":"NONE","stderr":true}`)
}
setting.NewContext()
if debug {
setting.RunMode = "dev"
}
func parseCmd(cmd string) (string, string) {
ss := strings.SplitN(cmd, " ", 2)
if len(ss) != 2 {
return "", ""
}
return ss[0], strings.Replace(ss[1], "'/", "'", 1)
}
var (
@ -70,35 +64,23 @@ var (
"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, "")
func fail(userMessage, logMessage string, args ...interface{}) {
fmt.Fprintln(os.Stderr, "Gitea:", userMessage)
if len(logMessage) > 0 {
if !setting.IsProd() {
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(fmt.Sprintf("Gitea: %s", userMessage), 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"))
setup("serv.log")
if setting.SSH.Disabled {
println("Gitea: SSH has been disabled")
@ -114,102 +96,70 @@ 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 models.KeyTypeDeploy:
if key.Type == models.KeyTypeDeploy {
println("Hi there! You've successfully authenticated with the deploy key named " + key.Name + ", but Gitea does not provide shell access.")
case models.KeyTypePrincipal:
println("Hi there! You've successfully authenticated with the principal " + key.Content + ", but Gitea does not provide shell access.")
default:
} else {
println("Hi there, " + user.Name + "! You've successfully authenticated with the key named " + key.Name + ", but Gitea does not provide shell access.")
}
println("If this is unexpected, please log in with password and setup Gitea under another user.")
return nil
} else if c.Bool("debug") {
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
}
words, err := shellquote.Split(cmd)
if err != nil {
return 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)
}
verb := words[0]
repoPath := words[1]
if repoPath[0] == '/' {
repoPath = repoPath[1:]
}
verb, args := parseCmd(cmd)
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 {
lfsVerb = words[2]
argsSplit := strings.Split(args, " ")
if len(argsSplit) >= 2 {
args = strings.TrimSpace(argsSplit[0])
lfsVerb = strings.TrimSpace(argsSplit[1])
}
}
// LowerCase and trim the repoPath as that's how they are stored.
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
repoPath := strings.ToLower(strings.Trim(args, "'"))
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", args)
}
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)
}
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 {
@ -218,53 +168,50 @@ func runServ(c *cli.Context) error {
} else if lfsVerb == "download" {
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.ProtectedBranchRepoID, strconv.FormatInt(results.RepoID, 10))
os.Setenv(models.ProtectedBranchPRID, fmt.Sprintf("%d", 0))
os.Setenv(models.EnvIsDeployKey, fmt.Sprintf("%t", results.IsDeployKey))
os.Setenv(models.EnvKeyID, fmt.Sprintf("%d", results.KeyID))
os.Setenv(models.EnvAppURL, setting.AppURL)
//LFS token authentication
if verb == lfsAuthenticateVerb {
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{
StandardClaims: jwt.StandardClaims{
ExpiresAt: now.Add(setting.LFS.HTTPAuthExpiry).Unix(),
NotBefore: now.Unix(),
},
RepoID: results.RepoID,
Op: lfsVerb,
UserID: results.UserID,
claims := jwt.MapClaims{
"repo": results.RepoID,
"op": lfsVerb,
"exp": now.Add(setting.LFS.HTTPAuthExpiry).Unix(),
"nbf": now.Unix(),
"user": results.UserID,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 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 := &models.LFSTokenResponse{
@ -276,7 +223,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
}
@ -289,9 +236,9 @@ 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)
}
gitcmd.Dir = setting.RepoRootPath
@ -299,13 +246,13 @@ func runServ(c *cli.Context) error {
gitcmd.Stdin = os.Stdin
gitcmd.Stderr = os.Stderr
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)
}
}

View File

@ -5,10 +5,10 @@
package cmd
import (
"context"
"fmt"
"net"
"net/http"
"net/http/fcgi"
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
"os"
"strings"
@ -17,10 +17,12 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/install"
"code.gitea.io/gitea/routers/routes"
context2 "github.com/gorilla/context"
"github.com/unknwon/com"
"github.com/urfave/cli"
"golang.org/x/crypto/acme/autocert"
ini "gopkg.in/ini.v1"
)
@ -37,24 +39,11 @@ 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,
Value: "/var/run/gitea.pid",
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",
},
},
}
@ -71,31 +60,44 @@ func runHTTPRedirector() {
http.Redirect(w, r, target, http.StatusTemporaryRedirect)
})
var err = runHTTP("tcp", source, "HTTP Redirector", context2.ClearHandler(handler))
var err = runHTTP(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, string(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(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(listenAddr, certManager.TLSConfig(), context2.ClearHandler(m))
}
managerCtx, cancel := context.WithCancel(context.Background())
graceful.InitManager(managerCtx)
defer cancel()
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.TrimRight(setting.AppURL, "/") + r.URL.RequestURI()
http.Redirect(w, r, target, http.StatusFound)
}
func runWeb(ctx *cli.Context) error {
if os.Getppid() > 1 && len(os.Getenv("LISTEN_FDS")) > 0 {
log.Info("Restarting Gitea on PID: %d from parent PID: %d", os.Getpid(), os.Getppid())
} else {
@ -104,36 +106,58 @@ func runWeb(ctx *cli.Context) error {
// Set pid file setting
if ctx.IsSet("pid") {
setting.PIDFile = ctx.String("pid")
setting.WritePIDFile = true
setting.CustomPID = ctx.String("pid")
}
// 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
}
}
c := install.Routes()
err := listen(c, false)
select {
case <-graceful.GetManager().IsShutdown():
<-graceful.GetManager().Done()
log.Info("PID: %d Gitea Web Finished", os.Getpid())
log.Close()
return err
// Perform global initialization
routers.GlobalInit()
// Set up Macaron
m := routes.NewMacaron()
routes.RegisterRoutes(m)
// Flag for port number in case first time run conflict.
if ctx.IsSet("port") {
setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, ctx.String("port"), 1)
setting.HTTPPort = ctx.String("port")
switch setting.Protocol {
case setting.UnixSocket:
case setting.FCGI:
default:
// Save LOCAL_ROOT_URL if port changed
cfg := ini.Empty()
if com.IsFile(setting.CustomConf) {
// Keeps custom settings if there is already something.
if err := cfg.Append(setting.CustomConf); err != nil {
return fmt.Errorf("Failed to load custom conf '%s': %v", setting.CustomConf, err)
}
}
defaultLocalURL := string(setting.Protocol) + "://"
if setting.HTTPAddr == "0.0.0.0" {
defaultLocalURL += "localhost"
} else {
defaultLocalURL += setting.HTTPAddr
}
defaultLocalURL += ":" + setting.HTTPPort + "/"
cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
if err := cfg.SaveTo(setting.CustomConf); err != nil {
return fmt.Errorf("Error saving generated JWT Secret to custom config: %v", err)
}
}
} else {
NoInstallListener()
}
listenAddr := setting.HTTPAddr
if setting.Protocol != setting.UnixSocket {
listenAddr += ":" + setting.HTTPPort
}
log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubURL)
if setting.LFS.StartServer {
log.Info("LFS server enabled")
}
if setting.EnablePprof {
@ -143,97 +167,58 @@ func runWeb(ctx *cli.Context) error {
}()
}
log.Info("Global init")
// Perform global initialization
routers.GlobalInit(graceful.GetManager().HammerContext())
// Override the provided port number within the configuration
if ctx.IsSet("port") {
if err := setPort(ctx.String("port")); err != nil {
return err
}
}
// Set up Chi routes
c := routers.NormalRoutes()
err := listen(c, true)
<-graceful.GetManager().Done()
log.Info("PID: %d Gitea Web Finished", os.Getpid())
log.Close()
return err
}
func setPort(port string) error {
setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, port, 1)
setting.HTTPPort = port
switch setting.Protocol {
case setting.UnixSocket:
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(func(cfg *ini.File) {
cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
})
}
return nil
}
func listen(m http.Handler, handleRedirector bool) error {
listenAddr := setting.HTTPAddr
if setting.Protocol != setting.UnixSocket && setting.Protocol != setting.FCGIUnix {
listenAddr = net.JoinHostPort(listenAddr, setting.HTTPPort)
}
log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubURL)
if setting.LFS.StartServer {
log.Info("LFS server enabled")
}
var err error
switch setting.Protocol {
case setting.HTTP:
if handleRedirector {
NoHTTPRedirector()
}
err = runHTTP("tcp", listenAddr, "Web", context2.ClearHandler(m))
NoHTTPRedirector()
err = runHTTP(listenAddr, context2.ClearHandler(m))
case setting.HTTPS:
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, context2.ClearHandler(m))
err = runHTTPS(listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m))
case setting.FCGI:
if handleRedirector {
NoHTTPRedirector()
NoHTTPRedirector()
// FCGI listeners are provided as stdin - this is orthogonal to the LISTEN_FDS approach
// in graceful and systemD
NoMainListener()
var listener net.Listener
listener, err = net.Listen("tcp", listenAddr)
if err != nil {
log.Fatal("Failed to bind %s: %v", listenAddr, err)
}
err = runFCGI("tcp", listenAddr, "FCGI Web", context2.ClearHandler(m))
defer func() {
if err := listener.Close(); err != nil {
log.Fatal("Failed to stop server: %v", err)
}
}()
err = fcgi.Serve(listener, context2.ClearHandler(m))
case setting.UnixSocket:
if handleRedirector {
NoHTTPRedirector()
// This could potentially be inherited using LISTEN_FDS but currently
// these cannot be inherited
NoHTTPRedirector()
NoMainListener()
if err := os.Remove(listenAddr); err != nil && !os.IsNotExist(err) {
log.Fatal("Failed to remove unix socket directory %s: %v", listenAddr, err)
}
err = runHTTP("unix", listenAddr, "Web", context2.ClearHandler(m))
case setting.FCGIUnix:
if handleRedirector {
NoHTTPRedirector()
var listener *net.UnixListener
listener, err = net.ListenUnix("unix", &net.UnixAddr{Name: listenAddr, Net: "unix"})
if err != nil {
break // Handle error after switch
}
err = runFCGI("unix", listenAddr, "Web", context2.ClearHandler(m))
// FIXME: add proper implementation of signal capture on all protocols
// execute this on SIGTERM or SIGINT: listener.Close()
if err = os.Chmod(listenAddr, os.FileMode(setting.UnixSocketPermission)); err != nil {
log.Fatal("Failed to set permission of unix socket: %v", err)
}
err = http.Serve(listener, context2.ClearHandler(m))
default:
log.Fatal("Invalid protocol: %s", setting.Protocol)
}
@ -242,5 +227,7 @@ func listen(m http.Handler, handleRedirector bool) error {
log.Critical("Failed to start server: %v", err)
}
log.Info("HTTP Listener: %s Closed", listenAddr)
return err
graceful.WaitForServers()
log.Close()
return nil
}

View File

@ -1,3 +1,5 @@
// +build !windows
// Copyright 2016 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
@ -6,60 +8,30 @@ 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) error {
return graceful.HTTPListenAndServe(network, listenAddr, name, m)
func runHTTP(listenAddr string, m http.Handler) error {
return graceful.HTTPListenAndServe("tcp", listenAddr, m)
}
func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler) error {
return graceful.HTTPListenAndServeTLS(network, listenAddr, name, certFile, keyFile, m)
func runHTTPS(listenAddr, certFile, keyFile string, m http.Handler) error {
return graceful.HTTPListenAndServeTLS("tcp", listenAddr, certFile, keyFile, m)
}
func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler) error {
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m)
func runHTTPSWithTLSConfig(listenAddr string, tlsConfig *tls.Config, m http.Handler) error {
return graceful.HTTPListenAndServeTLSConfig("tcp", listenAddr, tlsConfig, m)
}
// NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector
func NoHTTPRedirector() {
graceful.GetManager().InformCleanup()
graceful.InformCleanup()
}
// NoMainListener tells our cleanup routine that we will not be using a possibly provided listener
// for our main HTTP/HTTPS service
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) error {
// This needs to handle stdin as fcgi point
fcgiServer := graceful.NewServer(network, listenAddr, name)
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)
}))
})
if err != nil {
log.Fatal("Failed to start FCGI main server: %v", err)
}
log.Info("FCGI Listener: %s Closed", listenAddr)
return err
graceful.InformCleanup()
}

View File

@ -1,83 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"net/http"
"strconv"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/caddyserver/certmagic"
context2 "github.com/gorilla/context"
)
func runLetsEncrypt(listenAddr, domain, directory, email 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: directory}
myACME := certmagic.NewACMEManager(magic, certmagic.ACMEManager{
Email: email,
Agreed: setting.LetsEncryptTOS,
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([]string{domain})
if err != nil {
return err
}
tlsConfig := magic.TLSConfig()
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")
if enableHTTPChallenge {
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, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(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, "Web", 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)
}

37
cmd/web_windows.go Normal file
View File

@ -0,0 +1,37 @@
// +build windows
// Copyright 2016 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"crypto/tls"
"net/http"
)
func runHTTP(listenAddr string, m http.Handler) error {
return http.ListenAndServe(listenAddr, m)
}
func runHTTPS(listenAddr, certFile, keyFile string, m http.Handler) error {
return http.ListenAndServeTLS(listenAddr, certFile, keyFile, m)
}
func runHTTPSWithTLSConfig(listenAddr string, tlsConfig *tls.Config, m http.Handler) error {
server := &http.Server{
Addr: listenAddr,
Handler: m,
TLSConfig: tlsConfig,
}
return server.ListenAndServeTLS("", "")
}
// NoHTTPRedirector is a no-op on Windows
func NoHTTPRedirector() {
}
// NoMainListener is a no-op on Windows
func NoMainListener() {
}

View File

@ -1,47 +0,0 @@
Environment To Ini
==================
Multiple docker users have requested that the Gitea docker is changed
to permit arbitrary configuration via environment variables.
Gitea needs to use an ini file for configuration because the running
environment that starts the docker may not be the same as that used
by the hooks. An ini file also gives a good default and means that
users do not have to completely provide a full environment.
With those caveats above, this command provides a generic way of
converting suitably structured environment variables into any ini
value.
To use the command is very simple just run it and the default gitea
app.ini will be rewritten to take account of the variables provided,
however there are various options to give slightly different
behavior and these can be interrogated with the `-h` option.
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:
"""
...
[log.console]
COLORIZE=false
STDERR=true
...
"""
You would set the environment variables: "GITEA__LOG_0x2E_CONSOLE__COLORIZE=false"
and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found
on the configuration cheat sheet.
To build locally, run:
go build contrib/environment-to-ini/environment-to-ini.go

View File

@ -1,237 +0,0 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package main
import (
"os"
"regexp"
"strconv"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/urfave/cli"
ini "gopkg.in/ini.v1"
)
// EnvironmentPrefix environment variables prefixed with this represent ini values to write
const EnvironmentPrefix = "GITEA"
func main() {
app := cli.NewApp()
app.Name = "environment-to-ini"
app.Usage = "Use provided environment to update configuration ini"
app.Description = `As a helper to allow docker users to update the gitea configuration
through the environment, this command allows environment variables to
be mapped to values in the ini.
Environment variables of the form "GITEA__SECTION_NAME__KEY_NAME"
will be mapped to the ini section "[section_name]" and the key
"KEY_NAME" with the value as provided.
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 ".". The entire section and key names can be escaped as
a UTF8 byte string if necessary. E.g. to configure:
"""
...
[log.console]
COLORIZE=false
STDERR=true
...
"""
You would set the environment variables: "GITEA__LOG_0x2E_CONSOLE__COLORIZE=false"
and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found
on the configuration cheat sheet.`
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "custom-path, C",
Value: setting.CustomPath,
Usage: "Custom path file path",
},
cli.StringFlag{
Name: "config, c",
Value: setting.CustomConf,
Usage: "Custom configuration file path",
},
cli.StringFlag{
Name: "work-path, w",
Value: setting.AppWorkPath,
Usage: "Set the gitea working path",
},
cli.StringFlag{
Name: "out, o",
Value: "",
Usage: "Destination file to write to",
},
cli.BoolFlag{
Name: "clear",
Usage: "Clears the matched variables from the environment",
},
cli.StringFlag{
Name: "prefix, p",
Value: EnvironmentPrefix,
Usage: "Environment prefix to look for - will be suffixed by __ (2 underscores)",
},
}
app.Action = runEnvironmentToIni
setting.SetCustomPathAndConf("", "", "")
err := app.Run(os.Args)
if err != nil {
log.Fatal("Failed to run app with %s: %v", os.Args, err)
}
}
func runEnvironmentToIni(c *cli.Context) error {
providedCustom := c.String("custom-path")
providedConf := c.String("config")
providedWorkPath := c.String("work-path")
setting.SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath)
cfg := ini.Empty()
isFile, err := util.IsFile(setting.CustomConf)
if err != nil {
log.Fatal("Unable to check if %s is a file. Error: %v", setting.CustomConf, err)
}
if isFile {
if err := cfg.Append(setting.CustomConf); err != nil {
log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err)
}
} else {
log.Warn("Custom config '%s' not found, ignore this if you're running first time", setting.CustomConf)
}
cfg.NameMapper = ini.SnackCase
changed := false
prefix := c.String("prefix") + "__"
for _, kv := range os.Environ() {
idx := strings.IndexByte(kv, '=')
if idx < 0 {
continue
}
eKey := kv[:idx]
value := kv[idx+1:]
if !strings.HasPrefix(eKey, prefix) {
continue
}
eKey = eKey[len(prefix):]
sectionName, keyName := DecodeSectionKey(eKey)
if len(keyName) == 0 {
continue
}
section, err := cfg.GetSection(sectionName)
if err != nil {
section, err = cfg.NewSection(sectionName)
if err != nil {
log.Error("Error creating section: %s : %v", sectionName, err)
continue
}
}
key := section.Key(keyName)
if key == nil {
key, err = section.NewKey(keyName, value)
if err != nil {
log.Error("Error creating key: %s in section: %s with value: %s : %v", keyName, sectionName, value, err)
continue
}
}
oldValue := key.Value()
if !changed && oldValue != value {
changed = true
}
key.SetValue(value)
}
destination := c.String("out")
if len(destination) == 0 {
destination = setting.CustomConf
}
if destination != setting.CustomConf || changed {
err = cfg.SaveTo(destination)
if err != nil {
return err
}
}
if c.Bool("clear") {
for _, kv := range os.Environ() {
idx := strings.IndexByte(kv, '=')
if idx < 0 {
continue
}
eKey := kv[:idx]
if strings.HasPrefix(eKey, prefix) {
_ = os.Unsetenv(eKey)
}
}
}
return nil
}
const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_"
var escapeRegex = regexp.MustCompile(escapeRegexpString)
// DecodeSectionKey will decode a portable string encoded Section__Key pair
// Portable strings are considered to be of the form [A-Z0-9_]*
// We will encode a disallowed value as the UTF8 byte string preceded by _0X and
// followed by _. E.g. _0X2C_ for a '-' and _0X2E_ for '.'
// Section and Key are separated by a plain '__'.
// The entire section can be encoded as a UTF8 byte string
func DecodeSectionKey(encoded string) (string, string) {
section := ""
key := ""
inKey := false
last := 0
escapeStringIndices := escapeRegex.FindAllStringIndex(encoded, -1)
for _, unescapeIdx := range escapeStringIndices {
preceding := encoded[last:unescapeIdx[0]]
if !inKey {
if splitter := strings.Index(preceding, "__"); splitter > -1 {
section += preceding[:splitter]
inKey = true
key += preceding[splitter+2:]
} else {
section += preceding
}
} else {
key += preceding
}
toDecode := encoded[unescapeIdx[0]+3 : unescapeIdx[1]-1]
decodedBytes := make([]byte, len(toDecode)/2)
for i := 0; i < len(toDecode)/2; i++ {
// Can ignore error here as we know these should be hexadecimal from the regexp
byteInt, _ := strconv.ParseInt(toDecode[2*i:2*i+2], 16, 0)
decodedBytes[i] = byte(byteInt)
}
if inKey {
key += string(decodedBytes)
} else {
section += string(decodedBytes)
}
last = unescapeIdx[1]
}
remaining := encoded[last:]
if !inKey {
if splitter := strings.Index(remaining, "__"); splitter > -1 {
section += remaining[:splitter]
inKey = true
key += remaining[splitter+2:]
} else {
section += remaining
}
} else {
key += remaining
}
section = strings.ToLower(section)
return section, key
}

View File

@ -1,76 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"code.gitea.io/gitea/models"
)
// To generate derivative fixtures, execute the following from Gitea's repository base dir:
// go run -tags 'sqlite sqlite_unlock_notify' contrib/fixtures/fixture_generation.go [fixture...]
var (
generators = []struct {
gen func() (string, error)
name string
}{
{
models.GetYamlFixturesAccess, "access",
},
}
fixturesDir string
)
func main() {
pathToGiteaRoot := "."
fixturesDir = filepath.Join(pathToGiteaRoot, "models", "fixtures")
if err := models.CreateTestEngine(fixturesDir); err != nil {
fmt.Printf("CreateTestEngine: %+v", err)
os.Exit(1)
}
if err := models.PrepareTestDatabase(); err != nil {
fmt.Printf("PrepareTestDatabase: %+v\n", err)
os.Exit(1)
}
if len(os.Args) == 0 {
for _, r := range os.Args {
if err := generate(r); err != nil {
fmt.Printf("generate '%s': %+v\n", r, err)
os.Exit(1)
}
}
} else {
for _, g := range generators {
if err := generate(g.name); err != nil {
fmt.Printf("generate '%s': %+v\n", g.name, err)
os.Exit(1)
}
}
}
}
func generate(name string) error {
for _, g := range generators {
if g.name == name {
data, err := g.gen()
if err != nil {
return err
}
path := filepath.Join(fixturesDir, name+".yml")
if err := ioutil.WriteFile(path, []byte(data), 0644); err != nil {
return fmt.Errorf("%s: %+v", path, err)
}
fmt.Printf("%s created.\n", path)
return nil
}
}
return fmt.Errorf("generator not found")
}

View File

@ -19,7 +19,7 @@
"type": "go",
"request": "launch",
"mode": "debug",
"buildFlags": "-tags='sqlite sqlite_unlock_notify'",
"buildFlags": "-tags=\"sqlite sqlite_unlock_notify\"",
"port": 2345,
"host": "127.0.0.1",
"program": "${workspaceRoot}/main.go",

View File

@ -1,4 +0,0 @@
{
"go.buildTags": "'sqlite sqlite_unlock_notify'",
"go.testFlags": ["-v"]
}

View File

@ -2,7 +2,7 @@
"version": "2.0.0",
"tasks": [
{
"label": "Build",
"taskName": "Build",
"type": "shell",
"command": "go",
"group": "build",
@ -12,19 +12,20 @@
"focus": false,
"panel": "shared"
},
"args": ["build"],
"linux": {
"args": ["build", "-o", "gitea", "${workspaceRoot}/main.go" ]
"args": [ "-o", "gitea", "${workspaceRoot}/main.go" ]
},
"osx": {
"args": ["build", "-o", "gitea", "${workspaceRoot}/main.go" ]
"args": [ "-o", "gitea", "${workspaceRoot}/main.go" ]
},
"windows": {
"args": ["build", "-o", "gitea.exe", "\"${workspaceRoot}\\main.go\""]
"args": [ "-o", "gitea.exe", "\"${workspaceRoot}\\main.go\""]
},
"problemMatcher": ["$go"]
},
{
"label": "Build (with SQLite3)",
"taskName": "Build (with SQLite3)",
"type": "shell",
"command": "go",
"group": "build",
@ -34,14 +35,15 @@
"focus": false,
"panel": "shared"
},
"args": ["build", "-tags=\"sqlite sqlite_unlock_notify\""],
"linux": {
"args": ["build", "-tags=\"sqlite sqlite_unlock_notify\"", "-o", "gitea", "${workspaceRoot}/main.go"]
"args": ["-o", "gitea", "${workspaceRoot}/main.go"]
},
"osx": {
"args": ["build", "-tags=\"sqlite sqlite_unlock_notify\"", "-o", "gitea", "${workspaceRoot}/main.go"]
"args": ["-o", "gitea", "${workspaceRoot}/main.go"]
},
"windows": {
"args": ["build", "-tags=\"sqlite sqlite_unlock_notify\"", "-o", "gitea.exe", "\"${workspaceRoot}\\main.go\""]
"args": ["-o", "gitea.exe", "\"${workspaceRoot}\\main.go\""]
},
"problemMatcher": ["$go"]
}

View File

@ -18,7 +18,7 @@ PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
DESC="Gitea - Git with a cup of tea"
NAME=gitea
SERVICEVERBOSE=yes
PIDFILE=/run/$NAME.pid
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
WORKINGDIR=/var/lib/$NAME
DAEMON=/usr/local/bin/$NAME

View File

@ -7,7 +7,7 @@ start_stop_daemon_args="--user ${USER} --chdir ${DIR}"
command="/usr/local/bin/gitea"
command_args="web -c /etc/gitea/app.ini"
command_background=yes
pidfile=/run/gitea.pid
pidfile=/var/run/gitea.pid
depend()
{

View File

@ -93,7 +93,7 @@ case "$1" in
# Return value is slightly different for the status command:
# 0 - service up and running
# 1 - service dead, but /run/ pid file exists
# 1 - service dead, but /var/run/ pid file exists
# 2 - service dead, but /var/lock/ lock file exists
# 3 - service not running (unused)
# 4 - service status unknown :-(

107
contrib/k8s/gitea.yml Normal file
View File

@ -0,0 +1,107 @@
apiVersion: v1
kind: Namespace
metadata:
name: gitea
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitea
namespace: gitea
labels:
app: gitea
spec:
replicas: 1
template:
metadata:
name: gitea
labels:
app: gitea
spec:
containers:
- name: gitea
image: gitea/gitea:latest
imagePullPolicy: Always
volumeMounts:
- mountPath: "/var/lib/gitea"
name: "root"
- mountPath: "/data"
name: "data"
ports:
- containerPort: 22
name: ssh
protocol: TCP
- containerPort: 3000
name: http
protocol: TCP
restartPolicy: Always
volumes:
# Set up a data directory for gitea
# For production usage, you should consider using PV/PVC instead(or simply using storage like NAS)
# For more details, please see https://kubernetes.io/docs/concepts/storage/volumes/
- name: "root"
hostPath:
# directory location on host
path: "/var/lib/gitea"
# this field is optional
type: Directory
- name: "data"
hostPath:
path: "/data/gitea"
type: Directory
selector:
matchLabels:
app: gitea
---
# Using cluster mode
apiVersion: v1
kind: Service
metadata:
name: gitea-web
namespace: gitea
labels:
app: gitea-web
spec:
ports:
- port: 80
targetPort: 3000
name: http
selector:
app: gitea
---
# Using node-port mode
# This mainly open a specific TCP port for SSH usage on each host,
# so you can use a proxy layer to handle it(e.g. slb, nginx)
apiVersion: v1
kind: Service
metadata:
name: gitea-ssh
namespace: gitea
labels:
app: gitea-ssh
spec:
ports:
- port: 22
targetPort: 22
nodePort: 30022
name: ssh
selector:
app: gitea
type: NodePort
---
# Ingress is always suitable for HTTP usage,
# we suggest using an proxy layer such as slb to send traffic to different ports.
# Usually 80/443 for web and 22 directly for SSH.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: gitea
namespace: gitea
spec:
rules:
- host: your-gitea-host.com
http:
paths:
- backend:
serviceName: gitea-web
servicePort: 80

View File

@ -1,196 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Privacy Policy</title>
</head>
<body>
<h1>Privacy Policy</h1>
<h4>Last updated: January 29, 2020</h4>
<h2>Who We Are?</h2>
<p>Your Gitea Instance</p>
<h2>What Personal Data We Collect?</h2>
<p>We collect following personal data (collectively called User Personal Information):</p>
<ol>
<li>Registration information (username, email, password, etc.)</li>
<li>Profile information for your Account (such as your full name, biography, website, gpg key, and location.)</li>
<li>Usage information (pages you view, your IP address, referring site, session information, and request date and time.)</li>
<li>Device information (its IP address, client application information, language preference, operating system and application version, device type, ID, model and manufacturer.)</li>
<li>Git data that you upload to a repository</li>
<li>Cookies and Similar Technologies</li>
</ol>
<p>We may also collect User Personal Information from third-parties (vendors, partners, or affiliates). We don't purchase them from third-party data brokers, though.</p>
<p>However, we don't intentionally collect sensitive information (such as racial or ethnic origin, political affiliations, religious/philosophical beliefs, biometric data, etc.)</p>
<!--If you choose to store any of such data on our servers, you are responsible for complying with any regulations regarding them.-->
<h2>How We Share Information We Collect?</h2>
<p>We may share your User Personal Information with third-parties under following circumstances:</p>
<h3>With your Consent</h3>
<p>We share your User Personal Information, if you consent, after letting you know what information will be shared, with whom, and why. For example, if you allow third party applications to access your Account using <a href="https://docs.gitea.io/en-us/oauth2-provider/">OAuth2 providers</a>, we share all information associated with your Account, including private repos and organizations. You may also direct us through your action on Your Gitea Instance to share your User Personal Information, such as when joining an Organization.</p>
<h3>With Service Providers</h3>
<p>We share User Personal Information with a limited number of service providers who process it on our behalf to provide or improve our Service, and who have agreed to privacy restrictions similar to the ones in our Privacy Statement by signing data protection agreements or making similar commitments. Our service providers perform payment processing, customer support ticketing, network data transmission, security, and other similar services. While Your Gitea Instance processes all User Personal Information in the (country/state where Gitea is deployed), our service providers may process data outside of (country/state where Gitea is deployed), the United States or the European Union.</p>
<h3>For Security Purposes</h3>
<p>If you are a member of an Organization, Your Gitea Instance may share your username, Usage Information, and Device Information associated with that Organization with an owner and/or administrator of the Organization who has agreed to the Corporate Terms of Service or applicable customer agreements, to the extent that such information is provided only to investigate or respond to a security incident that affects or compromises the security of that particular Organization.</p>
<h3>For Legal Disclosure</h3>
<p>Your Gitea Instance strives for transparency in complying with legal process and legal obligations. Unless prevented from doing so by law or court order, or in rare, exigent circumstances, we make a reasonable effort to notify users of any legally compelled or required disclosure of their information. Your Gitea Instance may disclose User Personal Information or other information we collect about you to law enforcement if required in response to a valid subpoena, court order, search warrant, a similar government order, or when we believe in good faith that disclosure is necessary to comply with our legal obligations, to protect our property or rights, or those of third parties or the public at large.</p>
<h3>Change in Control or Sale</h3>
<p>We may share User Personal Information if we are involved in a merger, sale, or acquisition of corporate entities or business units. If any such change of ownership happens, we will ensure that it is under terms that preserve the confidentiality of User Personal Information, and we will notify you on our Website or by email before any transfer of your User Personal Information. The organization receiving any User Personal Information will have to honor any promises we made in our Privacy Statement or Terms of Service.</p>
<h3>Aggregate, Non-Personally Identifying Information</h3>
<p>We share certain aggregated, non-personally identifying information with others about how our users, collectively, use Your Gitea Instance, or how our users respond to our other offerings, such as our conferences or events. For example, we may compile statistics on the open source activity across Your Gitea Instance.</p>
<p>We <b>don't</b> sell your User Personal Information for monetary or other consideration. </p>
<h2>How We Use Your Information?</h2>
<p>We may use your information for following purposes:</p>
<ol>
<li>We use your Registration Information to create your account, and to provide you the Service.</li>
<li>We use your User Personal Information, specifically your username, to identify you on Your Gitea Instance.</li>
<li>We use your Profile Information to fill out your Account profile and to share that profile with other users if you ask us to.</li>
<li>We use your email address to communicate with you, if you've said that's okay, and only for the reasons youve said thats okay.</li>
<li>We use User Personal Information and other data to make recommendations for you, such as to suggest projects you may want to follow or contribute to. We learn from your public behavior on Your Gitea Instance—such as the projects you star—to determine your coding interests, and we recommend similar projects. These recommendations are automated decisions, but they have no legal impact on your rights.</li>
<li>We use Usage Information and Device Information to better understand how our Users use Your Gitea Instance and to improve our Website and Service.</li>
<li>We may use your User Personal Information if it is necessary for security purposes or to investigate possible fraud or attempts to harm Your Gitea Instance or our Users.</li>
<li>We may use your User Personal Information to comply with our legal obligations, protect our intellectual property, and enforce our Terms of Service.</li>
<li>We limit our use of your User Personal Information to the purposes listed in this Privacy Statement. If we need to use your User Personal Information for other purposes, we will ask your permission first. You can always see what information we have, how we're using it, and what permissions you have given us in your user profile.</li>
</ol>
<h2>How Your Gitea Instance Secures Your Information?</h2>
<p>Your Gitea Instance takes all measures reasonably necessary to protect User Personal Information from unauthorized access, alteration, or destruction; maintain data accuracy; and help ensure the appropriate use of User Personal Information.</p>
<p>To the extent above, we enforce a written security information program, which:</p>
<ul>
<li>aligns with industry recognized frameworks;</li>
<li>includes security safeguards reasonably designed to protect the confidentiality, integrity, availability, and resilience of our Users' data;</li>
<li>is appropriate to the nature, size, and complexity of Your Gitea Instances business operations;</li>
<li>includes incident response and data breach notification processes; and</li>
<li>complies with applicable information security-related laws and regulations in the geographic regions where Your Gitea Instance does business.</li>
</ul>
<p>In the event of a data breach that affects your User Personal Information, we will act promptly to mitigate the impact of a breach and notify any affected Users without undue delay.</p>
<p>Transmission of data on Your Gitea Instance is encrypted using SSH, HTTPS (TLS), and git repository content is encrypted at rest. We host Your Gitea Instance at our hosting partner, which they provide data centers with high level of physical and network security.</p>
<p><b>Disclaimer:</b> No method of transmission, or method of electronic storage, is 100% secure, therefore, we cannot guarantee absolute security.</p>
<h2>Cookies and Tracking Usage</h2>
<h3>Cookies</h3>
<p>We uses cookies to make interactions with our service easy and meaningful. Cookies are small text files that websites often store on computer hard drives or mobile devices of visitors. We use cookies (and similar technologies, like HTML5 localStorage) to keep you logged in, remember your preferences, and provide information for future development of Your Gitea Instance. For security purposes, we use cookies to identify a device. By using our Website, you agree that we can place these types of cookies on your computer or device. If you disable your browser or devices ability to accept these cookies, you will not be able to log in or use our services.</p>
<h3>Tracking and Analytics</h3>
<p>Out of the box, Gitea doesn't use third-party analytics. In case when we opt in to their usage, we do that to help us evaluate our Users' use of Your Gitea Instance, compile statistical reports on activity, and improve our content and Website performance. We only use these third-party analytics providers on certain areas of our Website, and all of them have signed data protection agreements with us that limit the type of User Personal Information they can collect and the purpose for which they can process the information. In addition, we may also deploy internal analytics software to provide similar functionality.</p>
<p>Some browsers have incorporated "Do Not Track" (DNT) features that can send a signal to the websites you visit indicating you do not wish to be tracked. Your Gitea Instance responds to browser DNT signals and follows the <a href="https://www.w3.org/TR/tracking-dnt/">W3C standard for responding to DNT signals</a>. If you have not enabled DNT on a browser that supports it, cookies on some parts of our Website will track your online browsing activity on other online services over time, though we do not permit third parties other than our analytics and service providers to track Your Gitea Instance Users' activity over time on Your Gitea Instance.</p>
<h2>Repository Contents</h2>
<p>Our employees do not access private repositories unless required to for security purposes, for support, to maintain integrity of the Service, or to comply with our legal obligations. While we don't generally search for content in your repositories, we may scan our servers and your content to detect tokens or security signatures, known malwares, or child exploitation imagery.</p>
<p>If your repository is public, anyone may view its contents. If you include private, confidential or Sensitive Personal Information, such as email addresses or passwords, in your public repository, that information may be indexed by search engines or used by third parties.</p>
<h2>Public Information</h2>
<p>Many of our services and feature are public-facing. If your content is public-facing, third parties may access and use it in compliance with our Terms of Service, such as by viewing your profile or repositories or pulling data via our API. We do not sell that content; it is yours. However, we do allow third parties, such as research organizations or archives, to compile public-facing Your Gitea Instance information. Other third parties, such as data brokers, have been known to scrape Your Gitea Instance and compile data as well.</p>
<p>Your User Personal Information associated with your content could be gathered by third parties in these compilations of Your Gitea Instance data. If you do not want your User Personal Information to appear in third parties compilations of Your Gitea Instance data, please do not make your User Personal Information publicly available and be sure to configure your email address to be private in your user profile and in your git commit settings.</p>
<p>If you would like to compile Your Gitea Instance data, you must comply with our Terms of Service regarding scraping and privacy, and you may only use any public-facing User Personal Information you gather for the purpose for which our user authorized it. For example, where a Your Gitea Instance user has made an email address public-facing for the purpose of identification and attribution, do not use that email address for commercial advertising. We expect you to reasonably secure any User Personal Information you have gathered from Your Gitea Instance, and to respond promptly to complaints, removal requests, and "do not contact" requests from Your Gitea Instance or Your Gitea Instance users.</p>
<p>In similar fashion, projects on Your Gitea Instance may include publicly available User Personal Information collected as part of the collaborative events.</p>
<h2>Organizations</h2>
<p>If you collaborate on or become a member of an Organization, then its Account owners may receive your User Personal Information. When you accept an invitation to an Organization, you will be notified of the types of information owners may be able to see. If you accept an invitation to an Organization with a verified domain, then the owners of that Organization will be able to see your full email address(es) within that Organization's verified domain(s).</p>
<p>Please note, Your Gitea Instance may share your username, Usage Information, and Device Information with the owner of the Organization you are a member of, to the extent that your User Personal Information is provided only to investigate or respond to a security incident that affects or compromises the security of that particular Organization.</p>
<p>If you collaborate with or become a member of an Account that has agreed to a Data Protection Addendum (DPA) to this Privacy Policy, then that DPA governs in the event of conflicts between this Privacy Policy and DPA with respect to your activity in the Account.</p>
<p>Please contact the Account owners for more information about how they might process your User Personal Information in their Organization and the ways for you to access, update, alter, or delete the User Personal Information stored in the Account.</p>
<h2>How You Can Access and Control the Information We Collect?</h2>
<p>If you're already a Your Gitea Instance user, you may access, update, alter, or delete your basic user information by editing your user profile. You can control the information we collect about you by limiting what information is in your profile, or by keeping your information current.</p>
<p>If Your Gitea Instance processes information about you, such as information receives from third parties, and you do not have an account, then you may, subject to applicable law, access, update, alter, delete, or object to the processing of your personal information by contacting our support.</p>
<h3>Data Portability</h3>
<p>As a Your Gitea Instance User, you can always take your data with you. You can clone your repositories to your computer, or you can <a href="https://docs.gitea.io/en-us/migrations-interfaces/">perform migrations using the provided interfaces</a>, for example.</p>
<h3>Data Retention and Deletion of Data</h3>
<p>In general, Your Gitea Instance retains User Personal Information for as long as your account is active, or as needed to provide you service.</p>
<p>If you would like to cancel your account or delete your User Personal Information, you may do so in your user profile. We retain and use your information as necessary to comply with our legal obligations, resolve disputes, and enforce our agreements, but barring legal requirements, we will delete your full profile (within reason) within 90 days of your request. Feel free to contact our support to request erasure of the data we process on the bassis of consent within 30 days.</p>
<p>After an account has been deleted, certain data, such as contributions to other Users' repositories and comments in others' issues, will remain. However, we will delete or de-identify your User Personal Information, including your username and email address, from the author field of issues, pull requests, and comments by associating them with a ghost user.</p>
<p>That said, the email address you have supplied via your Git commit settings will always be associated with your commits in the Git system. If you choose to make your email address private, you should also update your Git commit settings. We are unable to change or delete data in the Git commit history — the Git software is designed to maintain a record — but we do enable you to control what information you put in that record.</p>
<h2>Our Global Privacy Practices</h2>
<p>We store and process the information that we collect in the (country/state where Gitea is deployed) in accordance with this Privacy Statement though our service providers may store and process data outside the (country/state where Gitea is deployed). However, we understand that we have Users from different countries and regions with different privacy expectations, and we try to meet those needs even when the (country/state where Gitea is deployed) does not have the same privacy framework as other countries.</p>
<p>We provide a high standard of privacy protection—as described in this Privacy Statement—to all our users around the world, regardless of their country of origin or location, and we are proud of the levels of notice, choice, accountability, security, data integrity, access, and recourse we provide. We work hard to comply with the applicable data privacy laws wherever we do business, working with our Data Protection Officer as part of a cross-functional team that oversees our privacy compliance efforts. Additionally, if our vendors or affiliates have access to User Personal Information, they must sign agreements that require them to comply with our privacy policies and with applicable data privacy laws.</p>
<p>In particular:</p>
<ul>
<li>Your Gitea Instance provides clear methods of unambiguous, informed, specific, and freely given consent at the time of data collection, when we collect your User Personal Information using consent as a basis.</li>
<li>We collect only the minimum amount of User Personal Information necessary for our purposes, unless you choose to provide more. We encourage you to only give us the amount of data you are comfortable sharing.</li>
<li>We offer you simple methods of accessing, altering, or deleting the User Personal Information we have collected, where legally permitted.</li>
<li>We provide our Users notice, choice, accountability, security, and access regarding their User Personal Information, and we limit the purpose for processing it. We also provide our Users a method of recourse and enforcement. These are the Privacy Shield Principles, but they are also just good practices.</li>
</ul>
<h2>How We Communicate with You?</h2>
<p>We use your email address to communicate with you, if you've said that's okay, and only for the reasons youve said thats okay. For example, if you contact our support with a request, we respond to you via email. You have a lot of control over how your email address is used and shared on and through Your Gitea instance. You may manage your communication preferences in your user profile.</p>
<p>By design, the Git version control system associates many actions with a User's email address, such as commit messages. We are not able to change many aspects of the Git system. If you would like your email address to remain private, even when youre commenting on public repositories, you can create a private email address in your user profile. You should also update your local Git configuration to use your private email address. This will not change how we contact you, but it will affect how others see you.</p>
<p>Depending on your email settings, Your Gitea instance may occasionally send notification emails about changes in a repository youre watching, new features, requests for feedback, important policy changes, or to offer customer support. We also send marketing emails, based on your choices and in accordance with applicable laws and regulations. There's an “unsubscribe” link located at the bottom of each of the marketing emails we send you. Note that you can opt out of any communications with us, except the important ones (like from our support and system emails).</p>
<p>Our emails may contain a pixel tag, which is a small, clear image that can tell us whether or not you have opened an email and what your IP address is. We use this pixel tag to make our email more effective for you and to make sure were not sending you unwanted email.</p>
<h2>Changes to this Privacy Policy</h2>
<p>Although most changes are likely to be minor, Your Gitea Instance may change our Privacy Statement from time to time. We will provide notification to Users of material changes to this Privacy Statement through our Website at least 30 days prior to the change taking effect by posting a notice on our home page or sending email to the primary email address specified in your account.</p>
<h2>Contact</h2>
<p>If you have any concerns about privacy, please contact us at <a href="mailto:privacy@your-gitea-instance">privacy@your-gitea-instance</a>. We will respond promptly, within 45 days.</p>
<h2>COPYING</h2>
<p>This document is licensed under CC0 Public Domain License. See <a href="https://creativecommons.org/publicdomain/zero/1.0/legalcode">full legal code here</a>.</p>
</body>
</html>

View File

@ -1,245 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Terms of Service</title>
</head>
<body>
<h1>Terms of Service</h1>
<h4>Last updated: January 29, 2020</h4>
<p>Thank you for choosing Your Gitea Instance! Before you use it, please read this Terms of Service agreement carefully, which contains important contract between us and our users.</p>
<h2>Definitions</h2>
<ol>
<li>An "Account" represents your legal relationship with Your Gitea Instance. A “User Account” represents an individual Users authorization to log in to and use the Service and serves as a Users identity on Your Gitea Instance. “Organizations” are shared workspaces that may be associated with a single entity or with one or more Users where multiple Users can collaborate across many projects at once. A User Account can be a member of any number of Organizations.</li>
<li>The "Agreement" collectively refers to all terms, conditions, and notices referenced or contained in this document and other operating rules, policies (including Privacy Policy) and procedures that we may publish from time to time on this Website.</li>
<li>“Content” refers to content featured or displayed through the Website, including without limitation code, text, data, articles, images, photographs, graphics, software, applications, packages, designs, features, and other materials that are available on the Website or otherwise available through the Service. "Content" also includes Services. “User-Generated Content” is Content, written or otherwise, created or uploaded by our Users. "Your Content" is Content that you create or own.</li>
<li>"Your Gitea Instance", "We", and "Us" refers to Your Gitea Instance, as well as our affiliates, directors, subsidiaries, contractors, licensors, officers, agents, and employees.</li>
<li>The "Service" refers to applications/software, products, and services provided by Your Gitea Instance.</li>
<li>The "User", "You", and "Your" refers to individual person or institution (organizations or company) that has visited or using the Service; that have access or use any part of the Account; or that directs to use the Account to perform its function. Please note that additional terms may apply for Accounts related to business or government.</li>
<li>The "Website" refers to Your Gitea Instance's website at <a href="https://your-gitea-instance">your-gitea-instance</a>, including its subdomains and other websites owned by Your Gitea Instance.</li>
</ol>
<h2>Account Terms</h2>
<h3>Account Controls</h3>
<ul>
<li>Users: Subject to these Terms, you retain ultimate administrative control over your User Account and the Content within it.</li>
<li>Organizations. The "owner" of an Organization that was created under these Terms has ultimate administrative control over that Organization and the Content within it. Within the Service, an owner can manage User access to the Organizations data and projects. An Organization may have multiple owners, but there must be at least one User Account designated as an owner of an Organization. If you are the owner of an Organization under these Terms, we consider you responsible for the actions that are performed on or through that Organization.</li>
</ul>
<h3>Required Information</h3>
<p>You must provide a valid email address in order to complete the signup process. Any other information requested, such as your real name, is optional, unless you are accepting these terms on behalf of a legal entity (in which case we need more information about the legal entity).</p>
<h3>Account Requirements</h3>
<ul>
<li>You must be a human to create an Account. Accounts registered by "bots" or other automated methods are not permitted. We do permit machine accounts:</li>
<li>A machine account is an Account set up by an individual human who accepts the Terms on behalf of the Account, provides a valid email address, and is responsible for its actions. A machine account is used exclusively for performing automated tasks. Multiple users may direct the actions of a machine account, but the owner of the Account is ultimately responsible for the machine's actions.</li>
<li>You must be age 13 or older. If we learn of any User under that age, we will immediately terminate that User's Account. Different countries may have different minimum age; in such cases you are responsible for complying with your country's regulation. By using Your Gitea Instance, you agree to comply with <a href="https://www.ftc.gov/enforcement/rules/rulemaking-regulatory-reform-proceedings/childrens-online-privacy-protection-rule">COPPA</a> and/or similar law in your country.</li>
</ul>
<h3>User Account Security</h3>
<p>You are responsible for keeping your Account secure while you use our Service. We offer tools such as two-factor authentication to help you maintain your Account's security, but the content of your Account and its security are up to you.</p>
<h3>Additional Terms</h3>
<p>In some situations, third parties' terms may apply to your use of Your Gitea Instance. For example, you may be a member of an organization on Your Gitea Instance with its own terms or license agreements; you may download an application that integrates with Your Gitea Instance; or you may use Your Gitea Instance to authenticate to another service. Please be aware that while these Terms are our full agreement with you, other parties' terms govern their relationships with you.</p>
<h2>Acceptable Use</h2>
<p>Your use of the Website and Service must not violate any applicable laws, including copyright or trademark laws, export control or sanctions laws, or other laws in your jurisdiction. You are responsible for making sure that your use of the Service is in compliance with laws and any applicable regulations.</p>
<h2>User-Generated Content</h2>
<ol>
<li>You may create or upload User-Generated Content while using the Service. You are solely responsible for the content of, and for any harm resulting from, any User-Generated Content that you post, upload, link to or otherwise make available via the Service, regardless of the form of that Content. We are not responsible for any public display or misuse of your User-Generated Content.</li>
<li>We do not pre-screen User-Generated Content, but we have the right (though not the obligation) to refuse or remove any User-Generated Content that, in our sole discretion, violates any our terms or policies.</li>
<li>
<p>You retain ownership of and responsibility for Your Content. If you're posting anything you did not create yourself or do not own the rights to, you agree that you are responsible for any Content you post; that you will only submit Content that you have the right to post; and that you will fully comply with any third party licenses relating to Content you post.</p>
<p>Because of above, we need you to grant us -- and other Your Gitea Instance users -- certain legal permissions, listed below in this section. If you upload Content that already comes with a license granting Your Gitea Instance the permissions we need to run our Service, no additional license is required. You understand that you will not receive any payment for any of the rights granted below. The licenses you grant to us will end when you remove Your Content from our servers, unless other Users have forked it.</p>
</li>
<li>
<p>We need the legal right to do things like host Your Content, publish it, and share it. You grant us and our legal successors the right to store, parse, and display Your Content, and make incidental copies as necessary to render the Website and provide the Service. This includes the right to do things like copy it to our database and make backups; show it to you and other users; parse it into a search index or otherwise analyze it on our servers; share it with other users; and perform it, in case Your Content is something like music or video.</p>
<p>This license, however, doesn't grant Your Gitea Instance the right to sell Your Content or otherwise distribute or use it outside of our provision of the Service.</p>
</li>
<li>
<p>Any User-Generated Content you post publicly, including issues, comments, and contributions to other Users' repositories, may be viewed by others. By setting your repositories to be viewed publicly, you agree to allow others to view and "fork" your repositories (this means that others may make their own copies of Content from your repositories in repositories they control).</p>
<p>If you set your pages and repositories to be viewed publicly, you grant each User of Your Gitea Instance a nonexclusive, worldwide license to use, display, and perform Your Content through the Your Gitea Instance Service and to reproduce Your Content solely on Your Gitea Instance as permitted through Your Gitea Instance's functionality (for example, through forking). You may grant further rights if you adopt a license. If you are uploading Content you did not create or own, you are responsible for ensuring that the Content you upload is licensed under terms that grant these permissions to other Your Gitea Instance Users.</p>
</li>
<li>
<p>Whenever you make a contribution to a repository containing notice of a license, you license your contribution under the same terms, and you agree that you have the right to license your contribution under those terms. If you have a separate agreement to license your contributions under different terms, such as a contributor license agreement, that agreement will supersede.</p>
<p><i>Isn't this just how it works already? Yep. This is widely accepted as the norm in the open-source community; it's commonly referred to by the shorthand "inbound=outbound". We're just making it explicit.</i></p>
</li>
<li>
<p>You retain all moral rights to Your Content that you upload, publish, or submit to any part of the Service, including the rights of integrity and attribution. However, you waive these rights and agree not to assert them against us, to enable us to reasonably exercise the rights granted above, but not otherwise.</p>
<p>To the extent this agreement is not enforceable by applicable law, you grant Your Gitea Instance the rights we need to use Your Content without attribution and to make reasonable adaptations of Your Content as necessary to render the Website and provide the Service.</p>
</li>
</ol>
<h2>Private Repositories</h2>
<ol>
<li>Some Accounts may have private repositories, which allow the User to control access to Content.</li>
<li>Your Gitea Instance considers the contents of private repositories to be confidential to you. Your Gitea Instance will protect the contents of private repositories from unauthorized use, access, or disclosure in the same manner that we would use to protect our own confidential information of a similar nature and in no event with less than a reasonable degree of care.</li>
<li>
<p>Your Gitea Instance employees may only access the content of your private repositories in the following situations:</p>
<ul>
<li>With your consent and knowledge, for support reasons. If Your Gitea Instance accesses a private repository for support reasons, we will only do so with the owners consent and knowledge.</li>
<li>When access is required for security reasons, including when access is required to maintain ongoing confidentiality, integrity, availability and resilience of Your Gitea Instance's systems and Service.</li>
</ul>
</li>
<li>You may choose to enable additional access to your private repositories. For example: You may enable various Your Gitea Instance services or features that require additional rights to Your Content in private repositories. These rights may vary depending on the service or feature, but Your Gitea Instance will continue to treat your private repository Content as confidential. If those services or features require rights in addition to those we need to provide the Your Gitea Instance Service, we will provide an explanation of those rights.</li>
</ol>
<h2>Copyright Infringement and DMCA Policy</h2>
<p>If you are copyright owner and believe that content on our website violates your copyright, please contact us at <a href="mailto:copyright@your-gitea-instance">copyright@your-gitea-instance</a>. Please note that before sending a takedown notice, consider legal uses (such as fair use and licensed use); and legal consequences for sending false notices.</p>
<h2>Intellectual Properties and COPYING</h2>
<p>Your Gitea Instance and our licensors, vendors, agents, and/or our content providers retain ownership of all intellectual property rights of any kind related to the Website and Service. We reserve all rights that are not expressly granted to you under this Agreement or by law. The look and feel of the Website and Service is copyright © Your Gitea Instance. All rights reserved.</p>
<p>If you'd like to use our trademarks, you must follow all of our trademark guidelines.</p>
<p>This Agreement is licensed under <a href="https://creativecommons.org/publicdomain/zero/1.0/legalcode">CCO Public Domain License</a>.</p>
<h2>API Terms</h2>
<p>Abuse or excessively frequent requests to Your Gitea Instance via the API may result in the temporary or permanent suspension of your Account's access to the API. Your Gitea Instance, in our sole discretion, will determine abuse or excessive usage of the API. We will make a reasonable attempt to warn you via email prior to suspension.</p>
<p>You may not share API tokens to exceed Your Gitea Instance's rate limitations.</p>
<p>You may not use the API to download data or Content from Your Gitea Instance for spamming purposes, including for the purposes of selling Your Gitea Instance users' personal information, such as to recruiters, headhunters, and job boards.</p>
<p>All use of the Your Gitea Instance API is subject to these Terms of Service and the Your Gitea Instance Privacy Statement.</p>
<p>However, we may provide subscription-based access to our API for Users who need high-throughput access or reselling our Service.</p>
<h2>Cancellation and Termination</h2>
<h3>Account Cancellation</h3>
<p>It is your responsibility to properly cancel your Account with Your Gitea Instance. You can cancel your Account at any time by going into your Settings in the global navigation bar at the top of the screen. The Account screen provides a simple, no questions asked cancellation link. We are not able to cancel Accounts in response to an email or phone request.</p>
<h3>Upon Cancellation</h3>
<p>We will retain and use your information as necessary to comply with our legal obligations, resolve disputes, and enforce our agreements, but barring legal requirements, we will delete your full profile and the Content of your repositories within 90 days of cancellation or termination (though some information may remain in encrypted backups). This information can not be recovered once your Account is cancelled.</p>
<p>We will not delete Content that you have contributed to other Users' repositories or that other Users have forked.</p>
<p>Upon request, we will make a reasonable effort to provide an Account owner with a copy of your lawful, non-infringing Account contents after Account cancellation or termination. You must make this request within 90 days of cancellation or termination.</p>
<h3>We May Terminate</h3>
<p>Your Gitea Instance has the right to suspend or terminate your access to all or any part of the Website at any time, with or without cause, with or without notice, effective immediately. Your Gitea Instance reserves the right to refuse service to anyone for any reason at any time.</p>
<h3>Survival</h3>
<p>All provisions of this Agreement which, by their nature, should survive termination will survive termination — including, without limitation: ownership provisions, warranty disclaimers, indemnity, and limitations of liability.</p>
<h2>Communications with Us</h2>
<h3>Electronic Communications Required</h3>
<p>For contractual purposes, you (1) consent to receive communications from us in an electronic form via the email address you have submitted or via the Service; and (2) agree that all Terms of Service, agreements, notices, disclosures, and other communications that we provide to you electronically satisfy any legal requirement that those communications would satisfy if they were on paper. This section does not affect your non-waivable rights.</p>
<h3>Legal Notices to Us Must Be in Writing</h3>
<p>Communications made through email or Your Gitea Instance Support's messaging system will not constitute legal notice to Your Gitea Instance or any of its officers, employees, agents or representatives in any situation where notice to Your Gitea Instance is required by contract or any law or regulation. Legal notice to Your Gitea Instance must be in writing and served on Your Gitea Instance's legal agent.</p>
<h3>No Phone Support</h3>
<p>We only offer support via email, in-Service communications, and electronic messages. We do not offer telephone support.</p>
<h2>Disclaimer of Warranties</h2>
<p>Your Gitea Instance provides the Website and the Service “as is” and “as available,” without warranty of any kind. Without limiting this, we expressly disclaim all warranties, whether express, implied or statutory, regarding the Website and the Service including without limitation any warranty of merchantability, fitness for a particular purpose, title, security, accuracy and non-infringement.</p>
<p>Your Gitea Instance does not warrant that the Service will meet your requirements; that the Service will be uninterrupted, timely, secure, or error-free; that the information provided through the Service is accurate, reliable or correct; that any defects or errors will be corrected; that the Service will be available at any particular time or location; or that the Service is free of viruses or other harmful components. You assume full responsibility and risk of loss resulting from your downloading and/or use of files, information, content or other material obtained from the Service.</p>
<h2>Limitation of Liability</h2>
<p>You understand and agree that we will not be liable to you or any third party for any loss of profits, use, goodwill, or data, or for any incidental, indirect, special, consequential or exemplary damages, however arising, that result from:</p>
<ul>
<li>the use, disclosure, or display of your User-Generated Content;</li>
<li>your use or inability to use the Service;</li>
<li>any modification, price change, suspension or discontinuance of the Service;</li>
<li>the Service generally or the software or systems that make the Service available;</li>
<li>unauthorized access to or alterations of your transmissions or data;</li>
<li>statements or conduct of any third party on the Service;</li>
<li>any other user interactions that you input or receive through your use of the Service; or
any other matter relating to the Service.</li>
</ul>
<p>Our liability is limited whether or not we have been informed of the possibility of such damages, and even if a remedy set forth in this Agreement is found to have failed of its essential purpose. We will have no liability for any failure or delay due to matters beyond our reasonable control.</p>
<h2>Release and Indemnification</h2>
<p>If you have a dispute with one or more Users, you agree to release Your Gitea Instance from any and all claims, demands and damages (actual and consequential) of every kind and nature, known and unknown, arising out of or in any way connected with such disputes.</p>
<p>You agree to indemnify us, defend us, and hold us harmless from and against any and all claims, liabilities, and expenses, including attorneys fees, arising out of your use of the Website and the Service, including but not limited to your violation of this Agreement, provided that Your Gitea Instance (1) promptly gives you written notice of the claim, demand, suit or proceeding; (2) gives you sole control of the defense and settlement of the claim, demand, suit or proceeding (provided that you may not settle any claim, demand, suit or proceeding unless the settlement unconditionally releases Your Gitea Instance of all liability); and (3) provides to you all reasonable assistance, at your expense.</p>
<h2>Changes to These Terms</h2>
<p>We reserve the right, at our sole discretion, to amend these Terms of Service at any time and will update these Terms of Service in the event of any such amendments. We will notify our Users of material changes to this Agreement, such as price changes, at least 30 days prior to the change taking effect by posting a notice on our Website. For non-material modifications, your continued use of the Website constitutes agreement to our revisions of these Terms of Service.</p>
<h2>Miscellaneous</h2>
<h3>Governing Law</h3>
<p>Except to the extent applicable law provides otherwise, this Agreement between you and us and any access to or use of the Website or the Service are governed by (national laws of country/state where Gitea is deployed) and (regional laws of locality where Gitea is deployed), without regard to conflict of law provisions. You and Your Gitea Instance agree to submit to the exclusive jurisdiction and venue of the courts located in (locality where Gitea is deployed).</p>
<h3>Non-Assignability</h3>
<p>Your Gitea Instance may assign or delegate these Terms of Service and/or our Privacy Policy in whole or in part, to any person or entity at any time with or without your consent, including the license granted in <i>User-Generated Content</i>. You may not assign or delegate any rights or obligations under the Terms of Service or Privacy Statement without our prior written consent, and any unauthorized assignment and delegation by you is void.</p>
<h3>Severablity, No Waiver, and Survival</h3>
<p>If any part of this Agreement is held invalid or unenforceable, that portion of the Agreement will be construed to reflect the parties original intent. The remaining portions will remain in full force and effect. Any failure on the part of Your Gitea Instance to enforce any provision of this Agreement will not be considered a waiver of our right to enforce such provision. Our rights under this Agreement will survive any termination of this Agreement.</p>
<h3>Amendments and Complete Agreement</h3>
<p>This Agreement may only be modified by a written amendment signed by an authorized representative of Your Gitea Instance, or by the posting by Your Gitea Instance of a revised version in accordance with <i>Changes to These Terms</i>. These Terms of Service, together with the Your Gitea Instance Privacy Policy, represent the complete and exclusive statement of the agreement between you and us. This Agreement supersedes any proposal or prior agreement oral or written, and any other communications between you and Your Gitea Instance relating to the subject matter of these terms including any confidentiality or nondisclosure agreements.</p>
<h3>Contact</h3>
<p>If you have questions about these Terms of Service, you can <a href="mailto:support@your-gitea-instance">contact our support</a>.</p>
</body>
</html>

View File

@ -1,26 +0,0 @@
#424242 Status: Needs feedback ; Feedback is needed
#fbc02d Status: In progress ; Work is in progress
#43a047 Status: Completed ; Work is complete
#00796b Status: Help wanted ;
#880e4f Status: Blocked ;
#b0bec5 Status: Stale ;
#d32f2f Kind: Bug ;
#607d8b Kind: Question ;
#9c27b0 Kind: Security ;
#795548 Kind: Testing ;
#c62828 Kind: Breaking ;
#37474f Kind: Documentation ;
#0288d1 Kind: Feature ;
#2e7d32 Kind: Enhancement ;
#f4511e Kind: Maintenance ;
#546e7a Reviewed: Invalid ; Something was marked as invalid
#616161 Reviewed: Duplicate; Something exists already
#795548 Reviewed: Confirmed ; Something has been confirmed
#eeeeee Reviewed: Wontfix ; Something won't be fixed
#d32f2f Priority: High ; The priority is high
#e64a19 Priority: Medium ; The priority is medium
#4caf50 Priority: Low ; The priority is low
#b71c1c Priority: Critical ; The priority is critical

View File

@ -1,7 +1,3 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package main
/*
@ -9,7 +5,6 @@ Checkout a PR and load the tests data into sqlite database
*/
import (
"context"
"flag"
"fmt"
"io/ioutil"
@ -26,17 +21,18 @@ import (
"time"
"code.gitea.io/gitea/models"
gitea_git "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/external"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/routes"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
context2 "github.com/gorilla/context"
"github.com/unknwon/com"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/config"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/testfixtures.v2"
"xorm.io/xorm"
)
@ -80,7 +76,7 @@ func runPR() {
setting.RunUser = curUser.Username
log.Printf("[PR] Loading fixtures data ...\n")
gitea_git.CheckLFSVersion()
setting.CheckLFSVersion()
//models.LoadConfigs()
/*
setting.Database.Type = "sqlite3"
@ -95,12 +91,14 @@ func runPR() {
setting.Database.LogSQL = true
//x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
models.NewEngine(context.Background(), func(_ *xorm.Engine) error {
var helper testfixtures.Helper = &testfixtures.SQLite{}
models.NewEngine(func(_ *xorm.Engine) error {
return nil
})
models.HasEngine = true
//x.ShowSQL(true)
err = models.InitFixtures(
helper,
path.Join(curDir, "models/fixtures/"),
)
if err != nil {
@ -108,15 +106,16 @@ func runPR() {
os.Exit(1)
}
models.LoadFixtures()
util.RemoveAll(setting.RepoRootPath)
util.RemoveAll(models.LocalCopyPath())
util.CopyDir(path.Join(curDir, "integrations/gitea-repositories-meta"), setting.RepoRootPath)
os.RemoveAll(setting.RepoRootPath)
os.RemoveAll(models.LocalCopyPath())
com.CopyDir(path.Join(curDir, "integrations/gitea-repositories-meta"), setting.RepoRootPath)
log.Printf("[PR] Setting up router\n")
//routers.GlobalInit()
external.RegisterRenderers()
external.RegisterParsers()
markup.Init()
c := routers.NormalRoutes()
m := routes.NewMacaron()
routes.RegisterRoutes(m)
log.Printf("[PR] Ready for testing !\n")
log.Printf("[PR] Login with user1, user2, user3, ... with pass: password\n")
@ -136,24 +135,24 @@ func runPR() {
*/
//Start the server
http.ListenAndServe(":8080", context2.ClearHandler(c))
http.ListenAndServe(":8080", context2.ClearHandler(m))
log.Printf("[PR] Cleaning up ...\n")
/*
if err = util.RemoveAll(setting.Indexer.IssuePath); err != nil {
fmt.Printf("util.RemoveAll: %v\n", err)
if err = os.RemoveAll(setting.Indexer.IssuePath); err != nil {
fmt.Printf("os.RemoveAll: %v\n", err)
os.Exit(1)
}
if err = util.RemoveAll(setting.Indexer.RepoPath); err != nil {
if err = os.RemoveAll(setting.Indexer.RepoPath); err != nil {
fmt.Printf("Unable to remove repo indexer: %v\n", err)
os.Exit(1)
}
*/
if err = util.RemoveAll(setting.RepoRootPath); err != nil {
log.Fatalf("util.RemoveAll: %v\n", err)
if err = os.RemoveAll(setting.RepoRootPath); err != nil {
log.Fatalf("os.RemoveAll: %v\n", err)
}
if err = util.RemoveAll(setting.AppDataPath); err != nil {
log.Fatalf("util.RemoveAll: %v\n", err)
if err = os.RemoveAll(setting.AppDataPath); err != nil {
log.Fatalf("os.RemoveAll: %v\n", err)
}
}
@ -197,9 +196,7 @@ func main() {
}
remoteUpstream := "origin" //Default
for _, r := range remotes {
if r.Config().URLs[0] == "https://github.com/go-gitea/gitea.git" ||
r.Config().URLs[0] == "https://github.com/go-gitea/gitea" ||
r.Config().URLs[0] == "git@github.com:go-gitea/gitea.git" { //fetch at index 0
if r.Config().URLs[0] == "https://github.com/go-gitea/gitea" || r.Config().URLs[0] == "git@github.com:go-gitea/gitea.git" { //fetch at index 0
remoteUpstream = r.Config().Name
break
}
@ -249,10 +246,9 @@ func main() {
log.Fatalf("Failed to duplicate this code file in PR : %v", err)
}
}
//Force build of js, css, bin, ...
runCmd("make", "build")
time.Sleep(5 * time.Second)
//Start with integration test
runCmd("go", "run", "-mod", "vendor", "-tags", "sqlite sqlite_unlock_notify", codeFilePath, "-run")
runCmd("go", "run", "-tags", "sqlite sqlite_unlock_notify", codeFilePath, "-run")
}
func runCmd(cmd ...string) {
log.Printf("Executing : %s ...\n", cmd)

View File

@ -3,23 +3,14 @@ Description=Gitea (Git with a cup of tea)
After=syslog.target
After=network.target
###
# Don't forget to add the database service dependencies
# Don't forget to add the database service requirements
###
#
#Wants=mysql.service
#After=mysql.service
#
#Wants=mariadb.service
#After=mariadb.service
#
#Wants=postgresql.service
#After=postgresql.service
#
#Wants=memcached.service
#After=memcached.service
#
#Wants=redis.service
#After=redis.service
#Requires=mysql.service
#Requires=mariadb.service
#Requires=postgresql.service
#Requires=memcached.service
#Requires=redis.service
#
###
# If using socket activation for main http/s
@ -66,12 +57,6 @@ WorkingDirectory=/var/lib/gitea/
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
# If you install Git to directory prefix other than default PATH (which happens
# for example if you install other versions of Git side-to-side with
# distribution version), uncomment below line and add that prefix to PATH
# Don't forget to place git-lfs binary on the PATH below if you want to enable
# Git LFS support
#Environment=PATH=/path/to/git/bin:/bin:/sbin:/usr/bin:/usr/sbin
# If you want to bind Gitea to a port below 1024, uncomment
# the two values below, or use socket activation to pass Gitea its ports as above
###

View File

@ -1,8 +0,0 @@
#!/usr/bin/env bash
grep 'git' go.mod | grep '\.com' | grep -v indirect | grep -v replace | cut -f 2 | cut -d ' ' -f 1 | while read line; do
go get -u "$line"
make vendor
git add .
git commit -S -m "update $line"
done

File diff suppressed because it is too large Load Diff

892
custom/conf/app.ini.sample Normal file
View File

@ -0,0 +1,892 @@
; This file lists the default values used by Gitea
; Copy required sections to your own app.ini (default is custom/conf/app.ini)
; and modify as needed.
; see https://docs.gitea.io/en-us/config-cheat-sheet/ for additional documentation.
; App name that shows in every page title
APP_NAME = Gitea: Git with a cup of tea
; Change it if you run locally
RUN_USER = git
; Either "dev", "prod" or "test", default is "dev"
RUN_MODE = dev
[repository]
ROOT =
SCRIPT_TYPE = bash
; Default ANSI charset
ANSI_CHARSET =
; Force every new repository to be private
FORCE_PRIVATE = false
; Default privacy setting when creating a new repository, allowed values: last, private, public. Default is last which means the last setting used.
DEFAULT_PRIVATE = last
; Global limit of repositories per user, applied at creation time. -1 means no limit
MAX_CREATION_LIMIT = -1
; Mirror sync queue length, increase if mirror syncing starts hanging
MIRROR_QUEUE_LENGTH = 1000
; Patch test queue length, increase if pull request patch testing starts hanging
PULL_REQUEST_QUEUE_LENGTH = 1000
; Preferred Licenses to place at the top of the List
; The name here must match the filename in conf/license or custom/conf/license
PREFERRED_LICENSES = Apache License 2.0,MIT License
; Disable the ability to interact with repositories using the HTTP protocol
DISABLE_HTTP_GIT = false
; Value for Access-Control-Allow-Origin header, default is not to present
; WARNING: This maybe harmful to you website if you do not give it a right value.
ACCESS_CONTROL_ALLOW_ORIGIN =
; Force ssh:// clone url instead of scp-style uri when default SSH port is used
USE_COMPAT_SSH_URI = false
; Close issues as long as a commit on any branch marks it as fixed
DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
[repository.editor]
; List of file extensions for which lines should be wrapped in the CodeMirror editor
; Separate extensions with a comma. To line wrap files without an extension, just put a comma
LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd,
; Valid file modes that have a preview API associated with them, such as api/v1/markdown
; Separate the values by commas. The preview tab in edit mode won't be displayed if the file extension doesn't match
PREVIEWABLE_FILE_MODES = markdown
[repository.local]
; Path for local repository copy. Defaults to `tmp/local-repo`
LOCAL_COPY_PATH = tmp/local-repo
; Path for local wiki copy. Defaults to `tmp/local-wiki`
LOCAL_WIKI_PATH = tmp/local-wiki
[repository.upload]
; Whether repository file uploads are enabled. Defaults to `true`
ENABLED = true
; Path for uploads. Defaults to `data/tmp/uploads` (tmp gets deleted on gitea restart)
TEMP_PATH = data/tmp/uploads
; One or more allowed types, e.g. image/jpeg|image/png. Nothing means any file type
ALLOWED_TYPES =
; Max size of each file in megabytes. Defaults to 3MB
FILE_MAX_SIZE = 3
; Max number of files per upload. Defaults to 5
MAX_FILES = 5
[repository.pull-request]
; List of prefixes used in Pull Request title to mark them as Work In Progress
WORK_IN_PROGRESS_PREFIXES=WIP:,[WIP]
; List of keywords used in Pull Request comments to automatically close a related issue
CLOSE_KEYWORDS=close,closes,closed,fix,fixes,fixed,resolve,resolves,resolved
; List of keywords used in Pull Request comments to automatically reopen a related issue
REOPEN_KEYWORDS=reopen,reopens,reopened
[repository.issue]
; List of reasons why a Pull Request or Issue can be locked
LOCK_REASONS=Too heated,Off-topic,Resolved,Spam
[repository.signing]
; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey
; run in the context of the RUN_USER
; Switch to none to stop signing completely
SIGNING_KEY = default
; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer.
; These should match a publicized name and email address for the key. (When SIGNING_KEY is default these are set to
; the results of git config --get user.name and git config --get user.email respectively and can only be overrided
; by setting the SIGNING_KEY ID to the correct ID.)
SIGNING_NAME =
SIGNING_EMAIL =
; Determines when gitea should sign the initial commit when creating a repository
; Either:
; - never
; - pubkey: only sign if the user has a pubkey
; - twofa: only sign if the user has logged in with twofa
; - always
; options other than none and always can be combined as comma separated list
INITIAL_COMMIT = always
; Determines when to sign for CRUD actions
; - as above
; - parentsigned: requires that the parent commit is signed.
CRUD_ACTIONS = pubkey, twofa, parentsigned
; Determines when to sign Wiki commits
; - as above
WIKI = never
; Determines when to sign on merges
; - basesigned: require that the parent of commit on the base repo is signed.
; - commitssigned: require that all the commits in the head branch are signed.
MERGES = pubkey, twofa, basesigned, commitssigned
[cors]
; More information about CORS can be found here: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#The_HTTP_response_headers
; enable cors headers (disabled by default)
ENABLED=false
; scheme of allowed requests
SCHEME=http
; list of requesting domains that are allowed
ALLOW_DOMAIN=*
; allow subdomains of headers listed above to request
ALLOW_SUBDOMAIN=false
; list of methods allowed to request
METHODS=GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
; max time to cache response
MAX_AGE=10m
; allow request with credentials
ALLOW_CREDENTIALS=false
[ui]
; Number of repositories that are displayed on one explore page
EXPLORE_PAGING_NUM = 20
; Number of issues that are displayed on one page
ISSUE_PAGING_NUM = 10
; Number of maximum commits displayed in one activity feed
FEED_MAX_COMMIT_NUM = 5
; Number of maximum commits displayed in commit graph.
GRAPH_MAX_COMMIT_NUM = 100
; Number of line of codes shown for a code comment
CODE_COMMENT_LINES = 4
; Value of `theme-color` meta tag, used by Android >= 5.0
; An invalid color like "none" or "disable" will have the default style
; More info: https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android
THEME_COLOR_META_TAG = `#6cc644`
; Max size of files to be displayed (default is 8MiB)
MAX_DISPLAY_FILE_SIZE = 8388608
; Whether the email of the user should be shown in the Explore Users page
SHOW_USER_EMAIL = true
; Set the default theme for the Gitea install
DEFAULT_THEME = gitea
; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`.
THEMES = gitea,arc-green
; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used.
DEFAULT_SHOW_FULL_NAME = false
; Whether to search within description at repository search on explore page.
SEARCH_REPO_DESCRIPTION = true
[ui.admin]
; Number of users that are displayed on one page
USER_PAGING_NUM = 50
; Number of repos that are displayed on one page
REPO_PAGING_NUM = 50
; Number of notices that are displayed on one page
NOTICE_PAGING_NUM = 25
; Number of organizations that are displayed on one page
ORG_PAGING_NUM = 50
[ui.user]
; Number of repos that are displayed on one page
REPO_PAGING_NUM = 15
[ui.meta]
AUTHOR = Gitea - Git with a cup of tea
DESCRIPTION = Gitea (Git with a cup of tea) is a painless self-hosted Git service written in Go
KEYWORDS = go,git,self-hosted,gitea
[markdown]
; Enable hard line break extension
ENABLE_HARD_LINE_BREAK = false
; Comma separated list of custom URL-Schemes that are allowed as links when rendering Markdown
; for example git,magnet,ftp (more at https://en.wikipedia.org/wiki/List_of_URI_schemes)
; URLs starting with http and https are always displayed, whatever is put in this entry.
CUSTOM_URL_SCHEMES =
; List of file extensions that should be rendered/edited as Markdown
; Separate the extensions with a comma. To render files without any extension as markdown, just put a comma
FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd
[server]
; The protocol the server listens on. One of 'http', 'https', 'unix' or 'fcgi'.
PROTOCOL = http
DOMAIN = localhost
ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
; when STATIC_URL_PREFIX is empty it will follow APP_URL
STATIC_URL_PREFIX =
; The address to listen on. Either a IPv4/IPv6 address or the path to a unix socket.
HTTP_ADDR = 0.0.0.0
HTTP_PORT = 3000
; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server
; will be started on PORT_TO_REDIRECT and it will redirect plain, non-secure http requests to the main
; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for
; PORT_TO_REDIRECT.
REDIRECT_OTHER_PORT = false
PORT_TO_REDIRECT = 80
; Permission for unix socket
UNIX_SOCKET_PERMISSION = 666
; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service.
; In most cases you do not need to change the default value.
; Alter it only if your SSH server node is not the same as HTTP node.
; Do not set this variable if PROTOCOL is set to 'unix'.
LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/
; Disable SSH feature when not available
DISABLE_SSH = false
; Whether to use the builtin SSH server or not.
START_SSH_SERVER = false
; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
BUILTIN_SSH_SERVER_USER =
; Domain name to be exposed in clone URL
SSH_DOMAIN = %(DOMAIN)s
; The network interface the builtin SSH server should listen on
SSH_LISTEN_HOST =
; Port number to be exposed in clone URL
SSH_PORT = 22
; The port number the builtin SSH server should listen on
SSH_LISTEN_PORT = %(SSH_PORT)s
; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
SSH_ROOT_PATH =
; Gitea will create a authorized_keys file by default when it is not using the internal ssh server
; If you intend to use the AuthorizedKeysCommand functionality then you should turn this off.
SSH_CREATE_AUTHORIZED_KEYS_FILE = true
; For the built-in SSH server, choose the ciphers to support for SSH connections,
; for system SSH this setting has no effect
SSH_SERVER_CIPHERS = aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, arcfour256, arcfour128
; For the built-in SSH server, choose the key exchange algorithms to support for SSH connections,
; for system SSH this setting has no effect
SSH_SERVER_KEY_EXCHANGES = diffie-hellman-group1-sha1, diffie-hellman-group14-sha1, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, curve25519-sha256@libssh.org
; For the built-in SSH server, choose the MACs to support for SSH connections,
; for system SSH this setting has no effect
SSH_SERVER_MACS = hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1, hmac-sha1-96
; Directory to create temporary files in when testing public keys using ssh-keygen,
; default is the system temporary directory.
SSH_KEY_TEST_PATH =
; Path to ssh-keygen, default is 'ssh-keygen' which means the shell is responsible for finding out which one to call.
SSH_KEYGEN_PATH = ssh-keygen
; Enable SSH Authorized Key Backup when rewriting all keys, default is true
SSH_BACKUP_AUTHORIZED_KEYS = true
; Enable exposure of SSH clone URL to anonymous visitors, default is false
SSH_EXPOSE_ANONYMOUS = false
; Indicate whether to check minimum key size with corresponding type
MINIMUM_KEY_SIZE_CHECK = false
; Disable CDN even in "prod" mode
OFFLINE_MODE = false
DISABLE_ROUTER_LOG = false
; Generate steps:
; $ ./gitea cert -ca=true -duration=8760h0m0s -host=myhost.example.com
;
; Or from a .pfx file exported from the Windows certificate store (do
; not forget to export the private key):
; $ openssl pkcs12 -in cert.pfx -out cert.pem -nokeys
; $ openssl pkcs12 -in cert.pfx -out key.pem -nocerts -nodes
CERT_FILE = custom/https/cert.pem
KEY_FILE = custom/https/key.pem
; Root directory containing templates and static files.
; default is the path where Gitea is executed
STATIC_ROOT_PATH =
; Default path for App data
APP_DATA_PATH = data
; Application level GZIP support
ENABLE_GZIP = false
; Application profiling (memory and cpu)
; For "web" command it listens on localhost:6060
; For "serve" command it dumps to disk at PPROF_DATA_PATH as (cpuprofile|memprofile)_<username>_<temporary id>
ENABLE_PPROF = false
; PPROF_DATA_PATH, use an absolute path when you start gitea as service
PPROF_DATA_PATH = data/tmp/pprof
; Landing page, can be "home", "explore", or "organizations"
LANDING_PAGE = home
; Enables git-lfs support. true or false, default is false.
LFS_START_SERVER = false
; Where your lfs files reside, default is data/lfs.
LFS_CONTENT_PATH = data/lfs
; LFS authentication secret, change this yourself
LFS_JWT_SECRET =
; LFS authentication validity period (in time.Duration), pushes taking longer than this may fail.
LFS_HTTP_AUTH_EXPIRY = 20m
; Allow graceful restarts using SIGHUP to fork
ALLOW_GRACEFUL_RESTARTS = true
; After a restart the parent will finish ongoing requests before
; shutting down. Force shutdown if this process takes longer than this delay.
; set to a negative value to disable
GRACEFUL_HAMMER_TIME = 60s
; Static resources, includes resources on custom/, public/ and all uploaded avatars web browser cache time, default is 6h
STATIC_CACHE_TIME = 6h
; Define allowed algorithms and their minimum key length (use -1 to disable a type)
[ssh.minimum_key_sizes]
ED25519 = 256
ECDSA = 256
RSA = 2048
DSA = 1024
[database]
; Either "mysql", "postgres", "mssql" or "sqlite3", it's your choice
DB_TYPE = mysql
HOST = 127.0.0.1:3306
NAME = gitea
USER = root
; Use PASSWD = `your password` for quoting if you use special characters in the password.
PASSWD =
; For Postgres, either "disable" (default), "require", or "verify-full"
; For MySQL, either "false" (default), "true", or "skip-verify"
SSL_MODE = disable
; For MySQL only, either "utf8" or "utf8mb4", default is "utf8".
; NOTICE: for "utf8mb4" you must use MySQL InnoDB > 5.6. Gitea is unable to check this.
CHARSET = utf8
; For "sqlite3" and "tidb", use an absolute path when you start gitea as service
PATH = data/gitea.db
; For "sqlite3" only. Query timeout
SQLITE_TIMEOUT = 500
; For iterate buffer, default is 50
ITERATE_BUFFER_SIZE = 50
; Show the database generated SQL
LOG_SQL = true
; Maximum number of DB Connect retries
DB_RETRIES = 10
; Backoff time per DB retry (time.Duration)
DB_RETRY_BACKOFF = 3s
; Max idle database connections on connnection pool, default is 2
MAX_IDLE_CONNS = 2
; Database connection max life time, default is 0 or 3s mysql (See #6804 & #7071 for reasoning)
CONN_MAX_LIFETIME = 3s
; Database maximum number of open connections, default is 0 meaning no maximum
MAX_OPEN_CONNS = 0
[indexer]
; Issue indexer type, currently support: bleve or db, default is bleve
ISSUE_INDEXER_TYPE = bleve
; Issue indexer storage path, available when ISSUE_INDEXER_TYPE is bleve
ISSUE_INDEXER_PATH = indexers/issues.bleve
; Issue indexer queue, currently support: channel, levelqueue or redis, default is levelqueue
ISSUE_INDEXER_QUEUE_TYPE = levelqueue
; When ISSUE_INDEXER_QUEUE_TYPE is levelqueue, this will be the queue will be saved path,
; default is indexers/issues.queue
ISSUE_INDEXER_QUEUE_DIR = indexers/issues.queue
; When `ISSUE_INDEXER_QUEUE_TYPE` is `redis`, this will store the redis connection string.
ISSUE_INDEXER_QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0"
; Batch queue number, default is 20
ISSUE_INDEXER_QUEUE_BATCH_NUMBER = 20
; Timeout the indexer if it takes longer than this to start.
; Set to zero to disable timeout.
STARTUP_TIMEOUT=30s
; repo indexer by default disabled, since it uses a lot of disk space
REPO_INDEXER_ENABLED = false
REPO_INDEXER_PATH = indexers/repos.bleve
UPDATE_BUFFER_LEN = 20
MAX_FILE_SIZE = 1048576
; A comma separated list of glob patterns (see https://github.com/gobwas/glob) to include
; in the index; default is empty
REPO_INDEXER_INCLUDE =
; A comma separated list of glob patterns to exclude from the index; ; default is empty
REPO_INDEXER_EXCLUDE =
[admin]
; Disallow regular (non-admin) users from creating organizations.
DISABLE_REGULAR_ORG_CREATION = false
; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
DEFAULT_EMAIL_NOTIFICATIONS = enabled
[security]
; Whether the installer is disabled
INSTALL_LOCK = false
; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!!
SECRET_KEY = !#@FDEWREWR&*(
; How long to remember that a user is logged in before requiring relogin (in days)
LOGIN_REMEMBER_DAYS = 7
COOKIE_USERNAME = gitea_awesome
COOKIE_REMEMBER_NAME = gitea_incredible
; Reverse proxy authentication header name of user name
REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
REVERSE_PROXY_AUTHENTICATION_EMAIL = X-WEBAUTH-EMAIL
; The minimum password length for new Users
MIN_PASSWORD_LENGTH = 6
; Set to true to allow users to import local server paths
IMPORT_LOCAL_PATHS = false
; Set to true to prevent all users (including admin) from creating custom git hooks
DISABLE_GIT_HOOKS = false
;Comma separated list of character classes required to pass minimum complexity.
;If left empty or no valid values are specified, the default values ("lower,upper,digit,spec") will be used.
;Use "off" to disable checking.
PASSWORD_COMPLEXITY = lower,upper,digit,spec
; Password Hash algorithm, either "pbkdf2", "argon2", "scrypt" or "bcrypt"
PASSWORD_HASH_ALGO = pbkdf2
; Set false to allow JavaScript to read CSRF cookie
CSRF_COOKIE_HTTP_ONLY = true
[openid]
;
; OpenID is an open, standard and decentralized authentication protocol.
; Your identity is the address of a webpage you provide, which describes
; how to prove you are in control of that page.
;
; For more info: https://en.wikipedia.org/wiki/OpenID
;
; Current implementation supports OpenID-2.0
;
; Tested to work providers at the time of writing:
; - Any GNUSocial node (your.hostname.tld/username)
; - Any SimpleID provider (http://simpleid.koinic.net)
; - http://openid.org.cn/
; - openid.stackexchange.com
; - login.launchpad.net
; - <username>.livejournal.com
;
; Whether to allow signin in via OpenID
ENABLE_OPENID_SIGNIN = true
; Whether to allow registering via OpenID
; Do not include to rely on rhw DISABLE_REGISTRATION setting
;ENABLE_OPENID_SIGNUP = true
; Allowed URI patterns (POSIX regexp).
; Space separated.
; Only these would be allowed if non-blank.
; Example value: trusted.domain.org trusted.domain.net
WHITELISTED_URIS =
; Forbidden URI patterns (POSIX regexp).
; Space separated.
; Only used if WHITELISTED_URIS is blank.
; Example value: loadaverage.org/badguy stackexchange.com/.*spammer
BLACKLISTED_URIS =
[service]
; Time limit to confirm account/email registration
ACTIVE_CODE_LIVE_MINUTES = 180
; Time limit to perform the reset of a forgotten password
RESET_PASSWD_CODE_LIVE_MINUTES = 180
; Whether a new user needs to confirm their email when registering.
REGISTER_EMAIL_CONFIRM = false
; List of domain names that are allowed to be used to register on a Gitea instance
; gitea.io,example.com
EMAIL_DOMAIN_WHITELIST=
; Disallow registration, only allow admins to create accounts.
DISABLE_REGISTRATION = false
; Allow registration only using third-party services, it works only when DISABLE_REGISTRATION is false
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
; User must sign in to view anything.
REQUIRE_SIGNIN_VIEW = false
; Mail notification
ENABLE_NOTIFY_MAIL = false
; This setting enables gitea to be signed in with HTTP BASIC Authentication using the user's password
; If you set this to false you will not be able to access the tokens endpoints on the API with your password
; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token
ENABLE_BASIC_AUTHENTICATION = true
; More detail: https://github.com/gogits/gogs/issues/165
ENABLE_REVERSE_PROXY_AUTHENTICATION = false
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
ENABLE_REVERSE_PROXY_EMAIL = false
; Enable captcha validation for registration
ENABLE_CAPTCHA = false
; Type of captcha you want to use. Options: image, recaptcha
CAPTCHA_TYPE = image
; Enable recaptcha to use Google's recaptcha service
; Go to https://www.google.com/recaptcha/admin to sign up for a key
RECAPTCHA_SECRET =
RECAPTCHA_SITEKEY =
; Change this to use recaptcha.net or other recaptcha service
RECAPTCHA_URL = https://www.google.com/recaptcha/
; Default value for KeepEmailPrivate
; Each new user will get the value of this setting copied into their profile
DEFAULT_KEEP_EMAIL_PRIVATE = false
; Default value for AllowCreateOrganization
; Every new user will have rights set to create organizations depending on this setting
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
; Either "public", "limited" or "private", default is "public"
; Limited is for signed user only
; Private is only for member of the organization
; Public is for everyone
DEFAULT_ORG_VISIBILITY = public
; Default value for DefaultOrgMemberVisible
; True will make the membership of the users visible when added to the organisation
DEFAULT_ORG_MEMBER_VISIBLE = false
; Default value for EnableDependencies
; Repositories will use dependencies by default depending on this setting
DEFAULT_ENABLE_DEPENDENCIES = true
; Dependencies can be added from any repository where the user is granted access or only from the current repository depending on this setting.
ALLOW_CROSS_REPOSITORY_DEPENDENCIES = true
; Enable heatmap on users profiles.
ENABLE_USER_HEATMAP = true
; Enable Timetracking
ENABLE_TIMETRACKING = true
; Default value for EnableTimetracking
; Repositories will use timetracking by default depending on this setting
DEFAULT_ENABLE_TIMETRACKING = true
; Default value for AllowOnlyContributorsToTrackTime
; Only users with write permissions can track time if this is true
DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME = true
; Default value for the domain part of the user's email address in the git log
; if he has set KeepEmailPrivate to true. The user's email will be replaced with a
; concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS.
NO_REPLY_ADDRESS = noreply.example.org
; Show Registration button
SHOW_REGISTRATION_BUTTON = true
; Default value for AutoWatchNewRepos
; When adding a repo to a team or creating a new repo all team members will watch the
; repo automatically if enabled
AUTO_WATCH_NEW_REPOS = true
; Default value for AutoWatchOnChanges
; Make the user watch a repository When they commit for the first time
AUTO_WATCH_ON_CHANGES = false
[webhook]
; Hook task queue length, increase if webhook shooting starts hanging
QUEUE_LENGTH = 1000
; Deliver timeout in seconds
DELIVER_TIMEOUT = 5
; Allow insecure certification
SKIP_TLS_VERIFY = false
; Number of history information in each page
PAGING_NUM = 10
; Proxy server URL, support http://, https//, socks://, blank will follow environment http_proxy/https_proxy
PROXY_URL =
; Comma separated list of host names requiring proxy. Glob patterns (*) are accepted; use ** to match all hosts.
PROXY_HOSTS =
[mailer]
ENABLED = false
; Buffer length of channel, keep it as it is if you don't know what it is.
SEND_BUFFER_LEN = 100
; Prefix displayed before subject in mail
SUBJECT_PREFIX =
; Mail server
; Gmail: smtp.gmail.com:587
; QQ: smtp.qq.com:465
; Note, if the port ends with "465", SMTPS will be used. Using STARTTLS on port 587 is recommended per RFC 6409. If the server supports STARTTLS it will always be used.
HOST =
; Disable HELO operation when hostnames are different.
DISABLE_HELO =
; Custom hostname for HELO operation, if no value is provided, one is retrieved from system.
HELO_HOSTNAME =
; Do not verify the certificate of the server. Only use this for self-signed certificates
SKIP_VERIFY =
; Use client certificate
USE_CERTIFICATE = false
CERT_FILE = custom/mailer/cert.pem
KEY_FILE = custom/mailer/key.pem
; Should SMTP connection use TLS
IS_TLS_ENABLED = false
; Mail from address, RFC 5322. This can be just an email address, or the `"Name" <email@example.com>` format
FROM =
; Mailer user name and password
USER =
; Use PASSWD = `your password` for quoting if you use special characters in the password.
PASSWD =
; Send mails as plain text
SEND_AS_PLAIN_TEXT = false
; Set Mailer Type (either SMTP, sendmail or dummy to just send to the log)
MAILER_TYPE = smtp
; Specify an alternative sendmail binary
SENDMAIL_PATH = sendmail
; Specify any extra sendmail arguments
SENDMAIL_ARGS =
[cache]
; Either "memory", "redis", or "memcache", default is "memory"
ADAPTER = memory
; For "memory" only, GC interval in seconds, default is 60
INTERVAL = 60
; For "redis" and "memcache", connection host address
; redis: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180
; memcache: `127.0.0.1:11211`
HOST =
; Time to keep items in cache if not used, default is 16 hours.
; Setting it to 0 disables caching
ITEM_TTL = 16h
[session]
; Either "memory", "file", or "redis", default is "memory"
PROVIDER = memory
; Provider config options
; memory: doesn't have any config yet
; file: session file path, e.g. `data/sessions`
; redis: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180
; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table`
PROVIDER_CONFIG = data/sessions
; Session cookie name
COOKIE_NAME = i_like_gitea
; If you use session in https only, default is false
COOKIE_SECURE = false
; Enable set cookie, default is true
ENABLE_SET_COOKIE = true
; Session GC time interval in seconds, default is 86400 (1 day)
GC_INTERVAL_TIME = 86400
; Session life time in seconds, default is 86400 (1 day)
SESSION_LIFE_TIME = 86400
[picture]
AVATAR_UPLOAD_PATH = data/avatars
REPOSITORY_AVATAR_UPLOAD_PATH = data/repo-avatars
; How Gitea deals with missing repository avatars
; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
REPOSITORY_AVATAR_FALLBACK = none
REPOSITORY_AVATAR_FALLBACK_IMAGE = /img/repo_default.png
; Max Width and Height of uploaded avatars.
; This is to limit the amount of RAM used when resizing the image.
AVATAR_MAX_WIDTH = 4096
AVATAR_MAX_HEIGHT = 3072
; Maximum alloved file size for uploaded avatars.
; This is to limit the amount of RAM used when resizing the image.
AVATAR_MAX_FILE_SIZE = 1048576
; Chinese users can choose "duoshuo"
; or a custom avatar source, like: http://cn.gravatar.com/avatar/
GRAVATAR_SOURCE = gravatar
; This value will always be true in offline mode.
DISABLE_GRAVATAR = false
; Federated avatar lookup uses DNS to discover avatar associated
; with emails, see https://www.libravatar.org
; This value will always be false in offline mode or when Gravatar is disabled.
ENABLE_FEDERATED_AVATAR = false
[attachment]
; Whether attachments are enabled. Defaults to `true`
ENABLED = true
; Path for attachments. Defaults to `data/attachments`
PATH = data/attachments
; One or more allowed types, e.g. image/jpeg|image/png
ALLOWED_TYPES = image/jpeg|image/png|application/zip|application/gzip
; Max size of each file. Defaults to 4MB
MAX_SIZE = 4
; Max number of files per upload. Defaults to 5
MAX_FILES = 5
[time]
; Specifies the format for fully outputted dates. Defaults to RFC1123
; Special supported values are ANSIC, UnixDate, RubyDate, RFC822, RFC822Z, RFC850, RFC1123, RFC1123Z, RFC3339, RFC3339Nano, Kitchen, Stamp, StampMilli, StampMicro and StampNano
; For more information about the format see http://golang.org/pkg/time/#pkg-constants
FORMAT =
; Location the UI time display i.e. Asia/Shanghai
; Empty means server's location setting
DEFAULT_UI_LOCATION =
[log]
ROOT_PATH =
; Either "console", "file", "conn", "smtp" or "database", default is "console"
; Use comma to separate multiple modes, e.g. "console, file"
MODE = console
; Buffer length of the channel, keep it as it is if you don't know what it is.
BUFFER_LEN = 10000
REDIRECT_MACARON_LOG = false
MACARON = file
; Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "Info"
ROUTER_LOG_LEVEL = Info
ROUTER = console
ENABLE_ACCESS_LOG = false
ACCESS_LOG_TEMPLATE = {{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}"
ACCESS = file
; Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "Trace"
LEVEL = Info
; Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "None"
STACKTRACE_LEVEL = None
; Generic log modes
[log.x]
FLAGS = stdflags
EXPRESSION =
PREFIX =
COLORIZE = false
; For "console" mode only
[log.console]
LEVEL =
STDERR = false
; For "file" mode only
[log.file]
LEVEL =
; Set the file_name for the logger. If this is a relative path this
; will be relative to ROOT_PATH
FILE_NAME =
; This enables automated log rotate(switch of following options), default is true
LOG_ROTATE = true
; Max number of lines in a single file, default is 1000000
MAX_LINES = 1000000
; Max size shift of a single file, default is 28 means 1 << 28, 256MB
MAX_SIZE_SHIFT = 28
; Segment log daily, default is true
DAILY_ROTATE = true
; delete the log file after n days, default is 7
MAX_DAYS = 7
; compress logs with gzip
COMPRESS = true
; compression level see godoc for compress/gzip
COMPRESSION_LEVEL = -1
; For "conn" mode only
[log.conn]
LEVEL =
; Reconnect host for every single message, default is false
RECONNECT_ON_MSG = false
; Try to reconnect when connection is lost, default is false
RECONNECT = false
; Either "tcp", "unix" or "udp", default is "tcp"
PROTOCOL = tcp
; Host address
ADDR =
; For "smtp" mode only
[log.smtp]
LEVEL =
; Name displayed in mail title, default is "Diagnostic message from server"
SUBJECT = Diagnostic message from server
; Mail server
HOST =
; Mailer user name and password
USER =
; Use PASSWD = `your password` for quoting if you use special characters in the password.
PASSWD =
; Receivers, can be one or more, e.g. 1@example.com,2@example.com
RECEIVERS =
[cron]
; Enable running cron tasks periodically.
ENABLED = true
; Run cron tasks when Gitea starts.
RUN_AT_START = false
; Update mirrors
[cron.update_mirrors]
SCHEDULE = @every 10m
; Repository health check
[cron.repo_health_check]
SCHEDULE = @every 24h
TIMEOUT = 60s
; Arguments for command 'git fsck', e.g. "--unreachable --tags"
; see more on http://git-scm.com/docs/git-fsck
ARGS =
; Check repository statistics
[cron.check_repo_stats]
RUN_AT_START = true
SCHEDULE = @every 24h
; Clean up old repository archives
[cron.archive_cleanup]
; Whether to enable the job
ENABLED = true
; Whether to always run at least once at start up time (if ENABLED)
RUN_AT_START = true
; Time interval for job to run
SCHEDULE = @every 24h
; Archives created more than OLDER_THAN ago are subject to deletion
OLDER_THAN = 24h
; Synchronize external user data (only LDAP user synchronization is supported)
[cron.sync_external_users]
; Synchronize external user data when starting server (default false)
RUN_AT_START = false
; Interval as a duration between each synchronization (default every 24h)
SCHEDULE = @every 24h
; Create new users, update existing user data and disable users that are not in external source anymore (default)
; or only create new users if UPDATE_EXISTING is set to false
UPDATE_EXISTING = true
; Update migrated repositories' issues and comments' posterid, it will always attempt synchronization when the instance starts.
[cron.update_migration_post_id]
; Interval as a duration between each synchronization. (default every 24h)
SCHEDULE = @every 24h
[git]
; The path of git executable. If empty, Gitea searches through the PATH environment.
PATH =
; Disables highlight of added and removed changes
DISABLE_DIFF_HIGHLIGHT = false
; Max number of lines allowed in a single file in diff view
MAX_GIT_DIFF_LINES = 1000
; Max number of allowed characters in a line in diff view
MAX_GIT_DIFF_LINE_CHARACTERS = 5000
; Max number of files shown in diff view
MAX_GIT_DIFF_FILES = 100
; Arguments for command 'git gc', e.g. "--aggressive --auto"
; see more on http://git-scm.com/docs/git-gc/
GC_ARGS =
; If use git wire protocol version 2 when git version >= 2.18, default is true, set to false when you always want git wire protocol version 1
EnableAutoGitWireProtocol = true
; Operation timeout in seconds
[git.timeout]
DEFAULT = 360
MIGRATE = 600
MIRROR = 300
CLONE = 300
PULL = 300
GC = 60
[mirror]
; Default interval as a duration between each check
DEFAULT_INTERVAL = 8h
; Min interval as a duration must be > 1m
MIN_INTERVAL = 10m
[api]
; Enables Swagger. True or false; default is true.
ENABLE_SWAGGER = true
; Max number of items in a page
MAX_RESPONSE_ITEMS = 50
; Default paging number of api
DEFAULT_PAGING_NUM = 30
; Default and maximum number of items per page for git trees api
DEFAULT_GIT_TREES_PER_PAGE = 1000
; Default size of a blob returned by the blobs API (default is 10MiB)
DEFAULT_MAX_BLOB_SIZE = 10485760
[oauth2]
; Enables OAuth2 provider
ENABLE = true
; Lifetime of an OAuth2 access token in seconds
ACCESS_TOKEN_EXPIRATION_TIME=3600
; Lifetime of an OAuth2 access token in hours
REFRESH_TOKEN_EXPIRATION_TIME=730
; Check if refresh token got already used
INVALIDATE_REFRESH_TOKENS=false
; OAuth2 authentication secret for access and refresh tokens, change this to a unique string.
JWT_SECRET=Bk0yK7Y9g_p56v86KaHqjSbxvNvu3SbKoOdOt2ZcXvU
[i18n]
LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR
NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,Українська,日本語,español,português do Brasil,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어
; Used for datetimepicker
[i18n.datelang]
en-US = en
zh-CN = zh
zh-HK = zh-HK
zh-TW = zh-TW
de-DE = de
fr-FR = fr
nl-NL = nl
lv-LV = lv
ru-RU = ru
uk-UA = uk
ja-JP = ja
es-ES = es
pt-BR = pt-BR
pl-PL = pl
bg-BG = bg
it-IT = it
fi-FI = fi
tr-TR = tr
cs-CZ = cs-CZ
sr-SP = sr
sv-SE = sv
ko-KR = ko
[U2F]
; NOTE: THE DEFAULT VALUES HERE WILL NEED TO BE CHANGED
; Two Factor authentication with security keys
; https://developers.yubico.com/U2F/App_ID.html
;APP_ID = http://localhost:3000/
; Comma seperated list of trusted facets
;TRUSTED_FACETS = http://localhost:3000/
; Extension mapping to highlight class
; e.g. .toml=ini
[highlight.mapping]
[other]
SHOW_FOOTER_BRANDING = false
; Show version information about Gitea and Go in the footer
SHOW_FOOTER_VERSION = true
; Show template execution time in the footer
SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
[markup.asciidoc]
ENABLED = false
; List of file extensions that should be rendered by an external command
FILE_EXTENSIONS = .adoc,.asciidoc
; External command to render all matching extensions
RENDER_COMMAND = "asciidoc --out-file=- -"
; Don't pass the file on STDIN, pass the filename as argument instead.
IS_INPUT_FILE = false
[metrics]
; Enables metrics endpoint. True or false; default is false.
ENABLED = false
; If you want to add authorization, specify a token here
TOKEN =
[task]
; Task queue type, could be `channel` or `redis`.
QUEUE_TYPE = channel
; Task queue length, available only when `QUEUE_TYPE` is `channel`.
QUEUE_LENGTH = 1000
; Task queue connection string, available only when `QUEUE_TYPE` is `redis`.
; If there is a password of redis, use `addrs=127.0.0.1:6379 password=123 db=0`.
QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0"

14
docker/Makefile vendored Normal file
View File

@ -0,0 +1,14 @@
#Makefile related to docker
DOCKER_IMAGE ?= gitea/gitea
DOCKER_TAG ?= latest
DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)
.PHONY: docker
docker:
docker build --disable-content-trust=false -t $(DOCKER_REF) .
# support also build args docker build --build-arg GITEA_VERSION=v1.2.3 --build-arg TAGS="bindata sqlite sqlite_unlock_notify" .
.PHONY: docker-build
docker-build:
docker run -ti --rm -v $(CURDIR):/srv/app/src/code.gitea.io/gitea -w /srv/app/src/code.gitea.io/gitea -e TAGS="bindata $(TAGS)" LDFLAGS="$(LDFLAGS)" webhippie/golang:edge make clean generate build

7
docker/README.md vendored
View File

@ -1,7 +0,0 @@
# Gitea - Docker
Dockerfile is found in root of repository.
Docker image can be found on [docker hub](https://hub.docker.com/r/gitea/gitea)
Documentation on using docker image can be found on [Gitea Docs site](https://docs.gitea.io/en-us/install-with-docker/)

View File

@ -1,20 +0,0 @@
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-rootless
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}-rootless
{{/each}}
- "latest-rootless"
{{/if}}
manifests:
-
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-linux-amd64-rootless
platform:
architecture: amd64
os: linux
-
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-linux-arm64-rootless
platform:
architecture: arm64
os: linux
variant: v8

View File

@ -1,20 +1,19 @@
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
- "latest"
{{/if}}
manifests:
-
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{else}}dev-{{/if}}linux-amd64
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
platform:
architecture: amd64
os: linux
-
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{else}}dev-{{/if}}linux-arm64
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
platform:
architecture: arm64
os: linux
variant: v8
variant: v8

View File

@ -1,2 +1,2 @@
#!/bin/bash
s6-svscanctl -t /etc/s6/
exit 0

View File

@ -1,6 +1,6 @@
#!/bin/bash
[[ -f ./setup ]] && source ./setup
pushd /app/gitea >/dev/null
exec su-exec $USER /app/gitea/gitea web
pushd /app/gitea > /dev/null
exec su-exec $USER /app/gitea/gitea web
popd

View File

@ -23,10 +23,9 @@ if [ ! -f ${GITEA_CUSTOM}/conf/app.ini ]; then
INSTALL_LOCK=true
fi
# Substitute the environment variables in the template
# Substitude the environment variables in the template
APP_NAME=${APP_NAME:-"Gitea: Git with a cup of tea"} \
RUN_MODE=${RUN_MODE:-"prod"} \
DOMAIN=${DOMAIN:-"localhost"} \
RUN_MODE=${RUN_MODE:-"dev"} \
SSH_DOMAIN=${SSH_DOMAIN:-"localhost"} \
HTTP_PORT=${HTTP_PORT:-"3000"} \
ROOT_URL=${ROOT_URL:-""} \
@ -48,9 +47,6 @@ if [ ! -f ${GITEA_CUSTOM}/conf/app.ini ]; then
chown ${USER}:git ${GITEA_CUSTOM}/conf/app.ini
fi
# Replace app.ini settings with env variables in the form GITEA__SECTION_NAME__KEY_NAME
environment-to-ini --config ${GITEA_CUSTOM}/conf/app.ini
# only chown if current owner is not already the gitea ${USER}. No recursive check to save time
if ! [[ $(ls -ld /data/gitea | awk '{print $3}') = ${USER} ]]; then chown -R ${USER}:git /data/gitea; fi
if ! [[ $(ls -ld /app/gitea | awk '{print $3}') = ${USER} ]]; then chown -R ${USER}:git /app/gitea; fi

View File

@ -1,6 +1,6 @@
#!/bin/bash
[[ -f ./setup ]] && source ./setup
pushd /root >/dev/null
exec su-exec root /usr/sbin/sshd -D -e 2>&1
pushd /root > /dev/null
exec su-exec root /usr/sbin/sshd -D -e 2>&1
popd

View File

@ -24,31 +24,9 @@ if [ ! -f /data/ssh/ssh_host_ecdsa_key ]; then
ssh-keygen -t ecdsa -b 256 -f /data/ssh/ssh_host_ecdsa_key -N "" > /dev/null
fi
if [ -e /data/ssh/ssh_host_ed25519_cert ]; then
SSH_ED25519_CERT=${SSH_ED25519_CERT:-"/data/ssh/ssh_host_ed25519_cert"}
fi
if [ -e /data/ssh/ssh_host_rsa_cert ]; then
SSH_RSA_CERT=${SSH_RSA_CERT:-"/data/ssh/ssh_host_rsa_cert"}
fi
if [ -e /data/ssh/ssh_host_ecdsa_cert ]; then
SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"}
fi
if [ -e /data/ssh/ssh_host_dsa_cert ]; then
SSH_DSA_CERT=${SSH_DSA_CERT:-"/data/ssh/ssh_host_dsa_cert"}
fi
if [ -d /etc/ssh ]; then
SSH_PORT=${SSH_PORT:-"22"} \
SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \
SSH_ED25519_CERT="${SSH_ED25519_CERT:+"HostCertificate "}${SSH_ED25519_CERT}" \
SSH_RSA_CERT="${SSH_RSA_CERT:+"HostCertificate "}${SSH_RSA_CERT}" \
SSH_ECDSA_CERT="${SSH_ECDSA_CERT:+"HostCertificate "}${SSH_ECDSA_CERT}" \
SSH_DSA_CERT="${SSH_DSA_CERT:+"HostCertificate "}${SSH_DSA_CERT}" \
SSH_MAX_STARTUPS="${SSH_MAX_STARTUPS:+"MaxStartups "}${SSH_MAX_STARTUPS}" \
SSH_MAX_SESSIONS="${SSH_MAX_SESSIONS:+"MaxSessions "}${SSH_MAX_SESSIONS}" \
envsubst < /etc/templates/sshd_config > /etc/ssh/sshd_config
chmod 0644 /etc/ssh/sshd_config

View File

@ -12,7 +12,6 @@ TEMP_PATH = /data/gitea/uploads
[server]
APP_DATA_PATH = /data/gitea
DOMAIN = $DOMAIN
SSH_DOMAIN = $SSH_DOMAIN
HTTP_PORT = $HTTP_PORT
ROOT_URL = $ROOT_URL
@ -29,7 +28,6 @@ HOST = $DB_HOST
NAME = $DB_NAME
USER = $DB_USER
PASSWD = $DB_PASSWD
LOG_SQL = false
[indexer]
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve
@ -45,16 +43,11 @@ REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars
PATH = /data/gitea/attachments
[log]
MODE = console
LEVEL = info
ROUTER = console
ROOT_PATH = /data/gitea/log
[security]
INSTALL_LOCK = $INSTALL_LOCK
SECRET_KEY = $SECRET_KEY
REVERSE_PROXY_LIMIT = 1
REVERSE_PROXY_TRUSTED_PROXIES = *
[service]
DISABLE_REGISTRATION = $DISABLE_REGISTRATION

View File

@ -5,24 +5,14 @@ AddressFamily any
ListenAddress 0.0.0.0
ListenAddress ::
${SSH_MAX_STARTUPS}
${SSH_MAX_SESSIONS}
LogLevel INFO
HostKey /data/ssh/ssh_host_ed25519_key
${SSH_ED25519_CERT}
HostKey /data/ssh/ssh_host_rsa_key
${SSH_RSA_CERT}
HostKey /data/ssh/ssh_host_ecdsa_key
${SSH_ECDSA_CERT}
HostKey /data/ssh/ssh_host_dsa_key
${SSH_DSA_CERT}
HostKey /data/ssh/ssh_host_ecdsa_key
AuthorizedKeysFile .ssh/authorized_keys
AuthorizedPrincipalsFile .ssh/authorized_principals
TrustedUserCAKeys /data/git/.ssh/gitea-trusted-user-ca-keys.pem
CASignatureAlgorithms ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ecdsa-sha2-nistp256@openssh.com,ssh-ed25519,sk-ssh-ed25519@openssh.com,rsa-sha2-512,rsa-sha2-256,ssh-rsa
UseDNS no
AllowAgentForwarding no

View File

@ -1,57 +0,0 @@
APP_NAME = $APP_NAME
RUN_USER = $RUN_USER
RUN_MODE = $RUN_MODE
[repository]
ROOT = $GITEA_WORK_DIR/git/repositories
[repository.local]
LOCAL_COPY_PATH = $GITEA_TEMP/local-repo
[repository.upload]
TEMP_PATH = $GITEA_TEMP/uploads
[server]
APP_DATA_PATH = $GITEA_WORK_DIR
SSH_DOMAIN = $SSH_DOMAIN
HTTP_PORT = $HTTP_PORT
ROOT_URL = $ROOT_URL
DISABLE_SSH = $DISABLE_SSH
; In rootless gitea container only internal ssh server is supported
START_SSH_SERVER = true
SSH_PORT = $SSH_PORT
SSH_LISTEN_PORT = $SSH_LISTEN_PORT
BUILTIN_SSH_SERVER_USER = $RUN_USER
LFS_START_SERVER = $LFS_START_SERVER
LFS_CONTENT_PATH = $GITEA_WORK_DIR/git/lfs
[database]
PATH = $GITEA_WORK_DIR/data/gitea.db
DB_TYPE = $DB_TYPE
HOST = $DB_HOST
NAME = $DB_NAME
USER = $DB_USER
PASSWD = $DB_PASSWD
[session]
PROVIDER_CONFIG = $GITEA_WORK_DIR/data/sessions
[picture]
AVATAR_UPLOAD_PATH = $GITEA_WORK_DIR/data/avatars
REPOSITORY_AVATAR_UPLOAD_PATH = $GITEA_WORK_DIR/data/gitea/repo-avatars
[attachment]
PATH = $GITEA_WORK_DIR/data/attachments
[log]
ROOT_PATH = $GITEA_WORK_DIR/data/log
[security]
INSTALL_LOCK = $INSTALL_LOCK
SECRET_KEY = $SECRET_KEY
REVERSE_PROXY_LIMIT = 1
REVERSE_PROXY_TRUSTED_PROXIES = *
[service]
DISABLE_REGISTRATION = $DISABLE_REGISTRATION
REQUIRE_SIGNIN_VIEW = $REQUIRE_SIGNIN_VIEW

View File

@ -1,11 +0,0 @@
#!/bin/sh
if [ -x /usr/local/bin/docker-setup.sh ]; then
/usr/local/bin/docker-setup.sh || { echo 'docker setup failed' ; exit 1; }
fi
if [ $# -gt 0 ]; then
exec "$@"
else
exec /usr/local/bin/gitea -c ${GITEA_APP_INI} web
fi

View File

@ -1,51 +0,0 @@
#!/bin/bash
# Prepare git folder
mkdir -p ${HOME} && chmod 0700 ${HOME}
if [ ! -w ${HOME} ]; then echo "${HOME} is not writable"; exit 1; fi
# Prepare custom folder
mkdir -p ${GITEA_CUSTOM} && chmod 0500 ${GITEA_CUSTOM}
# Prepare temp folder
mkdir -p ${GITEA_TEMP} && chmod 0700 ${GITEA_TEMP}
if [ ! -w ${GITEA_TEMP} ]; then echo "${GITEA_TEMP} is not writable"; exit 1; fi
#Prepare config file
if [ ! -f ${GITEA_APP_INI} ]; then
#Prepare config file folder
GITEA_APP_INI_DIR=$(dirname ${GITEA_APP_INI})
mkdir -p ${GITEA_APP_INI_DIR} && chmod 0700 ${GITEA_APP_INI_DIR}
if [ ! -w ${GITEA_APP_INI_DIR} ]; then echo "${GITEA_APP_INI_DIR} is not writable"; exit 1; fi
# Set INSTALL_LOCK to true only if SECRET_KEY is not empty and
# INSTALL_LOCK is empty
if [ -n "$SECRET_KEY" ] && [ -z "$INSTALL_LOCK" ]; then
INSTALL_LOCK=true
fi
# Substitute the environment variables in the template
APP_NAME=${APP_NAME:-"Gitea: Git with a cup of tea"} \
RUN_MODE=${RUN_MODE:-"prod"} \
RUN_USER=${USER:-"git"} \
SSH_DOMAIN=${SSH_DOMAIN:-"localhost"} \
HTTP_PORT=${HTTP_PORT:-"3000"} \
ROOT_URL=${ROOT_URL:-""} \
DISABLE_SSH=${DISABLE_SSH:-"false"} \
SSH_PORT=${SSH_PORT:-"2222"} \
SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-$SSH_PORT} \
DB_TYPE=${DB_TYPE:-"sqlite3"} \
DB_HOST=${DB_HOST:-"localhost:3306"} \
DB_NAME=${DB_NAME:-"gitea"} \
DB_USER=${DB_USER:-"root"} \
DB_PASSWD=${DB_PASSWD:-""} \
INSTALL_LOCK=${INSTALL_LOCK:-"false"} \
DISABLE_REGISTRATION=${DISABLE_REGISTRATION:-"false"} \
REQUIRE_SIGNIN_VIEW=${REQUIRE_SIGNIN_VIEW:-"false"} \
SECRET_KEY=${SECRET_KEY:-""} \
envsubst < /etc/templates/app.ini > ${GITEA_APP_INI}
fi
# Replace app.ini settings with env variables in the form GITEA__SECTION_NAME__KEY_NAME
environment-to-ini --config ${GITEA_APP_INI}

34
docs/.editorconfig Normal file
View File

@ -0,0 +1,34 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
[*.go]
indent_style = tab
indent_size = 8
[*.{tmpl,html}]
indent_style = tab
indent_size = 4
[*.{less}]
indent_style = space
indent_size = 4
[*.{yml}]
indent_style = space
indent_size = 2
[*.js]
indent_style = space
indent_size = 4
[Makefile]
indent_style = tab
[*.md]
trim_trailing_whitespace = false

View File

@ -21,14 +21,10 @@ server: $(THEME)
build: $(THEME)
hugo --cleanDestinationDir
.PHONY: build-offline
build-offline: $(THEME)
hugo --baseURL="/" --cleanDestinationDir
.PHONY: update
update: $(THEME)
$(THEME): $(THEME)/theme.toml
$(THEME)/theme.toml:
mkdir -p $$(dirname $@)
curl -L -s $(ARCHIVE) | tar xz -C $$(dirname $@)
curl -s $(ARCHIVE) | tar xz -C $$(dirname $@)

View File

@ -1,7 +1,7 @@
# Gitea: 文档
[![Build Status](http://drone.gitea.io/api/badges/go-gitea/docs/status.svg)](http://drone.gitea.io/go-gitea/docs)
[![Join the chat at https://img.shields.io/discord/322538954119184384.svg](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/Gitea)
[![Join the chat at https://img.shields.io/discord/322538954119184384.svg](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/NsatcWJ)
[![](https://images.microbadger.com/badges/image/gitea/docs.svg)](http://microbadger.com/images/gitea/docs "Get your own image badge on microbadger.com")
## 关于托管方式

View File

@ -1,164 +1,166 @@
/* global Fuse, Mark */
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
if (document.readyState != 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
ready(doSearch);
const summaryInclude = 60;
const fuseOptions = {
shouldSort: true,
includeMatches: true,
matchAllTokens: true,
threshold: 0.0, // for parsing diacritics
tokenize: true,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [{
name: 'title',
weight: 0.8
},
{
name: 'contents',
weight: 0.5
},
{
name: 'tags',
weight: 0.3
},
{
name: 'categories',
weight: 0.3
}
]
shouldSort: true,
includeMatches: true,
matchAllTokens: true,
threshold: 0.0, // for parsing diacritics
tokenize: true,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [{
name: "title",
weight: 0.8
},
{
name: "contents",
weight: 0.5
},
{
name: "tags",
weight: 0.3
},
{
name: "categories",
weight: 0.3
}
]
};
function param(name) {
return decodeURIComponent((window.location.search.split(`${name}=`)[1] || '').split('&')[0]).replace(/\+/g, ' ');
return decodeURIComponent((location.search.split(name + '=')[1] || '').split('&')[0]).replace(/\+/g, ' ');
}
const searchQuery = param('s');
let searchQuery = param("s");
function doSearch() {
if (searchQuery) {
document.getElementById('search-query').value = searchQuery;
executeSearch(searchQuery);
} else {
const para = document.createElement('P');
para.innerText = 'Please enter a word or phrase above';
document.getElementById('search-results').appendChild(para);
}
if (searchQuery) {
document.getElementById("search-query").value = searchQuery;
executeSearch(searchQuery);
} else {
const para = document.createElement("P");
para.innerText = "Please enter a word or phrase above";
document.getElementById("search-results").appendChild(para);
}
}
function getJSON(url, fn) {
const request = new XMLHttpRequest();
request.open('GET', url, true);
request.onload = function () {
if (request.status >= 200 && request.status < 400) {
const data = JSON.parse(request.responseText);
fn(data);
} else {
console.error(`Target reached on ${url} with error ${request.status}`);
}
};
request.onerror = function () {
console.error(`Connection error ${request.status}`);
};
request.send();
const request = new XMLHttpRequest();
request.open('GET', url, true);
request.onload = function () {
if (request.status >= 200 && request.status < 400) {
const data = JSON.parse(request.responseText);
fn(data);
} else {
console.log("Target reached on " + url + " with error " + request.status);
}
};
request.onerror = function () {
console.log("Connection error " + request.status);
};
request.send();
}
function executeSearch(searchQuery) {
getJSON(`/${document.LANG}/index.json`, (data) => {
const pages = data;
const fuse = new Fuse(pages, fuseOptions);
const result = fuse.search(searchQuery);
document.getElementById('search-results').innerHTML = '';
if (result.length > 0) {
populateResults(result);
} else {
const para = document.createElement('P');
para.innerText = 'No matches found';
document.getElementById('search-results').appendChild(para);
}
});
getJSON("/" + document.LANG + "/index.json", function (data) {
const pages = data;
const fuse = new Fuse(pages, fuseOptions);
const result = fuse.search(searchQuery);
console.log({
"matches": result
});
document.getElementById("search-results").innerHTML = "";
if (result.length > 0) {
populateResults(result);
} else {
const para = document.createElement("P");
para.innerText = "No matches found";
document.getElementById("search-results").appendChild(para);
}
});
}
function populateResults(result) {
result.forEach((value, key) => {
const content = value.item.contents;
let snippet = '';
const snippetHighlights = [];
if (fuseOptions.tokenize) {
snippetHighlights.push(searchQuery);
value.matches.forEach((mvalue) => {
if (mvalue.key === 'tags' || mvalue.key === 'categories') {
snippetHighlights.push(mvalue.value);
} else if (mvalue.key === 'contents') {
const ind = content.toLowerCase().indexOf(searchQuery.toLowerCase());
const start = ind - summaryInclude > 0 ? ind - summaryInclude : 0;
const end = ind + searchQuery.length + summaryInclude < content.length ? ind + searchQuery.length + summaryInclude : content.length;
snippet += content.substring(start, end);
if (ind > -1) {
snippetHighlights.push(content.substring(ind, ind + searchQuery.length));
} else {
snippetHighlights.push(mvalue.value.substring(mvalue.indices[0][0], mvalue.indices[0][1] - mvalue.indices[0][0] + 1));
}
result.forEach(function (value, key) {
const content = value.item.contents;
let snippet = "";
const snippetHighlights = [];
if (fuseOptions.tokenize) {
snippetHighlights.push(searchQuery);
value.matches.forEach(function (mvalue) {
if (mvalue.key === "tags" || mvalue.key === "categories") {
snippetHighlights.push(mvalue.value);
} else if (mvalue.key === "contents") {
const ind = content.toLowerCase().indexOf(searchQuery.toLowerCase());
const start = ind - summaryInclude > 0 ? ind - summaryInclude : 0;
const end = ind + searchQuery.length + summaryInclude < content.length ? ind + searchQuery.length + summaryInclude : content.length;
snippet += content.substring(start, end);
if (ind > -1) {
snippetHighlights.push(content.substring(ind, ind + searchQuery.length))
} else {
snippetHighlights.push(mvalue.value.substring(mvalue.indices[0][0], mvalue.indices[0][1] - mvalue.indices[0][0] + 1));
}
}
});
}
});
}
if (snippet.length < 1) {
snippet += content.substring(0, summaryInclude * 2);
}
// pull template from hugo template definition
const templateDefinition = document.getElementById('search-result-template').innerHTML;
// replace values
const output = render(templateDefinition, {
key,
title: value.item.title,
link: value.item.permalink,
tags: value.item.tags,
categories: value.item.categories,
snippet
});
document.getElementById('search-results').appendChild(htmlToElement(output));
if (snippet.length < 1) {
snippet += content.substring(0, summaryInclude * 2);
}
//pull template from hugo templarte definition
const templateDefinition = document.getElementById("search-result-template").innerHTML;
//replace values
const output = render(templateDefinition, {
key: key,
title: value.item.title,
link: value.item.permalink,
tags: value.item.tags,
categories: value.item.categories,
snippet: snippet
});
document.getElementById("search-results").appendChild(htmlToElement(output));
snippetHighlights.forEach(function (snipvalue) {
new Mark(document.getElementById("summary-" + key)).mark(snipvalue);
});
snippetHighlights.forEach((snipvalue) => {
new Mark(document.getElementById(`summary-${key}`)).mark(snipvalue);
});
});
}
function render(templateString, data) {
let conditionalMatches, copy;
const conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g;
// since loop below depends on re.lastInxdex, we use a copy to capture any manipulations whilst inside the loop
copy = templateString;
while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) {
if (data[conditionalMatches[1]]) {
// valid key, remove conditionals, leave content.
copy = copy.replace(conditionalMatches[0], conditionalMatches[2]);
} else {
// not valid, remove entire section
copy = copy.replace(conditionalMatches[0], '');
let conditionalMatches, copy;
const conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g;
//since loop below depends on re.lastInxdex, we use a copy to capture any manipulations whilst inside the loop
copy = templateString;
while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) {
if (data[conditionalMatches[1]]) {
//valid key, remove conditionals, leave content.
copy = copy.replace(conditionalMatches[0], conditionalMatches[2]);
} else {
//not valid, remove entire section
copy = copy.replace(conditionalMatches[0], '');
}
}
}
templateString = copy;
// now any conditionals removed we can do simple substitution
let key, find, re;
for (key of Object.keys(data)) {
find = `\\$\\{\\s*${key}\\s*\\}`;
re = new RegExp(find, 'g');
templateString = templateString.replace(re, data[key]);
}
return templateString;
templateString = copy;
//now any conditionals removed we can do simple substitution
let key, find, re;
for (key in data) {
find = '\\$\\{\\s*' + key + '\\s*\\}';
re = new RegExp(find, 'g');
templateString = templateString.replace(re, data[key]);
}
return templateString;
}
/**
@ -167,8 +169,8 @@ function render(templateString, data) {
* @return {Element}
*/
function htmlToElement(html) {
const template = document.createElement('template');
html = html.trim(); // Never return a text node of whitespace as the result
template.innerHTML = html;
return template.content.firstChild;
const template = document.createElement('template');
html = html.trim(); // Never return a text node of whitespace as the result
template.innerHTML = html;
return template.content.firstChild;
}

View File

@ -18,10 +18,7 @@ params:
description: Git with a cup of tea
author: The Gitea Authors
website: https://docs.gitea.io
version: 1.15.0
minGoVersion: 1.16
goVersion: 1.17
minNodeVersion: 12.17
version: 1.9.5
outputs:
home:
@ -48,14 +45,10 @@ menu:
url: https://blog.gitea.io/
weight: 30
pre: rss
- name: Shop
url: https://shop.gitea.io/
- name: Code
url: https://code.gitea.io/
weight: 40
pre: shopping-cart
- name: Translation
url: https://crowdin.com/project/gitea
weight: 41
pre: language
pre: code
- name: Downloads
url: https://dl.gitea.io/
weight: 50
@ -65,7 +58,7 @@ menu:
weight: 60
pre: github
- name: Discord Chat
url: https://discord.gg/Gitea
url: https://discord.gg/NsatcWJ
weight: 70
pre: comment
- name: Forum
@ -104,10 +97,6 @@ languages:
url: https://code.gitea.io/
weight: 40
pre: code
- name: 翻译
url: https://crowdin.com/project/gitea
weight: 41
pre: language
- name: 下载
url: https://dl.gitea.io/
weight: 50
@ -117,7 +106,7 @@ languages:
weight: 60
pre: github
- name: Discord Chat
url: https://discord.gg/Gitea
url: https://discord.gg/NsatcWJ
weight: 70
pre: comment
- name: Forum
@ -147,15 +136,11 @@ languages:
url: https://blog.gitea.io/
weight: 30
pre: rss
- name: 商店
url: https://shop.gitea.io/
- name: 程式碼
url: https://code.gitea.io/
weight: 40
pre: shopping-cart
- name: 翻譯
url: https://crowdin.com/project/gitea
weight: 41
pre: language
- name: 下載
pre: code
- name: 下载
url: https://dl.gitea.io/
weight: 50
pre: download
@ -163,11 +148,11 @@ languages:
url: https://github.com/go-gitea/
weight: 60
pre: github
- name: Discord 聊天室
url: https://discord.gg/Gitea
- name: Discord Chat
url: https://discord.gg/NsatcWJ
weight: 70
pre: comment
- name: 討論區
- name: Forum
url: https://discourse.gitea.io/
weight: 80
pre: group
@ -198,10 +183,6 @@ languages:
url: https://code.gitea.io/
weight: 40
pre: code
- name: Translation
url: https://crowdin.com/project/gitea
weight: 41
pre: language
- name: Downloads
url: https://dl.gitea.io/
weight: 50
@ -211,7 +192,7 @@ languages:
weight: 60
pre: github
- name: Chat no Discord
url: https://discord.gg/Gitea
url: https://discord.gg/NsatcWJ
weight: 70
pre: comment
- name: Forum
@ -245,10 +226,6 @@ languages:
url: https://code.gitea.io/
weight: 40
pre: code
- name: Translation
url: https://crowdin.com/project/gitea
weight: 41
pre: language
- name: Downloads
url: https://dl.gitea.io/
weight: 50
@ -258,7 +235,7 @@ languages:
weight: 60
pre: github
- name: Discord Chat
url: https://discord.gg/Gitea
url: https://discord.gg/NsatcWJ
weight: 70
pre: comment
- name: Forum
@ -292,10 +269,6 @@ languages:
url: https://code.gitea.io/
weight: 40
pre: code
- name: Translation
url: https://crowdin.com/project/gitea
weight: 41
pre: language
- name: Téléchargement
url: https://dl.gitea.io/
weight: 50
@ -305,54 +278,7 @@ languages:
weight: 60
pre: github
- name: Discord Chat
url: https://discord.gg/Gitea
weight: 70
pre: comment
- name: Forum
url: https://discourse.gitea.io/
weight: 80
pre: group
de-de:
weight: 6
languageName: Deutsch
menu:
page:
- name: Webseite
url: https://gitea.io/en-us/
weight: 10
pre: home
post: active
- name: Dokumentation
url: /de-de/
weight: 20
pre: question
- name: API
url: https://try.gitea.io/api/swagger
weight: 45
pre: plug
- name: Blog
url: https://blog.gitea.io/
weight: 30
pre: rss
- name: Code
url: https://code.gitea.io/
weight: 40
pre: code
- name: Übersetzung
url: https://crowdin.com/project/gitea
weight: 41
pre: language
- name: Downloads
url: https://dl.gitea.io/
weight: 50
pre: download
- name: GitHub
url: https://github.com/go-gitea/
weight: 60
pre: github
- name: Discord Chat
url: https://discord.gg/Gitea
url: https://discord.gg/NsatcWJ
weight: 70
pre: comment
- name: Forum

View File

@ -1,13 +0,0 @@
---
date: "2016-12-01T16:00:00+02:00"
title: "進階"
slug: "advanced"
weight: 30
toc: false
draft: false
menu:
sidebar:
name: "進階"
weight: 40
identifier: "advanced"
---

View File

@ -1,38 +0,0 @@
---
date: "2019-12-28"
title: "Adding Legal Pages"
slug: adding-legal-pages
weight: 9
toc: false
draft: false
menu:
sidebar:
parent: "advanced"
name: "Adding Legal Pages"
identifier: "adding-legal-pages"
weight: 9
---
Some jurisdictions (such as EU), requires certain legal pages (e.g. Privacy Policy) to be added to website. Follow these steps to add them to your Gitea instance.
## Getting Pages
Gitea source code ships with sample pages, available in `contrib/legal` directory. Copy them to `custom/public/`. For example, to add Privacy Policy:
```
wget -O /path/to/custom/public/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/master/contrib/legal/privacy.html.sample
```
Now you need to edit the page to meet your requirements. In particular you must change the email addresses, web addresses and references to "Your Gitea Instance" to match your situation.
You absolutely must not place a general ToS or privacy statement that implies that the gitea project is responsible for your server.
## Make it Visible
Create or append to `/path/to/custom/templates/custom/extra_links_footer.tmpl`:
```go
<a class="item" href="{{AppSubUrl}}/assets/privacy.html">Privacy Policy</a>
```
Restart Gitea to see the changes.

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