mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-03 08:02:36 +09:00
Compare commits
81 Commits
v1.24.0
...
v1.20.0-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71d2a6a41a | ||
|
|
d1f1f1142e | ||
|
|
2cd9d6b3f9 | ||
|
|
050c38ca19 | ||
|
|
51789ba12d | ||
|
|
b0de3d08b8 | ||
|
|
2e64449de7 | ||
|
|
ec539b7a77 | ||
|
|
6fbdacb524 | ||
|
|
948f6ca029 | ||
|
|
061b68e995 | ||
|
|
734fd93f59 | ||
|
|
203fe2841d | ||
|
|
056829749e | ||
|
|
f18b8e7d8a | ||
|
|
ea00ed320d | ||
|
|
30a783879f | ||
|
|
cb3173a1e9 | ||
|
|
ffe089432f | ||
|
|
8302b95d6b | ||
|
|
6f1c95ec5b | ||
|
|
cda69a0363 | ||
|
|
4908cc9adf | ||
|
|
28ed763f55 | ||
|
|
8e89eb8f43 | ||
|
|
dfefe86045 | ||
|
|
10fcb55507 | ||
|
|
e9105ac281 | ||
|
|
e6e1cfd8e4 | ||
|
|
072997692c | ||
|
|
e9fab3ea3e | ||
|
|
e0bd6ebabd | ||
|
|
cc73f6e821 | ||
|
|
ff18c3ba65 | ||
|
|
b673edbeaf | ||
|
|
05431593ef | ||
|
|
aa4c9c3215 | ||
|
|
4e79c76ed0 | ||
|
|
3bd311c3f4 | ||
|
|
7e06e6a042 | ||
|
|
e5629d9701 | ||
|
|
4ea38bba73 | ||
|
|
25cb1fb994 | ||
|
|
e5422db5c7 | ||
|
|
3a29f6aaff | ||
|
|
99d71b2b65 | ||
|
|
783f7ccb2c | ||
|
|
3f75fbf8fe | ||
|
|
4124f8ef70 | ||
|
|
b45ea0280b | ||
|
|
031ddfcb7b | ||
|
|
d686aa0d31 | ||
|
|
037366f93f | ||
|
|
5191ab6445 | ||
|
|
bfd3eb9dbc | ||
|
|
8fa9d9dcc9 | ||
|
|
21cd5c2f3d | ||
|
|
22948048b2 | ||
|
|
fa28d0e706 | ||
|
|
3ea544d89c | ||
|
|
9cef7a4600 | ||
|
|
c207b94e0c | ||
|
|
506c70884a | ||
|
|
f64f5495af | ||
|
|
3e9fc36729 | ||
|
|
8e798ebbdf | ||
|
|
0ad5ae0dbf | ||
|
|
0cf467e9e0 | ||
|
|
5ff0f7d0ca | ||
|
|
224ee0d4e5 | ||
|
|
ee26d1c578 | ||
|
|
18093d4c9a | ||
|
|
de1d14590d | ||
|
|
0058453fd9 | ||
|
|
7679f4d51a | ||
|
|
82a8c26bbf | ||
|
|
cb113991a3 | ||
|
|
0bf07a7f61 | ||
|
|
adb5b9c061 | ||
|
|
f0c967560a | ||
|
|
1cc63ade82 |
439
.drone.yml
439
.drone.yml
@@ -1,138 +1,3 @@
|
|||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: release-latest
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
workspace:
|
|
||||||
base: /source
|
|
||||||
path: /
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
branch:
|
|
||||||
- main
|
|
||||||
- "release/*"
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
paths:
|
|
||||||
exclude:
|
|
||||||
- "docs/**"
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- name: deps
|
|
||||||
temp: {}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: fetch-tags
|
|
||||||
image: docker:git
|
|
||||||
pull: always
|
|
||||||
commands:
|
|
||||||
- git fetch --tags --force
|
|
||||||
|
|
||||||
- name: deps-frontend
|
|
||||||
image: node:20
|
|
||||||
pull: always
|
|
||||||
commands:
|
|
||||||
- make deps-frontend
|
|
||||||
|
|
||||||
- name: deps-backend
|
|
||||||
image: gitea/test_env:linux-1.20-amd64
|
|
||||||
pull: always
|
|
||||||
commands:
|
|
||||||
- make deps-backend
|
|
||||||
volumes:
|
|
||||||
- name: deps
|
|
||||||
path: /go
|
|
||||||
|
|
||||||
- name: static
|
|
||||||
image: techknowlogick/xgo:go-1.20.x
|
|
||||||
pull: always
|
|
||||||
commands:
|
|
||||||
# Upgrade to node 20 once https://github.com/techknowlogick/xgo/issues/163 is resolved
|
|
||||||
- curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get -qqy install nodejs
|
|
||||||
- export PATH=$PATH:$GOPATH/bin
|
|
||||||
- make release
|
|
||||||
environment:
|
|
||||||
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
|
|
||||||
TAGS: bindata sqlite sqlite_unlock_notify
|
|
||||||
DEBIAN_FRONTEND: noninteractive
|
|
||||||
volumes:
|
|
||||||
- name: deps
|
|
||||||
path: /go
|
|
||||||
|
|
||||||
- name: gpg-sign
|
|
||||||
image: plugins/gpgsign:1
|
|
||||||
pull: always
|
|
||||||
settings:
|
|
||||||
detach_sign: true
|
|
||||||
excludes:
|
|
||||||
- "dist/release/*.sha256"
|
|
||||||
files:
|
|
||||||
- "dist/release/*"
|
|
||||||
environment:
|
|
||||||
GPGSIGN_KEY:
|
|
||||||
from_secret: gpgsign_key
|
|
||||||
GPGSIGN_PASSPHRASE:
|
|
||||||
from_secret: gpgsign_passphrase
|
|
||||||
|
|
||||||
- name: release-branch
|
|
||||||
image: woodpeckerci/plugin-s3:latest
|
|
||||||
pull: always
|
|
||||||
settings:
|
|
||||||
acl:
|
|
||||||
from_secret: aws_s3_acl
|
|
||||||
region:
|
|
||||||
from_secret: aws_s3_region
|
|
||||||
bucket:
|
|
||||||
from_secret: aws_s3_bucket
|
|
||||||
endpoint:
|
|
||||||
from_secret: aws_s3_endpoint
|
|
||||||
path_style:
|
|
||||||
from_secret: aws_s3_path_style
|
|
||||||
source: "dist/release/*"
|
|
||||||
strip_prefix: dist/release/
|
|
||||||
target: "/gitea/${DRONE_BRANCH##release/v}"
|
|
||||||
environment:
|
|
||||||
AWS_ACCESS_KEY_ID:
|
|
||||||
from_secret: aws_access_key_id
|
|
||||||
AWS_SECRET_ACCESS_KEY:
|
|
||||||
from_secret: aws_secret_access_key
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- "release/*"
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
|
|
||||||
- name: release-main
|
|
||||||
image: woodpeckerci/plugin-s3:latest
|
|
||||||
settings:
|
|
||||||
acl:
|
|
||||||
from_secret: aws_s3_acl
|
|
||||||
region:
|
|
||||||
from_secret: aws_s3_region
|
|
||||||
bucket:
|
|
||||||
from_secret: aws_s3_bucket
|
|
||||||
endpoint:
|
|
||||||
from_secret: aws_s3_endpoint
|
|
||||||
path_style:
|
|
||||||
from_secret: aws_s3_path_style
|
|
||||||
source: "dist/release/*"
|
|
||||||
strip_prefix: dist/release/
|
|
||||||
target: /gitea/main
|
|
||||||
environment:
|
|
||||||
AWS_ACCESS_KEY_ID:
|
|
||||||
from_secret: aws_access_key_id
|
|
||||||
AWS_SECRET_ACCESS_KEY:
|
|
||||||
from_secret: aws_secret_access_key
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- main
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
name: release-version
|
name: release-version
|
||||||
@@ -379,133 +244,6 @@ steps:
|
|||||||
exclude:
|
exclude:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: docker-linux-amd64-release
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
ref:
|
|
||||||
- refs/heads/main
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: fetch-tags
|
|
||||||
image: docker:git
|
|
||||||
pull: always
|
|
||||||
commands:
|
|
||||||
- git fetch --tags --force
|
|
||||||
|
|
||||||
- name: publish
|
|
||||||
image: plugins/docker:latest
|
|
||||||
pull: always
|
|
||||||
settings:
|
|
||||||
auto_tag: false
|
|
||||||
tags: nightly-linux-amd64
|
|
||||||
repo: gitea/gitea
|
|
||||||
build_args:
|
|
||||||
- GOPROXY=https://goproxy.io
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
environment:
|
|
||||||
PLUGIN_MIRROR:
|
|
||||||
from_secret: plugin_mirror
|
|
||||||
DOCKER_BUILDKIT: 1
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
- name: publish-rootless
|
|
||||||
image: plugins/docker:latest
|
|
||||||
settings:
|
|
||||||
dockerfile: Dockerfile.rootless
|
|
||||||
auto_tag: false
|
|
||||||
tags: nightly-linux-amd64-rootless
|
|
||||||
repo: gitea/gitea
|
|
||||||
build_args:
|
|
||||||
- GOPROXY=https://goproxy.io
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
environment:
|
|
||||||
PLUGIN_MIRROR:
|
|
||||||
from_secret: plugin_mirror
|
|
||||||
DOCKER_BUILDKIT: 1
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: docker-linux-amd64-release-branch
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
ref:
|
|
||||||
- "refs/heads/release/v*"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: fetch-tags
|
|
||||||
image: docker:git
|
|
||||||
pull: always
|
|
||||||
commands:
|
|
||||||
- git fetch --tags --force
|
|
||||||
|
|
||||||
- name: publish
|
|
||||||
image: plugins/docker:latest
|
|
||||||
pull: always
|
|
||||||
settings:
|
|
||||||
auto_tag: false
|
|
||||||
tags: ${DRONE_BRANCH##release/v}-nightly-linux-amd64
|
|
||||||
repo: gitea/gitea
|
|
||||||
build_args:
|
|
||||||
- GOPROXY=https://goproxy.io
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
environment:
|
|
||||||
PLUGIN_MIRROR:
|
|
||||||
from_secret: plugin_mirror
|
|
||||||
DOCKER_BUILDKIT: 1
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
- name: publish-rootless
|
|
||||||
image: plugins/docker:latest
|
|
||||||
settings:
|
|
||||||
dockerfile: Dockerfile.rootless
|
|
||||||
auto_tag: false
|
|
||||||
tags: ${DRONE_BRANCH##release/v}-nightly-linux-amd64-rootless
|
|
||||||
repo: gitea/gitea
|
|
||||||
build_args:
|
|
||||||
- GOPROXY=https://goproxy.io
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
environment:
|
|
||||||
PLUGIN_MIRROR:
|
|
||||||
from_secret: plugin_mirror
|
|
||||||
DOCKER_BUILDKIT: 1
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
@@ -641,136 +379,6 @@ steps:
|
|||||||
exclude:
|
exclude:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: docker-linux-arm64-release
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
ref:
|
|
||||||
- refs/heads/main
|
|
||||||
paths:
|
|
||||||
exclude:
|
|
||||||
- "docs/**"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: fetch-tags
|
|
||||||
image: docker:git
|
|
||||||
pull: always
|
|
||||||
commands:
|
|
||||||
- git fetch --tags --force
|
|
||||||
|
|
||||||
- name: publish
|
|
||||||
image: plugins/docker:latest
|
|
||||||
pull: always
|
|
||||||
settings:
|
|
||||||
auto_tag: false
|
|
||||||
tags: nightly-linux-arm64
|
|
||||||
repo: gitea/gitea
|
|
||||||
build_args:
|
|
||||||
- GOPROXY=https://goproxy.io
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
environment:
|
|
||||||
PLUGIN_MIRROR:
|
|
||||||
from_secret: plugin_mirror
|
|
||||||
DOCKER_BUILDKIT: 1
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
- name: publish-rootless
|
|
||||||
image: plugins/docker:latest
|
|
||||||
settings:
|
|
||||||
dockerfile: Dockerfile.rootless
|
|
||||||
auto_tag: false
|
|
||||||
tags: nightly-linux-arm64-rootless
|
|
||||||
repo: gitea/gitea
|
|
||||||
build_args:
|
|
||||||
- GOPROXY=https://goproxy.io
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
environment:
|
|
||||||
PLUGIN_MIRROR:
|
|
||||||
from_secret: plugin_mirror
|
|
||||||
DOCKER_BUILDKIT: 1
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: docker-linux-arm64-release-branch
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
ref:
|
|
||||||
- "refs/heads/release/v*"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: fetch-tags
|
|
||||||
image: docker:git
|
|
||||||
pull: always
|
|
||||||
commands:
|
|
||||||
- git fetch --tags --force
|
|
||||||
|
|
||||||
- name: publish
|
|
||||||
image: plugins/docker:latest
|
|
||||||
pull: always
|
|
||||||
settings:
|
|
||||||
auto_tag: false
|
|
||||||
tags: ${DRONE_BRANCH##release/v}-nightly-linux-arm64
|
|
||||||
repo: gitea/gitea
|
|
||||||
build_args:
|
|
||||||
- GOPROXY=https://goproxy.io
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
environment:
|
|
||||||
PLUGIN_MIRROR:
|
|
||||||
from_secret: plugin_mirror
|
|
||||||
DOCKER_BUILDKIT: 1
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
- name: publish-rootless
|
|
||||||
image: plugins/docker:latest
|
|
||||||
settings:
|
|
||||||
dockerfile: Dockerfile.rootless
|
|
||||||
auto_tag: false
|
|
||||||
tags: ${DRONE_BRANCH##release/v}-nightly-linux-arm64-rootless
|
|
||||||
repo: gitea/gitea
|
|
||||||
build_args:
|
|
||||||
- GOPROXY=https://goproxy.io
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
environment:
|
|
||||||
PLUGIN_MIRROR:
|
|
||||||
from_secret: plugin_mirror
|
|
||||||
DOCKER_BUILDKIT: 1
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
@@ -816,50 +424,3 @@ depends_on:
|
|||||||
- docker-linux-amd64-release-candidate-version
|
- docker-linux-amd64-release-candidate-version
|
||||||
- docker-linux-arm64-release-version
|
- docker-linux-arm64-release-version
|
||||||
- docker-linux-arm64-release-candidate-version
|
- docker-linux-arm64-release-candidate-version
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: docker-manifest
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: manifest-rootless
|
|
||||||
image: plugins/manifest
|
|
||||||
pull: always
|
|
||||||
settings:
|
|
||||||
auto_tag: false
|
|
||||||
ignore_missing: true
|
|
||||||
spec: docker/manifest.rootless.tmpl
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
|
|
||||||
- name: manifest
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: false
|
|
||||||
ignore_missing: true
|
|
||||||
spec: docker/manifest.tmpl
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
ref:
|
|
||||||
- refs/heads/main
|
|
||||||
- "refs/heads/release/v*"
|
|
||||||
paths:
|
|
||||||
exclude:
|
|
||||||
- "docs/**"
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- docker-linux-amd64-release
|
|
||||||
- docker-linux-arm64-release
|
|
||||||
- docker-linux-amd64-release-branch
|
|
||||||
- docker-linux-arm64-release-branch
|
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ globals:
|
|||||||
__webpack_public_path__: true
|
__webpack_public_path__: true
|
||||||
|
|
||||||
overrides:
|
overrides:
|
||||||
- files: ["web_src/**/*.js", "docs/**/*.js"]
|
- files: ["web_src/**/*", "docs/**/*"]
|
||||||
env:
|
env:
|
||||||
browser: true
|
browser: true
|
||||||
node: false
|
node: false
|
||||||
- files: ["web_src/**/*worker.js"]
|
- files: ["web_src/**/*worker.*"]
|
||||||
env:
|
env:
|
||||||
worker: true
|
worker: true
|
||||||
rules:
|
rules:
|
||||||
@@ -42,7 +42,7 @@ overrides:
|
|||||||
rules:
|
rules:
|
||||||
import/no-unresolved: [0]
|
import/no-unresolved: [0]
|
||||||
import/no-extraneous-dependencies: [0]
|
import/no-extraneous-dependencies: [0]
|
||||||
- files: ["*.config.js"]
|
- files: ["*.config.*"]
|
||||||
rules:
|
rules:
|
||||||
import/no-unused-modules: [0]
|
import/no-unused-modules: [0]
|
||||||
|
|
||||||
|
|||||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -5,5 +5,6 @@
|
|||||||
/templates/swagger/v1_json.tmpl linguist-generated
|
/templates/swagger/v1_json.tmpl linguist-generated
|
||||||
/vendor/** -text -eol linguist-vendored
|
/vendor/** -text -eol linguist-vendored
|
||||||
/web_src/fomantic/build/** linguist-generated
|
/web_src/fomantic/build/** linguist-generated
|
||||||
|
/web_src/fomantic/_site/globals/site.variables linguist-language=Less
|
||||||
/web_src/js/vendor/** -text -eol linguist-vendored
|
/web_src/js/vendor/** -text -eol linguist-vendored
|
||||||
Dockerfile.* linguist-language=Dockerfile
|
Dockerfile.* linguist-language=Dockerfile
|
||||||
|
|||||||
1
.github/workflows/cron-licenses.yml
vendored
1
.github/workflows/cron-licenses.yml
vendored
@@ -3,6 +3,7 @@ name: cron-licenses
|
|||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "7 0 * * 1" # every Monday at 00:07 UTC
|
- cron: "7 0 * * 1" # every Monday at 00:07 UTC
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cron-licenses:
|
cron-licenses:
|
||||||
|
|||||||
1
.github/workflows/cron-translations.yml
vendored
1
.github/workflows/cron-translations.yml
vendored
@@ -3,6 +3,7 @@ name: cron-translations
|
|||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "7 0 * * *" # every day at 00:07 UTC
|
- cron: "7 0 * * *" # every day at 00:07 UTC
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
crowdin-pull:
|
crowdin-pull:
|
||||||
|
|||||||
24
.github/workflows/pull-compliance.yml
vendored
24
.github/workflows/pull-compliance.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
uses: ./.github/workflows/files-changed.yml
|
uses: ./.github/workflows/files-changed.yml
|
||||||
|
|
||||||
lint-backend:
|
lint-backend:
|
||||||
if: needs.files-changed.outputs.backend == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
TAGS: bindata sqlite sqlite_unlock_notify
|
TAGS: bindata sqlite sqlite_unlock_notify
|
||||||
|
|
||||||
lint-go-windows:
|
lint-go-windows:
|
||||||
if: needs.files-changed.outputs.backend == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
GOARCH: amd64
|
GOARCH: amd64
|
||||||
|
|
||||||
lint-go-gogit:
|
lint-go-gogit:
|
||||||
if: needs.files-changed.outputs.backend == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||||
|
|
||||||
checks-backend:
|
checks-backend:
|
||||||
if: needs.files-changed.outputs.backend == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
- run: make --always-make checks-backend # ensure the "go-licenses" make target runs
|
- run: make --always-make checks-backend # ensure the "go-licenses" make target runs
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
if: needs.files-changed.outputs.frontend == 'true'
|
if: needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -83,9 +83,10 @@ jobs:
|
|||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
- run: make lint-frontend
|
- run: make lint-frontend
|
||||||
- run: make checks-frontend
|
- run: make checks-frontend
|
||||||
|
- run: make frontend
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
if: needs.files-changed.outputs.backend == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -94,12 +95,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ">=1.20"
|
go-version: ">=1.20"
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/setup-node@v3
|
# no frontend build here as backend should be able to build
|
||||||
with:
|
# even without any frontend files
|
||||||
node-version: 20
|
|
||||||
- run: make deps-backend deps-tools
|
- run: make deps-backend deps-tools
|
||||||
- run: make deps-frontend
|
|
||||||
- run: make frontend
|
|
||||||
- run: go build -o gitea_no_gcc # test if build succeeds without the sqlite tag
|
- run: go build -o gitea_no_gcc # test if build succeeds without the sqlite tag
|
||||||
- name: build-backend-arm64
|
- name: build-backend-arm64
|
||||||
run: make backend # test cross compile
|
run: make backend # test cross compile
|
||||||
@@ -120,7 +118,7 @@ jobs:
|
|||||||
GOARCH: 386
|
GOARCH: 386
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
if: needs.files-changed.outputs.docs == 'true'
|
if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -133,7 +131,7 @@ jobs:
|
|||||||
- run: make docs # test if build could succeed
|
- run: make docs # test if build could succeed
|
||||||
|
|
||||||
actions:
|
actions:
|
||||||
if: needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
12
.github/workflows/pull-db-tests.yml
vendored
12
.github/workflows/pull-db-tests.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
uses: ./.github/workflows/files-changed.yml
|
uses: ./.github/workflows/files-changed.yml
|
||||||
|
|
||||||
test-pgsql:
|
test-pgsql:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
USE_REPO_TEST_DIR: 1
|
USE_REPO_TEST_DIR: 1
|
||||||
|
|
||||||
test-sqlite:
|
test-sqlite:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
USE_REPO_TEST_DIR: 1
|
USE_REPO_TEST_DIR: 1
|
||||||
|
|
||||||
test-unit:
|
test-unit:
|
||||||
if: needs.files-changed.outputs.backend == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
@@ -144,7 +144,7 @@ jobs:
|
|||||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
||||||
|
|
||||||
test-mysql5:
|
test-mysql5:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
@@ -188,7 +188,7 @@ jobs:
|
|||||||
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
|
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
|
||||||
|
|
||||||
test-mysql8:
|
test-mysql8:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
@@ -217,7 +217,7 @@ jobs:
|
|||||||
USE_REPO_TEST_DIR: 1
|
USE_REPO_TEST_DIR: 1
|
||||||
|
|
||||||
test-mssql:
|
test-mssql:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
|
|||||||
2
.github/workflows/pull-docker-dryrun.yml
vendored
2
.github/workflows/pull-docker-dryrun.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
uses: ./.github/workflows/files-changed.yml
|
uses: ./.github/workflows/files-changed.yml
|
||||||
|
|
||||||
docker-dryrun:
|
docker-dryrun:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
2
.github/workflows/pull-e2e-tests.yml
vendored
2
.github/workflows/pull-e2e-tests.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
uses: ./.github/workflows/files-changed.yml
|
uses: ./.github/workflows/files-changed.yml
|
||||||
|
|
||||||
test-e2e:
|
test-e2e:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
92
.github/workflows/release-nightly.yml
vendored
Normal file
92
.github/workflows/release-nightly.yml
vendored
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
name: release-nightly-assets
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, release/v* ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
nightly-binary:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
|
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||||
|
- run: git fetch --unshallow --quiet --tags --force
|
||||||
|
- uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ">=1.20"
|
||||||
|
check-latest: true
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
- run: make deps-frontend deps-backend
|
||||||
|
# xgo build
|
||||||
|
- run: make release
|
||||||
|
env:
|
||||||
|
TAGS: bindata sqlite sqlite_unlock_notify
|
||||||
|
- name: import gpg key
|
||||||
|
id: import_gpg
|
||||||
|
uses: crazy-max/ghaction-import-gpg@v5
|
||||||
|
with:
|
||||||
|
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
||||||
|
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
||||||
|
- name: sign binaries
|
||||||
|
run: |
|
||||||
|
for f in dist/release/*; do
|
||||||
|
echo '${{ secrets.GPGSIGN_PASSPHRASE }}' | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u ${{ steps.import_gpg.outputs.fingerprint }} --output "$f.asc" "$f"
|
||||||
|
done
|
||||||
|
# clean branch name to get the folder name in S3
|
||||||
|
- name: Get cleaned branch name
|
||||||
|
id: clean_name
|
||||||
|
run: |
|
||||||
|
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
||||||
|
echo "Cleaned name is ${REF_NAME}"
|
||||||
|
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: upload binaries to s3
|
||||||
|
uses: jakejarvis/s3-sync-action@master
|
||||||
|
env:
|
||||||
|
AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||||
|
SOURCE_DIR: dist/release
|
||||||
|
DEST_DIR: gitea/${{ steps.clean_name.outputs.branch }}
|
||||||
|
nightly-docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
|
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||||
|
- run: git fetch --unshallow --quiet --tags --force
|
||||||
|
- uses: docker/setup-qemu-action@v2
|
||||||
|
- uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Get cleaned branch name
|
||||||
|
id: clean_name
|
||||||
|
run: |
|
||||||
|
# if main then say nightly otherwise cleanup name
|
||||||
|
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||||
|
echo "branch=nightly" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
||||||
|
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: build rootful docker image
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||||
|
- name: build rootless docker image
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
file: Dockerfile.rootless
|
||||||
|
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -16,10 +16,6 @@ _test
|
|||||||
.vscode
|
.vscode
|
||||||
__debug_bin
|
__debug_bin
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
*.cgo1.go
|
||||||
*.cgo2.c
|
*.cgo2.c
|
||||||
_cgo_defun.c
|
_cgo_defun.c
|
||||||
@@ -57,8 +53,6 @@ cpu.out
|
|||||||
/bin
|
/bin
|
||||||
/dist
|
/dist
|
||||||
/custom/*
|
/custom/*
|
||||||
!/custom/conf
|
|
||||||
/custom/conf/*
|
|
||||||
!/custom/conf/app.example.ini
|
!/custom/conf/app.example.ini
|
||||||
/data
|
/data
|
||||||
/indexers
|
/indexers
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ rules:
|
|||||||
media-feature-name-no-vendor-prefix: true
|
media-feature-name-no-vendor-prefix: true
|
||||||
media-feature-name-unit-allowed-list: null
|
media-feature-name-unit-allowed-list: null
|
||||||
media-feature-name-value-allowed-list: null
|
media-feature-name-value-allowed-list: null
|
||||||
|
media-feature-name-value-no-unknown: true
|
||||||
media-feature-range-notation: null
|
media-feature-range-notation: null
|
||||||
named-grid-areas-no-invalid: true
|
named-grid-areas-no-invalid: true
|
||||||
no-descending-specificity: null
|
no-descending-specificity: null
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ Here's how to run the test suite:
|
|||||||
## Translation
|
## Translation
|
||||||
|
|
||||||
All translation work happens on [Crowdin](https://crowdin.com/project/gitea).
|
All translation work happens on [Crowdin](https://crowdin.com/project/gitea).
|
||||||
The only translation that is maintained in this repository is [the English translation](https://github.com/go-gitea/gitea/blob/master/options/locale/locale_en-US.ini).
|
The only translation that is maintained in this repository is [the English translation](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.ini).
|
||||||
It is synced regularly with Crowdin. \
|
It is synced regularly with Crowdin. \
|
||||||
Other locales on main branch **should not** be updated manually as they will be overwritten with each sync. \
|
Other locales on main branch **should not** be updated manually as they will be overwritten with each sync. \
|
||||||
Once a language has reached a **satisfactory percentage** of translated keys (~25%), it will be synced back into this repo and included in the next released version.
|
Once a language has reached a **satisfactory percentage** of translated keys (~25%), it will be synced back into this repo and included in the next released version.
|
||||||
@@ -557,7 +557,7 @@ be reviewed by two maintainers and must pass the automatic tests.
|
|||||||
- And then push the tag as `git push origin v$vmaj.$vmin.$`. Drone CI will automatically create a release and upload all the compiled binary. (But currently it doesn't add the release notes automatically. Maybe we should fix that.)
|
- And then push the tag as `git push origin v$vmaj.$vmin.$`. Drone CI will automatically create a release and upload all the compiled binary. (But currently it doesn't add the release notes automatically. Maybe we should fix that.)
|
||||||
- If needed send a frontport PR for the changelog to branch `main` and update the version in `docs/config.yaml` to refer to the new version.
|
- If needed send a frontport PR for the changelog to branch `main` and update the version in `docs/config.yaml` to refer to the new version.
|
||||||
- Send PR to [blog repository](https://gitea.com/gitea/blog) announcing the release.
|
- Send PR to [blog repository](https://gitea.com/gitea/blog) announcing the release.
|
||||||
- Verify all release assets were correctly published through CI on dl.gitea.io and GitHub releases. Once ACKed:
|
- Verify all release assets were correctly published through CI on dl.gitea.com and GitHub releases. Once ACKed:
|
||||||
- bump the version of https://dl.gitea.io/gitea/version.json
|
- bump the version of https://dl.gitea.com/gitea/version.json
|
||||||
- merge the blog post PR
|
- merge the blog post PR
|
||||||
- announce the release in discord `#announcements`
|
- announce the release in discord `#announcements`
|
||||||
|
|||||||
27
Makefile
27
Makefile
@@ -79,12 +79,21 @@ endif
|
|||||||
STORED_VERSION_FILE := VERSION
|
STORED_VERSION_FILE := VERSION
|
||||||
HUGO_VERSION ?= 0.111.3
|
HUGO_VERSION ?= 0.111.3
|
||||||
|
|
||||||
|
GITHUB_REF_TYPE ?= branch
|
||||||
|
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
|
|
||||||
|
# backwards compatible to build with Drone
|
||||||
ifneq ($(DRONE_TAG),)
|
ifneq ($(DRONE_TAG),)
|
||||||
VERSION ?= $(subst v,,$(DRONE_TAG))
|
GITHUB_REF_TYPE := tag
|
||||||
|
GITHUB_REF_NAME := $(DRONE_TAG)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifneq ($(GITHUB_REF_TYPE),branch)
|
||||||
|
VERSION ?= $(subst v,,$(GITHUB_REF_NAME))
|
||||||
GITEA_VERSION ?= $(VERSION)
|
GITEA_VERSION ?= $(VERSION)
|
||||||
else
|
else
|
||||||
ifneq ($(DRONE_BRANCH),)
|
ifneq ($(GITHUB_REF_NAME),)
|
||||||
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
|
VERSION ?= $(subst release/v,,$(GITHUB_REF_NAME))
|
||||||
else
|
else
|
||||||
VERSION ?= main
|
VERSION ?= main
|
||||||
endif
|
endif
|
||||||
@@ -831,28 +840,28 @@ release-windows: | $(DIST_DIRS)
|
|||||||
ifeq (,$(findstring gogit,$(TAGS)))
|
ifeq (,$(findstring gogit,$(TAGS)))
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
|
||||||
endif
|
endif
|
||||||
ifeq ($(CI),true)
|
ifneq ($(DRONE_TAG),)
|
||||||
cp /build/* $(DIST)/binaries
|
cp /build/* $(DIST)/binaries
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: release-linux
|
.PHONY: release-linux
|
||||||
release-linux: | $(DIST_DIRS)
|
release-linux: | $(DIST_DIRS)
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
|
||||||
ifeq ($(CI),true)
|
ifneq ($(DRONE_TAG),)
|
||||||
cp /build/* $(DIST)/binaries
|
cp /build/* $(DIST)/binaries
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: release-darwin
|
.PHONY: release-darwin
|
||||||
release-darwin: | $(DIST_DIRS)
|
release-darwin: | $(DIST_DIRS)
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
|
||||||
ifeq ($(CI),true)
|
ifneq ($(DRONE_TAG),)
|
||||||
cp /build/* $(DIST)/binaries
|
cp /build/* $(DIST)/binaries
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: release-freebsd
|
.PHONY: release-freebsd
|
||||||
release-freebsd: | $(DIST_DIRS)
|
release-freebsd: | $(DIST_DIRS)
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) .
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) .
|
||||||
ifeq ($(CI),true)
|
ifneq ($(DRONE_TAG),)
|
||||||
cp /build/* $(DIST)/binaries
|
cp /build/* $(DIST)/binaries
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -1010,9 +1019,5 @@ docker:
|
|||||||
docker build --disable-content-trust=false -t $(DOCKER_REF) .
|
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" .
|
# 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
|
# This endif closes the if at the top of the file
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -173,8 +173,8 @@ for the full license text.
|
|||||||
|
|
||||||
Looking for an overview of the interface? Check it out!
|
Looking for an overview of the interface? Check it out!
|
||||||
|
|
||||||
||||
|
||||
|
||||||
|:---:|:---:|:---:|
|
|:---:|:---:|:---:|
|
||||||
||||
|
||||
|
||||||
|||
|
|||
|
||||||
|||
|
|||
|
||||||
|
|||||||
@@ -91,8 +91,8 @@ Fork -> Patch -> Push -> Pull Request
|
|||||||
|
|
||||||
## 截图
|
## 截图
|
||||||
|
|
||||||
||||
|
||||
|
||||||
|:---:|:---:|:---:|
|
|:---:|:---:|:---:|
|
||||||
||||
|
||||
|
||||||
|||
|
|||
|
||||||
|||
|
|||
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func runGenerateActionsRunnerToken(c *cli.Context) error {
|
|||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setting.Init(&setting.Options{})
|
setting.MustInstalled()
|
||||||
|
|
||||||
scope := c.String("scope")
|
scope := c.String("scope")
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func confirm() (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initDB(ctx context.Context) error {
|
func initDB(ctx context.Context) error {
|
||||||
setting.Init(&setting.Options{})
|
setting.MustInstalled()
|
||||||
setting.LoadDBSetting()
|
setting.LoadDBSetting()
|
||||||
setting.InitSQLLoggersForCli(log.INFO)
|
setting.InitSQLLoggersForCli(log.INFO)
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func runRecreateTable(ctx *cli.Context) error {
|
|||||||
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
|
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
|
||||||
|
|
||||||
debug := ctx.Bool("debug")
|
debug := ctx.Bool("debug")
|
||||||
setting.Init(&setting.Options{})
|
setting.MustInstalled()
|
||||||
setting.LoadDBSetting()
|
setting.LoadDBSetting()
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ func runDump(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
fileName += "." + outType
|
fileName += "." + outType
|
||||||
}
|
}
|
||||||
setting.Init(&setting.Options{})
|
setting.MustInstalled()
|
||||||
|
|
||||||
// make sure we are logging to the console no matter what the configuration tells us do to
|
// make sure we are logging to the console no matter what the configuration tells us do to
|
||||||
// FIXME: don't use CfgProvider directly
|
// FIXME: don't use CfgProvider directly
|
||||||
@@ -353,9 +353,9 @@ func runDump(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
excludes = append(excludes, setting.RepoRootPath)
|
excludes = append(excludes, setting.RepoRootPath)
|
||||||
excludes = append(excludes, setting.LFS.Path)
|
excludes = append(excludes, setting.LFS.Storage.Path)
|
||||||
excludes = append(excludes, setting.Attachment.Path)
|
excludes = append(excludes, setting.Attachment.Storage.Path)
|
||||||
excludes = append(excludes, setting.Packages.Path)
|
excludes = append(excludes, setting.Packages.Storage.Path)
|
||||||
excludes = append(excludes, setting.Log.RootPath)
|
excludes = append(excludes, setting.Log.RootPath)
|
||||||
excludes = append(excludes, absFileName)
|
excludes = append(excludes, absFileName)
|
||||||
if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil {
|
if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil {
|
||||||
|
|||||||
@@ -99,11 +99,6 @@ type assetFile struct {
|
|||||||
func initEmbeddedExtractor(c *cli.Context) error {
|
func initEmbeddedExtractor(c *cli.Context) error {
|
||||||
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
|
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
|
||||||
|
|
||||||
// Read configuration file
|
|
||||||
setting.Init(&setting.Options{
|
|
||||||
AllowEmpty: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
patterns, err := compileCollectPatterns(c.Args())
|
patterns, err := compileCollectPatterns(c.Args())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func runSendMail(c *cli.Context) error {
|
|||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setting.Init(&setting.Options{})
|
setting.MustInstalled()
|
||||||
|
|
||||||
if err := argsSet(c, "title"); err != nil {
|
if err := argsSet(c, "title"); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ func runMigrateStorage(ctx *cli.Context) error {
|
|||||||
switch strings.ToLower(ctx.String("storage")) {
|
switch strings.ToLower(ctx.String("storage")) {
|
||||||
case "":
|
case "":
|
||||||
fallthrough
|
fallthrough
|
||||||
case string(storage.LocalStorageType):
|
case string(setting.LocalStorageType):
|
||||||
p := ctx.String("path")
|
p := ctx.String("path")
|
||||||
if p == "" {
|
if p == "" {
|
||||||
log.Fatal("Path must be given when storage is loal")
|
log.Fatal("Path must be given when storage is loal")
|
||||||
@@ -187,22 +187,24 @@ func runMigrateStorage(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
dstStorage, err = storage.NewLocalStorage(
|
dstStorage, err = storage.NewLocalStorage(
|
||||||
stdCtx,
|
stdCtx,
|
||||||
storage.LocalStorageConfig{
|
&setting.Storage{
|
||||||
Path: p,
|
Path: p,
|
||||||
})
|
})
|
||||||
case string(storage.MinioStorageType):
|
case string(setting.MinioStorageType):
|
||||||
dstStorage, err = storage.NewMinioStorage(
|
dstStorage, err = storage.NewMinioStorage(
|
||||||
stdCtx,
|
stdCtx,
|
||||||
storage.MinioStorageConfig{
|
&setting.Storage{
|
||||||
Endpoint: ctx.String("minio-endpoint"),
|
MinioConfig: setting.MinioStorageConfig{
|
||||||
AccessKeyID: ctx.String("minio-access-key-id"),
|
Endpoint: ctx.String("minio-endpoint"),
|
||||||
SecretAccessKey: ctx.String("minio-secret-access-key"),
|
AccessKeyID: ctx.String("minio-access-key-id"),
|
||||||
Bucket: ctx.String("minio-bucket"),
|
SecretAccessKey: ctx.String("minio-secret-access-key"),
|
||||||
Location: ctx.String("minio-location"),
|
Bucket: ctx.String("minio-bucket"),
|
||||||
BasePath: ctx.String("minio-base-path"),
|
Location: ctx.String("minio-location"),
|
||||||
UseSSL: ctx.Bool("minio-use-ssl"),
|
BasePath: ctx.String("minio-base-path"),
|
||||||
InsecureSkipVerify: ctx.Bool("minio-insecure-skip-verify"),
|
UseSSL: ctx.Bool("minio-use-ssl"),
|
||||||
ChecksumAlgorithm: ctx.String("minio-checksum-algorithm"),
|
InsecureSkipVerify: ctx.Bool("minio-insecure-skip-verify"),
|
||||||
|
ChecksumAlgorithm: ctx.String("minio-checksum-algorithm"),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported storage type: %s", ctx.String("storage"))
|
return fmt.Errorf("unsupported storage type: %s", ctx.String("storage"))
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
packages_module "code.gitea.io/gitea/modules/packages"
|
packages_module "code.gitea.io/gitea/modules/packages"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
packages_service "code.gitea.io/gitea/services/packages"
|
packages_service "code.gitea.io/gitea/services/packages"
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@ func TestMigratePackages(t *testing.T) {
|
|||||||
|
|
||||||
dstStorage, err := storage.NewLocalStorage(
|
dstStorage, err := storage.NewLocalStorage(
|
||||||
ctx,
|
ctx,
|
||||||
storage.LocalStorageConfig{
|
&setting.Storage{
|
||||||
Path: p,
|
Path: p,
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func runRestoreRepository(c *cli.Context) error {
|
|||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setting.Init(&setting.Options{})
|
setting.MustInstalled()
|
||||||
var units []string
|
var units []string
|
||||||
if s := c.String("units"); s != "" {
|
if s := c.String("units"); s != "" {
|
||||||
units = strings.Split(s, ",")
|
units = strings.Split(s, ",")
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func setup(ctx context.Context, debug bool) {
|
|||||||
} else {
|
} else {
|
||||||
setupConsoleLogger(log.FATAL, false, os.Stderr)
|
setupConsoleLogger(log.FATAL, false, os.Stderr)
|
||||||
}
|
}
|
||||||
setting.Init(&setting.Options{})
|
setting.MustInstalled()
|
||||||
if debug {
|
if debug {
|
||||||
setting.RunMode = "dev"
|
setting.RunMode = "dev"
|
||||||
}
|
}
|
||||||
|
|||||||
180
cmd/web.go
180
cmd/web.go
@@ -101,6 +101,110 @@ func createPIDFile(pidPath string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serveInstall(ctx *cli.Context) error {
|
||||||
|
log.Info("Gitea version: %s%s", setting.AppVer, setting.AppBuiltWith)
|
||||||
|
log.Info("App path: %s", setting.AppPath)
|
||||||
|
log.Info("Work path: %s", setting.AppWorkPath)
|
||||||
|
log.Info("Custom path: %s", setting.CustomPath)
|
||||||
|
log.Info("Config file: %s", setting.CustomConf)
|
||||||
|
log.Info("Prepare to run install page")
|
||||||
|
|
||||||
|
routers.InitWebInstallPage(graceful.GetManager().HammerContext())
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
if err != nil {
|
||||||
|
log.Critical("Unable to open listener for installer. Is Gitea already running?")
|
||||||
|
graceful.GetManager().DoGracefulShutdown()
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-graceful.GetManager().IsShutdown():
|
||||||
|
<-graceful.GetManager().Done()
|
||||||
|
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
||||||
|
log.GetManager().Close()
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveInstalled(ctx *cli.Context) error {
|
||||||
|
setting.InitCfgProvider(setting.CustomConf)
|
||||||
|
setting.LoadCommonSettings()
|
||||||
|
setting.MustInstalled()
|
||||||
|
|
||||||
|
log.Info("Gitea version: %s%s", setting.AppVer, setting.AppBuiltWith)
|
||||||
|
log.Info("App path: %s", setting.AppPath)
|
||||||
|
log.Info("Work path: %s", setting.AppWorkPath)
|
||||||
|
log.Info("Custom path: %s", setting.CustomPath)
|
||||||
|
log.Info("Config file: %s", setting.CustomConf)
|
||||||
|
log.Info("Run mode: %s", setting.RunMode)
|
||||||
|
log.Info("Prepare to run web server")
|
||||||
|
|
||||||
|
if setting.AppWorkPathMismatch {
|
||||||
|
log.Error("WORK_PATH from config %q doesn't match other paths from environment variables or command arguments. "+
|
||||||
|
"Only WORK_PATH in config should be set and used. Please remove the other outdated work paths from environment variables and command arguments", setting.CustomConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCfg := setting.CfgProvider
|
||||||
|
if rootCfg.Section("").Key("WORK_PATH").String() == "" {
|
||||||
|
saveCfg, err := rootCfg.PrepareSaving()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to prepare saving WORK_PATH=%s to config %q: %v\nYou must set it manually, otherwise there might be bugs when accessing the git repositories.", setting.AppWorkPath, setting.CustomConf, err)
|
||||||
|
} else {
|
||||||
|
rootCfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath)
|
||||||
|
saveCfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath)
|
||||||
|
if err = saveCfg.Save(); err != nil {
|
||||||
|
log.Error("Unable to update WORK_PATH=%s to config %q: %v\nYou must set it manually, otherwise there might be bugs when accessing the git repositories.", setting.AppWorkPath, setting.CustomConf, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
routers.InitWebInstalled(graceful.GetManager().HammerContext())
|
||||||
|
|
||||||
|
// We check that AppDataPath exists here (it should have been created during installation)
|
||||||
|
// We can't check it in `InitWebInstalled`, because some integration tests
|
||||||
|
// use cmd -> InitWebInstalled, but the AppDataPath doesn't exist during those tests.
|
||||||
|
if _, err := os.Stat(setting.AppDataPath); err != nil {
|
||||||
|
log.Fatal("Can not find APP_DATA_PATH %q", setting.AppDataPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(graceful.GetManager().HammerContext())
|
||||||
|
err := listen(c, true)
|
||||||
|
<-graceful.GetManager().Done()
|
||||||
|
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
||||||
|
log.GetManager().Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func servePprof() {
|
||||||
|
http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler())
|
||||||
|
_, _, finished := process.GetManager().AddTypedContext(context.Background(), "Web: PProf Server", process.SystemProcessType, true)
|
||||||
|
// The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment it's not worth to introduce a configurable option for it.
|
||||||
|
log.Info("Starting pprof server on localhost:6060")
|
||||||
|
log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", nil))
|
||||||
|
finished()
|
||||||
|
}
|
||||||
|
|
||||||
func runWeb(ctx *cli.Context) error {
|
func runWeb(ctx *cli.Context) error {
|
||||||
if ctx.Bool("verbose") {
|
if ctx.Bool("verbose") {
|
||||||
setupConsoleLogger(log.TRACE, log.CanColorStdout, os.Stdout)
|
setupConsoleLogger(log.TRACE, log.CanColorStdout, os.Stdout)
|
||||||
@@ -128,75 +232,19 @@ func runWeb(ctx *cli.Context) error {
|
|||||||
createPIDFile(ctx.String("pid"))
|
createPIDFile(ctx.String("pid"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform pre-initialization
|
if !setting.InstallLock {
|
||||||
needsInstall := install.PreloadSettings(graceful.GetManager().HammerContext())
|
if err := serveInstall(ctx); err != nil {
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
log.Critical("Unable to open listener for installer. Is Gitea already running?")
|
|
||||||
graceful.GetManager().DoGracefulShutdown()
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-graceful.GetManager().IsShutdown():
|
|
||||||
<-graceful.GetManager().Done()
|
|
||||||
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
|
||||||
log.GetManager().Close()
|
|
||||||
return err
|
return err
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
NoInstallListener()
|
NoInstallListener()
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.EnablePprof {
|
if setting.EnablePprof {
|
||||||
go func() {
|
go servePprof()
|
||||||
http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler())
|
|
||||||
_, _, finished := process.GetManager().AddTypedContext(context.Background(), "Web: PProf Server", process.SystemProcessType, true)
|
|
||||||
// The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment it's not worth to introduce a configurable option for it.
|
|
||||||
log.Info("Starting pprof server on localhost:6060")
|
|
||||||
log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", nil))
|
|
||||||
finished()
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Global init")
|
return serveInstalled(ctx)
|
||||||
// Perform global initialization
|
|
||||||
setting.Init(&setting.Options{})
|
|
||||||
routers.GlobalInitInstalled(graceful.GetManager().HammerContext())
|
|
||||||
|
|
||||||
// We check that AppDataPath exists here (it should have been created during installation)
|
|
||||||
// We can't check it in `GlobalInitInstalled`, because some integration tests
|
|
||||||
// use cmd -> GlobalInitInstalled, but the AppDataPath doesn't exist during those tests.
|
|
||||||
if _, err := os.Stat(setting.AppDataPath); err != nil {
|
|
||||||
log.Fatal("Can not find APP_DATA_PATH '%s'", setting.AppDataPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(graceful.GetManager().HammerContext())
|
|
||||||
err := listen(c, true)
|
|
||||||
<-graceful.GetManager().Done()
|
|
||||||
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
|
||||||
log.GetManager().Close()
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setPort(port string) error {
|
func setPort(port string) error {
|
||||||
@@ -217,9 +265,15 @@ func setPort(port string) error {
|
|||||||
defaultLocalURL += ":" + setting.HTTPPort + "/"
|
defaultLocalURL += ":" + setting.HTTPPort + "/"
|
||||||
|
|
||||||
// Save LOCAL_ROOT_URL if port changed
|
// Save LOCAL_ROOT_URL if port changed
|
||||||
setting.CfgProvider.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
|
rootCfg := setting.CfgProvider
|
||||||
if err := setting.CfgProvider.Save(); err != nil {
|
saveCfg, err := rootCfg.PrepareSaving()
|
||||||
return fmt.Errorf("Failed to save config file: %v", err)
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to save config file: %v", err)
|
||||||
|
}
|
||||||
|
rootCfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
|
||||||
|
saveCfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
|
||||||
|
if err = saveCfg.Save(); err != nil {
|
||||||
|
return fmt.Errorf("failed to save config file: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -81,8 +81,6 @@ func main() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
app.Action = runEnvironmentToIni
|
app.Action = runEnvironmentToIni
|
||||||
setting.SetCustomPathAndConf("", "", "")
|
|
||||||
|
|
||||||
err := app.Run(os.Args)
|
err := app.Run(os.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed to run app with %s: %v", os.Args, err)
|
log.Fatal("Failed to run app with %s: %v", os.Args, err)
|
||||||
@@ -90,12 +88,13 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runEnvironmentToIni(c *cli.Context) error {
|
func runEnvironmentToIni(c *cli.Context) error {
|
||||||
providedCustom := c.String("custom-path")
|
setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{
|
||||||
providedConf := c.String("config")
|
WorkPath: c.String("work-path"),
|
||||||
providedWorkPath := c.String("work-path")
|
CustomPath: c.String("custom-path"),
|
||||||
setting.SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath)
|
CustomConf: c.String("config"),
|
||||||
|
})
|
||||||
|
|
||||||
cfg, err := setting.NewConfigProviderFromFile(&setting.Options{CustomConf: setting.CustomConf, AllowEmpty: true})
|
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err)
|
log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2159,7 +2159,7 @@ LEVEL = Info
|
|||||||
;RUN_AT_START = false
|
;RUN_AT_START = false
|
||||||
;ENABLE_SUCCESS_NOTICE = false
|
;ENABLE_SUCCESS_NOTICE = false
|
||||||
;SCHEDULE = @every 168h
|
;SCHEDULE = @every 168h
|
||||||
;HTTP_ENDPOINT = https://dl.gitea.io/gitea/version.json
|
;HTTP_ENDPOINT = https://dl.gitea.com/gitea/version.json
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -2392,6 +2392,10 @@ LEVEL = Info
|
|||||||
;; Enable/Disable package registry capabilities
|
;; Enable/Disable package registry capabilities
|
||||||
;ENABLED = true
|
;ENABLED = true
|
||||||
;;
|
;;
|
||||||
|
;STORAGE_TYPE = local
|
||||||
|
;; override the minio base path if storage type is minio
|
||||||
|
;MINIO_BASE_PATH = packages/
|
||||||
|
;;
|
||||||
;; Path for chunked uploads. Defaults to APP_DATA_PATH + `tmp/package-upload`
|
;; Path for chunked uploads. Defaults to APP_DATA_PATH + `tmp/package-upload`
|
||||||
;CHUNKED_UPLOAD_PATH = tmp/package-upload
|
;CHUNKED_UPLOAD_PATH = tmp/package-upload
|
||||||
;;
|
;;
|
||||||
@@ -2452,6 +2456,19 @@ LEVEL = Info
|
|||||||
;; storage type
|
;; storage type
|
||||||
;STORAGE_TYPE = local
|
;STORAGE_TYPE = local
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; repo-archive storage will override storage
|
||||||
|
;;
|
||||||
|
;[repo-archive]
|
||||||
|
;STORAGE_TYPE = local
|
||||||
|
;;
|
||||||
|
;; Where your lfs files reside, default is data/lfs.
|
||||||
|
;PATH = data/repo-archive
|
||||||
|
;;
|
||||||
|
;; override the minio base path if storage type is minio
|
||||||
|
;MINIO_BASE_PATH = repo-archive/
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; settings for repository archives, will override storage setting
|
;; settings for repository archives, will override storage setting
|
||||||
@@ -2471,6 +2488,9 @@ LEVEL = Info
|
|||||||
;;
|
;;
|
||||||
;; Where your lfs files reside, default is data/lfs.
|
;; Where your lfs files reside, default is data/lfs.
|
||||||
;PATH = data/lfs
|
;PATH = data/lfs
|
||||||
|
;;
|
||||||
|
;; override the minio base path if storage type is minio
|
||||||
|
;MINIO_BASE_PATH = lfs/
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -2520,6 +2540,7 @@ LEVEL = Info
|
|||||||
; [actions]
|
; [actions]
|
||||||
;; Enable/Disable actions capabilities
|
;; Enable/Disable actions capabilities
|
||||||
;ENABLED = false
|
;ENABLED = false
|
||||||
|
;;
|
||||||
;; Default address to get action plugins, e.g. the default value means downloading from "https://gitea.com/actions/checkout" for "uses: actions/checkout@v3"
|
;; Default address to get action plugins, e.g. the default value means downloading from "https://gitea.com/actions/checkout" for "uses: actions/checkout@v3"
|
||||||
;DEFAULT_ACTIONS_URL = https://gitea.com
|
;DEFAULT_ACTIONS_URL = https://gitea.com
|
||||||
|
|
||||||
|
|||||||
@@ -1013,7 +1013,7 @@ Default templates for project boards:
|
|||||||
- `RUN_AT_START`: **false**: Run tasks at start up time (if ENABLED).
|
- `RUN_AT_START`: **false**: Run tasks at start up time (if ENABLED).
|
||||||
- `ENABLE_SUCCESS_NOTICE`: **true**: Set to false to switch off success notices.
|
- `ENABLE_SUCCESS_NOTICE`: **true**: Set to false to switch off success notices.
|
||||||
- `SCHEDULE`: **@every 168h**: Cron syntax for scheduling a work, e.g. `@every 168h`.
|
- `SCHEDULE`: **@every 168h**: Cron syntax for scheduling a work, e.g. `@every 168h`.
|
||||||
- `HTTP_ENDPOINT`: **https://dl.gitea.io/gitea/version.json**: the endpoint that Gitea will check for newer versions
|
- `HTTP_ENDPOINT`: **https://dl.gitea.com/gitea/version.json**: the endpoint that Gitea will check for newer versions
|
||||||
|
|
||||||
#### Cron - Delete all old system notices from database (`cron.delete_old_system_notices`)
|
#### Cron - Delete all old system notices from database (`cron.delete_old_system_notices`)
|
||||||
|
|
||||||
@@ -1254,8 +1254,9 @@ is `data/lfs` and the default of `MINIO_BASE_PATH` is `lfs/`.
|
|||||||
|
|
||||||
## Storage (`storage`)
|
## Storage (`storage`)
|
||||||
|
|
||||||
Default storage configuration for attachments, lfs, avatars and etc.
|
Default storage configuration for attachments, lfs, avatars, repo-avatars, repo-archive, packages, actions_log, actions_artifact.
|
||||||
|
|
||||||
|
- `STORAGE_TYPE`: **local**: Storage type, `local` for local disk or `minio` for s3 compatible object storage service.
|
||||||
- `SERVE_DIRECT`: **false**: Allows the storage driver to redirect to authenticated URLs to serve files directly. Currently, only Minio/S3 is supported via signed URLs, local does nothing.
|
- `SERVE_DIRECT`: **false**: Allows the storage driver to redirect to authenticated URLs to serve files directly. Currently, only Minio/S3 is supported via signed URLs, local does nothing.
|
||||||
- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when `STORAGE_TYPE` is `minio`
|
- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when `STORAGE_TYPE` is `minio`
|
||||||
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio`
|
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio`
|
||||||
@@ -1265,9 +1266,56 @@ Default storage configuration for attachments, lfs, avatars and etc.
|
|||||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
|
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
|
||||||
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
||||||
|
|
||||||
And you can also define a customize storage like below:
|
The recommanded storage configuration for minio like below:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
|
[storage]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
; Minio endpoint to connect only available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_ENDPOINT = localhost:9000
|
||||||
|
; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_ACCESS_KEY_ID =
|
||||||
|
; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_SECRET_ACCESS_KEY =
|
||||||
|
; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_BUCKET = gitea
|
||||||
|
; Minio location to create bucket only available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_LOCATION = us-east-1
|
||||||
|
; Minio enabled ssl only available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_USE_SSL = false
|
||||||
|
; Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_INSECURE_SKIP_VERIFY = false
|
||||||
|
SERVE_DIRECT = true
|
||||||
|
```
|
||||||
|
|
||||||
|
Defaultly every storage has their default base path like below
|
||||||
|
|
||||||
|
| storage | default base path |
|
||||||
|
| ----------------- | ------------------ |
|
||||||
|
| attachments | attachments/ |
|
||||||
|
| lfs | lfs/ |
|
||||||
|
| avatars | avatars/ |
|
||||||
|
| repo-avatars | repo-avatars/ |
|
||||||
|
| repo-archive | repo-archive/ |
|
||||||
|
| packages | packages/ |
|
||||||
|
| actions_log | actions_log/ |
|
||||||
|
| actions_artifacts | actions_artifacts/ |
|
||||||
|
|
||||||
|
And bucket, basepath or `SERVE_DIRECT` could be special or overrided, if you want to use a different you can:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[storage.actions_log]
|
||||||
|
MINIO_BUCKET = gitea_actions_log
|
||||||
|
SERVE_DIRECT = true
|
||||||
|
MINIO_BASE_PATH = my_actions_log/ ; default is actions_log/ if blank
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to customerize a different storage for `lfs` if above default storage defined
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[lfs]
|
||||||
|
STORAGE_TYPE = my_minio
|
||||||
|
|
||||||
[storage.my_minio]
|
[storage.my_minio]
|
||||||
STORAGE_TYPE = minio
|
STORAGE_TYPE = minio
|
||||||
; Minio endpoint to connect only available when STORAGE_TYPE is `minio`
|
; Minio endpoint to connect only available when STORAGE_TYPE is `minio`
|
||||||
@@ -1286,8 +1334,6 @@ MINIO_USE_SSL = false
|
|||||||
MINIO_INSECURE_SKIP_VERIFY = false
|
MINIO_INSECURE_SKIP_VERIFY = false
|
||||||
```
|
```
|
||||||
|
|
||||||
And used by `[attachment]`, `[lfs]` and etc. as `STORAGE_TYPE`.
|
|
||||||
|
|
||||||
## Repository Archive Storage (`storage.repo-archive`)
|
## Repository Archive Storage (`storage.repo-archive`)
|
||||||
|
|
||||||
Configuration for repository archive storage. It will inherit from default `[storage]` or
|
Configuration for repository archive storage. It will inherit from default `[storage]` or
|
||||||
@@ -1306,6 +1352,11 @@ is `data/repo-archive` and the default of `MINIO_BASE_PATH` is `repo-archive/`.
|
|||||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
|
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
|
||||||
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
||||||
|
|
||||||
|
## Repository Archives (`repo-archive`)
|
||||||
|
|
||||||
|
- `STORAGE_TYPE`: **local**: Storage type for actions logs, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]`
|
||||||
|
- `MINIO_BASE_PATH`: **repo-archive/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio`
|
||||||
|
|
||||||
## Proxy (`proxy`)
|
## Proxy (`proxy`)
|
||||||
|
|
||||||
- `PROXY_ENABLED`: **false**: Enable the proxy if true, all requests to external via HTTP will be affected, if false, no proxy will be used even environment http_proxy/https_proxy
|
- `PROXY_ENABLED`: **false**: Enable the proxy if true, all requests to external via HTTP will be affected, if false, no proxy will be used even environment http_proxy/https_proxy
|
||||||
@@ -1324,6 +1375,8 @@ PROXY_HOSTS = *.github.com
|
|||||||
|
|
||||||
- `ENABLED`: **false**: Enable/Disable actions capabilities
|
- `ENABLED`: **false**: Enable/Disable actions capabilities
|
||||||
- `DEFAULT_ACTIONS_URL`: **https://gitea.com**: Default address to get action plugins, e.g. the default value means downloading from "<https://gitea.com/actions/checkout>" for "uses: actions/checkout@v3"
|
- `DEFAULT_ACTIONS_URL`: **https://gitea.com**: Default address to get action plugins, e.g. the default value means downloading from "<https://gitea.com/actions/checkout>" for "uses: actions/checkout@v3"
|
||||||
|
- `STORAGE_TYPE`: **local**: Storage type for actions logs, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]`
|
||||||
|
- `MINIO_BASE_PATH`: **actions_log/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio`
|
||||||
|
|
||||||
`DEFAULT_ACTIONS_URL` indicates where should we find the relative path action plugin. i.e. when use an action in a workflow file like
|
`DEFAULT_ACTIONS_URL` indicates where should we find the relative path action plugin. i.e. when use an action in a workflow file like
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ menu:
|
|||||||
# 配置说明
|
# 配置说明
|
||||||
|
|
||||||
这是针对Gitea配置文件的说明,你可以了解Gitea的强大配置。需要说明的是,你的所有改变请修改 `custom/conf/app.ini` 文件而不是源文件。
|
这是针对Gitea配置文件的说明,你可以了解Gitea的强大配置。需要说明的是,你的所有改变请修改 `custom/conf/app.ini` 文件而不是源文件。
|
||||||
所有默认值可以通过 [app.example.ini](https://github.com/go-gitea/gitea/blob/master/custom/conf/app.example.ini) 查看到。
|
所有默认值可以通过 [app.example.ini](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini) 查看到。
|
||||||
如果你发现 `%(X)s` 这样的内容,请查看 [ini](https://github.com/go-ini/ini/#recursive-values) 这里的说明。
|
如果你发现 `%(X)s` 这样的内容,请查看 [ini](https://github.com/go-ini/ini/#recursive-values) 这里的说明。
|
||||||
标注了 :exclamation: 的配置项表明除非你真的理解这个配置项的意义,否则最好使用默认值。
|
标注了 :exclamation: 的配置项表明除非你真的理解这个配置项的意义,否则最好使用默认值。
|
||||||
|
|
||||||
@@ -414,7 +414,7 @@ LFS 的存储配置。 如果 `STORAGE_TYPE` 为空,则此配置将从 `[stora
|
|||||||
|
|
||||||
## Storage (`storage`)
|
## Storage (`storage`)
|
||||||
|
|
||||||
Attachments, lfs, avatars and etc 的默认存储配置。
|
Attachments, lfs, avatars, repo-avatars, repo-archive, packages, actions_log, actions_artifact 的默认存储配置。
|
||||||
|
|
||||||
- `STORAGE_TYPE`: **local**: 附件存储类型,`local` 将存储到本地文件夹, `minio` 将存储到 s3 兼容的对象存储服务中。
|
- `STORAGE_TYPE`: **local**: 附件存储类型,`local` 将存储到本地文件夹, `minio` 将存储到 s3 兼容的对象存储服务中。
|
||||||
- `SERVE_DIRECT`: **false**: 允许直接重定向到存储系统。当前,仅 Minio/S3 是支持的。
|
- `SERVE_DIRECT`: **false**: 允许直接重定向到存储系统。当前,仅 Minio/S3 是支持的。
|
||||||
@@ -425,9 +425,59 @@ Attachments, lfs, avatars and etc 的默认存储配置。
|
|||||||
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket,仅当 `STORAGE_TYPE` 是 `minio` 时有效。
|
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket,仅当 `STORAGE_TYPE` 是 `minio` 时有效。
|
||||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl,仅当 `STORAGE_TYPE` 是 `minio` 时有效。
|
- `MINIO_USE_SSL`: **false**: Minio enabled ssl,仅当 `STORAGE_TYPE` 是 `minio` 时有效。
|
||||||
|
|
||||||
你也可以自定义一个存储的名字如下:
|
以下为推荐的 recommanded storage configuration for minio like below:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
|
[storage]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
; uncomment when STORAGE_TYPE = local
|
||||||
|
; PATH = storage root path
|
||||||
|
; Minio endpoint to connect only available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_ENDPOINT = localhost:9000
|
||||||
|
; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_ACCESS_KEY_ID =
|
||||||
|
; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_SECRET_ACCESS_KEY =
|
||||||
|
; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_BUCKET = gitea
|
||||||
|
; Minio location to create bucket only available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_LOCATION = us-east-1
|
||||||
|
; Minio enabled ssl only available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_USE_SSL = false
|
||||||
|
; Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_INSECURE_SKIP_VERIFY = false
|
||||||
|
SERVE_DIRECT = true
|
||||||
|
```
|
||||||
|
|
||||||
|
默认的,每一个存储都会有各自默认的 BasePath 在同一个minio中,默认值如下:
|
||||||
|
|
||||||
|
| storage | default base path |
|
||||||
|
| ----------------- | ------------------ |
|
||||||
|
| attachments | attachments/ |
|
||||||
|
| lfs | lfs/ |
|
||||||
|
| avatars | avatars/ |
|
||||||
|
| repo-avatars | repo-avatars/ |
|
||||||
|
| repo-archive | repo-archive/ |
|
||||||
|
| packages | packages/ |
|
||||||
|
| actions_log | actions_log/ |
|
||||||
|
| actions_artifacts | actions_artifacts/ |
|
||||||
|
|
||||||
|
同时 bucket, basepath or `SERVE_DIRECT` 是可以被覆写的,像如下所示:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[storage.actions_log]
|
||||||
|
MINIO_BUCKET = gitea_actions_log
|
||||||
|
SERVE_DIRECT = true
|
||||||
|
MINIO_BASE_PATH = my_actions_log/ ; default is actions_log/ if blank
|
||||||
|
```
|
||||||
|
|
||||||
|
当然你也可以完全自定义,像如下
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[lfs]
|
||||||
|
STORAGE_TYPE = my_minio
|
||||||
|
MINIO_BASE_PATH = my_lfs_basepath
|
||||||
|
|
||||||
[storage.my_minio]
|
[storage.my_minio]
|
||||||
STORAGE_TYPE = minio
|
STORAGE_TYPE = minio
|
||||||
; Minio endpoint to connect only available when STORAGE_TYPE is `minio`
|
; Minio endpoint to connect only available when STORAGE_TYPE is `minio`
|
||||||
@@ -444,10 +494,9 @@ MINIO_LOCATION = us-east-1
|
|||||||
MINIO_USE_SSL = false
|
MINIO_USE_SSL = false
|
||||||
; Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
; Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
||||||
MINIO_INSECURE_SKIP_VERIFY = false
|
MINIO_INSECURE_SKIP_VERIFY = false
|
||||||
|
SERVE_DIRECT = true
|
||||||
```
|
```
|
||||||
|
|
||||||
然后你在 `[attachment]`, `[lfs]` 等中可以把这个名字用作 `STORAGE_TYPE` 的值。
|
|
||||||
|
|
||||||
## Repository Archive Storage (`storage.repo-archive`)
|
## Repository Archive Storage (`storage.repo-archive`)
|
||||||
|
|
||||||
Repository archive 的存储配置。 如果 `STORAGE_TYPE` 为空,则此配置将从 `[storage]` 继承。如果不为 `local` 或者 `minio` 而为 `xxx`, 则从 `[storage.xxx]` 继承。当继承时, `PATH` 默认为 `data/repo-archive`,`MINIO_BASE_PATH` 默认为 `repo-archive/`。
|
Repository archive 的存储配置。 如果 `STORAGE_TYPE` 为空,则此配置将从 `[storage]` 继承。如果不为 `local` 或者 `minio` 而为 `xxx`, 则从 `[storage.xxx]` 继承。当继承时, `PATH` 默认为 `data/repo-archive`,`MINIO_BASE_PATH` 默认为 `repo-archive/`。
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ Gitea 引用 `custom` 目录中的自定义配置文件来覆盖配置、模板
|
|||||||
`custom/conf/app.ini` 当中。在发行版中可能会以 `/etc/gitea/` 的形式为 `custom` 设置一个符号链接,查看配置详情请移步:
|
`custom/conf/app.ini` 当中。在发行版中可能会以 `/etc/gitea/` 的形式为 `custom` 设置一个符号链接,查看配置详情请移步:
|
||||||
|
|
||||||
- [快速备忘单](https://docs.gitea.io/en-us/config-cheat-sheet/)
|
- [快速备忘单](https://docs.gitea.io/en-us/config-cheat-sheet/)
|
||||||
- [完整配置清单](https://github.com/go-gitea/gitea/blob/master/custom/conf/app.example.ini)
|
- [完整配置清单](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini)
|
||||||
|
|
||||||
如果您在 binary 同目录下无法找到 `custom` 文件夹,请检查您的 `GITEA_CUSTOM`
|
如果您在 binary 同目录下无法找到 `custom` 文件夹,请检查您的 `GITEA_CUSTOM`
|
||||||
环境变量配置, 因为它可能被配置到了其他地方(可能被一些启动脚本设置指定了目录)。
|
环境变量配置, 因为它可能被配置到了其他地方(可能被一些启动脚本设置指定了目录)。
|
||||||
@@ -67,7 +67,7 @@ Gitea 引用 `custom` 目录中的自定义配置文件来覆盖配置、模板
|
|||||||
|
|
||||||
同理,您可以将页签添加到 `extra_tabs.tmpl` 中,使用同样的方式来添加页签。它的具体样式需要与
|
同理,您可以将页签添加到 `extra_tabs.tmpl` 中,使用同样的方式来添加页签。它的具体样式需要与
|
||||||
`templates/repo/header.tmpl` 中已有的其他选项卡的样式匹配
|
`templates/repo/header.tmpl` 中已有的其他选项卡的样式匹配
|
||||||
([source in GitHub](https://github.com/go-gitea/gitea/blob/master/templates/repo/header.tmpl))
|
([source in GitHub](https://github.com/go-gitea/gitea/blob/main/templates/repo/header.tmpl))
|
||||||
|
|
||||||
### 页面的其他新增内容
|
### 页面的其他新增内容
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ menu:
|
|||||||
Gitea's localization happens through our [Crowdin project](https://crowdin.com/project/gitea).
|
Gitea's localization happens through our [Crowdin project](https://crowdin.com/project/gitea).
|
||||||
|
|
||||||
For changes to an **English** translation, a pull request can be made that changes the appropriate key in
|
For changes to an **English** translation, a pull request can be made that changes the appropriate key in
|
||||||
the [english locale](https://github.com/go-gitea/gitea/blob/master/options/locale/locale_en-US.ini).
|
the [english locale](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.ini).
|
||||||
|
|
||||||
For changes to a **non-English** translation, refer to the Crowdin project above.
|
For changes to a **non-English** translation, refer to the Crowdin project above.
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ menu:
|
|||||||
|
|
||||||
Gitea的本地化是通过我们的[Crowdin项目](https://crowdin.com/project/gitea)进行的。
|
Gitea的本地化是通过我们的[Crowdin项目](https://crowdin.com/project/gitea)进行的。
|
||||||
|
|
||||||
对于对**英语翻译**的更改,可以发出pull-request,来更改[英语语言环境](https://github.com/go-gitea/gitea/blob/master/options/locale/locale_en-US.ini)中合适的关键字。
|
对于对**英语翻译**的更改,可以发出pull-request,来更改[英语语言环境](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.ini)中合适的关键字。
|
||||||
|
|
||||||
有关对**非英语**翻译的更改,请参阅上面的 Crowdin 项目。
|
有关对**非英语**翻译的更改,请参阅上面的 Crowdin 项目。
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ menu:
|
|||||||
|
|
||||||
我們在 [Crowdin 專案](https://crowdin.com/project/gitea)上進行在地化工作。
|
我們在 [Crowdin 專案](https://crowdin.com/project/gitea)上進行在地化工作。
|
||||||
|
|
||||||
**英語系**的翻譯,可在修改[英文語言檔](https://github.com/go-gitea/gitea/blob/master/options/locale/locale_en-US.ini)後提出合併請求。
|
**英語系**的翻譯,可在修改[英文語言檔](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.ini)後提出合併請求。
|
||||||
|
|
||||||
**非英語系**的翻譯,請前往上述的 Crowdin 專案。
|
**非英語系**的翻譯,請前往上述的 Crowdin 專案。
|
||||||
|
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ Gitea 定義了一些基本物件於套件 [modules/migration](https://github.co
|
|||||||
- 您必須實作一個 `DownloaderFactory`,它用來偵測 URL 是否符合並建立上述的 `Downloader`。
|
- 您必須實作一個 `DownloaderFactory`,它用來偵測 URL 是否符合並建立上述的 `Downloader`。
|
||||||
- 您需要在 `init()` 透過 `RegisterDownloaderFactory` 來註冊 `DownloaderFactory`。
|
- 您需要在 `init()` 透過 `RegisterDownloaderFactory` 來註冊 `DownloaderFactory`。
|
||||||
|
|
||||||
您可以在 [downloader.go](https://github.com/go-gitea/gitea/blob/master/modules/migration/downloader.go) 中找到這些介面。
|
您可以在 [downloader.go](https://github.com/go-gitea/gitea/blob/main/modules/migration/downloader.go) 中找到這些介面。
|
||||||
|
|
||||||
## Uploader 介面
|
## Uploader 介面
|
||||||
|
|
||||||
目前只有 `GiteaLocalUploader` 被實作出來,所以我們只能通過 `Uploader` 儲存已下載的資料到本地的 Gitea 實例。
|
目前只有 `GiteaLocalUploader` 被實作出來,所以我們只能通過 `Uploader` 儲存已下載的資料到本地的 Gitea 實例。
|
||||||
目前尚未支援其它 Uploader。
|
目前尚未支援其它 Uploader。
|
||||||
|
|
||||||
您可以在 [uploader.go](https://github.com/go-gitea/gitea/blob/master/modules/migration/uploader.go) 中找到這些介面。
|
您可以在 [uploader.go](https://github.com/go-gitea/gitea/blob/main/modules/migration/uploader.go) 中找到這些介面。
|
||||||
|
|||||||
@@ -25,21 +25,19 @@ For more help resources, check all [Support Options]({{< relref "doc/help/suppor
|
|||||||
|
|
||||||
{{< toc >}}
|
{{< toc >}}
|
||||||
|
|
||||||
## Difference between 1.x and 1.x.x downloads
|
## Difference between 1.x and 1.x.x downloads, how can I get latest stable release with bug fixes?
|
||||||
|
|
||||||
Version 1.7.x will be used for this example.
|
Version 1.20.x will be used for this example.
|
||||||
|
|
||||||
**NOTE:** this example applies to Docker images as well!
|
On our [downloads page](https://dl.gitea.com/gitea/) you will see a 1.20 directory, as well as directories for 1.20.0, 1.20.1.
|
||||||
|
|
||||||
On our [downloads page](https://dl.gitea.io/gitea/) you will see a 1.7 directory, as well as directories for 1.7.0, 1.7.1, 1.7.2, 1.7.3, 1.7.4, 1.7.5, and 1.7.6.
|
The 1.20 directory is the nightly build, which is built on each merged commit to the [`release/v1.20`](https://github.com/go-gitea/gitea/tree/release/v1.20) branch.
|
||||||
|
|
||||||
The 1.7 and 1.7.0 directories are **not** the same. The 1.7 directory is built on each merged commit to the [`release/v1.7`](https://github.com/go-gitea/gitea/tree/release/v1.7) branch.
|
The 1.20.0 directory is a release build that was created when the [`v1.20.0`](https://github.com/go-gitea/gitea/releases/tag/v1.20.0) tag was created.
|
||||||
|
|
||||||
The 1.7.0 directory, however, is a build that was created when the [`v1.7.0`](https://github.com/go-gitea/gitea/releases/tag/v1.7.0) tag was created.
|
The nightly builds (1.x) downloads will change as commits are merged to their respective branch, they contain the latest changes/fixes before a tag release is built.
|
||||||
|
|
||||||
This means that 1.x downloads will change as commits are merged to their respective branch (think of it as a separate "main" branch for each release).
|
If a bug fix is targeted on 1.20.1 but 1.20.1 is not released yet, you can get the "1.20-nightly" build to get the bug fix.
|
||||||
|
|
||||||
On the other hand, 1.x.x downloads should never change.
|
|
||||||
|
|
||||||
## How to migrate from Gogs/GitHub/etc. to Gitea
|
## How to migrate from Gogs/GitHub/etc. to Gitea
|
||||||
|
|
||||||
@@ -404,14 +402,6 @@ You will also need to change the app.ini database charset to `CHARSET=utf8mb4`.
|
|||||||
|
|
||||||
Gitea requires the system or browser to have one of the supported Emoji fonts installed, which are Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji and Twemoji Mozilla. Generally, the operating system should already provide one of these fonts, but especially on Linux, it may be necessary to install them manually.
|
Gitea requires the system or browser to have one of the supported Emoji fonts installed, which are Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji and Twemoji Mozilla. Generally, the operating system should already provide one of these fonts, but especially on Linux, it may be necessary to install them manually.
|
||||||
|
|
||||||
## Stdout logging on SystemD and Docker
|
|
||||||
|
|
||||||
Stdout on systemd goes to the journal by default. Try using `journalctl`, `journalctl -u gitea`, or `journalctl <path-to-gitea-binary>`.
|
|
||||||
|
|
||||||
Similarly, stdout on docker can be viewed using `docker logs <container>`.
|
|
||||||
|
|
||||||
To collect logs for help and issue report, see [Support Options]({{< relref "doc/help/support.en-us.md" >}}).
|
|
||||||
|
|
||||||
## Initial logging
|
## Initial logging
|
||||||
|
|
||||||
Before Gitea has read the configuration file and set-up its logging it will log a number of things to stdout in order to help debug things if logging does not work.
|
Before Gitea has read the configuration file and set-up its logging it will log a number of things to stdout in order to help debug things if logging does not work.
|
||||||
@@ -454,12 +444,6 @@ gitea doctor recreate-table
|
|||||||
|
|
||||||
It is highly recommended to back-up your database before running these commands.
|
It is highly recommended to back-up your database before running these commands.
|
||||||
|
|
||||||
## Why are tabs/indents wrong when viewing files
|
|
||||||
|
|
||||||
If you are using Cloudflare, turn off the auto-minify option in the dashboard.
|
|
||||||
|
|
||||||
`Speed` -> `Optimization` -> Uncheck `HTML` within the `Auto-Minify` settings.
|
|
||||||
|
|
||||||
## How to adopt repositories from disk
|
## How to adopt repositories from disk
|
||||||
|
|
||||||
- Add your (bare) repositories to the correct spot for your configuration (`repository.ROOT`), ensuring they are in the correct layout `<REPO_ROOT>/[user]/[repo].git`.
|
- Add your (bare) repositories to the correct spot for your configuration (`repository.ROOT`), ensuring they are in the correct layout `<REPO_ROOT>/[user]/[repo].git`.
|
||||||
@@ -470,3 +454,17 @@ If you are using Cloudflare, turn off the auto-minify option in the dashboard.
|
|||||||
- Users can also be given similar permissions via config [`ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES`]({{< relref "doc/administration/config-cheat-sheet.en-us.md#repository" >}}).
|
- Users can also be given similar permissions via config [`ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES`]({{< relref "doc/administration/config-cheat-sheet.en-us.md#repository" >}}).
|
||||||
- If the above steps are done correctly, you should be able to select repositories to adopt.
|
- If the above steps are done correctly, you should be able to select repositories to adopt.
|
||||||
- If no repositories are found, enable [debug logging]({{< relref "doc/administration/config-cheat-sheet.en-us.md#repository" >}}) to check for any specific errors.
|
- If no repositories are found, enable [debug logging]({{< relref "doc/administration/config-cheat-sheet.en-us.md#repository" >}}) to check for any specific errors.
|
||||||
|
|
||||||
|
## Gitea can't start on NFS
|
||||||
|
|
||||||
|
In most cases, it's caused by broken NFS lock system. You can try to stop Gitea process and
|
||||||
|
run `flock -n /data-nfs/gitea/queues/LOCK echo 'lock acquired'` to see whether the lock can be acquired immediately.
|
||||||
|
If the lock can't be acquired, NFS might report some errors like `lockd: cannot monitor node-3, statd: server rpc.statd not responding, timed out` in its server logs.
|
||||||
|
|
||||||
|
Then the NFS lock could be reset by:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# /etc/init.d/nfs stop
|
||||||
|
# rm -rf /var/lib/nfs/sm/*
|
||||||
|
# /etc/init.d/nfs start
|
||||||
|
```
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ menu:
|
|||||||
|
|
||||||
**注意:**此示例也适用于Docker镜像!
|
**注意:**此示例也适用于Docker镜像!
|
||||||
|
|
||||||
在我们的[下载页面](https://dl.gitea.io/gitea/)上,您会看到一个1.7目录,以及1.7.0、1.7.1、1.7.2、1.7.3、1.7.4、1.7.5和1.7.6的目录。
|
在我们的[下载页面](https://dl.gitea.com/gitea/)上,您会看到一个1.7目录,以及1.7.0、1.7.1、1.7.2、1.7.3、1.7.4、1.7.5和1.7.6的目录。
|
||||||
|
|
||||||
1.7目录和1.7.0目录是**不同**的。1.7目录是在每个合并到[`release/v1.7`](https://github.com/go-gitea/gitea/tree/release/v1.7)分支的提交上构建的。
|
1.7目录和1.7.0目录是**不同**的。1.7目录是在每个合并到[`release/v1.7`](https://github.com/go-gitea/gitea/tree/release/v1.7)分支的提交上构建的。
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ menu:
|
|||||||
|
|
||||||
# Support Options
|
# Support Options
|
||||||
|
|
||||||
|
- [Paid Commercial Support](https://about.gitea.com/)
|
||||||
- [Discord](https://discord.gg/Gitea)
|
- [Discord](https://discord.gg/Gitea)
|
||||||
- [Discourse Forum](https://discourse.gitea.io/)
|
- [Discourse Forum](https://discourse.gitea.io/)
|
||||||
|
|
||||||
@@ -35,30 +36,13 @@ menu:
|
|||||||
[log]
|
[log]
|
||||||
LEVEL=debug
|
LEVEL=debug
|
||||||
MODE=console,file
|
MODE=console,file
|
||||||
ROUTER=console,file
|
|
||||||
XORM=console,file
|
|
||||||
ENABLE_XORM_LOG=true
|
|
||||||
FILE_NAME=gitea.log
|
|
||||||
[log.file.router]
|
|
||||||
FILE_NAME=router.log
|
|
||||||
[log.file.xorm]
|
|
||||||
FILE_NAME=xorm.log
|
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Any error messages you are seeing.
|
3. Any error messages you are seeing.
|
||||||
4. When possible, try to replicate the issue on [try.gitea.io](https://try.gitea.io) and include steps so that others can reproduce the issue.
|
4. When possible, try to replicate the issue on [try.gitea.io](https://try.gitea.io) and include steps so that others can reproduce the issue.
|
||||||
- This will greatly improve the chance that the root of the issue can be quickly discovered and resolved.
|
- This will greatly improve the chance that the root of the issue can be quickly discovered and resolved.
|
||||||
5. If you meet slow/hanging/deadlock problems, please report the stack trace when the problem occurs:
|
5. If you encounter slow/hanging/deadlock problems, please report the stack trace when the problem occurs.
|
||||||
1. Enable pprof in `app.ini` and restart Gitea
|
Go to the "Site Admin" -> "Monitoring" -> "Stacktrace" -> "Download diagnosis report".
|
||||||
|
|
||||||
```ini
|
|
||||||
[server]
|
|
||||||
ENABLE_PPROF = true
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Trigger the bug, when Gitea gets stuck, use curl or browser to visit: `http://127.0.0.1:6060/debug/pprof/goroutine?debug=1` (IP must be `127.0.0.1` and port must be `6060`).
|
|
||||||
3. If you are using Docker, please use `docker exec -it <container-name> curl "http://127.0.0.1:6060/debug/pprof/goroutine?debug=1"`.
|
|
||||||
4. Report the output (the stack trace doesn't contain sensitive data)
|
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ menu:
|
|||||||
|
|
||||||
# Installation avec le binaire pré-compilé
|
# Installation avec le binaire pré-compilé
|
||||||
|
|
||||||
Tous les binaires sont livrés avec le support de SQLite, MySQL et PostgreSQL, et sont construits avec les ressources incorporées. Gardez à l'esprit que cela peut être différent pour les versions antérieures. L'installation basée sur nos binaires est assez simple, il suffit de choisir le fichier correspondant à votre plateforme à partir de la [page de téléchargement](https://dl.gitea.io/gitea). Copiez l'URL et remplacer l'URL dans les commandes suivantes par la nouvelle:
|
Tous les binaires sont livrés avec le support de SQLite, MySQL et PostgreSQL, et sont construits avec les ressources incorporées. Gardez à l'esprit que cela peut être différent pour les versions antérieures. L'installation basée sur nos binaires est assez simple, il suffit de choisir le fichier correspondant à votre plateforme à partir de la [page de téléchargement](https://dl.gitea.com/gitea). Copiez l'URL et remplacer l'URL dans les commandes suivantes par la nouvelle:
|
||||||
|
|
||||||
```
|
```
|
||||||
wget -O gitea https://dl.gitea.io/gitea/{{< version >}}/gitea-{{< version >}}-linux-amd64
|
wget -O gitea https://dl.gitea.com/gitea/{{< version >}}/gitea-{{< version >}}-linux-amd64
|
||||||
chmod +x gitea
|
chmod +x gitea
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ menu:
|
|||||||
|
|
||||||
# 從執行檔安裝
|
# 從執行檔安裝
|
||||||
|
|
||||||
所有的執行檔皆支援 SQLite, MySQL and PostgreSQL,且所有檔案都已經包在執行檔內,這一點跟之前的版本有所不同。關於執行檔的安裝方式非常簡單,只要從[下載頁面](https://dl.gitea.io/gitea)選擇相對應平台,複製下載連結,使用底下指令就可以完成了:
|
所有的執行檔皆支援 SQLite, MySQL and PostgreSQL,且所有檔案都已經包在執行檔內,這一點跟之前的版本有所不同。關於執行檔的安裝方式非常簡單,只要從[下載頁面](https://dl.gitea.com/gitea)選擇相對應平台,複製下載連結,使用底下指令就可以完成了:
|
||||||
|
|
||||||
```
|
```
|
||||||
wget -O gitea https://dl.gitea.io/gitea/{{< version >}}/gitea-{{< version >}}-linux-amd64
|
wget -O gitea https://dl.gitea.com/gitea/{{< version >}}/gitea-{{< version >}}-linux-amd64
|
||||||
chmod +x gitea
|
chmod +x gitea
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ git checkout pr-xyz
|
|||||||
|
|
||||||
## Compilation
|
## Compilation
|
||||||
|
|
||||||
Comme nous regroupons déjà toutes les bibliothèques requises pour compiler Gitea, vous pouvez continuer avec le processus de compilation lui-même. Nous fournissons diverses [tâches Make](https://github.com/go-gitea/gitea/blob/master/Makefile) pour rendre le processus de construction aussi simple que possible. [Voyez ici comment obtenir Make](/fr-fr/hacking-on-gitea/). Selon vos besoins, vous pourrez éventuellement ajouter diverses options de compilation, vous pouvez choisir entre ces options :
|
Comme nous regroupons déjà toutes les bibliothèques requises pour compiler Gitea, vous pouvez continuer avec le processus de compilation lui-même. Nous fournissons diverses [tâches Make](https://github.com/go-gitea/gitea/blob/main/Makefile) pour rendre le processus de construction aussi simple que possible. [Voyez ici comment obtenir Make](/fr-fr/hacking-on-gitea/). Selon vos besoins, vous pourrez éventuellement ajouter diverses options de compilation, vous pouvez choisir entre ces options :
|
||||||
|
|
||||||
* `bindata`: Intègre toutes les ressources nécessaires à l'exécution d'une instance de Gitea, ce qui rend un déploiement facile car il n'est pas nécessaire de se préoccuper des fichiers supplémentaires.
|
* `bindata`: Intègre toutes les ressources nécessaires à l'exécution d'une instance de Gitea, ce qui rend un déploiement facile car il n'est pas nécessaire de se préoccuper des fichiers supplémentaires.
|
||||||
* `sqlite sqlite_unlock_notify`: Active la prise en charge d'une base de données [SQLite3](https://sqlite.org/), ceci n'est recommandé que pour les petites installations de Gitea.
|
* `sqlite sqlite_unlock_notify`: Active la prise en charge d'une base de données [SQLite3](https://sqlite.org/), ceci n'est recommandé que pour les petites installations de Gitea.
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ git checkout v{{< version >}}
|
|||||||
|
|
||||||
## 編譯
|
## 編譯
|
||||||
|
|
||||||
完成設定相依性套件環境等工作後,您就可以開始編譯工作了。我們提供了不同的[編譯選項](https://github.com/go-gitea/gitea/blob/master/Makefile) ,讓編譯過程更加簡單。您可以根據需求來調整編譯選項,底下是可用的編譯選項說明:
|
完成設定相依性套件環境等工作後,您就可以開始編譯工作了。我們提供了不同的[編譯選項](https://github.com/go-gitea/gitea/blob/main/Makefile) ,讓編譯過程更加簡單。您可以根據需求來調整編譯選項,底下是可用的編譯選項說明:
|
||||||
|
|
||||||
* `bindata`: 使用此標籤來嵌入所有 Gitea 相關資源,您不用擔心其他額外檔案,對於部署來說非常方便。
|
* `bindata`: 使用此標籤來嵌入所有 Gitea 相關資源,您不用擔心其他額外檔案,對於部署來說非常方便。
|
||||||
* `sqlite sqlite_unlock_notify`: 使用此標籤來啟用 [SQLite3](https://sqlite.org/) 資料庫,建議只有少數人時才使用此模式。
|
* `sqlite sqlite_unlock_notify`: 使用此標籤來啟用 [SQLite3](https://sqlite.org/) 資料庫,建議只有少數人時才使用此模式。
|
||||||
|
|||||||
@@ -56,3 +56,13 @@ To deploy Gitea to Linode, have a look at the [Linode Marketplace](https://www.l
|
|||||||
[alwaysdata](https://www.alwaysdata.com/) has Gitea as an app in their marketplace.
|
[alwaysdata](https://www.alwaysdata.com/) has Gitea as an app in their marketplace.
|
||||||
|
|
||||||
To deploy Gitea to alwaysdata, have a look at the [alwaysdata Marketplace](https://www.alwaysdata.com/en/marketplace/gitea/).
|
To deploy Gitea to alwaysdata, have a look at the [alwaysdata Marketplace](https://www.alwaysdata.com/en/marketplace/gitea/).
|
||||||
|
|
||||||
|
## Exoscale
|
||||||
|
|
||||||
|
[Exoscale](https://www.exoscale.com/) provides Gitea managed by [Glasskube](https://glasskube.eu/) in their marketplace.
|
||||||
|
|
||||||
|
Exoscale is a European cloud service provider.
|
||||||
|
|
||||||
|
The package is maintained and update via the open source [Glasskube Kubernetes Operator](https://github.com/glasskube/operator).
|
||||||
|
|
||||||
|
To deploy Gitea to Exoscale, have a look at the [Exoscale Marketplace](https://www.exoscale.com/marketplace/listing/glasskube-gitea/).
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Gitea provides a Helm Chart to allow for installation on kubernetes.
|
|||||||
A non-customized install can be done with:
|
A non-customized install can be done with:
|
||||||
|
|
||||||
```
|
```
|
||||||
helm repo add gitea-charts https://dl.gitea.io/charts/
|
helm repo add gitea-charts https://dl.gitea.com/charts/
|
||||||
helm install gitea gitea-charts/gitea
|
helm install gitea gitea-charts/gitea
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Gitea 已经提供了便于在 Kubernetes 云原生环境中安装所需的 Helm
|
|||||||
默认安装指令为:
|
默认安装指令为:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm repo add gitea https://dl.gitea.io/charts
|
helm repo add gitea https://dl.gitea.com/charts
|
||||||
helm repo update
|
helm repo update
|
||||||
helm install gitea gitea/gitea
|
helm install gitea gitea/gitea
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Gitea 提供 Helm Chart 用來安裝於 kubernetes。
|
|||||||
非自訂安裝可使用下列指令:
|
非自訂安裝可使用下列指令:
|
||||||
|
|
||||||
```
|
```
|
||||||
helm repo add gitea-charts https://dl.gitea.io/charts/
|
helm repo add gitea-charts https://dl.gitea.com/charts/
|
||||||
helm install gitea gitea-charts/gitea
|
helm install gitea gitea-charts/gitea
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ menu:
|
|||||||
sudo vim /etc/systemd/system/gitea.service
|
sudo vim /etc/systemd/system/gitea.service
|
||||||
```
|
```
|
||||||
|
|
||||||
接着拷贝示例代码 [gitea.service](https://github.com/go-gitea/gitea/blob/master/contrib/systemd/gitea.service) 并取消对任何需要运行在主机上的服务部分的注释,譬如 MySQL。
|
接着拷贝示例代码 [gitea.service](https://github.com/go-gitea/gitea/blob/main/contrib/systemd/gitea.service) 并取消对任何需要运行在主机上的服务部分的注释,譬如 MySQL。
|
||||||
|
|
||||||
修改 user,home 目录以及其他必须的初始化参数,如果使用自定义端口,则需修改 PORT 参数,反之如果使用默认端口则需删除 -p 标记。
|
修改 user,home 目录以及其他必须的初始化参数,如果使用自定义端口,则需修改 PORT 参数,反之如果使用默认端口则需删除 -p 标记。
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ sudo vim /etc/supervisor/supervisord.conf
|
|||||||
```
|
```
|
||||||
|
|
||||||
增加如下示例配置
|
增加如下示例配置
|
||||||
[supervisord config](https://github.com/go-gitea/gitea/blob/master/contrib/supervisor/gitea)。
|
[supervisord config](https://github.com/go-gitea/gitea/blob/main/contrib/supervisor/gitea)。
|
||||||
|
|
||||||
将 user(git) 和 home(/home/git) 设置为与上文部署中匹配的值。如果使用自定义端口,则需修改 PORT 参数,反之如果使用默认端口则需删除 -p 标记。
|
将 user(git) 和 home(/home/git) 设置为与上文部署中匹配的值。如果使用自定义端口,则需修改 PORT 参数,反之如果使用默认端口则需删除 -p 标记。
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ menu:
|
|||||||
|
|
||||||
#### 使用 systemd
|
#### 使用 systemd
|
||||||
|
|
||||||
複製範例 [gitea.service](https://github.com/go-gitea/gitea/blob/master/contrib/systemd/gitea.service) 到 `/etc/systemd/system/gitea.service` 後用您喜愛的文字編輯器開啟檔案。
|
複製範例 [gitea.service](https://github.com/go-gitea/gitea/blob/main/contrib/systemd/gitea.service) 到 `/etc/systemd/system/gitea.service` 後用您喜愛的文字編輯器開啟檔案。
|
||||||
|
|
||||||
取消註解任何需要在此系統上啟動的服務像是 MySQL。
|
取消註解任何需要在此系統上啟動的服務像是 MySQL。
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ sudo apt install supervisor
|
|||||||
mkdir /home/git/gitea/log/supervisor
|
mkdir /home/git/gitea/log/supervisor
|
||||||
```
|
```
|
||||||
|
|
||||||
附加範例 [supervisord config](https://github.com/go-gitea/gitea/blob/master/contrib/supervisor/gitea) 的設定值到 `/etc/supervisor/supervisord.conf`。
|
附加範例 [supervisord config](https://github.com/go-gitea/gitea/blob/main/contrib/supervisor/gitea) 的設定值到 `/etc/supervisor/supervisord.conf`。
|
||||||
|
|
||||||
用您喜愛的文字編輯器修改使用者(git)和家目錄(/home/git)設定以符合部署環境。若預設埠已被占用請修改埠號或移除「-p」旗標。
|
用您喜愛的文字編輯器修改使用者(git)和家目錄(/home/git)設定以符合部署環境。若預設埠已被占用請修改埠號或移除「-p」旗標。
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ There are some basic steps to follow. On a Linux system run as the Gogs user:
|
|||||||
|
|
||||||
* Create a Gogs backup with `gogs backup`. This creates `gogs-backup-[timestamp].zip` file
|
* Create a Gogs backup with `gogs backup`. This creates `gogs-backup-[timestamp].zip` file
|
||||||
containing all important Gogs data. You would need it if you wanted to move to the `gogs` back later.
|
containing all important Gogs data. You would need it if you wanted to move to the `gogs` back later.
|
||||||
* Download the file matching the destination platform from the [downloads page](https://dl.gitea.io/gitea/).
|
* Download the file matching the destination platform from the [downloads page](https://dl.gitea.com/gitea/).
|
||||||
It should be `1.0.x` version. Migrating from `gogs` to any other version is impossible.
|
It should be `1.0.x` version. Migrating from `gogs` to any other version is impossible.
|
||||||
* Put the binary at the desired install location.
|
* Put the binary at the desired install location.
|
||||||
* Copy `gogs/custom/conf/app.ini` to `gitea/custom/conf/app.ini`.
|
* Copy `gogs/custom/conf/app.ini` to `gitea/custom/conf/app.ini`.
|
||||||
@@ -79,11 +79,11 @@ There are some basic steps to follow. On a Linux system run as the Gogs user:
|
|||||||
After successful migration from `gogs` to `gitea 1.0.x`, it is possible to upgrade `gitea` to a modern version
|
After successful migration from `gogs` to `gitea 1.0.x`, it is possible to upgrade `gitea` to a modern version
|
||||||
in a two steps process.
|
in a two steps process.
|
||||||
|
|
||||||
Upgrade to [`gitea 1.6.4`](https://dl.gitea.io/gitea/1.6.4/) first. Download the file matching
|
Upgrade to [`gitea 1.6.4`](https://dl.gitea.com/gitea/1.6.4/) first. Download the file matching
|
||||||
the destination platform from the [downloads page](https://dl.gitea.io/gitea/1.6.4/) and replace the binary.
|
the destination platform from the [downloads page](https://dl.gitea.com/gitea/1.6.4/) and replace the binary.
|
||||||
Run Gitea at least once and check that everything works as expected.
|
Run Gitea at least once and check that everything works as expected.
|
||||||
|
|
||||||
Then repeat the procedure, but this time using the [latest release](https://dl.gitea.io/gitea/{{< version >}}/).
|
Then repeat the procedure, but this time using the [latest release](https://dl.gitea.com/gitea/{{< version >}}/).
|
||||||
|
|
||||||
## Upgrading from a more recent version of Gogs
|
## Upgrading from a more recent version of Gogs
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ menu:
|
|||||||
Veuillez suivre les étapes ci-dessous. Sur Unix, toute les commandes s'exécutent en tant que l'utilisateur utilisé pour votre installation de Gogs :
|
Veuillez suivre les étapes ci-dessous. Sur Unix, toute les commandes s'exécutent en tant que l'utilisateur utilisé pour votre installation de Gogs :
|
||||||
|
|
||||||
* Crééer une sauvegarde de Gogs avec la commande `gogs dump`. Le fichier nouvellement créé `gogs-dump-[timestamp].zip` contient toutes les données de votre instance de Gogs.
|
* Crééer une sauvegarde de Gogs avec la commande `gogs dump`. Le fichier nouvellement créé `gogs-dump-[timestamp].zip` contient toutes les données de votre instance de Gogs.
|
||||||
* Téléchargez le fichier correspondant à votre plateforme à partir de la [page de téléchargements](https://dl.gitea.io/gitea).
|
* Téléchargez le fichier correspondant à votre plateforme à partir de la [page de téléchargements](https://dl.gitea.com/gitea).
|
||||||
* Mettez la binaire dans le répertoire d'installation souhaité.
|
* Mettez la binaire dans le répertoire d'installation souhaité.
|
||||||
* Copiez le fichier `gogs/custom/conf/app.ini` vers `gitea/custom/conf/app.ini`.
|
* Copiez le fichier `gogs/custom/conf/app.ini` vers `gitea/custom/conf/app.ini`.
|
||||||
* Si vous avez personnalisé les répertoires `templates, public` dans `gogs/custom/`, copiez-les vers `gitea/custom/`.
|
* Si vous avez personnalisé les répertoires `templates, public` dans `gogs/custom/`, copiez-les vers `gitea/custom/`.
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ menu:
|
|||||||
|
|
||||||
- 使用 `gogs backup` 建立 Gogs 的備份。這會建立檔案 `gogs-backup-[timestamp].zip` 包含所有重要的 Gogs 資料。
|
- 使用 `gogs backup` 建立 Gogs 的備份。這會建立檔案 `gogs-backup-[timestamp].zip` 包含所有重要的 Gogs 資料。
|
||||||
如果稍後您要恢復到 `gogs` 時會用到它。
|
如果稍後您要恢復到 `gogs` 時會用到它。
|
||||||
- 從[下載頁](https://dl.gitea.io/gitea/)下載對應您平臺的檔案。請下載 `1.0.x` 版,從 `gogs` 遷移到其它版本是不可行的。
|
- 從[下載頁](https://dl.gitea.com/gitea/)下載對應您平臺的檔案。請下載 `1.0.x` 版,從 `gogs` 遷移到其它版本是不可行的。
|
||||||
- 將二進位檔放到適當的安裝位置。
|
- 將二進位檔放到適當的安裝位置。
|
||||||
- 複製 `gogs/custom/conf/app.ini` 到 `gitea/custom/conf/app.ini`。
|
- 複製 `gogs/custom/conf/app.ini` 到 `gitea/custom/conf/app.ini`。
|
||||||
- 從 `gogs/custom/` 複製自訂 `templates, public` 到 `gitea/custom/`。
|
- 從 `gogs/custom/` 複製自訂 `templates, public` 到 `gitea/custom/`。
|
||||||
@@ -77,10 +77,10 @@ menu:
|
|||||||
|
|
||||||
成功從 `gogs` 升級到 `gitea 1.0.x` 後再用 2 個步驟即可升級到最新版的 `gitea`。
|
成功從 `gogs` 升級到 `gitea 1.0.x` 後再用 2 個步驟即可升級到最新版的 `gitea`。
|
||||||
|
|
||||||
請先升級到 [`gitea 1.6.4`](https://dl.gitea.io/gitea/1.6.4/),先從[下載頁](https://dl.gitea.io/gitea/1.6.4/)下載
|
請先升級到 [`gitea 1.6.4`](https://dl.gitea.com/gitea/1.6.4/),先從[下載頁](https://dl.gitea.com/gitea/1.6.4/)下載
|
||||||
您平臺的二進位檔取代既有的。至少執行一次 Gitea 並確認一切符合預期。
|
您平臺的二進位檔取代既有的。至少執行一次 Gitea 並確認一切符合預期。
|
||||||
|
|
||||||
接著重複上述步驟,但這次請使用[最新發行版本](https://dl.gitea.io/gitea/{{< version >}}/)。
|
接著重複上述步驟,但這次請使用[最新發行版本](https://dl.gitea.com/gitea/{{< version >}}/)。
|
||||||
|
|
||||||
## 從更新版本的 Gogs 升級
|
## 從更新版本的 Gogs 升級
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,12 @@ The default configuration is safe to use without any modification, so you can ju
|
|||||||
./act_runner --config config.yaml [command]
|
./act_runner --config config.yaml [command]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You could also generate config file with docker:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --entrypoint="" --rm -it gitea/act_runner:latest act_runner generate-config > config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
When you are using the docker image, you can specify the configuration file by using the `CONFIG_FILE` environment variable. Make sure that the file is mounted into the container as a volume:
|
When you are using the docker image, you can specify the configuration file by using the `CONFIG_FILE` environment variable. Make sure that the file is mounted into the container as a volume:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -172,6 +178,27 @@ It is because the act runner will run jobs in docker containers, so it needs to
|
|||||||
As mentioned, you can remove it if you want to run jobs in the host directly.
|
As mentioned, you can remove it if you want to run jobs in the host directly.
|
||||||
To be clear, the "host" actually means the container which is running the act runner now, instead of the host machine.
|
To be clear, the "host" actually means the container which is running the act runner now, instead of the host machine.
|
||||||
|
|
||||||
|
### Set up the runner using docker compose
|
||||||
|
|
||||||
|
You could also set up the runner using the following `docker-compose.yml`:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
runner:
|
||||||
|
image: gitea/act_runner:nightly
|
||||||
|
environment:
|
||||||
|
CONFIG_FILE: /config.yaml
|
||||||
|
GITEA_INSTANCE_URL: "${INSTANCE_URL}"
|
||||||
|
GITEA_RUNNER_REGISTRATION_TOKEN: "${REGISTRATION_TOKEN}"
|
||||||
|
GITEA_RUNNER_NAME: "${RUNNER_NAME}"
|
||||||
|
GITEA_RUNNER_LABELS: "${RUNNER_LABELS}"
|
||||||
|
volumes:
|
||||||
|
- ./config.yaml:/config.yaml
|
||||||
|
- ./data:/data
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
```
|
||||||
|
|
||||||
### Configuring cache when starting a Runner using docker image
|
### Configuring cache when starting a Runner using docker image
|
||||||
|
|
||||||
If you do not intend to use `actions/cache` in workflow, you can ignore this section.
|
If you do not intend to use `actions/cache` in workflow, you can ignore this section.
|
||||||
|
|||||||
@@ -76,6 +76,12 @@ docker pull gitea/act_runner:nightly # for the latest nightly build
|
|||||||
./act_runner --config config.yaml [command]
|
./act_runner --config config.yaml [command]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
您亦可以如下使用 docker 创建配置文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --entrypoint="" --rm -it gitea/act_runner:latest act_runner generate-config > config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
当使用Docker镜像时,可以使用`CONFIG_FILE`环境变量指定配置文件。确保将文件作为卷挂载到容器中:
|
当使用Docker镜像时,可以使用`CONFIG_FILE`环境变量指定配置文件。确保将文件作为卷挂载到容器中:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -169,6 +175,27 @@ docker run \
|
|||||||
如前所述,如果要在主机上直接运行Job,可以将其移除。
|
如前所述,如果要在主机上直接运行Job,可以将其移除。
|
||||||
需要明确的是,这里的 "主机" 实际上指的是当前运行 Act Runner的容器,而不是主机机器本身。
|
需要明确的是,这里的 "主机" 实际上指的是当前运行 Act Runner的容器,而不是主机机器本身。
|
||||||
|
|
||||||
|
### 使用 Docker compose 运行 Runner
|
||||||
|
|
||||||
|
您亦可使用如下的 `docker-compose.yml`:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
runner:
|
||||||
|
image: gitea/act_runner:nightly
|
||||||
|
environment:
|
||||||
|
CONFIG_FILE: /config.yaml
|
||||||
|
GITEA_INSTANCE_URL: "${INSTANCE_URL}"
|
||||||
|
GITEA_RUNNER_REGISTRATION_TOKEN: "${REGISTRATION_TOKEN}"
|
||||||
|
GITEA_RUNNER_NAME: "${RUNNER_NAME}"
|
||||||
|
GITEA_RUNNER_LABELS: "${RUNNER_LABELS}"
|
||||||
|
volumes:
|
||||||
|
- ./config.yaml:/config.yaml
|
||||||
|
- ./data:/data
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
```
|
||||||
|
|
||||||
### 当您使用 Docker 镜像启动 Runner,如何配置 Cache
|
### 当您使用 Docker 镜像启动 Runner,如何配置 Cache
|
||||||
|
|
||||||
如果你不打算在工作流中使用 `actions/cache`,你可以忽略本段。
|
如果你不打算在工作流中使用 `actions/cache`,你可以忽略本段。
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ Le but de ce projet est de fournir de la manière la plus simple, la plus rapide
|
|||||||
- MSSQL
|
- MSSQL
|
||||||
- [TiDB](https://github.com/pingcap/tidb) (MySQL protocol)
|
- [TiDB](https://github.com/pingcap/tidb) (MySQL protocol)
|
||||||
- Fichier de configuration
|
- Fichier de configuration
|
||||||
- Voir [ici](https://github.com/go-gitea/gitea/blob/master/custom/conf/app.example.ini)
|
- Voir [ici](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini)
|
||||||
- Panel d'administration
|
- Panel d'administration
|
||||||
- Statistiques
|
- Statistiques
|
||||||
- Actions
|
- Actions
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ Gitea 是從 [Gogs](http://gogs.io) Fork 出來的,請閱讀部落格文章 [G
|
|||||||
- MSSQL
|
- MSSQL
|
||||||
- TiDB(MySQL 協議)
|
- TiDB(MySQL 協議)
|
||||||
- 設定檔
|
- 設定檔
|
||||||
- [app.ini](https://github.com/go-gitea/gitea/blob/master/custom/conf/app.example.ini)
|
- [app.ini](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini)
|
||||||
- 管理員面板
|
- 管理員面板
|
||||||
- 系統摘要
|
- 系統摘要
|
||||||
- 維護操作
|
- 維護操作
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -122,7 +122,7 @@ require (
|
|||||||
mvdan.cc/xurls/v2 v2.4.0
|
mvdan.cc/xurls/v2 v2.4.0
|
||||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
|
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
|
||||||
xorm.io/builder v0.3.12
|
xorm.io/builder v0.3.12
|
||||||
xorm.io/xorm v1.3.3-0.20230219231735-056cecc97e9e
|
xorm.io/xorm v1.3.3-0.20230623150031-18f8e7a86c75
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -1923,5 +1923,5 @@ strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:
|
|||||||
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||||
xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM=
|
xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM=
|
||||||
xorm.io/builder v0.3.12/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
xorm.io/builder v0.3.12/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||||
xorm.io/xorm v1.3.3-0.20230219231735-056cecc97e9e h1:d5PY6mwuQK5/7T6VKfFswaKMzLmGTHkJ/ZS7+cUIAjk=
|
xorm.io/xorm v1.3.3-0.20230623150031-18f8e7a86c75 h1:ReBAlO50dCIXCWF8Gbi0ZRa62AGAwCJNCPaUNUa7JSg=
|
||||||
xorm.io/xorm v1.3.3-0.20230219231735-056cecc97e9e/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
|
xorm.io/xorm v1.3.3-0.20230623150031-18f8e7a86c75/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
|
||||||
|
|||||||
171
main.go
171
main.go
@@ -33,30 +33,58 @@ var (
|
|||||||
Tags = ""
|
Tags = ""
|
||||||
// MakeVersion holds the current Make version if built with make
|
// MakeVersion holds the current Make version if built with make
|
||||||
MakeVersion = ""
|
MakeVersion = ""
|
||||||
|
|
||||||
originalAppHelpTemplate = ""
|
|
||||||
originalCommandHelpTemplate = ""
|
|
||||||
originalSubcommandHelpTemplate = ""
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
setting.AppVer = Version
|
setting.AppVer = Version
|
||||||
setting.AppBuiltWith = formatBuiltWith()
|
setting.AppBuiltWith = formatBuiltWith()
|
||||||
setting.AppStartTime = time.Now().UTC()
|
setting.AppStartTime = time.Now().UTC()
|
||||||
|
}
|
||||||
|
|
||||||
// Grab the original help templates
|
// cmdHelp is our own help subcommand with more information
|
||||||
originalAppHelpTemplate = cli.AppHelpTemplate
|
// test cases:
|
||||||
originalCommandHelpTemplate = cli.CommandHelpTemplate
|
// ./gitea help
|
||||||
originalSubcommandHelpTemplate = cli.SubcommandHelpTemplate
|
// ./gitea -h
|
||||||
|
// ./gitea web help
|
||||||
|
// ./gitea web -h (due to cli lib limitation, this won't call our cmdHelp, so no extra info)
|
||||||
|
// ./gitea admin help auth
|
||||||
|
// ./gitea -c /tmp/app.ini -h
|
||||||
|
// ./gitea -c /tmp/app.ini help
|
||||||
|
// ./gitea help -c /tmp/app.ini
|
||||||
|
// GITEA_WORK_DIR=/tmp ./gitea help
|
||||||
|
// GITEA_WORK_DIR=/tmp ./gitea help --work-path /tmp/other
|
||||||
|
// GITEA_WORK_DIR=/tmp ./gitea help --config /tmp/app-other.ini
|
||||||
|
var cmdHelp = cli.Command{
|
||||||
|
Name: "help",
|
||||||
|
Aliases: []string{"h"},
|
||||||
|
Usage: "Shows a list of commands or help for one command",
|
||||||
|
ArgsUsage: "[command]",
|
||||||
|
Action: func(c *cli.Context) (err error) {
|
||||||
|
args := c.Args()
|
||||||
|
if args.Present() {
|
||||||
|
err = cli.ShowCommandHelp(c, args.First())
|
||||||
|
} else {
|
||||||
|
err = cli.ShowAppHelp(c)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(c.App.Writer, `
|
||||||
|
DEFAULT CONFIGURATION:
|
||||||
|
AppPath: %s
|
||||||
|
WorkPath: %s
|
||||||
|
CustomPath: %s
|
||||||
|
ConfigFile: %s
|
||||||
|
|
||||||
|
`, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
|
||||||
|
return err
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Name = "Gitea"
|
app.Name = "Gitea"
|
||||||
app.Usage = "A painless self-hosted Git service"
|
app.Usage = "A painless self-hosted Git service"
|
||||||
app.Description = `By default, gitea will start serving using the webserver with no
|
app.Description = `By default, Gitea will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".`
|
||||||
arguments - which can alternatively be run by running the subcommand web.`
|
|
||||||
app.Version = Version + formatBuiltWith()
|
app.Version = Version + formatBuiltWith()
|
||||||
|
app.EnableBashCompletion = true
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
cmd.CmdWeb,
|
cmd.CmdWeb,
|
||||||
cmd.CmdServ,
|
cmd.CmdServ,
|
||||||
@@ -77,118 +105,83 @@ arguments - which can alternatively be run by running the subcommand web.`
|
|||||||
cmd.CmdRestoreRepository,
|
cmd.CmdRestoreRepository,
|
||||||
cmd.CmdActions,
|
cmd.CmdActions,
|
||||||
}
|
}
|
||||||
// Now adjust these commands to add our global configuration options
|
|
||||||
|
|
||||||
// First calculate the default paths and set the AppHelpTemplates in this context
|
|
||||||
setting.SetCustomPathAndConf("", "", "")
|
|
||||||
setAppHelpTemplates()
|
|
||||||
|
|
||||||
// default configuration flags
|
// default configuration flags
|
||||||
defaultFlags := []cli.Flag{
|
globalFlags := []cli.Flag{
|
||||||
|
cli.HelpFlag,
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "custom-path, C",
|
Name: "custom-path, C",
|
||||||
Value: setting.CustomPath,
|
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
|
||||||
Usage: "Custom path file path",
|
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "config, c",
|
Name: "config, c",
|
||||||
Value: setting.CustomConf,
|
Value: setting.CustomConf,
|
||||||
Usage: "Custom configuration file path",
|
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
|
||||||
},
|
},
|
||||||
cli.VersionFlag,
|
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "work-path, w",
|
Name: "work-path, w",
|
||||||
Value: setting.AppWorkPath,
|
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
|
||||||
Usage: "Set the gitea working path",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the default to be equivalent to cmdWeb and add the default flags
|
// Set the default to be equivalent to cmdWeb and add the default flags
|
||||||
|
app.Flags = append(app.Flags, globalFlags...)
|
||||||
app.Flags = append(app.Flags, cmd.CmdWeb.Flags...)
|
app.Flags = append(app.Flags, cmd.CmdWeb.Flags...)
|
||||||
app.Flags = append(app.Flags, defaultFlags...)
|
app.Action = prepareWorkPathAndCustomConf(cmd.CmdWeb.Action)
|
||||||
app.Action = cmd.CmdWeb.Action
|
app.HideHelp = true // use our own help action to show helps (with more information like default config)
|
||||||
|
app.Commands = append(app.Commands, cmdHelp)
|
||||||
// Add functions to set these paths and these flags to the commands
|
|
||||||
app.Before = establishCustomPath
|
|
||||||
for i := range app.Commands {
|
for i := range app.Commands {
|
||||||
setFlagsAndBeforeOnSubcommands(&app.Commands[i], defaultFlags, establishCustomPath)
|
prepareSubcommands(&app.Commands[i], globalFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.EnableBashCompletion = true
|
|
||||||
|
|
||||||
err := app.Run(os.Args)
|
err := app.Run(os.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed to run app with %s: %v", os.Args, err)
|
_, _ = fmt.Fprintf(app.Writer, "\nFailed to run with %s: %v\n", os.Args, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.GetManager().Close()
|
log.GetManager().Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFlagsAndBeforeOnSubcommands(command *cli.Command, defaultFlags []cli.Flag, before cli.BeforeFunc) {
|
func prepareSubcommands(command *cli.Command, defaultFlags []cli.Flag) {
|
||||||
command.Flags = append(command.Flags, defaultFlags...)
|
command.Flags = append(command.Flags, defaultFlags...)
|
||||||
command.Before = establishCustomPath
|
command.Action = prepareWorkPathAndCustomConf(command.Action)
|
||||||
|
command.HideHelp = true
|
||||||
|
if command.Name != "help" {
|
||||||
|
command.Subcommands = append(command.Subcommands, cmdHelp)
|
||||||
|
}
|
||||||
for i := range command.Subcommands {
|
for i := range command.Subcommands {
|
||||||
setFlagsAndBeforeOnSubcommands(&command.Subcommands[i], defaultFlags, before)
|
prepareSubcommands(&command.Subcommands[i], defaultFlags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func establishCustomPath(ctx *cli.Context) error {
|
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
|
||||||
var providedCustom string
|
// It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times
|
||||||
var providedConf string
|
func prepareWorkPathAndCustomConf(a any) func(ctx *cli.Context) error {
|
||||||
var providedWorkPath string
|
if a == nil {
|
||||||
|
return nil
|
||||||
currentCtx := ctx
|
|
||||||
for {
|
|
||||||
if len(providedCustom) != 0 && len(providedConf) != 0 && len(providedWorkPath) != 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if currentCtx == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if currentCtx.IsSet("custom-path") && len(providedCustom) == 0 {
|
|
||||||
providedCustom = currentCtx.String("custom-path")
|
|
||||||
}
|
|
||||||
if currentCtx.IsSet("config") && len(providedConf) == 0 {
|
|
||||||
providedConf = currentCtx.String("config")
|
|
||||||
}
|
|
||||||
if currentCtx.IsSet("work-path") && len(providedWorkPath) == 0 {
|
|
||||||
providedWorkPath = currentCtx.String("work-path")
|
|
||||||
}
|
|
||||||
currentCtx = currentCtx.Parent()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
setting.SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath)
|
action := a.(func(*cli.Context) error)
|
||||||
|
return func(ctx *cli.Context) error {
|
||||||
setAppHelpTemplates()
|
var args setting.ArgWorkPathAndCustomConf
|
||||||
|
curCtx := ctx
|
||||||
if ctx.IsSet("version") {
|
for curCtx != nil {
|
||||||
cli.ShowVersion(ctx)
|
if curCtx.IsSet("work-path") && args.WorkPath == "" {
|
||||||
os.Exit(0)
|
args.WorkPath = curCtx.String("work-path")
|
||||||
|
}
|
||||||
|
if curCtx.IsSet("custom-path") && args.CustomPath == "" {
|
||||||
|
args.CustomPath = curCtx.String("custom-path")
|
||||||
|
}
|
||||||
|
if curCtx.IsSet("config") && args.CustomConf == "" {
|
||||||
|
args.CustomConf = curCtx.String("config")
|
||||||
|
}
|
||||||
|
curCtx = curCtx.Parent()
|
||||||
|
}
|
||||||
|
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
|
||||||
|
if ctx.Bool("help") {
|
||||||
|
return cmdHelp.Action.(func(ctx *cli.Context) error)(ctx)
|
||||||
|
}
|
||||||
|
return action(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setAppHelpTemplates() {
|
|
||||||
cli.AppHelpTemplate = adjustHelpTemplate(originalAppHelpTemplate)
|
|
||||||
cli.CommandHelpTemplate = adjustHelpTemplate(originalCommandHelpTemplate)
|
|
||||||
cli.SubcommandHelpTemplate = adjustHelpTemplate(originalSubcommandHelpTemplate)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adjustHelpTemplate(originalTemplate string) string {
|
|
||||||
overridden := ""
|
|
||||||
if _, ok := os.LookupEnv("GITEA_CUSTOM"); ok {
|
|
||||||
overridden = "(GITEA_CUSTOM)"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(`%s
|
|
||||||
DEFAULT CONFIGURATION:
|
|
||||||
CustomPath: %s %s
|
|
||||||
CustomConf: %s
|
|
||||||
AppPath: %s
|
|
||||||
AppWorkPath: %s
|
|
||||||
|
|
||||||
`, originalTemplate, setting.CustomPath, overridden, setting.CustomConf, setting.AppPath, setting.AppWorkPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatBuiltWith() string {
|
func formatBuiltWith() string {
|
||||||
|
|||||||
@@ -123,7 +123,10 @@ func newXORMEngine() (*xorm.Engine, error) {
|
|||||||
|
|
||||||
// SyncAllTables sync the schemas of all tables, is required by unit test code
|
// SyncAllTables sync the schemas of all tables, is required by unit test code
|
||||||
func SyncAllTables() error {
|
func SyncAllTables() error {
|
||||||
return x.StoreEngine("InnoDB").Sync2(tables...)
|
_, err := x.StoreEngine("InnoDB").SyncWithOptions(xorm.SyncOptions{
|
||||||
|
WarnIfDatabaseColumnMissed: true,
|
||||||
|
}, tables...)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitEngine initializes the xorm.Engine and sets it as db.DefaultContext
|
// InitEngine initializes the xorm.Engine and sets it as db.DefaultContext
|
||||||
|
|||||||
@@ -89,6 +89,33 @@ func mysqlGetNextResourceIndex(ctx context.Context, tableName string, groupID in
|
|||||||
return idx, nil
|
return idx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mssqlGetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
|
||||||
|
if _, err := GetEngine(ctx).Exec(fmt.Sprintf(`
|
||||||
|
MERGE INTO %s WITH (HOLDLOCK) AS target
|
||||||
|
USING (SELECT %d AS group_id) AS source
|
||||||
|
(group_id)
|
||||||
|
ON target.group_id = source.group_id
|
||||||
|
WHEN MATCHED
|
||||||
|
THEN UPDATE
|
||||||
|
SET max_index = max_index + 1
|
||||||
|
WHEN NOT MATCHED
|
||||||
|
THEN INSERT (group_id, max_index)
|
||||||
|
VALUES (%d, 1);
|
||||||
|
`, tableName, groupID, groupID)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx int64
|
||||||
|
_, err := GetEngine(ctx).SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ?", tableName), groupID).Get(&idx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if idx == 0 {
|
||||||
|
return 0, errors.New("cannot get the correct index")
|
||||||
|
}
|
||||||
|
return idx, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetNextResourceIndex generates a resource index, it must run in the same transaction where the resource is created
|
// GetNextResourceIndex generates a resource index, it must run in the same transaction where the resource is created
|
||||||
func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
|
func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
|
||||||
switch {
|
switch {
|
||||||
@@ -96,6 +123,8 @@ func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64)
|
|||||||
return postgresGetNextResourceIndex(ctx, tableName, groupID)
|
return postgresGetNextResourceIndex(ctx, tableName, groupID)
|
||||||
case setting.Database.Type.IsMySQL():
|
case setting.Database.Type.IsMySQL():
|
||||||
return mysqlGetNextResourceIndex(ctx, tableName, groupID)
|
return mysqlGetNextResourceIndex(ctx, tableName, groupID)
|
||||||
|
case setting.Database.Type.IsMSSQL():
|
||||||
|
return mssqlGetNextResourceIndex(ctx, tableName, groupID)
|
||||||
}
|
}
|
||||||
|
|
||||||
e := GetEngine(ctx)
|
e := GetEngine(ctx)
|
||||||
|
|||||||
@@ -83,13 +83,47 @@ func mysqlGetCommitStatusIndex(ctx context.Context, repoID int64, sha string) (i
|
|||||||
return idx, nil
|
return idx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mssqlGetCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
|
||||||
|
if _, err := db.GetEngine(ctx).Exec(`
|
||||||
|
MERGE INTO commit_status_index WITH (HOLDLOCK) AS target
|
||||||
|
USING (SELECT ? AS repo_id, ? AS sha) AS source
|
||||||
|
(repo_id, sha)
|
||||||
|
ON target.repo_id = source.repo_id AND target.sha = source.sha
|
||||||
|
WHEN MATCHED
|
||||||
|
THEN UPDATE
|
||||||
|
SET max_index = max_index + 1
|
||||||
|
WHEN NOT MATCHED
|
||||||
|
THEN INSERT (repo_id, sha, max_index)
|
||||||
|
VALUES (?, ?, 1);
|
||||||
|
`, repoID, sha, repoID, sha); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx int64
|
||||||
|
_, err := db.GetEngine(ctx).SQL("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ?",
|
||||||
|
repoID, sha).Get(&idx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if idx == 0 {
|
||||||
|
return 0, errors.New("cannot get the correct index")
|
||||||
|
}
|
||||||
|
return idx, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetNextCommitStatusIndex retried 3 times to generate a resource index
|
// GetNextCommitStatusIndex retried 3 times to generate a resource index
|
||||||
func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
|
func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
|
||||||
|
if !git.IsValidSHAPattern(sha) {
|
||||||
|
return 0, git.ErrInvalidSHA{SHA: sha}
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case setting.Database.Type.IsPostgreSQL():
|
case setting.Database.Type.IsPostgreSQL():
|
||||||
return postgresGetCommitStatusIndex(ctx, repoID, sha)
|
return postgresGetCommitStatusIndex(ctx, repoID, sha)
|
||||||
case setting.Database.Type.IsMySQL():
|
case setting.Database.Type.IsMySQL():
|
||||||
return mysqlGetCommitStatusIndex(ctx, repoID, sha)
|
return mysqlGetCommitStatusIndex(ctx, repoID, sha)
|
||||||
|
case setting.Database.Type.IsMSSQL():
|
||||||
|
return mssqlGetCommitStatusIndex(ctx, repoID, sha)
|
||||||
}
|
}
|
||||||
|
|
||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ func AsCommentType(typeName string) CommentType {
|
|||||||
|
|
||||||
func (t CommentType) HasContentSupport() bool {
|
func (t CommentType) HasContentSupport() bool {
|
||||||
switch t {
|
switch t {
|
||||||
case CommentTypeComment, CommentTypeCode, CommentTypeReview:
|
case CommentTypeComment, CommentTypeCode, CommentTypeReview, CommentTypeDismissReview:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ import (
|
|||||||
type CodeComments map[string]map[int64][]*Comment
|
type CodeComments map[string]map[int64][]*Comment
|
||||||
|
|
||||||
// FetchCodeComments will return a 2d-map: ["Path"]["Line"] = Comments at line
|
// FetchCodeComments will return a 2d-map: ["Path"]["Line"] = Comments at line
|
||||||
func FetchCodeComments(ctx context.Context, issue *Issue, currentUser *user_model.User) (CodeComments, error) {
|
func FetchCodeComments(ctx context.Context, issue *Issue, currentUser *user_model.User, showOutdatedComments bool) (CodeComments, error) {
|
||||||
return fetchCodeCommentsByReview(ctx, issue, currentUser, nil)
|
return fetchCodeCommentsByReview(ctx, issue, currentUser, nil, showOutdatedComments)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *user_model.User, review *Review) (CodeComments, error) {
|
func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *user_model.User, review *Review, showOutdatedComments bool) (CodeComments, error) {
|
||||||
pathToLineToComment := make(CodeComments)
|
pathToLineToComment := make(CodeComments)
|
||||||
if review == nil {
|
if review == nil {
|
||||||
review = &Review{ID: 0}
|
review = &Review{ID: 0}
|
||||||
@@ -33,7 +33,7 @@ func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *u
|
|||||||
ReviewID: review.ID,
|
ReviewID: review.ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
comments, err := findCodeComments(ctx, opts, issue, currentUser, review)
|
comments, err := findCodeComments(ctx, opts, issue, currentUser, review, showOutdatedComments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -47,15 +47,17 @@ func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *u
|
|||||||
return pathToLineToComment, nil
|
return pathToLineToComment, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, currentUser *user_model.User, review *Review) ([]*Comment, error) {
|
func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, currentUser *user_model.User, review *Review, showOutdatedComments bool) ([]*Comment, error) {
|
||||||
var comments CommentList
|
var comments CommentList
|
||||||
if review == nil {
|
if review == nil {
|
||||||
review = &Review{ID: 0}
|
review = &Review{ID: 0}
|
||||||
}
|
}
|
||||||
conds := opts.ToConds()
|
conds := opts.ToConds()
|
||||||
if review.ID == 0 {
|
|
||||||
|
if !showOutdatedComments && review.ID == 0 {
|
||||||
conds = conds.And(builder.Eq{"invalidated": false})
|
conds = conds.And(builder.Eq{"invalidated": false})
|
||||||
}
|
}
|
||||||
|
|
||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
if err := e.Where(conds).
|
if err := e.Where(conds).
|
||||||
Asc("comment.created_unix").
|
Asc("comment.created_unix").
|
||||||
@@ -118,12 +120,12 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FetchCodeCommentsByLine fetches the code comments for a given treePath and line number
|
// FetchCodeCommentsByLine fetches the code comments for a given treePath and line number
|
||||||
func FetchCodeCommentsByLine(ctx context.Context, issue *Issue, currentUser *user_model.User, treePath string, line int64) ([]*Comment, error) {
|
func FetchCodeCommentsByLine(ctx context.Context, issue *Issue, currentUser *user_model.User, treePath string, line int64, showOutdatedComments bool) ([]*Comment, error) {
|
||||||
opts := FindCommentsOptions{
|
opts := FindCommentsOptions{
|
||||||
Type: CommentTypeCode,
|
Type: CommentTypeCode,
|
||||||
IssueID: issue.ID,
|
IssueID: issue.ID,
|
||||||
TreePath: treePath,
|
TreePath: treePath,
|
||||||
Line: line,
|
Line: line,
|
||||||
}
|
}
|
||||||
return findCodeComments(ctx, opts, issue, currentUser, nil)
|
return findCodeComments(ctx, opts, issue, currentUser, nil, showOutdatedComments)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func TestFetchCodeComments(t *testing.T) {
|
|||||||
|
|
||||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
res, err := issues_model.FetchCodeComments(db.DefaultContext, issue, user)
|
res, err := issues_model.FetchCodeComments(db.DefaultContext, issue, user, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Contains(t, res, "README.md")
|
assert.Contains(t, res, "README.md")
|
||||||
assert.Contains(t, res["README.md"], int64(4))
|
assert.Contains(t, res["README.md"], int64(4))
|
||||||
@@ -58,7 +58,7 @@ func TestFetchCodeComments(t *testing.T) {
|
|||||||
assert.Equal(t, int64(4), res["README.md"][4][0].ID)
|
assert.Equal(t, int64(4), res["README.md"][4][0].ID)
|
||||||
|
|
||||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
res, err = issues_model.FetchCodeComments(db.DefaultContext, issue, user2)
|
res, err = issues_model.FetchCodeComments(db.DefaultContext, issue, user2, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, res, 1)
|
assert.Len(t, res, 1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ func (r *Review) LoadCodeComments(ctx context.Context) (err error) {
|
|||||||
if err = r.loadIssue(ctx); err != nil {
|
if err = r.loadIssue(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.CodeComments, err = fetchCodeCommentsByReview(ctx, r.Issue, nil, r)
|
r.CodeComments, err = fetchCodeCommentsByReview(ctx, r.Issue, nil, r, false)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -147,9 +147,9 @@ func MainTest(m *testing.M) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
|
||||||
setting.AppDataPath = tmpDataPath
|
setting.AppDataPath = tmpDataPath
|
||||||
|
|
||||||
setting.SetCustomPathAndConf("", "", "")
|
|
||||||
unittest.InitSettings()
|
unittest.InitSettings()
|
||||||
if err = git.InitFull(context.Background()); err != nil {
|
if err = git.InitFull(context.Background()); err != nil {
|
||||||
fmt.Printf("Unable to InitFull: %v\n", err)
|
fmt.Printf("Unable to InitFull: %v\n", err)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func DeleteOrphanedAttachments(x *xorm.Engine) error {
|
|||||||
|
|
||||||
for _, attachment := range attachments {
|
for _, attachment := range attachments {
|
||||||
uuid := attachment.UUID
|
uuid := attachment.UUID
|
||||||
if err := util.RemoveAll(filepath.Join(setting.Attachment.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
|
if err := util.RemoveAll(filepath.Join(setting.Attachment.Storage.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func RemoveAttachmentMissedRepo(x *xorm.Engine) error {
|
|||||||
|
|
||||||
for i := 0; i < len(attachments); i++ {
|
for i := 0; i < len(attachments); i++ {
|
||||||
uuid := attachments[i].UUID
|
uuid := attachments[i].UUID
|
||||||
if err = util.RemoveAll(filepath.Join(setting.Attachment.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
|
if err = util.RemoveAll(filepath.Join(setting.Attachment.Storage.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
|
||||||
fmt.Printf("Error: %v", err) //nolint:forbidigo
|
fmt.Printf("Error: %v", err) //nolint:forbidigo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func RenameExistingUserAvatarName(x *xorm.Engine) error {
|
|||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
oldAvatar := user.Avatar
|
oldAvatar := user.Avatar
|
||||||
|
|
||||||
if stat, err := os.Stat(filepath.Join(setting.Avatar.Path, oldAvatar)); err != nil || !stat.Mode().IsRegular() {
|
if stat, err := os.Stat(filepath.Join(setting.Avatar.Storage.Path, oldAvatar)); err != nil || !stat.Mode().IsRegular() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = fmt.Errorf("Error: \"%s\" is not a regular file", oldAvatar)
|
err = fmt.Errorf("Error: \"%s\" is not a regular file", oldAvatar)
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ func RenameExistingUserAvatarName(x *xorm.Engine) error {
|
|||||||
return fmt.Errorf("[user: %s] user table update: %w", user.LowerName, err)
|
return fmt.Errorf("[user: %s] user table update: %w", user.LowerName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteList.Add(filepath.Join(setting.Avatar.Path, oldAvatar))
|
deleteList.Add(filepath.Join(setting.Avatar.Storage.Path, oldAvatar))
|
||||||
migrated++
|
migrated++
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
@@ -135,7 +135,7 @@ func RenameExistingUserAvatarName(x *xorm.Engine) error {
|
|||||||
// copyOldAvatarToNewLocation copies oldAvatar to newAvatarLocation
|
// copyOldAvatarToNewLocation copies oldAvatar to newAvatarLocation
|
||||||
// and returns newAvatar location
|
// and returns newAvatar location
|
||||||
func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error) {
|
func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error) {
|
||||||
fr, err := os.Open(filepath.Join(setting.Avatar.Path, oldAvatar))
|
fr, err := os.Open(filepath.Join(setting.Avatar.Storage.Path, oldAvatar))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("os.Open: %w", err)
|
return "", fmt.Errorf("os.Open: %w", err)
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error)
|
|||||||
return newAvatar, nil
|
return newAvatar, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(filepath.Join(setting.Avatar.Path, newAvatar), data, 0o666); err != nil {
|
if err := os.WriteFile(filepath.Join(setting.Avatar.Storage.Path, newAvatar), data, 0o666); err != nil {
|
||||||
return "", fmt.Errorf("os.WriteFile: %w", err)
|
return "", fmt.Errorf("os.WriteFile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -262,6 +262,10 @@ func GetRepositories(ctx context.Context, actor *user_model.User, n int, last st
|
|||||||
cond = cond.And(builder.Gt{"package_property.value": strings.ToLower(last)})
|
cond = cond.And(builder.Gt{"package_property.value": strings.ToLower(last)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if actor.IsGhost() {
|
||||||
|
actor = nil
|
||||||
|
}
|
||||||
|
|
||||||
cond = cond.And(user_model.BuildCanSeeUserCondition(actor))
|
cond = cond.And(user_model.BuildCanSeeUserCondition(actor))
|
||||||
|
|
||||||
sess := db.GetEngine(ctx).
|
sess := db.GetEngine(ctx).
|
||||||
|
|||||||
@@ -42,12 +42,14 @@ func fatalTestError(fmtStr string, args ...interface{}) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitSettings initializes config provider and load common setttings for tests
|
// InitSettings initializes config provider and load common settings for tests
|
||||||
func InitSettings(extraConfigs ...string) {
|
func InitSettings(extraConfigs ...string) {
|
||||||
setting.Init(&setting.Options{
|
if setting.CustomConf == "" {
|
||||||
AllowEmpty: true,
|
setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini")
|
||||||
ExtraConfig: strings.Join(extraConfigs, "\n"),
|
_ = os.Remove(setting.CustomConf)
|
||||||
})
|
}
|
||||||
|
setting.InitCfgProvider(setting.CustomConf, strings.Join(extraConfigs, "\n"))
|
||||||
|
setting.LoadCommonSettings()
|
||||||
|
|
||||||
if err := setting.PrepareAppDataPath(); err != nil {
|
if err := setting.PrepareAppDataPath(); err != nil {
|
||||||
log.Fatalf("Can not prepare APP_DATA_PATH: %v", err)
|
log.Fatalf("Can not prepare APP_DATA_PATH: %v", err)
|
||||||
@@ -69,7 +71,7 @@ type TestOptions struct {
|
|||||||
// MainTest a reusable TestMain(..) function for unit tests that need to use a
|
// MainTest a reusable TestMain(..) function for unit tests that need to use a
|
||||||
// test database. Creates the test database, and sets necessary settings.
|
// test database. Creates the test database, and sets necessary settings.
|
||||||
func MainTest(m *testing.M, testOpts *TestOptions) {
|
func MainTest(m *testing.M, testOpts *TestOptions) {
|
||||||
setting.SetCustomPathAndConf("", "", "")
|
setting.CustomPath = filepath.Join(testOpts.GiteaRootPath, "custom")
|
||||||
InitSettings()
|
InitSettings()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ const (
|
|||||||
SettingsKeyHiddenCommentTypes = "issue.hidden_comment_types"
|
SettingsKeyHiddenCommentTypes = "issue.hidden_comment_types"
|
||||||
// SettingsKeyDiffWhitespaceBehavior is the setting key for whitespace behavior of diff
|
// SettingsKeyDiffWhitespaceBehavior is the setting key for whitespace behavior of diff
|
||||||
SettingsKeyDiffWhitespaceBehavior = "diff.whitespace_behaviour"
|
SettingsKeyDiffWhitespaceBehavior = "diff.whitespace_behaviour"
|
||||||
|
// SettingsKeyShowOutdatedComments is the setting key wether or not to show outdated comments in PRs
|
||||||
|
SettingsKeyShowOutdatedComments = "comment_code.show_outdated"
|
||||||
// UserActivityPubPrivPem is user's private key
|
// UserActivityPubPrivPem is user's private key
|
||||||
UserActivityPubPrivPem = "activitypub.priv_pem"
|
UserActivityPubPrivPem = "activitypub.priv_pem"
|
||||||
// UserActivityPubPubPem is user's public key
|
// UserActivityPubPubPem is user's public key
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ func (ctx *Context) RedirectToFirst(location ...string) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unfortunately browsers consider a redirect Location with preceding "//" and "/\" as meaning redirect to "http(s)://REST_OF_PATH"
|
// Unfortunately browsers consider a redirect Location with preceding "//", "\\" and "/\" as meaning redirect to "http(s)://REST_OF_PATH"
|
||||||
// Therefore we should ignore these redirect locations to prevent open redirects
|
// Therefore we should ignore these redirect locations to prevent open redirects
|
||||||
if len(loc) > 1 && loc[0] == '/' && (loc[1] == '/' || loc[1] == '\\') {
|
if len(loc) > 1 && (loc[0] == '/' || loc[0] == '\\') && (loc[1] == '/' || loc[1] == '\\') {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ type Check struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initDBSkipLogger(ctx context.Context) error {
|
func initDBSkipLogger(ctx context.Context) error {
|
||||||
setting.Init(&setting.Options{})
|
setting.MustInstalled()
|
||||||
setting.LoadDBSetting()
|
setting.LoadDBSetting()
|
||||||
if err := db.InitEngine(ctx); err != nil {
|
if err := db.InitEngine(ctx); err != nil {
|
||||||
return fmt.Errorf("db.InitEngine: %w", err)
|
return fmt.Errorf("db.InitEngine: %w", err)
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func checkConfigurationFiles(ctx context.Context, logger log.Logger, autofix boo
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
setting.Init(&setting.Options{})
|
setting.MustInstalled()
|
||||||
|
|
||||||
configurationFiles := []configurationFile{
|
configurationFiles := []configurationFile{
|
||||||
{"Configuration File Path", setting.CustomConf, false, true, false},
|
{"Configuration File Path", setting.CustomConf, false, true, false},
|
||||||
|
|||||||
@@ -20,17 +20,18 @@ func (b *Blob) Name() string {
|
|||||||
return b.name
|
return b.name
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlobContent Gets the content of the blob as raw text
|
// GetBlobContent Gets the limited content of the blob as raw text
|
||||||
func (b *Blob) GetBlobContent() (string, error) {
|
func (b *Blob) GetBlobContent(limit int64) (string, error) {
|
||||||
|
if limit <= 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
dataRc, err := b.DataAsync()
|
dataRc, err := b.DataAsync()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer dataRc.Close()
|
defer dataRc.Close()
|
||||||
buf := make([]byte, 1024)
|
buf, err := util.ReadWithLimit(dataRc, int(limit))
|
||||||
n, _ := util.ReadAtMost(dataRc, buf)
|
return string(buf), err
|
||||||
buf = buf[:n]
|
|
||||||
return string(buf), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlobLineCount gets line count of the blob
|
// GetBlobLineCount gets line count of the blob
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ func (ref RefName) ShortName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RefGroup returns the group type of the reference
|
// RefGroup returns the group type of the reference
|
||||||
|
// Using the name of the directory under .git/refs
|
||||||
func (ref RefName) RefGroup() string {
|
func (ref RefName) RefGroup() string {
|
||||||
if ref.IsBranch() {
|
if ref.IsBranch() {
|
||||||
return "heads"
|
return "heads"
|
||||||
@@ -182,6 +183,19 @@ func (ref RefName) RefGroup() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RefType returns the simple ref type of the reference, e.g. branch, tag
|
||||||
|
// It's differrent from RefGroup, which is using the name of the directory under .git/refs
|
||||||
|
// Here we using branch but not heads, using tag but not tags
|
||||||
|
func (ref RefName) RefType() string {
|
||||||
|
var refType string
|
||||||
|
if ref.IsBranch() {
|
||||||
|
refType = "branch"
|
||||||
|
} else if ref.IsTag() {
|
||||||
|
refType = "tag"
|
||||||
|
}
|
||||||
|
return refType
|
||||||
|
}
|
||||||
|
|
||||||
// RefURL returns the absolute URL for a ref in a repository
|
// RefURL returns the absolute URL for a ref in a repository
|
||||||
func RefURL(repoURL, ref string) string {
|
func RefURL(repoURL, ref string) string {
|
||||||
refFullName := RefName(ref)
|
refFullName := RefName(ref)
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ func IsValidSHAPattern(sha string) bool {
|
|||||||
return shaPattern.MatchString(sha)
|
return shaPattern.MatchString(sha)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ErrInvalidSHA struct {
|
||||||
|
SHA string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrInvalidSHA) Error() string {
|
||||||
|
return fmt.Sprintf("invalid sha: %s", err.SHA)
|
||||||
|
}
|
||||||
|
|
||||||
// MustID always creates a new SHA1 from a [20]byte array with no validation of input.
|
// MustID always creates a new SHA1 from a [20]byte array with no validation of input.
|
||||||
func MustID(b []byte) SHA1 {
|
func MustID(b []byte) SHA1 {
|
||||||
var id SHA1
|
var id SHA1
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/emoji"
|
"code.gitea.io/gitea/modules/emoji"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@@ -28,9 +29,7 @@ var localMetas = map[string]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
setting.Init(&setting.Options{
|
unittest.InitSettings()
|
||||||
AllowEmpty: true,
|
|
||||||
})
|
|
||||||
if err := git.InitSimple(context.Background()); err != nil {
|
if err := git.InitSimple(context.Background()); err != nil {
|
||||||
log.Fatal("git init failed, err: %v", err)
|
log.Fatal("git init failed, err: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ func IsSummary(node ast.Node) bool {
|
|||||||
// TaskCheckBoxListItem is a block that represents a list item of a markdown block with a checkbox
|
// TaskCheckBoxListItem is a block that represents a list item of a markdown block with a checkbox
|
||||||
type TaskCheckBoxListItem struct {
|
type TaskCheckBoxListItem struct {
|
||||||
*ast.ListItem
|
*ast.ListItem
|
||||||
IsChecked bool
|
IsChecked bool
|
||||||
|
SourcePosition int
|
||||||
}
|
}
|
||||||
|
|
||||||
// KindTaskCheckBoxListItem is the NodeKind for TaskCheckBoxListItem
|
// KindTaskCheckBoxListItem is the NodeKind for TaskCheckBoxListItem
|
||||||
@@ -86,6 +87,7 @@ var KindTaskCheckBoxListItem = ast.NewNodeKind("TaskCheckBoxListItem")
|
|||||||
func (n *TaskCheckBoxListItem) Dump(source []byte, level int) {
|
func (n *TaskCheckBoxListItem) Dump(source []byte, level int) {
|
||||||
m := map[string]string{}
|
m := map[string]string{}
|
||||||
m["IsChecked"] = strconv.FormatBool(n.IsChecked)
|
m["IsChecked"] = strconv.FormatBool(n.IsChecked)
|
||||||
|
m["SourcePosition"] = strconv.FormatInt(int64(n.SourcePosition), 10)
|
||||||
ast.DumpHelper(n, source, level, m, nil)
|
ast.DumpHelper(n, source, level, m, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -177,6 +177,11 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
|||||||
newChild := NewTaskCheckBoxListItem(listItem)
|
newChild := NewTaskCheckBoxListItem(listItem)
|
||||||
newChild.IsChecked = taskCheckBox.IsChecked
|
newChild.IsChecked = taskCheckBox.IsChecked
|
||||||
newChild.SetAttributeString("class", []byte("task-list-item"))
|
newChild.SetAttributeString("class", []byte("task-list-item"))
|
||||||
|
segments := newChild.FirstChild().Lines()
|
||||||
|
if segments.Len() > 0 {
|
||||||
|
segment := segments.At(0)
|
||||||
|
newChild.SourcePosition = rc.metaLength + segment.Start
|
||||||
|
}
|
||||||
v.AppendChild(v, newChild)
|
v.AppendChild(v, newChild)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -457,12 +462,7 @@ func (r *HTMLRenderer) renderTaskCheckBoxListItem(w util.BufWriter, source []byt
|
|||||||
} else {
|
} else {
|
||||||
_, _ = w.WriteString("<li>")
|
_, _ = w.WriteString("<li>")
|
||||||
}
|
}
|
||||||
_, _ = w.WriteString(`<input type="checkbox" disabled=""`)
|
fmt.Fprintf(w, `<input type="checkbox" disabled="" data-source-position="%d"`, n.SourcePosition)
|
||||||
segments := node.FirstChild().Lines()
|
|
||||||
if segments.Len() > 0 {
|
|
||||||
segment := segments.At(0)
|
|
||||||
_, _ = w.WriteString(fmt.Sprintf(` data-source-position="%d"`, segment.Start))
|
|
||||||
}
|
|
||||||
if n.IsChecked {
|
if n.IsChecked {
|
||||||
_, _ = w.WriteString(` checked=""`)
|
_, _ = w.WriteString(` checked=""`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,6 +178,9 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer)
|
|||||||
}
|
}
|
||||||
buf = giteautil.NormalizeEOL(buf)
|
buf = giteautil.NormalizeEOL(buf)
|
||||||
|
|
||||||
|
// Preserve original length.
|
||||||
|
bufWithMetadataLength := len(buf)
|
||||||
|
|
||||||
rc := &RenderConfig{
|
rc := &RenderConfig{
|
||||||
Meta: renderMetaModeFromString(string(ctx.RenderMetaAs)),
|
Meta: renderMetaModeFromString(string(ctx.RenderMetaAs)),
|
||||||
Icon: "table",
|
Icon: "table",
|
||||||
@@ -185,6 +188,12 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer)
|
|||||||
}
|
}
|
||||||
buf, _ = ExtractMetadataBytes(buf, rc)
|
buf, _ = ExtractMetadataBytes(buf, rc)
|
||||||
|
|
||||||
|
metaLength := bufWithMetadataLength - len(buf)
|
||||||
|
if metaLength < 0 {
|
||||||
|
metaLength = 0
|
||||||
|
}
|
||||||
|
rc.metaLength = metaLength
|
||||||
|
|
||||||
pc.Set(renderConfigKey, rc)
|
pc.Set(renderConfigKey, rc)
|
||||||
|
|
||||||
if err := converter.Convert(buf, lw, parser.WithContext(pc)); err != nil {
|
if err := converter.Convert(buf, lw, parser.WithContext(pc)); err != nil {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
@@ -33,9 +34,7 @@ var localMetas = map[string]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
setting.Init(&setting.Options{
|
unittest.InitSettings()
|
||||||
AllowEmpty: true,
|
|
||||||
})
|
|
||||||
if err := git.InitSimple(context.Background()); err != nil {
|
if err := git.InitSimple(context.Background()); err != nil {
|
||||||
log.Fatal("git init failed, err: %v", err)
|
log.Fatal("git init failed, err: %v", err)
|
||||||
}
|
}
|
||||||
@@ -520,3 +519,40 @@ func TestMathBlock(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTaskList(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
testcase string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// data-source-position should take into account YAML frontmatter.
|
||||||
|
`---
|
||||||
|
foo: bar
|
||||||
|
---
|
||||||
|
- [ ] task 1`,
|
||||||
|
`<details><summary><i class="icon table"></i></summary><table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>foo</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>bar</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</details><ul>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled="" data-source-position="19"/>task 1</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testcases {
|
||||||
|
res, err := RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
|
||||||
|
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
|
||||||
|
assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ type RenderConfig struct {
|
|||||||
TOC string // "false": hide, "side"/empty: in sidebar, "main"/"true": in main view
|
TOC string // "false": hide, "side"/empty: in sidebar, "main"/"true": in main view
|
||||||
Lang string
|
Lang string
|
||||||
yamlNode *yaml.Node
|
yamlNode *yaml.Node
|
||||||
|
|
||||||
|
// Used internally. Cannot be controlled by frontmatter.
|
||||||
|
metaLength int
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderMetaModeFromString(s string) markup.RenderMetaMode {
|
func renderMetaModeFromString(s string) markup.RenderMetaMode {
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
package setting
|
package setting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/gitea/modules/log"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Actions settings
|
// Actions settings
|
||||||
var (
|
var (
|
||||||
Actions = struct {
|
Actions = struct {
|
||||||
LogStorage Storage // how the created logs should be stored
|
LogStorage *Storage // how the created logs should be stored
|
||||||
ArtifactStorage Storage // how the created artifacts should be stored
|
ArtifactStorage *Storage // how the created artifacts should be stored
|
||||||
Enabled bool
|
Enabled bool
|
||||||
DefaultActionsURL string `ini:"DEFAULT_ACTIONS_URL"`
|
DefaultActionsURL string `ini:"DEFAULT_ACTIONS_URL"`
|
||||||
}{
|
}{
|
||||||
@@ -20,15 +20,22 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadActionsFrom(rootCfg ConfigProvider) {
|
func loadActionsFrom(rootCfg ConfigProvider) error {
|
||||||
sec := rootCfg.Section("actions")
|
sec := rootCfg.Section("actions")
|
||||||
if err := sec.MapTo(&Actions); err != nil {
|
err := sec.MapTo(&Actions)
|
||||||
log.Fatal("Failed to map Actions settings: %v", err)
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to map Actions settings: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsSec := rootCfg.Section("actions.artifacts")
|
// don't support to read configuration from [actions]
|
||||||
storageType := actionsSec.Key("STORAGE_TYPE").MustString("")
|
Actions.LogStorage, err = getStorage(rootCfg, "actions_log", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
Actions.LogStorage = getStorage(rootCfg, "actions_log", "", nil)
|
actionsSec, _ := rootCfg.GetSection("actions.artifacts")
|
||||||
Actions.ArtifactStorage = getStorage(rootCfg, "actions_artifacts", storageType, actionsSec)
|
|
||||||
|
Actions.ArtifactStorage, err = getStorage(rootCfg, "actions_artifacts", "", actionsSec)
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
97
modules/setting/actions_test.go
Normal file
97
modules/setting/actions_test.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_getStorageInheritNameSectionTypeForActions(t *testing.T) {
|
||||||
|
iniStr := `
|
||||||
|
[storage]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, loadActionsFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", Actions.LogStorage.Type)
|
||||||
|
assert.EqualValues(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath)
|
||||||
|
assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type)
|
||||||
|
assert.EqualValues(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath)
|
||||||
|
|
||||||
|
iniStr = `
|
||||||
|
[storage.actions_log]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err = NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, loadActionsFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", Actions.LogStorage.Type)
|
||||||
|
assert.EqualValues(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath)
|
||||||
|
assert.EqualValues(t, "local", Actions.ArtifactStorage.Type)
|
||||||
|
assert.EqualValues(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path))
|
||||||
|
|
||||||
|
iniStr = `
|
||||||
|
[storage.actions_log]
|
||||||
|
STORAGE_TYPE = my_storage
|
||||||
|
|
||||||
|
[storage.my_storage]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err = NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, loadActionsFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", Actions.LogStorage.Type)
|
||||||
|
assert.EqualValues(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath)
|
||||||
|
assert.EqualValues(t, "local", Actions.ArtifactStorage.Type)
|
||||||
|
assert.EqualValues(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path))
|
||||||
|
|
||||||
|
iniStr = `
|
||||||
|
[storage.actions_artifacts]
|
||||||
|
STORAGE_TYPE = my_storage
|
||||||
|
|
||||||
|
[storage.my_storage]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err = NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, loadActionsFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "local", Actions.LogStorage.Type)
|
||||||
|
assert.EqualValues(t, "actions_log", filepath.Base(Actions.LogStorage.Path))
|
||||||
|
assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type)
|
||||||
|
assert.EqualValues(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath)
|
||||||
|
|
||||||
|
iniStr = `
|
||||||
|
[storage.actions_artifacts]
|
||||||
|
STORAGE_TYPE = my_storage
|
||||||
|
|
||||||
|
[storage.my_storage]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err = NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, loadActionsFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "local", Actions.LogStorage.Type)
|
||||||
|
assert.EqualValues(t, "actions_log", filepath.Base(Actions.LogStorage.Path))
|
||||||
|
assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type)
|
||||||
|
assert.EqualValues(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath)
|
||||||
|
|
||||||
|
iniStr = ``
|
||||||
|
cfg, err = NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, loadActionsFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "local", Actions.LogStorage.Type)
|
||||||
|
assert.EqualValues(t, "actions_log", filepath.Base(Actions.LogStorage.Path))
|
||||||
|
assert.EqualValues(t, "local", Actions.ArtifactStorage.Type)
|
||||||
|
assert.EqualValues(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path))
|
||||||
|
}
|
||||||
@@ -5,29 +5,31 @@ package setting
|
|||||||
|
|
||||||
// Attachment settings
|
// Attachment settings
|
||||||
var Attachment = struct {
|
var Attachment = struct {
|
||||||
Storage
|
Storage *Storage
|
||||||
AllowedTypes string
|
AllowedTypes string
|
||||||
MaxSize int64
|
MaxSize int64
|
||||||
MaxFiles int
|
MaxFiles int
|
||||||
Enabled bool
|
Enabled bool
|
||||||
}{
|
}{
|
||||||
Storage: Storage{
|
Storage: &Storage{},
|
||||||
ServeDirect: false,
|
AllowedTypes: ".csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip",
|
||||||
},
|
|
||||||
AllowedTypes: "image/jpeg,image/png,application/zip,application/gzip",
|
|
||||||
MaxSize: 4,
|
MaxSize: 4,
|
||||||
MaxFiles: 5,
|
MaxFiles: 5,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadAttachmentFrom(rootCfg ConfigProvider) {
|
func loadAttachmentFrom(rootCfg ConfigProvider) (err error) {
|
||||||
sec := rootCfg.Section("attachment")
|
sec, _ := rootCfg.GetSection("attachment")
|
||||||
storageType := sec.Key("STORAGE_TYPE").MustString("")
|
if sec == nil {
|
||||||
|
Attachment.Storage, err = getStorage(rootCfg, "attachments", "", nil)
|
||||||
Attachment.Storage = getStorage(rootCfg, "attachments", storageType, sec)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip")
|
Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip")
|
||||||
Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(4)
|
Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(4)
|
||||||
Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5)
|
Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5)
|
||||||
Attachment.Enabled = sec.Key("ENABLED").MustBool(true)
|
Attachment.Enabled = sec.Key("ENABLED").MustBool(true)
|
||||||
|
|
||||||
|
Attachment.Storage, err = getStorage(rootCfg, "attachments", "", sec)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
133
modules/setting/attachment_test.go
Normal file
133
modules/setting/attachment_test.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_getStorageCustomType(t *testing.T) {
|
||||||
|
iniStr := `
|
||||||
|
[attachment]
|
||||||
|
STORAGE_TYPE = my_minio
|
||||||
|
MINIO_BUCKET = gitea-attachment
|
||||||
|
|
||||||
|
[storage.my_minio]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
MINIO_ENDPOINT = my_minio:9000
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, loadAttachmentFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", Attachment.Storage.Type)
|
||||||
|
assert.EqualValues(t, "my_minio:9000", Attachment.Storage.MinioConfig.Endpoint)
|
||||||
|
assert.EqualValues(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket)
|
||||||
|
assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_getStorageTypeSectionOverridesStorageSection(t *testing.T) {
|
||||||
|
iniStr := `
|
||||||
|
[attachment]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
|
||||||
|
[storage.minio]
|
||||||
|
MINIO_BUCKET = gitea-minio
|
||||||
|
|
||||||
|
[storage]
|
||||||
|
MINIO_BUCKET = gitea
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, loadAttachmentFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", Attachment.Storage.Type)
|
||||||
|
assert.EqualValues(t, "gitea-minio", Attachment.Storage.MinioConfig.Bucket)
|
||||||
|
assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_getStorageSpecificOverridesStorage(t *testing.T) {
|
||||||
|
iniStr := `
|
||||||
|
[attachment]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
MINIO_BUCKET = gitea-attachment
|
||||||
|
|
||||||
|
[storage.attachments]
|
||||||
|
MINIO_BUCKET = gitea
|
||||||
|
|
||||||
|
[storage]
|
||||||
|
STORAGE_TYPE = local
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, loadAttachmentFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", Attachment.Storage.Type)
|
||||||
|
assert.EqualValues(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket)
|
||||||
|
assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_getStorageGetDefaults(t *testing.T) {
|
||||||
|
cfg, err := NewConfigProviderFromData("")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, loadAttachmentFrom(cfg))
|
||||||
|
|
||||||
|
// default storage is local, so bucket is empty
|
||||||
|
assert.EqualValues(t, "", Attachment.Storage.MinioConfig.Bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_getStorageInheritNameSectionType(t *testing.T) {
|
||||||
|
iniStr := `
|
||||||
|
[storage.attachments]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, loadAttachmentFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", Attachment.Storage.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AttachmentStorage(t *testing.T) {
|
||||||
|
iniStr := `
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
[storage]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
MINIO_ENDPOINT = s3.my-domain.net
|
||||||
|
MINIO_BUCKET = gitea
|
||||||
|
MINIO_LOCATION = homenet
|
||||||
|
MINIO_USE_SSL = true
|
||||||
|
MINIO_ACCESS_KEY_ID = correct_key
|
||||||
|
MINIO_SECRET_ACCESS_KEY = correct_key
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, loadAttachmentFrom(cfg))
|
||||||
|
storage := Attachment.Storage
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", storage.Type)
|
||||||
|
assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AttachmentStorage1(t *testing.T) {
|
||||||
|
iniStr := `
|
||||||
|
[storage]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, loadAttachmentFrom(cfg))
|
||||||
|
assert.EqualValues(t, "minio", Attachment.Storage.Type)
|
||||||
|
assert.EqualValues(t, "gitea", Attachment.Storage.MinioConfig.Bucket)
|
||||||
|
assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath)
|
||||||
|
}
|
||||||
@@ -4,9 +4,11 @@
|
|||||||
package setting
|
package setting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -50,12 +52,18 @@ type ConfigProvider interface {
|
|||||||
GetSection(name string) (ConfigSection, error)
|
GetSection(name string) (ConfigSection, error)
|
||||||
Save() error
|
Save() error
|
||||||
SaveTo(filename string) error
|
SaveTo(filename string) error
|
||||||
|
|
||||||
|
DisableSaving()
|
||||||
|
PrepareSaving() (ConfigProvider, error)
|
||||||
|
IsLoadedFromEmpty() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type iniConfigProvider struct {
|
type iniConfigProvider struct {
|
||||||
opts *Options
|
file string
|
||||||
ini *ini.File
|
ini *ini.File
|
||||||
newFile bool // whether the file has not existed previously
|
|
||||||
|
disableSaving bool // disable the "Save" method because the config options could be polluted
|
||||||
|
loadedFromEmpty bool // whether the file has not existed previously
|
||||||
}
|
}
|
||||||
|
|
||||||
type iniConfigSection struct {
|
type iniConfigSection struct {
|
||||||
@@ -95,6 +103,18 @@ func ConfigSectionKeyString(sec ConfigSection, key string, def ...string) string
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ConfigSectionKeyBool(sec ConfigSection, key string, def ...bool) bool {
|
||||||
|
k := ConfigSectionKey(sec, key)
|
||||||
|
if k != nil && k.String() != "" {
|
||||||
|
b, _ := strconv.ParseBool(k.String())
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
if len(def) > 0 {
|
||||||
|
return def[0]
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// ConfigInheritedKey works like ini.Section.Key(), but it always returns a new key instance, it is O(n) because NewKey is O(n)
|
// ConfigInheritedKey works like ini.Section.Key(), but it always returns a new key instance, it is O(n) because NewKey is O(n)
|
||||||
// and the returned key is safe to be used with "MustXxx", it doesn't change the parent's values.
|
// and the returned key is safe to be used with "MustXxx", it doesn't change the parent's values.
|
||||||
// Otherwise, ini.Section.Key().MustXxx would pollute the parent section's keys.
|
// Otherwise, ini.Section.Key().MustXxx would pollute the parent section's keys.
|
||||||
@@ -162,53 +182,43 @@ func NewConfigProviderFromData(configContent string) (ConfigProvider, error) {
|
|||||||
}
|
}
|
||||||
cfg.NameMapper = ini.SnackCase
|
cfg.NameMapper = ini.SnackCase
|
||||||
return &iniConfigProvider{
|
return &iniConfigProvider{
|
||||||
ini: cfg,
|
ini: cfg,
|
||||||
newFile: true,
|
loadedFromEmpty: true,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
CustomConf string // the ini file path
|
|
||||||
AllowEmpty bool // whether not finding configuration files is allowed
|
|
||||||
ExtraConfig string
|
|
||||||
|
|
||||||
DisableLoadCommonSettings bool // only used by "Init()", not used by "NewConfigProvider()"
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConfigProviderFromFile load configuration from file.
|
// NewConfigProviderFromFile load configuration from file.
|
||||||
// NOTE: do not print any log except error.
|
// NOTE: do not print any log except error.
|
||||||
func NewConfigProviderFromFile(opts *Options) (ConfigProvider, error) {
|
func NewConfigProviderFromFile(file string, extraConfigs ...string) (ConfigProvider, error) {
|
||||||
cfg := ini.Empty()
|
cfg := ini.Empty(ini.LoadOptions{KeyValueDelimiterOnWrite: " = "})
|
||||||
newFile := true
|
loadedFromEmpty := true
|
||||||
|
|
||||||
if opts.CustomConf != "" {
|
if file != "" {
|
||||||
isFile, err := util.IsFile(opts.CustomConf)
|
isFile, err := util.IsFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to check if %s is a file. Error: %v", opts.CustomConf, err)
|
return nil, fmt.Errorf("unable to check if %q is a file. Error: %v", file, err)
|
||||||
}
|
}
|
||||||
if isFile {
|
if isFile {
|
||||||
if err := cfg.Append(opts.CustomConf); err != nil {
|
if err = cfg.Append(file); err != nil {
|
||||||
return nil, fmt.Errorf("failed to load custom conf '%s': %v", opts.CustomConf, err)
|
return nil, fmt.Errorf("failed to load config file %q: %v", file, err)
|
||||||
}
|
}
|
||||||
newFile = false
|
loadedFromEmpty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if newFile && !opts.AllowEmpty {
|
if len(extraConfigs) > 0 {
|
||||||
return nil, fmt.Errorf("unable to find configuration file: %q, please ensure you are running in the correct environment or set the correct configuration file with -c", CustomConf)
|
for _, s := range extraConfigs {
|
||||||
}
|
if err := cfg.Append([]byte(s)); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to append more config: %v", err)
|
||||||
if opts.ExtraConfig != "" {
|
}
|
||||||
if err := cfg.Append([]byte(opts.ExtraConfig)); err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to append more config: %v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.NameMapper = ini.SnackCase
|
cfg.NameMapper = ini.SnackCase
|
||||||
return &iniConfigProvider{
|
return &iniConfigProvider{
|
||||||
opts: opts,
|
file: file,
|
||||||
ini: cfg,
|
ini: cfg,
|
||||||
newFile: newFile,
|
loadedFromEmpty: loadedFromEmpty,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,22 +249,24 @@ func (p *iniConfigProvider) GetSection(name string) (ConfigSection, error) {
|
|||||||
return &iniConfigSection{sec: sec}, nil
|
return &iniConfigSection{sec: sec}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errDisableSaving = errors.New("this config can't be saved, developers should prepare a new config to save")
|
||||||
|
|
||||||
// Save saves the content into file
|
// Save saves the content into file
|
||||||
func (p *iniConfigProvider) Save() error {
|
func (p *iniConfigProvider) Save() error {
|
||||||
filename := p.opts.CustomConf
|
if p.disableSaving {
|
||||||
if filename == "" {
|
return errDisableSaving
|
||||||
if !p.opts.AllowEmpty {
|
|
||||||
return fmt.Errorf("custom config path must not be empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
if p.newFile {
|
filename := p.file
|
||||||
|
if filename == "" {
|
||||||
|
return fmt.Errorf("config file path must not be empty")
|
||||||
|
}
|
||||||
|
if p.loadedFromEmpty {
|
||||||
if err := os.MkdirAll(filepath.Dir(filename), os.ModePerm); err != nil {
|
if err := os.MkdirAll(filepath.Dir(filename), os.ModePerm); err != nil {
|
||||||
return fmt.Errorf("failed to create '%s': %v", filename, err)
|
return fmt.Errorf("failed to create %q: %v", filename, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := p.ini.SaveTo(filename); err != nil {
|
if err := p.ini.SaveTo(filename); err != nil {
|
||||||
return fmt.Errorf("failed to save '%s': %v", filename, err)
|
return fmt.Errorf("failed to save %q: %v", filename, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change permissions to be more restrictive
|
// Change permissions to be more restrictive
|
||||||
@@ -272,9 +284,32 @@ func (p *iniConfigProvider) Save() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *iniConfigProvider) SaveTo(filename string) error {
|
func (p *iniConfigProvider) SaveTo(filename string) error {
|
||||||
|
if p.disableSaving {
|
||||||
|
return errDisableSaving
|
||||||
|
}
|
||||||
return p.ini.SaveTo(filename)
|
return p.ini.SaveTo(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisableSaving disables the saving function, use PrepareSaving to get clear config options.
|
||||||
|
func (p *iniConfigProvider) DisableSaving() {
|
||||||
|
p.disableSaving = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareSaving loads the ini from file again to get clear config options.
|
||||||
|
// Otherwise, the "MustXxx" calls would have polluted the current config provider,
|
||||||
|
// it makes the "Save" outputs a lot of garbage options
|
||||||
|
// After the INI package gets refactored, no "MustXxx" pollution, this workaround can be dropped.
|
||||||
|
func (p *iniConfigProvider) PrepareSaving() (ConfigProvider, error) {
|
||||||
|
if p.file == "" {
|
||||||
|
return nil, errors.New("no config file to save")
|
||||||
|
}
|
||||||
|
return NewConfigProviderFromFile(p.file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *iniConfigProvider) IsLoadedFromEmpty() bool {
|
||||||
|
return p.loadedFromEmpty
|
||||||
|
}
|
||||||
|
|
||||||
func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting any) {
|
func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting any) {
|
||||||
if err := rootCfg.Section(sectionName).MapTo(setting); err != nil {
|
if err := rootCfg.Section(sectionName).MapTo(setting); err != nil {
|
||||||
log.Fatal("Failed to map %s settings: %v", sectionName, err)
|
log.Fatal("Failed to map %s settings: %v", sectionName, err)
|
||||||
@@ -287,6 +322,12 @@ func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, n
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deprecatedSettingFatal(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) {
|
||||||
|
if rootCfg.Section(oldSection).HasKey(oldKey) {
|
||||||
|
log.Fatal("Deprecated fallback `[%s]` `%s` present. Use `[%s]` `%s` instead. This fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
|
// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
|
||||||
func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
|
func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
|
||||||
if rootCfg.Section(oldSection).HasKey(oldKey) {
|
if rootCfg.Section(oldSection).HasKey(oldKey) {
|
||||||
@@ -305,8 +346,8 @@ func NewConfigProviderForLocale(source any, others ...any) (ConfigProvider, erro
|
|||||||
}
|
}
|
||||||
iniFile.BlockMode = false
|
iniFile.BlockMode = false
|
||||||
return &iniConfigProvider{
|
return &iniConfigProvider{
|
||||||
ini: iniFile,
|
ini: iniFile,
|
||||||
newFile: true,
|
loadedFromEmpty: true,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,13 +67,14 @@ key = 123
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewConfigProviderFromFile(t *testing.T) {
|
func TestNewConfigProviderFromFile(t *testing.T) {
|
||||||
_, err := NewConfigProviderFromFile(&Options{CustomConf: "no-such.ini", AllowEmpty: false})
|
cfg, err := NewConfigProviderFromFile("no-such.ini")
|
||||||
assert.ErrorContains(t, err, "unable to find configuration file")
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, cfg.IsLoadedFromEmpty())
|
||||||
|
|
||||||
// load non-existing file and save
|
// load non-existing file and save
|
||||||
testFile := t.TempDir() + "/test.ini"
|
testFile := t.TempDir() + "/test.ini"
|
||||||
testFile1 := t.TempDir() + "/test1.ini"
|
testFile1 := t.TempDir() + "/test1.ini"
|
||||||
cfg, err := NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true})
|
cfg, err = NewConfigProviderFromFile(testFile)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
sec, _ := cfg.NewSection("foo")
|
sec, _ := cfg.NewSection("foo")
|
||||||
@@ -84,14 +85,14 @@ func TestNewConfigProviderFromFile(t *testing.T) {
|
|||||||
|
|
||||||
bs, err := os.ReadFile(testFile)
|
bs, err := os.ReadFile(testFile)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "[foo]\nk1=a\n", string(bs))
|
assert.Equal(t, "[foo]\nk1 = a\n", string(bs))
|
||||||
|
|
||||||
bs, err = os.ReadFile(testFile1)
|
bs, err = os.ReadFile(testFile1)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "[foo]\nk1=a\nk2=b\n", string(bs))
|
assert.Equal(t, "[foo]\nk1 = a\nk2 = b\n", string(bs))
|
||||||
|
|
||||||
// load existing file and save
|
// load existing file and save
|
||||||
cfg, err = NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true})
|
cfg, err = NewConfigProviderFromFile(testFile)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "a", cfg.Section("foo").Key("k1").String())
|
assert.Equal(t, "a", cfg.Section("foo").Key("k1").String())
|
||||||
sec, _ = cfg.NewSection("bar")
|
sec, _ = cfg.NewSection("bar")
|
||||||
@@ -99,7 +100,7 @@ func TestNewConfigProviderFromFile(t *testing.T) {
|
|||||||
assert.NoError(t, cfg.Save())
|
assert.NoError(t, cfg.Save())
|
||||||
bs, err = os.ReadFile(testFile)
|
bs, err = os.ReadFile(testFile)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "[foo]\nk1=a\n\n[bar]\nk1=b\n", string(bs))
|
assert.Equal(t, "[foo]\nk1 = a\n\n[bar]\nk1 = b\n", string(bs))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewConfigProviderForLocale(t *testing.T) {
|
func TestNewConfigProviderForLocale(t *testing.T) {
|
||||||
@@ -119,3 +120,27 @@ func TestNewConfigProviderForLocale(t *testing.T) {
|
|||||||
assert.Equal(t, "foo", cfg.Section("").Key("k1").String())
|
assert.Equal(t, "foo", cfg.Section("").Key("k1").String())
|
||||||
assert.Equal(t, "xxx", cfg.Section("").Key("k2").String())
|
assert.Equal(t, "xxx", cfg.Section("").Key("k2").String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDisableSaving(t *testing.T) {
|
||||||
|
testFile := t.TempDir() + "/test.ini"
|
||||||
|
_ = os.WriteFile(testFile, []byte("k1=a\nk2=b"), 0o644)
|
||||||
|
cfg, err := NewConfigProviderFromFile(testFile)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
cfg.DisableSaving()
|
||||||
|
err = cfg.Save()
|
||||||
|
assert.ErrorIs(t, err, errDisableSaving)
|
||||||
|
|
||||||
|
saveCfg, err := cfg.PrepareSaving()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
saveCfg.Section("").Key("k1").MustString("x")
|
||||||
|
saveCfg.Section("").Key("k2").SetValue("y")
|
||||||
|
saveCfg.Section("").Key("k3").SetValue("z")
|
||||||
|
err = saveCfg.Save()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
bs, err := os.ReadFile(testFile)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "k1 = a\nk2 = y\nk3 = z\n", string(bs))
|
||||||
|
}
|
||||||
|
|||||||
@@ -60,11 +60,6 @@ func LoadDBSetting() {
|
|||||||
func loadDBSetting(rootCfg ConfigProvider) {
|
func loadDBSetting(rootCfg ConfigProvider) {
|
||||||
sec := rootCfg.Section("database")
|
sec := rootCfg.Section("database")
|
||||||
Database.Type = DatabaseType(sec.Key("DB_TYPE").String())
|
Database.Type = DatabaseType(sec.Key("DB_TYPE").String())
|
||||||
defaultCharset := "utf8"
|
|
||||||
|
|
||||||
if Database.Type.IsMySQL() {
|
|
||||||
defaultCharset = "utf8mb4"
|
|
||||||
}
|
|
||||||
|
|
||||||
Database.Host = sec.Key("HOST").String()
|
Database.Host = sec.Key("HOST").String()
|
||||||
Database.Name = sec.Key("NAME").String()
|
Database.Name = sec.Key("NAME").String()
|
||||||
@@ -74,9 +69,10 @@ func loadDBSetting(rootCfg ConfigProvider) {
|
|||||||
}
|
}
|
||||||
Database.Schema = sec.Key("SCHEMA").String()
|
Database.Schema = sec.Key("SCHEMA").String()
|
||||||
Database.SSLMode = sec.Key("SSL_MODE").MustString("disable")
|
Database.SSLMode = sec.Key("SSL_MODE").MustString("disable")
|
||||||
Database.Charset = sec.Key("CHARSET").In(defaultCharset, []string{"utf8", "utf8mb4"})
|
|
||||||
if Database.Type.IsMySQL() && defaultCharset != "utf8mb4" {
|
Database.Charset = sec.Key("CHARSET").MustString("utf8mb4")
|
||||||
log.Error("Deprecated database mysql charset utf8 support, please use utf8mb4 or convert utf8 to utf8mb4.")
|
if Database.Type.IsMySQL() && Database.Charset != "utf8mb4" {
|
||||||
|
log.Error(`Deprecated database mysql charset utf8 support, please use utf8mb4 and convert utf8 database to utf8mb4 by "gitea convert".`)
|
||||||
}
|
}
|
||||||
|
|
||||||
Database.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "gitea.db"))
|
Database.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "gitea.db"))
|
||||||
@@ -101,9 +97,9 @@ func loadDBSetting(rootCfg ConfigProvider) {
|
|||||||
// DBConnStr returns database connection string
|
// DBConnStr returns database connection string
|
||||||
func DBConnStr() (string, error) {
|
func DBConnStr() (string, error) {
|
||||||
var connStr string
|
var connStr string
|
||||||
Param := "?"
|
paramSep := "?"
|
||||||
if strings.Contains(Database.Name, Param) {
|
if strings.Contains(Database.Name, paramSep) {
|
||||||
Param = "&"
|
paramSep = "&"
|
||||||
}
|
}
|
||||||
switch Database.Type {
|
switch Database.Type {
|
||||||
case "mysql":
|
case "mysql":
|
||||||
@@ -116,15 +112,15 @@ func DBConnStr() (string, error) {
|
|||||||
tls = "false"
|
tls = "false"
|
||||||
}
|
}
|
||||||
connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=%s&parseTime=true&tls=%s",
|
connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=%s&parseTime=true&tls=%s",
|
||||||
Database.User, Database.Passwd, connType, Database.Host, Database.Name, Param, Database.Charset, tls)
|
Database.User, Database.Passwd, connType, Database.Host, Database.Name, paramSep, Database.Charset, tls)
|
||||||
case "postgres":
|
case "postgres":
|
||||||
connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, Param, Database.SSLMode)
|
connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, paramSep, Database.SSLMode)
|
||||||
case "mssql":
|
case "mssql":
|
||||||
host, port := ParseMSSQLHostPort(Database.Host)
|
host, port := ParseMSSQLHostPort(Database.Host)
|
||||||
connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, Database.Name, Database.User, Database.Passwd)
|
connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, Database.Name, Database.User, Database.Passwd)
|
||||||
case "sqlite3":
|
case "sqlite3":
|
||||||
if !EnableSQLite3 {
|
if !EnableSQLite3 {
|
||||||
return "", errors.New("this binary version does not build support for SQLite3")
|
return "", errors.New("this Gitea binary was not built with SQLite3 support")
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll(path.Dir(Database.Path), os.ModePerm); err != nil {
|
if err := os.MkdirAll(path.Dir(Database.Path), os.ModePerm); err != nil {
|
||||||
return "", fmt.Errorf("Failed to create directories: %w", err)
|
return "", fmt.Errorf("Failed to create directories: %w", err)
|
||||||
@@ -136,7 +132,7 @@ func DBConnStr() (string, error) {
|
|||||||
connStr = fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d&_txlock=immediate%s",
|
connStr = fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d&_txlock=immediate%s",
|
||||||
Database.Path, Database.Timeout, journalMode)
|
Database.Path, Database.Timeout, journalMode)
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("Unknown database type: %s", Database.Type)
|
return "", fmt.Errorf("unknown database type: %s", Database.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
return connStr, nil
|
return connStr, nil
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ package setting
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/generate"
|
"code.gitea.io/gitea/modules/generate"
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LFS represents the configuration for Git LFS
|
// LFS represents the configuration for Git LFS
|
||||||
@@ -20,25 +20,27 @@ var LFS = struct {
|
|||||||
MaxFileSize int64 `ini:"LFS_MAX_FILE_SIZE"`
|
MaxFileSize int64 `ini:"LFS_MAX_FILE_SIZE"`
|
||||||
LocksPagingNum int `ini:"LFS_LOCKS_PAGING_NUM"`
|
LocksPagingNum int `ini:"LFS_LOCKS_PAGING_NUM"`
|
||||||
|
|
||||||
Storage
|
Storage *Storage
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
func loadLFSFrom(rootCfg ConfigProvider) {
|
func loadLFSFrom(rootCfg ConfigProvider) error {
|
||||||
sec := rootCfg.Section("server")
|
sec := rootCfg.Section("server")
|
||||||
if err := sec.MapTo(&LFS); err != nil {
|
if err := sec.MapTo(&LFS); err != nil {
|
||||||
log.Fatal("Failed to map LFS settings: %v", err)
|
return fmt.Errorf("failed to map LFS settings: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lfsSec := rootCfg.Section("lfs")
|
lfsSec, _ := rootCfg.GetSection("lfs")
|
||||||
storageType := lfsSec.Key("STORAGE_TYPE").MustString("")
|
|
||||||
|
|
||||||
// Specifically default PATH to LFS_CONTENT_PATH
|
// Specifically default PATH to LFS_CONTENT_PATH
|
||||||
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
|
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
|
||||||
// if these are removed, the warning will not be shown
|
// if these are removed, the warning will not be shown
|
||||||
deprecatedSetting(rootCfg, "server", "LFS_CONTENT_PATH", "lfs", "PATH", "v1.19.0")
|
deprecatedSettingFatal(rootCfg, "server", "LFS_CONTENT_PATH", "lfs", "PATH", "v1.19.0")
|
||||||
lfsSec.Key("PATH").MustString(sec.Key("LFS_CONTENT_PATH").String())
|
|
||||||
|
|
||||||
LFS.Storage = getStorage(rootCfg, "lfs", storageType, lfsSec)
|
var err error
|
||||||
|
LFS.Storage, err = getStorage(rootCfg, "lfs", "", lfsSec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Rest of LFS service settings
|
// Rest of LFS service settings
|
||||||
if LFS.LocksPagingNum == 0 {
|
if LFS.LocksPagingNum == 0 {
|
||||||
@@ -47,23 +49,30 @@ func loadLFSFrom(rootCfg ConfigProvider) {
|
|||||||
|
|
||||||
LFS.HTTPAuthExpiry = sec.Key("LFS_HTTP_AUTH_EXPIRY").MustDuration(24 * time.Hour)
|
LFS.HTTPAuthExpiry = sec.Key("LFS_HTTP_AUTH_EXPIRY").MustDuration(24 * time.Hour)
|
||||||
|
|
||||||
if LFS.StartServer {
|
if !LFS.StartServer {
|
||||||
LFS.JWTSecretBytes = make([]byte, 32)
|
return nil
|
||||||
n, err := base64.RawURLEncoding.Decode(LFS.JWTSecretBytes, []byte(LFS.JWTSecretBase64))
|
}
|
||||||
|
|
||||||
if err != nil || n != 32 {
|
LFS.JWTSecretBytes = make([]byte, 32)
|
||||||
LFS.JWTSecretBase64, err = generate.NewJwtSecretBase64()
|
n, err := base64.RawURLEncoding.Decode(LFS.JWTSecretBytes, []byte(LFS.JWTSecretBase64))
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error generating JWT Secret for custom config: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save secret
|
if err != nil || n != 32 {
|
||||||
sec.Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
|
LFS.JWTSecretBase64, err = generate.NewJwtSecretBase64()
|
||||||
if err := rootCfg.Save(); err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error saving JWT Secret for custom config: %v", err)
|
return fmt.Errorf("error generating JWT Secret for custom config: %v", err)
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
// Save secret
|
||||||
|
saveCfg, err := rootCfg.PrepareSaving()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error saving JWT Secret for custom config: %v", err)
|
||||||
|
}
|
||||||
|
rootCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
|
||||||
|
saveCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
|
||||||
|
if err := saveCfg.Save(); err != nil {
|
||||||
|
return fmt.Errorf("error saving JWT Secret for custom config: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
77
modules/setting/lfs_test.go
Normal file
77
modules/setting/lfs_test.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_getStorageInheritNameSectionTypeForLFS(t *testing.T) {
|
||||||
|
iniStr := `
|
||||||
|
[storage]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, loadLFSFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", LFS.Storage.Type)
|
||||||
|
assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath)
|
||||||
|
|
||||||
|
iniStr = `
|
||||||
|
[storage.lfs]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err = NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, loadLFSFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", LFS.Storage.Type)
|
||||||
|
assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath)
|
||||||
|
|
||||||
|
iniStr = `
|
||||||
|
[lfs]
|
||||||
|
STORAGE_TYPE = my_minio
|
||||||
|
|
||||||
|
[storage.my_minio]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err = NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, loadLFSFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", LFS.Storage.Type)
|
||||||
|
assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath)
|
||||||
|
|
||||||
|
iniStr = `
|
||||||
|
[lfs]
|
||||||
|
STORAGE_TYPE = my_minio
|
||||||
|
MINIO_BASE_PATH = my_lfs/
|
||||||
|
|
||||||
|
[storage.my_minio]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err = NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, loadLFSFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", LFS.Storage.Type)
|
||||||
|
assert.EqualValues(t, "my_lfs/", LFS.Storage.MinioConfig.BasePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_LFSStorage1(t *testing.T) {
|
||||||
|
iniStr := `
|
||||||
|
[storage]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, loadLFSFrom(cfg))
|
||||||
|
assert.EqualValues(t, "minio", LFS.Storage.Type)
|
||||||
|
assert.EqualValues(t, "gitea", LFS.Storage.MinioConfig.Bucket)
|
||||||
|
assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath)
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ func loadMirrorFrom(rootCfg ConfigProvider) {
|
|||||||
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
|
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
|
||||||
// if these are removed, the warning will not be shown
|
// if these are removed, the warning will not be shown
|
||||||
deprecatedSetting(rootCfg, "repository", "DISABLE_MIRRORS", "mirror", "ENABLED", "v1.19.0")
|
deprecatedSetting(rootCfg, "repository", "DISABLE_MIRRORS", "mirror", "ENABLED", "v1.19.0")
|
||||||
if rootCfg.Section("repository").Key("DISABLE_MIRRORS").MustBool(false) {
|
if ConfigSectionKeyBool(rootCfg.Section("repository"), "DISABLE_MIRRORS") {
|
||||||
Mirror.DisableNewPull = true
|
Mirror.DisableNewPull = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,18 +120,25 @@ func loadOAuth2From(rootCfg ConfigProvider) {
|
|||||||
OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile)
|
OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
key := make([]byte, 32)
|
if InstallLock {
|
||||||
n, err := base64.RawURLEncoding.Decode(key, []byte(OAuth2.JWTSecretBase64))
|
key := make([]byte, 32)
|
||||||
if err != nil || n != 32 {
|
n, err := base64.RawURLEncoding.Decode(key, []byte(OAuth2.JWTSecretBase64))
|
||||||
key, err = generate.NewJwtSecret()
|
if err != nil || n != 32 {
|
||||||
if err != nil {
|
key, err = generate.NewJwtSecret()
|
||||||
log.Fatal("error generating JWT secret: %v", err)
|
if err != nil {
|
||||||
}
|
log.Fatal("error generating JWT secret: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
secretBase64 := base64.RawURLEncoding.EncodeToString(key)
|
secretBase64 := base64.RawURLEncoding.EncodeToString(key)
|
||||||
rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64)
|
saveCfg, err := rootCfg.PrepareSaving()
|
||||||
if err := rootCfg.Save(); err != nil {
|
if err != nil {
|
||||||
log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
|
log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
|
||||||
|
}
|
||||||
|
rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64)
|
||||||
|
saveCfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64)
|
||||||
|
if err := saveCfg.Save(); err != nil {
|
||||||
|
log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,19 @@
|
|||||||
package setting
|
package setting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Package registry settings
|
// Package registry settings
|
||||||
var (
|
var (
|
||||||
Packages = struct {
|
Packages = struct {
|
||||||
Storage
|
Storage *Storage
|
||||||
Enabled bool
|
Enabled bool
|
||||||
ChunkedUploadPath string
|
ChunkedUploadPath string
|
||||||
RegistryHost string
|
RegistryHost string
|
||||||
@@ -51,13 +50,21 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadPackagesFrom(rootCfg ConfigProvider) {
|
func loadPackagesFrom(rootCfg ConfigProvider) (err error) {
|
||||||
sec := rootCfg.Section("packages")
|
sec, _ := rootCfg.GetSection("packages")
|
||||||
if err := sec.MapTo(&Packages); err != nil {
|
if sec == nil {
|
||||||
log.Fatal("Failed to map Packages settings: %v", err)
|
Packages.Storage, err = getStorage(rootCfg, "packages", "", nil)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
Packages.Storage = getStorage(rootCfg, "packages", "", nil)
|
if err = sec.MapTo(&Packages); err != nil {
|
||||||
|
return fmt.Errorf("failed to map Packages settings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Packages.Storage, err = getStorage(rootCfg, "packages", "", sec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
appURL, _ := url.Parse(AppURL)
|
appURL, _ := url.Parse(AppURL)
|
||||||
Packages.RegistryHost = appURL.Host
|
Packages.RegistryHost = appURL.Host
|
||||||
@@ -68,7 +75,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(Packages.ChunkedUploadPath, os.ModePerm); err != nil {
|
if err := os.MkdirAll(Packages.ChunkedUploadPath, os.ModePerm); err != nil {
|
||||||
log.Error("Unable to create chunked upload directory: %s (%v)", Packages.ChunkedUploadPath, err)
|
return fmt.Errorf("unable to create chunked upload directory: %s (%v)", Packages.ChunkedUploadPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
Packages.LimitTotalOwnerSize = mustBytes(sec, "LIMIT_TOTAL_OWNER_SIZE")
|
Packages.LimitTotalOwnerSize = mustBytes(sec, "LIMIT_TOTAL_OWNER_SIZE")
|
||||||
@@ -93,6 +100,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) {
|
|||||||
Packages.LimitSizeRubyGems = mustBytes(sec, "LIMIT_SIZE_RUBYGEMS")
|
Packages.LimitSizeRubyGems = mustBytes(sec, "LIMIT_SIZE_RUBYGEMS")
|
||||||
Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT")
|
Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT")
|
||||||
Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT")
|
Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustBytes(section ConfigSection, key string) int64 {
|
func mustBytes(section ConfigSection, key string) int64 {
|
||||||
|
|||||||
@@ -29,3 +29,170 @@ func TestMustBytes(t *testing.T) {
|
|||||||
assert.EqualValues(t, 1782579, test("1.7mib"))
|
assert.EqualValues(t, 1782579, test("1.7mib"))
|
||||||
assert.EqualValues(t, -1, test("1 yib")) // too large
|
assert.EqualValues(t, -1, test("1 yib")) // too large
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_getStorageInheritNameSectionTypeForPackages(t *testing.T) {
|
||||||
|
// packages storage inherits from storage if nothing configured
|
||||||
|
iniStr := `
|
||||||
|
[storage]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, loadPackagesFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", Packages.Storage.Type)
|
||||||
|
assert.EqualValues(t, "packages/", Packages.Storage.MinioConfig.BasePath)
|
||||||
|
|
||||||
|
// we can also configure packages storage directly
|
||||||
|
iniStr = `
|
||||||
|
[storage.packages]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err = NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, loadPackagesFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", Packages.Storage.Type)
|
||||||
|
assert.EqualValues(t, "packages/", Packages.Storage.MinioConfig.BasePath)
|
||||||
|
|
||||||
|
// or we can indicate the storage type in the packages section
|
||||||
|
iniStr = `
|
||||||
|
[packages]
|
||||||
|
STORAGE_TYPE = my_minio
|
||||||
|
|
||||||
|
[storage.my_minio]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err = NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, loadPackagesFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", Packages.Storage.Type)
|
||||||
|
assert.EqualValues(t, "packages/", Packages.Storage.MinioConfig.BasePath)
|
||||||
|
|
||||||
|
// or we can indicate the storage type and minio base path in the packages section
|
||||||
|
iniStr = `
|
||||||
|
[packages]
|
||||||
|
STORAGE_TYPE = my_minio
|
||||||
|
MINIO_BASE_PATH = my_packages/
|
||||||
|
|
||||||
|
[storage.my_minio]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
`
|
||||||
|
cfg, err = NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, loadPackagesFrom(cfg))
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", Packages.Storage.Type)
|
||||||
|
assert.EqualValues(t, "my_packages/", Packages.Storage.MinioConfig.BasePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_PackageStorage1(t *testing.T) {
|
||||||
|
iniStr := `
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
[packages]
|
||||||
|
MINIO_BASE_PATH = packages/
|
||||||
|
SERVE_DIRECT = true
|
||||||
|
[storage]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
MINIO_ENDPOINT = s3.my-domain.net
|
||||||
|
MINIO_BUCKET = gitea
|
||||||
|
MINIO_LOCATION = homenet
|
||||||
|
MINIO_USE_SSL = true
|
||||||
|
MINIO_ACCESS_KEY_ID = correct_key
|
||||||
|
MINIO_SECRET_ACCESS_KEY = correct_key
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, loadPackagesFrom(cfg))
|
||||||
|
storage := Packages.Storage
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", storage.Type)
|
||||||
|
assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket)
|
||||||
|
assert.EqualValues(t, "packages/", storage.MinioConfig.BasePath)
|
||||||
|
assert.True(t, storage.MinioConfig.ServeDirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_PackageStorage2(t *testing.T) {
|
||||||
|
iniStr := `
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
[storage.packages]
|
||||||
|
MINIO_BASE_PATH = packages/
|
||||||
|
SERVE_DIRECT = true
|
||||||
|
[storage]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
MINIO_ENDPOINT = s3.my-domain.net
|
||||||
|
MINIO_BUCKET = gitea
|
||||||
|
MINIO_LOCATION = homenet
|
||||||
|
MINIO_USE_SSL = true
|
||||||
|
MINIO_ACCESS_KEY_ID = correct_key
|
||||||
|
MINIO_SECRET_ACCESS_KEY = correct_key
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, loadPackagesFrom(cfg))
|
||||||
|
storage := Packages.Storage
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", storage.Type)
|
||||||
|
assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket)
|
||||||
|
assert.EqualValues(t, "packages/", storage.MinioConfig.BasePath)
|
||||||
|
assert.True(t, storage.MinioConfig.ServeDirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_PackageStorage3(t *testing.T) {
|
||||||
|
iniStr := `
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
[packages]
|
||||||
|
STORAGE_TYPE = my_cfg
|
||||||
|
MINIO_BASE_PATH = my_packages/
|
||||||
|
SERVE_DIRECT = true
|
||||||
|
[storage.my_cfg]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
MINIO_ENDPOINT = s3.my-domain.net
|
||||||
|
MINIO_BUCKET = gitea
|
||||||
|
MINIO_LOCATION = homenet
|
||||||
|
MINIO_USE_SSL = true
|
||||||
|
MINIO_ACCESS_KEY_ID = correct_key
|
||||||
|
MINIO_SECRET_ACCESS_KEY = correct_key
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, loadPackagesFrom(cfg))
|
||||||
|
storage := Packages.Storage
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", storage.Type)
|
||||||
|
assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket)
|
||||||
|
assert.EqualValues(t, "my_packages/", storage.MinioConfig.BasePath)
|
||||||
|
assert.True(t, storage.MinioConfig.ServeDirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_PackageStorage4(t *testing.T) {
|
||||||
|
iniStr := `
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
[storage.packages]
|
||||||
|
STORAGE_TYPE = my_cfg
|
||||||
|
MINIO_BASE_PATH = my_packages/
|
||||||
|
SERVE_DIRECT = true
|
||||||
|
[storage.my_cfg]
|
||||||
|
STORAGE_TYPE = minio
|
||||||
|
MINIO_ENDPOINT = s3.my-domain.net
|
||||||
|
MINIO_BUCKET = gitea
|
||||||
|
MINIO_LOCATION = homenet
|
||||||
|
MINIO_USE_SSL = true
|
||||||
|
MINIO_ACCESS_KEY_ID = correct_key
|
||||||
|
MINIO_SECRET_ACCESS_KEY = correct_key
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, loadPackagesFrom(cfg))
|
||||||
|
storage := Packages.Storage
|
||||||
|
|
||||||
|
assert.EqualValues(t, "minio", storage.Type)
|
||||||
|
assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket)
|
||||||
|
assert.EqualValues(t, "my_packages/", storage.MinioConfig.BasePath)
|
||||||
|
assert.True(t, storage.MinioConfig.ServeDirect)
|
||||||
|
}
|
||||||
|
|||||||
195
modules/setting/path.go
Normal file
195
modules/setting/path.go
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// AppPath represents the path to the gitea binary
|
||||||
|
AppPath string
|
||||||
|
|
||||||
|
// AppWorkPath is the "working directory" of Gitea. It maps to the environment variable GITEA_WORK_DIR.
|
||||||
|
// If that is not set it is the default set here by the linker or failing that the directory of AppPath.
|
||||||
|
// It is used as the base path for several other paths.
|
||||||
|
AppWorkPath string
|
||||||
|
CustomPath string // Custom directory path. Env: GITEA_CUSTOM
|
||||||
|
CustomConf string
|
||||||
|
|
||||||
|
appWorkPathBuiltin string
|
||||||
|
customPathBuiltin string
|
||||||
|
customConfBuiltin string
|
||||||
|
|
||||||
|
AppWorkPathMismatch bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func getAppPath() (string, error) {
|
||||||
|
var appPath string
|
||||||
|
var err error
|
||||||
|
if IsWindows && filepath.IsAbs(os.Args[0]) {
|
||||||
|
appPath = filepath.Clean(os.Args[0])
|
||||||
|
} else {
|
||||||
|
appPath, err = exec.LookPath(os.Args[0])
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, exec.ErrDot) {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
appPath, err = filepath.Abs(os.Args[0])
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
appPath, err = filepath.Abs(appPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Note: (legacy code) we don't use path.Dir here because it does not handle case which path starts with two "/" in Windows: "//psf/Home/..."
|
||||||
|
return strings.ReplaceAll(appPath, "\\", "/"), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
if AppPath, err = getAppPath(); err != nil {
|
||||||
|
log.Fatal("Failed to get app path: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if AppWorkPath == "" {
|
||||||
|
AppWorkPath = filepath.Dir(AppPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
appWorkPathBuiltin = AppWorkPath
|
||||||
|
customPathBuiltin = CustomPath
|
||||||
|
customConfBuiltin = CustomConf
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArgWorkPathAndCustomConf struct {
|
||||||
|
WorkPath string
|
||||||
|
CustomPath string
|
||||||
|
CustomConf string
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringWithDefault struct {
|
||||||
|
Value string
|
||||||
|
IsSet bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringWithDefault) Set(v string) {
|
||||||
|
s.Value = v
|
||||||
|
s.IsSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitWorkPathAndCommonConfig will set AppWorkPath, CustomPath and CustomConf, init default config provider by CustomConf and load common settings,
|
||||||
|
func InitWorkPathAndCommonConfig(getEnvFn func(name string) string, args ArgWorkPathAndCustomConf) {
|
||||||
|
InitWorkPathAndCfgProvider(getEnvFn, args)
|
||||||
|
LoadCommonSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitWorkPathAndCfgProvider will set AppWorkPath, CustomPath and CustomConf, init default config provider by CustomConf
|
||||||
|
func InitWorkPathAndCfgProvider(getEnvFn func(name string) string, args ArgWorkPathAndCustomConf) {
|
||||||
|
tryAbsPath := func(paths ...string) string {
|
||||||
|
s := paths[len(paths)-1]
|
||||||
|
for i := len(paths) - 2; i >= 0; i-- {
|
||||||
|
if filepath.IsAbs(s) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s = filepath.Join(paths[i], s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
tmpWorkPath := stringWithDefault{Value: appWorkPathBuiltin}
|
||||||
|
if tmpWorkPath.Value == "" {
|
||||||
|
tmpWorkPath.Value = filepath.Dir(AppPath)
|
||||||
|
}
|
||||||
|
tmpCustomPath := stringWithDefault{Value: customPathBuiltin}
|
||||||
|
if tmpCustomPath.Value == "" {
|
||||||
|
tmpCustomPath.Value = "custom"
|
||||||
|
}
|
||||||
|
tmpCustomConf := stringWithDefault{Value: customConfBuiltin}
|
||||||
|
if tmpCustomConf.Value == "" {
|
||||||
|
tmpCustomConf.Value = "conf/app.ini"
|
||||||
|
}
|
||||||
|
|
||||||
|
readFromEnv := func() {
|
||||||
|
envWorkPath := getEnvFn("GITEA_WORK_DIR")
|
||||||
|
if envWorkPath != "" {
|
||||||
|
tmpWorkPath.Set(envWorkPath)
|
||||||
|
if !filepath.IsAbs(tmpWorkPath.Value) {
|
||||||
|
log.Fatal("GITEA_WORK_DIR (work path) must be absolute path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
envCustomPath := getEnvFn("GITEA_CUSTOM")
|
||||||
|
if envCustomPath != "" {
|
||||||
|
tmpCustomPath.Set(envCustomPath)
|
||||||
|
if !filepath.IsAbs(tmpCustomPath.Value) {
|
||||||
|
log.Fatal("GITEA_CUSTOM (custom path) must be absolute path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readFromArgs := func() {
|
||||||
|
if args.WorkPath != "" {
|
||||||
|
tmpWorkPath.Set(args.WorkPath)
|
||||||
|
if !filepath.IsAbs(tmpWorkPath.Value) {
|
||||||
|
log.Fatal("--work-path must be absolute path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if args.CustomPath != "" {
|
||||||
|
tmpCustomPath.Set(args.CustomPath) // if it is not abs, it will be based on work-path, it shouldn't happen
|
||||||
|
if !filepath.IsAbs(tmpCustomPath.Value) {
|
||||||
|
log.Error("--custom-path must be absolute path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if args.CustomConf != "" {
|
||||||
|
tmpCustomConf.Set(args.CustomConf)
|
||||||
|
if !filepath.IsAbs(tmpCustomConf.Value) {
|
||||||
|
// the config path can be relative to the real current working path
|
||||||
|
if tmpCustomConf.Value, err = filepath.Abs(tmpCustomConf.Value); err != nil {
|
||||||
|
log.Fatal("Failed to get absolute path of config %q: %v", tmpCustomConf.Value, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readFromEnv()
|
||||||
|
readFromArgs()
|
||||||
|
|
||||||
|
if !tmpCustomConf.IsSet {
|
||||||
|
tmpCustomConf.Set(tryAbsPath(tmpWorkPath.Value, tmpCustomPath.Value, tmpCustomConf.Value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// only read the config but do not load/init anything more, because the AppWorkPath and CustomPath are not ready
|
||||||
|
InitCfgProvider(tmpCustomConf.Value)
|
||||||
|
configWorkPath := ConfigSectionKeyString(CfgProvider.Section(""), "WORK_PATH")
|
||||||
|
if configWorkPath != "" {
|
||||||
|
if !filepath.IsAbs(configWorkPath) {
|
||||||
|
log.Fatal("WORK_PATH in %q must be absolute path", configWorkPath)
|
||||||
|
}
|
||||||
|
configWorkPath = filepath.Clean(configWorkPath)
|
||||||
|
if tmpWorkPath.Value != "" && (getEnvFn("GITEA_WORK_DIR") != "" || args.WorkPath != "") {
|
||||||
|
fi1, err1 := os.Stat(tmpWorkPath.Value)
|
||||||
|
fi2, err2 := os.Stat(configWorkPath)
|
||||||
|
if err1 != nil || err2 != nil || !os.SameFile(fi1, fi2) {
|
||||||
|
AppWorkPathMismatch = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmpWorkPath.Set(configWorkPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpCustomPath.Set(tryAbsPath(tmpWorkPath.Value, tmpCustomPath.Value))
|
||||||
|
|
||||||
|
AppWorkPath = tmpWorkPath.Value
|
||||||
|
CustomPath = tmpCustomPath.Value
|
||||||
|
CustomConf = tmpCustomConf.Value
|
||||||
|
}
|
||||||
151
modules/setting/path_test.go
Normal file
151
modules/setting/path_test.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type envVars map[string]string
|
||||||
|
|
||||||
|
func (e envVars) Getenv(key string) string {
|
||||||
|
return e[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitWorkPathAndCommonConfig(t *testing.T) {
|
||||||
|
testInit := func(defaultWorkPath, defaultCustomPath, defaultCustomConf string) {
|
||||||
|
AppWorkPathMismatch = false
|
||||||
|
AppWorkPath = defaultWorkPath
|
||||||
|
appWorkPathBuiltin = defaultWorkPath
|
||||||
|
CustomPath = defaultCustomPath
|
||||||
|
customPathBuiltin = defaultCustomPath
|
||||||
|
CustomConf = defaultCustomConf
|
||||||
|
customConfBuiltin = defaultCustomConf
|
||||||
|
}
|
||||||
|
|
||||||
|
fp := filepath.Join
|
||||||
|
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
dirFoo := fp(tmpDir, "foo")
|
||||||
|
dirBar := fp(tmpDir, "bar")
|
||||||
|
dirXxx := fp(tmpDir, "xxx")
|
||||||
|
dirYyy := fp(tmpDir, "yyy")
|
||||||
|
|
||||||
|
t.Run("Default", func(t *testing.T) {
|
||||||
|
testInit(dirFoo, "", "")
|
||||||
|
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{})
|
||||||
|
assert.Equal(t, dirFoo, AppWorkPath)
|
||||||
|
assert.Equal(t, fp(dirFoo, "custom"), CustomPath)
|
||||||
|
assert.Equal(t, fp(dirFoo, "custom/conf/app.ini"), CustomConf)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WorkDir(env)", func(t *testing.T) {
|
||||||
|
testInit(dirFoo, "", "")
|
||||||
|
InitWorkPathAndCommonConfig(envVars{"GITEA_WORK_DIR": dirBar}.Getenv, ArgWorkPathAndCustomConf{})
|
||||||
|
assert.Equal(t, dirBar, AppWorkPath)
|
||||||
|
assert.Equal(t, fp(dirBar, "custom"), CustomPath)
|
||||||
|
assert.Equal(t, fp(dirBar, "custom/conf/app.ini"), CustomConf)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WorkDir(env,arg)", func(t *testing.T) {
|
||||||
|
testInit(dirFoo, "", "")
|
||||||
|
InitWorkPathAndCommonConfig(envVars{"GITEA_WORK_DIR": dirBar}.Getenv, ArgWorkPathAndCustomConf{WorkPath: dirXxx})
|
||||||
|
assert.Equal(t, dirXxx, AppWorkPath)
|
||||||
|
assert.Equal(t, fp(dirXxx, "custom"), CustomPath)
|
||||||
|
assert.Equal(t, fp(dirXxx, "custom/conf/app.ini"), CustomConf)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CustomPath(env)", func(t *testing.T) {
|
||||||
|
testInit(dirFoo, "", "")
|
||||||
|
InitWorkPathAndCommonConfig(envVars{"GITEA_CUSTOM": fp(dirBar, "custom1")}.Getenv, ArgWorkPathAndCustomConf{})
|
||||||
|
assert.Equal(t, dirFoo, AppWorkPath)
|
||||||
|
assert.Equal(t, fp(dirBar, "custom1"), CustomPath)
|
||||||
|
assert.Equal(t, fp(dirBar, "custom1/conf/app.ini"), CustomConf)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CustomPath(env,arg)", func(t *testing.T) {
|
||||||
|
testInit(dirFoo, "", "")
|
||||||
|
InitWorkPathAndCommonConfig(envVars{"GITEA_CUSTOM": fp(dirBar, "custom1")}.Getenv, ArgWorkPathAndCustomConf{CustomPath: "custom2"})
|
||||||
|
assert.Equal(t, dirFoo, AppWorkPath)
|
||||||
|
assert.Equal(t, fp(dirFoo, "custom2"), CustomPath)
|
||||||
|
assert.Equal(t, fp(dirFoo, "custom2/conf/app.ini"), CustomConf)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CustomConf", func(t *testing.T) {
|
||||||
|
testInit(dirFoo, "", "")
|
||||||
|
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{CustomConf: "app1.ini"})
|
||||||
|
assert.Equal(t, dirFoo, AppWorkPath)
|
||||||
|
cwd, _ := os.Getwd()
|
||||||
|
assert.Equal(t, fp(cwd, "app1.ini"), CustomConf)
|
||||||
|
|
||||||
|
testInit(dirFoo, "", "")
|
||||||
|
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{CustomConf: fp(dirBar, "app1.ini")})
|
||||||
|
assert.Equal(t, dirFoo, AppWorkPath)
|
||||||
|
assert.Equal(t, fp(dirBar, "app1.ini"), CustomConf)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CustomConfOverrideWorkPath", func(t *testing.T) {
|
||||||
|
iniWorkPath := fp(tmpDir, "app-workpath.ini")
|
||||||
|
_ = os.WriteFile(iniWorkPath, []byte("WORK_PATH="+dirXxx), 0o644)
|
||||||
|
|
||||||
|
testInit(dirFoo, "", "")
|
||||||
|
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{CustomConf: iniWorkPath})
|
||||||
|
assert.Equal(t, dirXxx, AppWorkPath)
|
||||||
|
assert.Equal(t, fp(dirXxx, "custom"), CustomPath)
|
||||||
|
assert.Equal(t, iniWorkPath, CustomConf)
|
||||||
|
assert.False(t, AppWorkPathMismatch)
|
||||||
|
|
||||||
|
testInit(dirFoo, "", "")
|
||||||
|
InitWorkPathAndCommonConfig(envVars{"GITEA_WORK_DIR": dirBar}.Getenv, ArgWorkPathAndCustomConf{CustomConf: iniWorkPath})
|
||||||
|
assert.Equal(t, dirXxx, AppWorkPath)
|
||||||
|
assert.Equal(t, fp(dirXxx, "custom"), CustomPath)
|
||||||
|
assert.Equal(t, iniWorkPath, CustomConf)
|
||||||
|
assert.True(t, AppWorkPathMismatch)
|
||||||
|
|
||||||
|
testInit(dirFoo, "", "")
|
||||||
|
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{WorkPath: dirBar, CustomConf: iniWorkPath})
|
||||||
|
assert.Equal(t, dirXxx, AppWorkPath)
|
||||||
|
assert.Equal(t, fp(dirXxx, "custom"), CustomPath)
|
||||||
|
assert.Equal(t, iniWorkPath, CustomConf)
|
||||||
|
assert.True(t, AppWorkPathMismatch)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Builtin", func(t *testing.T) {
|
||||||
|
testInit(dirFoo, dirBar, dirXxx)
|
||||||
|
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{})
|
||||||
|
assert.Equal(t, dirFoo, AppWorkPath)
|
||||||
|
assert.Equal(t, dirBar, CustomPath)
|
||||||
|
assert.Equal(t, dirXxx, CustomConf)
|
||||||
|
|
||||||
|
testInit(dirFoo, "custom1", "cfg.ini")
|
||||||
|
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{})
|
||||||
|
assert.Equal(t, dirFoo, AppWorkPath)
|
||||||
|
assert.Equal(t, fp(dirFoo, "custom1"), CustomPath)
|
||||||
|
assert.Equal(t, fp(dirFoo, "custom1/cfg.ini"), CustomConf)
|
||||||
|
|
||||||
|
testInit(dirFoo, "custom1", "cfg.ini")
|
||||||
|
InitWorkPathAndCommonConfig(envVars{"GITEA_WORK_DIR": dirYyy}.Getenv, ArgWorkPathAndCustomConf{})
|
||||||
|
assert.Equal(t, dirYyy, AppWorkPath)
|
||||||
|
assert.Equal(t, fp(dirYyy, "custom1"), CustomPath)
|
||||||
|
assert.Equal(t, fp(dirYyy, "custom1/cfg.ini"), CustomConf)
|
||||||
|
|
||||||
|
testInit(dirFoo, "custom1", "cfg.ini")
|
||||||
|
InitWorkPathAndCommonConfig(envVars{"GITEA_CUSTOM": dirYyy}.Getenv, ArgWorkPathAndCustomConf{})
|
||||||
|
assert.Equal(t, dirFoo, AppWorkPath)
|
||||||
|
assert.Equal(t, dirYyy, CustomPath)
|
||||||
|
assert.Equal(t, fp(dirYyy, "cfg.ini"), CustomConf)
|
||||||
|
|
||||||
|
iniWorkPath := fp(tmpDir, "app-workpath.ini")
|
||||||
|
_ = os.WriteFile(iniWorkPath, []byte("WORK_PATH="+dirXxx), 0o644)
|
||||||
|
testInit(dirFoo, "custom1", "cfg.ini")
|
||||||
|
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{CustomConf: iniWorkPath})
|
||||||
|
assert.Equal(t, dirXxx, AppWorkPath)
|
||||||
|
assert.Equal(t, fp(dirXxx, "custom1"), CustomPath)
|
||||||
|
assert.Equal(t, iniWorkPath, CustomConf)
|
||||||
|
})
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user