mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-21 09:31:19 +02:00
Compare commits
81 Commits
bc78a9a38a
...
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
|
||||
name: release-version
|
||||
@@ -379,133 +244,6 @@ steps:
|
||||
exclude:
|
||||
- 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
|
||||
type: docker
|
||||
@@ -641,136 +379,6 @@ steps:
|
||||
exclude:
|
||||
- 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
|
||||
type: docker
|
||||
@@ -816,50 +424,3 @@ depends_on:
|
||||
- docker-linux-amd64-release-candidate-version
|
||||
- docker-linux-arm64-release-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
|
||||
|
||||
overrides:
|
||||
- files: ["web_src/**/*.js", "docs/**/*.js"]
|
||||
- files: ["web_src/**/*", "docs/**/*"]
|
||||
env:
|
||||
browser: true
|
||||
node: false
|
||||
- files: ["web_src/**/*worker.js"]
|
||||
- files: ["web_src/**/*worker.*"]
|
||||
env:
|
||||
worker: true
|
||||
rules:
|
||||
@@ -42,7 +42,7 @@ overrides:
|
||||
rules:
|
||||
import/no-unresolved: [0]
|
||||
import/no-extraneous-dependencies: [0]
|
||||
- files: ["*.config.js"]
|
||||
- files: ["*.config.*"]
|
||||
rules:
|
||||
import/no-unused-modules: [0]
|
||||
|
||||
|
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -5,5 +5,6 @@
|
||||
/templates/swagger/v1_json.tmpl linguist-generated
|
||||
/vendor/** -text -eol linguist-vendored
|
||||
/web_src/fomantic/build/** linguist-generated
|
||||
/web_src/fomantic/_site/globals/site.variables linguist-language=Less
|
||||
/web_src/js/vendor/** -text -eol linguist-vendored
|
||||
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:
|
||||
schedule:
|
||||
- cron: "7 0 * * 1" # every Monday at 00:07 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
cron-licenses:
|
||||
|
1
.github/workflows/cron-translations.yml
vendored
1
.github/workflows/cron-translations.yml
vendored
@@ -3,6 +3,7 @@ name: cron-translations
|
||||
on:
|
||||
schedule:
|
||||
- cron: "7 0 * * *" # every day at 00:07 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
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
|
||||
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
TAGS: bindata sqlite sqlite_unlock_notify
|
||||
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
GOARCH: amd64
|
||||
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
- run: make --always-make checks-backend # ensure the "go-licenses" make target runs
|
||||
|
||||
frontend:
|
||||
if: needs.files-changed.outputs.frontend == 'true'
|
||||
if: needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -83,9 +83,10 @@ jobs:
|
||||
- run: make deps-frontend
|
||||
- run: make lint-frontend
|
||||
- run: make checks-frontend
|
||||
- run: make frontend
|
||||
|
||||
backend:
|
||||
if: needs.files-changed.outputs.backend == 'true'
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -94,12 +95,9 @@ jobs:
|
||||
with:
|
||||
go-version: ">=1.20"
|
||||
check-latest: true
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
# no frontend build here as backend should be able to build
|
||||
# even without any frontend files
|
||||
- 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
|
||||
- name: build-backend-arm64
|
||||
run: make backend # test cross compile
|
||||
@@ -120,7 +118,7 @@ jobs:
|
||||
GOARCH: 386
|
||||
|
||||
docs:
|
||||
if: needs.files-changed.outputs.docs == 'true'
|
||||
if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -133,7 +131,7 @@ jobs:
|
||||
- run: make docs # test if build could succeed
|
||||
|
||||
actions:
|
||||
if: needs.files-changed.outputs.actions == 'true'
|
||||
if: needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
USE_REPO_TEST_DIR: 1
|
||||
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
USE_REPO_TEST_DIR: 1
|
||||
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
@@ -144,7 +144,7 @@ jobs:
|
||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
||||
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
@@ -188,7 +188,7 @@ jobs:
|
||||
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
|
||||
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
@@ -217,7 +217,7 @@ jobs:
|
||||
USE_REPO_TEST_DIR: 1
|
||||
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
__debug_bin
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
@@ -57,8 +53,6 @@ cpu.out
|
||||
/bin
|
||||
/dist
|
||||
/custom/*
|
||||
!/custom/conf
|
||||
/custom/conf/*
|
||||
!/custom/conf/app.example.ini
|
||||
/data
|
||||
/indexers
|
||||
|
@@ -80,6 +80,7 @@ rules:
|
||||
media-feature-name-no-vendor-prefix: true
|
||||
media-feature-name-unit-allowed-list: null
|
||||
media-feature-name-value-allowed-list: null
|
||||
media-feature-name-value-no-unknown: true
|
||||
media-feature-range-notation: null
|
||||
named-grid-areas-no-invalid: true
|
||||
no-descending-specificity: null
|
||||
|
@@ -174,7 +174,7 @@ Here's how to run the test suite:
|
||||
## Translation
|
||||
|
||||
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. \
|
||||
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.
|
||||
@@ -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.)
|
||||
- If needed send a frontport PR for the changelog to branch `main` and update the version in `docs/config.yaml` to refer to the new version.
|
||||
- Send PR to [blog repository](https://gitea.com/gitea/blog) announcing the release.
|
||||
- Verify all release assets were correctly published through CI on dl.gitea.io and GitHub releases. Once ACKed:
|
||||
- bump the version of https://dl.gitea.io/gitea/version.json
|
||||
- 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.com/gitea/version.json
|
||||
- merge the blog post PR
|
||||
- announce the release in discord `#announcements`
|
||||
|
27
Makefile
27
Makefile
@@ -79,12 +79,21 @@ endif
|
||||
STORED_VERSION_FILE := VERSION
|
||||
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),)
|
||||
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)
|
||||
else
|
||||
ifneq ($(DRONE_BRANCH),)
|
||||
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
|
||||
ifneq ($(GITHUB_REF_NAME),)
|
||||
VERSION ?= $(subst release/v,,$(GITHUB_REF_NAME))
|
||||
else
|
||||
VERSION ?= main
|
||||
endif
|
||||
@@ -831,28 +840,28 @@ release-windows: | $(DIST_DIRS)
|
||||
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 .
|
||||
endif
|
||||
ifeq ($(CI),true)
|
||||
ifneq ($(DRONE_TAG),)
|
||||
cp /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
.PHONY: release-linux
|
||||
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) .
|
||||
ifeq ($(CI),true)
|
||||
ifneq ($(DRONE_TAG),)
|
||||
cp /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
.PHONY: release-darwin
|
||||
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) .
|
||||
ifeq ($(CI),true)
|
||||
ifneq ($(DRONE_TAG),)
|
||||
cp /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
.PHONY: release-freebsd
|
||||
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) .
|
||||
ifeq ($(CI),true)
|
||||
ifneq ($(DRONE_TAG),)
|
||||
cp /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
@@ -1010,9 +1019,5 @@ docker:
|
||||
docker build --disable-content-trust=false -t $(DOCKER_REF) .
|
||||
# support also build args docker build --build-arg GITEA_VERSION=v1.2.3 --build-arg TAGS="bindata sqlite sqlite_unlock_notify" .
|
||||
|
||||
.PHONY: docker-build
|
||||
docker-build:
|
||||
docker run -ti --rm -v "$(CURDIR):/srv/app/src/code.gitea.io/gitea" -w /srv/app/src/code.gitea.io/gitea -e TAGS="bindata $(TAGS)" LDFLAGS="$(LDFLAGS)" CGO_EXTRA_CFLAGS="$(CGO_EXTRA_CFLAGS)" webhippie/golang:edge make clean build
|
||||
|
||||
# This endif closes the if at the top of the file
|
||||
endif
|
||||
|
@@ -173,8 +173,8 @@ for the full license text.
|
||||
|
||||
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()
|
||||
defer cancel()
|
||||
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
|
||||
scope := c.String("scope")
|
||||
|
||||
|
@@ -58,7 +58,7 @@ func confirm() (bool, error) {
|
||||
}
|
||||
|
||||
func initDB(ctx context.Context) error {
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
setting.LoadDBSetting()
|
||||
setting.InitSQLLoggersForCli(log.INFO)
|
||||
|
||||
|
@@ -91,7 +91,7 @@ func runRecreateTable(ctx *cli.Context) error {
|
||||
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
|
||||
|
||||
debug := ctx.Bool("debug")
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
setting.LoadDBSetting()
|
||||
|
||||
if debug {
|
||||
|
@@ -182,7 +182,7 @@ func runDump(ctx *cli.Context) error {
|
||||
}
|
||||
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
|
||||
// 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.LFS.Path)
|
||||
excludes = append(excludes, setting.Attachment.Path)
|
||||
excludes = append(excludes, setting.Packages.Path)
|
||||
excludes = append(excludes, setting.LFS.Storage.Path)
|
||||
excludes = append(excludes, setting.Attachment.Storage.Path)
|
||||
excludes = append(excludes, setting.Packages.Storage.Path)
|
||||
excludes = append(excludes, setting.Log.RootPath)
|
||||
excludes = append(excludes, absFileName)
|
||||
if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil {
|
||||
|
@@ -99,11 +99,6 @@ type assetFile struct {
|
||||
func initEmbeddedExtractor(c *cli.Context) error {
|
||||
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
|
||||
|
||||
// Read configuration file
|
||||
setting.Init(&setting.Options{
|
||||
AllowEmpty: true,
|
||||
})
|
||||
|
||||
patterns, err := compileCollectPatterns(c.Args())
|
||||
if err != nil {
|
||||
return err
|
||||
|
@@ -16,7 +16,7 @@ func runSendMail(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
|
||||
if err := argsSet(c, "title"); err != nil {
|
||||
return err
|
||||
|
@@ -179,7 +179,7 @@ func runMigrateStorage(ctx *cli.Context) error {
|
||||
switch strings.ToLower(ctx.String("storage")) {
|
||||
case "":
|
||||
fallthrough
|
||||
case string(storage.LocalStorageType):
|
||||
case string(setting.LocalStorageType):
|
||||
p := ctx.String("path")
|
||||
if p == "" {
|
||||
log.Fatal("Path must be given when storage is loal")
|
||||
@@ -187,22 +187,24 @@ func runMigrateStorage(ctx *cli.Context) error {
|
||||
}
|
||||
dstStorage, err = storage.NewLocalStorage(
|
||||
stdCtx,
|
||||
storage.LocalStorageConfig{
|
||||
&setting.Storage{
|
||||
Path: p,
|
||||
})
|
||||
case string(storage.MinioStorageType):
|
||||
case string(setting.MinioStorageType):
|
||||
dstStorage, err = storage.NewMinioStorage(
|
||||
stdCtx,
|
||||
storage.MinioStorageConfig{
|
||||
Endpoint: ctx.String("minio-endpoint"),
|
||||
AccessKeyID: ctx.String("minio-access-key-id"),
|
||||
SecretAccessKey: ctx.String("minio-secret-access-key"),
|
||||
Bucket: ctx.String("minio-bucket"),
|
||||
Location: ctx.String("minio-location"),
|
||||
BasePath: ctx.String("minio-base-path"),
|
||||
UseSSL: ctx.Bool("minio-use-ssl"),
|
||||
InsecureSkipVerify: ctx.Bool("minio-insecure-skip-verify"),
|
||||
ChecksumAlgorithm: ctx.String("minio-checksum-algorithm"),
|
||||
&setting.Storage{
|
||||
MinioConfig: setting.MinioStorageConfig{
|
||||
Endpoint: ctx.String("minio-endpoint"),
|
||||
AccessKeyID: ctx.String("minio-access-key-id"),
|
||||
SecretAccessKey: ctx.String("minio-secret-access-key"),
|
||||
Bucket: ctx.String("minio-bucket"),
|
||||
Location: ctx.String("minio-location"),
|
||||
BasePath: ctx.String("minio-base-path"),
|
||||
UseSSL: ctx.Bool("minio-use-ssl"),
|
||||
InsecureSkipVerify: ctx.Bool("minio-insecure-skip-verify"),
|
||||
ChecksumAlgorithm: ctx.String("minio-checksum-algorithm"),
|
||||
},
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("unsupported storage type: %s", ctx.String("storage"))
|
||||
|
@@ -13,6 +13,7 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
|
||||
@@ -57,7 +58,7 @@ func TestMigratePackages(t *testing.T) {
|
||||
|
||||
dstStorage, err := storage.NewLocalStorage(
|
||||
ctx,
|
||||
storage.LocalStorageConfig{
|
||||
&setting.Storage{
|
||||
Path: p,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
@@ -51,7 +51,7 @@ func runRestoreRepository(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
var units []string
|
||||
if s := c.String("units"); s != "" {
|
||||
units = strings.Split(s, ",")
|
||||
|
@@ -61,7 +61,7 @@ func setup(ctx context.Context, debug bool) {
|
||||
} else {
|
||||
setupConsoleLogger(log.FATAL, false, os.Stderr)
|
||||
}
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
if debug {
|
||||
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 {
|
||||
if ctx.Bool("verbose") {
|
||||
setupConsoleLogger(log.TRACE, log.CanColorStdout, os.Stdout)
|
||||
@@ -128,75 +232,19 @@ func runWeb(ctx *cli.Context) error {
|
||||
createPIDFile(ctx.String("pid"))
|
||||
}
|
||||
|
||||
// Perform pre-initialization
|
||||
needsInstall := install.PreloadSettings(graceful.GetManager().HammerContext())
|
||||
if needsInstall {
|
||||
// Flag for port number in case first time run conflict
|
||||
if ctx.IsSet("port") {
|
||||
if err := setPort(ctx.String("port")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if ctx.IsSet("install-port") {
|
||||
if err := setPort(ctx.String("install-port")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c := install.Routes()
|
||||
err := listen(c, false)
|
||||
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()
|
||||
if !setting.InstallLock {
|
||||
if err := serveInstall(ctx); err != nil {
|
||||
return err
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
NoInstallListener()
|
||||
}
|
||||
|
||||
if setting.EnablePprof {
|
||||
go func() {
|
||||
http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler())
|
||||
_, _, finished := process.GetManager().AddTypedContext(context.Background(), "Web: PProf Server", process.SystemProcessType, true)
|
||||
// The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment it's not worth to introduce a configurable option for it.
|
||||
log.Info("Starting pprof server on localhost:6060")
|
||||
log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", nil))
|
||||
finished()
|
||||
}()
|
||||
go servePprof()
|
||||
}
|
||||
|
||||
log.Info("Global init")
|
||||
// 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
|
||||
return serveInstalled(ctx)
|
||||
}
|
||||
|
||||
func setPort(port string) error {
|
||||
@@ -217,9 +265,15 @@ func setPort(port string) error {
|
||||
defaultLocalURL += ":" + setting.HTTPPort + "/"
|
||||
|
||||
// Save LOCAL_ROOT_URL if port changed
|
||||
setting.CfgProvider.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
|
||||
if err := setting.CfgProvider.Save(); err != nil {
|
||||
return fmt.Errorf("Failed to save config file: %v", err)
|
||||
rootCfg := setting.CfgProvider
|
||||
saveCfg, err := rootCfg.PrepareSaving()
|
||||
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
|
||||
|
@@ -81,8 +81,6 @@ func main() {
|
||||
},
|
||||
}
|
||||
app.Action = runEnvironmentToIni
|
||||
setting.SetCustomPathAndConf("", "", "")
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to run app with %s: %v", os.Args, err)
|
||||
@@ -90,12 +88,13 @@ func main() {
|
||||
}
|
||||
|
||||
func runEnvironmentToIni(c *cli.Context) error {
|
||||
providedCustom := c.String("custom-path")
|
||||
providedConf := c.String("config")
|
||||
providedWorkPath := c.String("work-path")
|
||||
setting.SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath)
|
||||
setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{
|
||||
WorkPath: c.String("work-path"),
|
||||
CustomPath: c.String("custom-path"),
|
||||
CustomConf: c.String("config"),
|
||||
})
|
||||
|
||||
cfg, err := setting.NewConfigProviderFromFile(&setting.Options{CustomConf: setting.CustomConf, AllowEmpty: true})
|
||||
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err)
|
||||
}
|
||||
|
@@ -2159,7 +2159,7 @@ LEVEL = Info
|
||||
;RUN_AT_START = false
|
||||
;ENABLE_SUCCESS_NOTICE = false
|
||||
;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
|
||||
;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`
|
||||
;CHUNKED_UPLOAD_PATH = tmp/package-upload
|
||||
;;
|
||||
@@ -2452,6 +2456,19 @@ LEVEL = Info
|
||||
;; storage type
|
||||
;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
|
||||
@@ -2471,6 +2488,9 @@ LEVEL = Info
|
||||
;;
|
||||
;; Where your lfs files reside, default is 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]
|
||||
;; Enable/Disable actions capabilities
|
||||
;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_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).
|
||||
- `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`.
|
||||
- `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`)
|
||||
|
||||
@@ -1254,8 +1254,9 @@ is `data/lfs` and the default of `MINIO_BASE_PATH` is `lfs/`.
|
||||
|
||||
## 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.
|
||||
- `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`
|
||||
@@ -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_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
|
||||
[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_TYPE = 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
|
||||
```
|
||||
|
||||
And used by `[attachment]`, `[lfs]` and etc. as `STORAGE_TYPE`.
|
||||
|
||||
## Repository Archive Storage (`storage.repo-archive`)
|
||||
|
||||
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_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_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
|
||||
- `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
|
||||
|
||||
|
@@ -18,7 +18,7 @@ menu:
|
||||
# 配置说明
|
||||
|
||||
这是针对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) 这里的说明。
|
||||
标注了 :exclamation: 的配置项表明除非你真的理解这个配置项的意义,否则最好使用默认值。
|
||||
|
||||
@@ -414,7 +414,7 @@ LFS 的存储配置。 如果 `STORAGE_TYPE` 为空,则此配置将从 `[stora
|
||||
|
||||
## 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 兼容的对象存储服务中。
|
||||
- `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_USE_SSL`: **false**: Minio enabled ssl,仅当 `STORAGE_TYPE` 是 `minio` 时有效。
|
||||
|
||||
你也可以自定义一个存储的名字如下:
|
||||
以下为推荐的 recommanded storage configuration for minio like below:
|
||||
|
||||
```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_TYPE = 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 skip SSL verification available when STORAGE_TYPE is `minio`
|
||||
MINIO_INSECURE_SKIP_VERIFY = false
|
||||
SERVE_DIRECT = true
|
||||
```
|
||||
|
||||
然后你在 `[attachment]`, `[lfs]` 等中可以把这个名字用作 `STORAGE_TYPE` 的值。
|
||||
|
||||
## 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/`。
|
||||
|
@@ -24,7 +24,7 @@ Gitea 引用 `custom` 目录中的自定义配置文件来覆盖配置、模板
|
||||
`custom/conf/app.ini` 当中。在发行版中可能会以 `/etc/gitea/` 的形式为 `custom` 设置一个符号链接,查看配置详情请移步:
|
||||
|
||||
- [快速备忘单](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`
|
||||
环境变量配置, 因为它可能被配置到了其他地方(可能被一些启动脚本设置指定了目录)。
|
||||
@@ -67,7 +67,7 @@ Gitea 引用 `custom` 目录中的自定义配置文件来覆盖配置、模板
|
||||
|
||||
同理,您可以将页签添加到 `extra_tabs.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).
|
||||
|
||||
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.
|
||||
|
||||
|
@@ -19,7 +19,7 @@ menu:
|
||||
|
||||
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 项目。
|
||||
|
||||
|
@@ -19,7 +19,7 @@ menu:
|
||||
|
||||
我們在 [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 專案。
|
||||
|
||||
|
@@ -31,11 +31,11 @@ Gitea 定義了一些基本物件於套件 [modules/migration](https://github.co
|
||||
- 您必須實作一個 `DownloaderFactory`,它用來偵測 URL 是否符合並建立上述的 `Downloader`。
|
||||
- 您需要在 `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 介面
|
||||
|
||||
目前只有 `GiteaLocalUploader` 被實作出來,所以我們只能通過 `Uploader` 儲存已下載的資料到本地的 Gitea 實例。
|
||||
目前尚未支援其它 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 >}}
|
||||
|
||||
## 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).
|
||||
|
||||
On the other hand, 1.x.x downloads should never change.
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
- 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" >}}).
|
||||
- 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.
|
||||
|
||||
## 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镜像!
|
||||
|
||||
在我们的[下载页面](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)分支的提交上构建的。
|
||||
|
||||
|
@@ -17,6 +17,7 @@ menu:
|
||||
|
||||
# Support Options
|
||||
|
||||
- [Paid Commercial Support](https://about.gitea.com/)
|
||||
- [Discord](https://discord.gg/Gitea)
|
||||
- [Discourse Forum](https://discourse.gitea.io/)
|
||||
|
||||
@@ -35,30 +36,13 @@ menu:
|
||||
[log]
|
||||
LEVEL=debug
|
||||
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.
|
||||
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.
|
||||
5. If you meet slow/hanging/deadlock problems, please report the stack trace when the problem occurs:
|
||||
1. Enable pprof in `app.ini` and restart Gitea
|
||||
|
||||
```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)
|
||||
5. If you encounter slow/hanging/deadlock problems, please report the stack trace when the problem occurs.
|
||||
Go to the "Site Admin" -> "Monitoring" -> "Stacktrace" -> "Download diagnosis report".
|
||||
|
||||
## Bugs
|
||||
|
||||
|
@@ -17,10 +17,10 @@ menu:
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
|
@@ -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
|
||||
```
|
||||
|
||||
|
@@ -55,7 +55,7 @@ git checkout pr-xyz
|
||||
|
||||
## 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.
|
||||
* `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 相關資源,您不用擔心其他額外檔案,對於部署來說非常方便。
|
||||
* `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.
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
|
@@ -22,7 +22,7 @@ Gitea 已经提供了便于在 Kubernetes 云原生环境中安装所需的 Helm
|
||||
默认安装指令为:
|
||||
|
||||
```bash
|
||||
helm repo add gitea https://dl.gitea.io/charts
|
||||
helm repo add gitea https://dl.gitea.com/charts
|
||||
helm repo update
|
||||
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
|
||||
```
|
||||
|
||||
|
@@ -25,7 +25,7 @@ menu:
|
||||
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 标记。
|
||||
|
||||
@@ -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 标记。
|
||||
|
||||
|
@@ -21,7 +21,7 @@ menu:
|
||||
|
||||
#### 使用 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。
|
||||
|
||||
@@ -55,7 +55,7 @@ sudo apt install 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」旗標。
|
||||
|
||||
|
@@ -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
|
||||
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.
|
||||
* Put the binary at the desired install location.
|
||||
* 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
|
||||
in a two steps process.
|
||||
|
||||
Upgrade to [`gitea 1.6.4`](https://dl.gitea.io/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.
|
||||
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.com/gitea/1.6.4/) and replace the binary.
|
||||
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
|
||||
|
||||
|
@@ -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 :
|
||||
|
||||
* 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é.
|
||||
* 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/`.
|
||||
|
@@ -27,7 +27,7 @@ menu:
|
||||
|
||||
- 使用 `gogs backup` 建立 Gogs 的備份。這會建立檔案 `gogs-backup-[timestamp].zip` 包含所有重要的 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/` 複製自訂 `templates, public` 到 `gitea/custom/`。
|
||||
@@ -77,10 +77,10 @@ menu:
|
||||
|
||||
成功從 `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 並確認一切符合預期。
|
||||
|
||||
接著重複上述步驟,但這次請使用[最新發行版本](https://dl.gitea.io/gitea/{{< version >}}/)。
|
||||
接著重複上述步驟,但這次請使用[最新發行版本](https://dl.gitea.com/gitea/{{< version >}}/)。
|
||||
|
||||
## 從更新版本的 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]
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```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.
|
||||
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
|
||||
|
||||
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]
|
||||
```
|
||||
|
||||
您亦可以如下使用 docker 创建配置文件:
|
||||
|
||||
```bash
|
||||
docker run --entrypoint="" --rm -it gitea/act_runner:latest act_runner generate-config > config.yaml
|
||||
```
|
||||
|
||||
当使用Docker镜像时,可以使用`CONFIG_FILE`环境变量指定配置文件。确保将文件作为卷挂载到容器中:
|
||||
|
||||
```bash
|
||||
@@ -169,6 +175,27 @@ docker run \
|
||||
如前所述,如果要在主机上直接运行Job,可以将其移除。
|
||||
需要明确的是,这里的 "主机" 实际上指的是当前运行 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
|
||||
|
||||
如果你不打算在工作流中使用 `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
|
||||
- [TiDB](https://github.com/pingcap/tidb) (MySQL protocol)
|
||||
- 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
|
||||
- Statistiques
|
||||
- Actions
|
||||
|
@@ -71,7 +71,7 @@ Gitea 是從 [Gogs](http://gogs.io) Fork 出來的,請閱讀部落格文章 [G
|
||||
- MSSQL
|
||||
- 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
|
||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
|
||||
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 (
|
||||
|
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.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM=
|
||||
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.20230219231735-056cecc97e9e/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
|
||||
xorm.io/xorm v1.3.3-0.20230623150031-18f8e7a86c75 h1:ReBAlO50dCIXCWF8Gbi0ZRa62AGAwCJNCPaUNUa7JSg=
|
||||
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 = ""
|
||||
// MakeVersion holds the current Make version if built with make
|
||||
MakeVersion = ""
|
||||
|
||||
originalAppHelpTemplate = ""
|
||||
originalCommandHelpTemplate = ""
|
||||
originalSubcommandHelpTemplate = ""
|
||||
)
|
||||
|
||||
func init() {
|
||||
setting.AppVer = Version
|
||||
setting.AppBuiltWith = formatBuiltWith()
|
||||
setting.AppStartTime = time.Now().UTC()
|
||||
}
|
||||
|
||||
// Grab the original help templates
|
||||
originalAppHelpTemplate = cli.AppHelpTemplate
|
||||
originalCommandHelpTemplate = cli.CommandHelpTemplate
|
||||
originalSubcommandHelpTemplate = cli.SubcommandHelpTemplate
|
||||
// cmdHelp is our own help subcommand with more information
|
||||
// test cases:
|
||||
// ./gitea help
|
||||
// ./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() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "Gitea"
|
||||
app.Usage = "A painless self-hosted Git service"
|
||||
app.Description = `By default, gitea will start serving using the webserver with no
|
||||
arguments - which can alternatively be run by running the subcommand web.`
|
||||
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".`
|
||||
app.Version = Version + formatBuiltWith()
|
||||
app.EnableBashCompletion = true
|
||||
app.Commands = []cli.Command{
|
||||
cmd.CmdWeb,
|
||||
cmd.CmdServ,
|
||||
@@ -77,118 +105,83 @@ arguments - which can alternatively be run by running the subcommand web.`
|
||||
cmd.CmdRestoreRepository,
|
||||
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
|
||||
defaultFlags := []cli.Flag{
|
||||
globalFlags := []cli.Flag{
|
||||
cli.HelpFlag,
|
||||
cli.StringFlag{
|
||||
Name: "custom-path, C",
|
||||
Value: setting.CustomPath,
|
||||
Usage: "Custom path file path",
|
||||
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "config, c",
|
||||
Value: setting.CustomConf,
|
||||
Usage: "Custom configuration file path",
|
||||
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
|
||||
},
|
||||
cli.VersionFlag,
|
||||
cli.StringFlag{
|
||||
Name: "work-path, w",
|
||||
Value: setting.AppWorkPath,
|
||||
Usage: "Set the gitea working path",
|
||||
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
|
||||
},
|
||||
}
|
||||
|
||||
// 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, defaultFlags...)
|
||||
app.Action = cmd.CmdWeb.Action
|
||||
|
||||
// Add functions to set these paths and these flags to the commands
|
||||
app.Before = establishCustomPath
|
||||
app.Action = prepareWorkPathAndCustomConf(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)
|
||||
for i := range app.Commands {
|
||||
setFlagsAndBeforeOnSubcommands(&app.Commands[i], defaultFlags, establishCustomPath)
|
||||
prepareSubcommands(&app.Commands[i], globalFlags)
|
||||
}
|
||||
|
||||
app.EnableBashCompletion = true
|
||||
|
||||
err := app.Run(os.Args)
|
||||
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()
|
||||
}
|
||||
|
||||
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.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 {
|
||||
setFlagsAndBeforeOnSubcommands(&command.Subcommands[i], defaultFlags, before)
|
||||
prepareSubcommands(&command.Subcommands[i], defaultFlags)
|
||||
}
|
||||
}
|
||||
|
||||
func establishCustomPath(ctx *cli.Context) error {
|
||||
var providedCustom string
|
||||
var providedConf string
|
||||
var providedWorkPath string
|
||||
|
||||
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()
|
||||
|
||||
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
|
||||
// It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times
|
||||
func prepareWorkPathAndCustomConf(a any) func(ctx *cli.Context) error {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
setting.SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath)
|
||||
|
||||
setAppHelpTemplates()
|
||||
|
||||
if ctx.IsSet("version") {
|
||||
cli.ShowVersion(ctx)
|
||||
os.Exit(0)
|
||||
action := a.(func(*cli.Context) error)
|
||||
return func(ctx *cli.Context) error {
|
||||
var args setting.ArgWorkPathAndCustomConf
|
||||
curCtx := ctx
|
||||
for curCtx != nil {
|
||||
if curCtx.IsSet("work-path") && args.WorkPath == "" {
|
||||
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 {
|
||||
|
@@ -123,7 +123,10 @@ func newXORMEngine() (*xorm.Engine, error) {
|
||||
|
||||
// SyncAllTables sync the schemas of all tables, is required by unit test code
|
||||
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
|
||||
|
@@ -89,6 +89,33 @@ func mysqlGetNextResourceIndex(ctx context.Context, tableName string, groupID in
|
||||
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
|
||||
func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
|
||||
switch {
|
||||
@@ -96,6 +123,8 @@ func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64)
|
||||
return postgresGetNextResourceIndex(ctx, tableName, groupID)
|
||||
case setting.Database.Type.IsMySQL():
|
||||
return mysqlGetNextResourceIndex(ctx, tableName, groupID)
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
return mssqlGetNextResourceIndex(ctx, tableName, groupID)
|
||||
}
|
||||
|
||||
e := GetEngine(ctx)
|
||||
|
@@ -83,13 +83,47 @@ func mysqlGetCommitStatusIndex(ctx context.Context, repoID int64, sha string) (i
|
||||
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
|
||||
func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
|
||||
if !git.IsValidSHAPattern(sha) {
|
||||
return 0, git.ErrInvalidSHA{SHA: sha}
|
||||
}
|
||||
|
||||
switch {
|
||||
case setting.Database.Type.IsPostgreSQL():
|
||||
return postgresGetCommitStatusIndex(ctx, repoID, sha)
|
||||
case setting.Database.Type.IsMySQL():
|
||||
return mysqlGetCommitStatusIndex(ctx, repoID, sha)
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
return mssqlGetCommitStatusIndex(ctx, repoID, sha)
|
||||
}
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
|
@@ -167,7 +167,7 @@ func AsCommentType(typeName string) CommentType {
|
||||
|
||||
func (t CommentType) HasContentSupport() bool {
|
||||
switch t {
|
||||
case CommentTypeComment, CommentTypeCode, CommentTypeReview:
|
||||
case CommentTypeComment, CommentTypeCode, CommentTypeReview, CommentTypeDismissReview:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@@ -18,11 +18,11 @@ import (
|
||||
type CodeComments map[string]map[int64][]*Comment
|
||||
|
||||
// FetchCodeComments will return a 2d-map: ["Path"]["Line"] = Comments at line
|
||||
func FetchCodeComments(ctx context.Context, issue *Issue, currentUser *user_model.User) (CodeComments, error) {
|
||||
return fetchCodeCommentsByReview(ctx, issue, currentUser, nil)
|
||||
func FetchCodeComments(ctx context.Context, issue *Issue, currentUser *user_model.User, showOutdatedComments bool) (CodeComments, error) {
|
||||
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)
|
||||
if review == nil {
|
||||
review = &Review{ID: 0}
|
||||
@@ -33,7 +33,7 @@ func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *u
|
||||
ReviewID: review.ID,
|
||||
}
|
||||
|
||||
comments, err := findCodeComments(ctx, opts, issue, currentUser, review)
|
||||
comments, err := findCodeComments(ctx, opts, issue, currentUser, review, showOutdatedComments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -47,15 +47,17 @@ func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *u
|
||||
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
|
||||
if review == nil {
|
||||
review = &Review{ID: 0}
|
||||
}
|
||||
conds := opts.ToConds()
|
||||
if review.ID == 0 {
|
||||
|
||||
if !showOutdatedComments && review.ID == 0 {
|
||||
conds = conds.And(builder.Eq{"invalidated": false})
|
||||
}
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
if err := e.Where(conds).
|
||||
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
|
||||
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{
|
||||
Type: CommentTypeCode,
|
||||
IssueID: issue.ID,
|
||||
TreePath: treePath,
|
||||
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})
|
||||
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.Contains(t, res, "README.md")
|
||||
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)
|
||||
|
||||
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.Len(t, res, 1)
|
||||
}
|
||||
|
@@ -141,7 +141,7 @@ func (r *Review) LoadCodeComments(ctx context.Context) (err error) {
|
||||
if err = r.loadIssue(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
r.CodeComments, err = fetchCodeCommentsByReview(ctx, r.Issue, nil, r)
|
||||
r.CodeComments, err = fetchCodeCommentsByReview(ctx, r.Issue, nil, r, false)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@@ -147,9 +147,9 @@ func MainTest(m *testing.M) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
|
||||
setting.AppDataPath = tmpDataPath
|
||||
|
||||
setting.SetCustomPathAndConf("", "", "")
|
||||
unittest.InitSettings()
|
||||
if err = git.InitFull(context.Background()); err != nil {
|
||||
fmt.Printf("Unable to InitFull: %v\n", err)
|
||||
|
@@ -53,7 +53,7 @@ func DeleteOrphanedAttachments(x *xorm.Engine) error {
|
||||
|
||||
for _, attachment := range attachments {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ func RemoveAttachmentMissedRepo(x *xorm.Engine) error {
|
||||
|
||||
for i := 0; i < len(attachments); i++ {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@@ -61,7 +61,7 @@ func RenameExistingUserAvatarName(x *xorm.Engine) error {
|
||||
for _, user := range users {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
deleteList.Add(filepath.Join(setting.Avatar.Path, oldAvatar))
|
||||
deleteList.Add(filepath.Join(setting.Avatar.Storage.Path, oldAvatar))
|
||||
migrated++
|
||||
select {
|
||||
case <-ticker.C:
|
||||
@@ -135,7 +135,7 @@ func RenameExistingUserAvatarName(x *xorm.Engine) error {
|
||||
// copyOldAvatarToNewLocation copies oldAvatar to newAvatarLocation
|
||||
// and returns newAvatar location
|
||||
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 {
|
||||
return "", fmt.Errorf("os.Open: %w", err)
|
||||
}
|
||||
@@ -151,7 +151,7 @@ func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error)
|
||||
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)
|
||||
}
|
||||
|
||||
|
@@ -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)})
|
||||
}
|
||||
|
||||
if actor.IsGhost() {
|
||||
actor = nil
|
||||
}
|
||||
|
||||
cond = cond.And(user_model.BuildCanSeeUserCondition(actor))
|
||||
|
||||
sess := db.GetEngine(ctx).
|
||||
|
@@ -42,12 +42,14 @@ func fatalTestError(fmtStr string, args ...interface{}) {
|
||||
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) {
|
||||
setting.Init(&setting.Options{
|
||||
AllowEmpty: true,
|
||||
ExtraConfig: strings.Join(extraConfigs, "\n"),
|
||||
})
|
||||
if setting.CustomConf == "" {
|
||||
setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini")
|
||||
_ = os.Remove(setting.CustomConf)
|
||||
}
|
||||
setting.InitCfgProvider(setting.CustomConf, strings.Join(extraConfigs, "\n"))
|
||||
setting.LoadCommonSettings()
|
||||
|
||||
if err := setting.PrepareAppDataPath(); err != nil {
|
||||
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
|
||||
// test database. Creates the test database, and sets necessary settings.
|
||||
func MainTest(m *testing.M, testOpts *TestOptions) {
|
||||
setting.SetCustomPathAndConf("", "", "")
|
||||
setting.CustomPath = filepath.Join(testOpts.GiteaRootPath, "custom")
|
||||
InitSettings()
|
||||
|
||||
var err error
|
||||
|
@@ -8,6 +8,8 @@ const (
|
||||
SettingsKeyHiddenCommentTypes = "issue.hidden_comment_types"
|
||||
// SettingsKeyDiffWhitespaceBehavior is the setting key for whitespace behavior of diff
|
||||
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 = "activitypub.priv_pem"
|
||||
// UserActivityPubPubPem is user's public key
|
||||
|
@@ -49,9 +49,9 @@ func (ctx *Context) RedirectToFirst(location ...string) {
|
||||
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
|
||||
if len(loc) > 1 && loc[0] == '/' && (loc[1] == '/' || loc[1] == '\\') {
|
||||
if len(loc) > 1 && (loc[0] == '/' || loc[0] == '\\') && (loc[1] == '/' || loc[1] == '\\') {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@@ -28,7 +28,7 @@ type Check struct {
|
||||
}
|
||||
|
||||
func initDBSkipLogger(ctx context.Context) error {
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
setting.LoadDBSetting()
|
||||
if err := db.InitEngine(ctx); err != nil {
|
||||
return fmt.Errorf("db.InitEngine: %w", err)
|
||||
|
@@ -66,7 +66,7 @@ func checkConfigurationFiles(ctx context.Context, logger log.Logger, autofix boo
|
||||
return err
|
||||
}
|
||||
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
|
||||
configurationFiles := []configurationFile{
|
||||
{"Configuration File Path", setting.CustomConf, false, true, false},
|
||||
|
@@ -20,17 +20,18 @@ func (b *Blob) Name() string {
|
||||
return b.name
|
||||
}
|
||||
|
||||
// GetBlobContent Gets the content of the blob as raw text
|
||||
func (b *Blob) GetBlobContent() (string, error) {
|
||||
// GetBlobContent Gets the limited content of the blob as raw text
|
||||
func (b *Blob) GetBlobContent(limit int64) (string, error) {
|
||||
if limit <= 0 {
|
||||
return "", nil
|
||||
}
|
||||
dataRc, err := b.DataAsync()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer dataRc.Close()
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := util.ReadAtMost(dataRc, buf)
|
||||
buf = buf[:n]
|
||||
return string(buf), nil
|
||||
buf, err := util.ReadWithLimit(dataRc, int(limit))
|
||||
return string(buf), err
|
||||
}
|
||||
|
||||
// GetBlobLineCount gets line count of the blob
|
||||
|
@@ -163,6 +163,7 @@ func (ref RefName) ShortName() string {
|
||||
}
|
||||
|
||||
// RefGroup returns the group type of the reference
|
||||
// Using the name of the directory under .git/refs
|
||||
func (ref RefName) RefGroup() string {
|
||||
if ref.IsBranch() {
|
||||
return "heads"
|
||||
@@ -182,6 +183,19 @@ func (ref RefName) RefGroup() string {
|
||||
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
|
||||
func RefURL(repoURL, ref string) string {
|
||||
refFullName := RefName(ref)
|
||||
|
@@ -28,6 +28,14 @@ func IsValidSHAPattern(sha string) bool {
|
||||
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.
|
||||
func MustID(b []byte) SHA1 {
|
||||
var id SHA1
|
||||
|
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/emoji"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@@ -28,9 +29,7 @@ var localMetas = map[string]string{
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setting.Init(&setting.Options{
|
||||
AllowEmpty: true,
|
||||
})
|
||||
unittest.InitSettings()
|
||||
if err := git.InitSimple(context.Background()); err != nil {
|
||||
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
|
||||
type TaskCheckBoxListItem struct {
|
||||
*ast.ListItem
|
||||
IsChecked bool
|
||||
IsChecked bool
|
||||
SourcePosition int
|
||||
}
|
||||
|
||||
// KindTaskCheckBoxListItem is the NodeKind for TaskCheckBoxListItem
|
||||
@@ -86,6 +87,7 @@ var KindTaskCheckBoxListItem = ast.NewNodeKind("TaskCheckBoxListItem")
|
||||
func (n *TaskCheckBoxListItem) Dump(source []byte, level int) {
|
||||
m := map[string]string{}
|
||||
m["IsChecked"] = strconv.FormatBool(n.IsChecked)
|
||||
m["SourcePosition"] = strconv.FormatInt(int64(n.SourcePosition), 10)
|
||||
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.IsChecked = taskCheckBox.IsChecked
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -457,12 +462,7 @@ func (r *HTMLRenderer) renderTaskCheckBoxListItem(w util.BufWriter, source []byt
|
||||
} else {
|
||||
_, _ = w.WriteString("<li>")
|
||||
}
|
||||
_, _ = w.WriteString(`<input type="checkbox" disabled=""`)
|
||||
segments := node.FirstChild().Lines()
|
||||
if segments.Len() > 0 {
|
||||
segment := segments.At(0)
|
||||
_, _ = w.WriteString(fmt.Sprintf(` data-source-position="%d"`, segment.Start))
|
||||
}
|
||||
fmt.Fprintf(w, `<input type="checkbox" disabled="" data-source-position="%d"`, n.SourcePosition)
|
||||
if n.IsChecked {
|
||||
_, _ = w.WriteString(` checked=""`)
|
||||
}
|
||||
|
@@ -178,6 +178,9 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer)
|
||||
}
|
||||
buf = giteautil.NormalizeEOL(buf)
|
||||
|
||||
// Preserve original length.
|
||||
bufWithMetadataLength := len(buf)
|
||||
|
||||
rc := &RenderConfig{
|
||||
Meta: renderMetaModeFromString(string(ctx.RenderMetaAs)),
|
||||
Icon: "table",
|
||||
@@ -185,6 +188,12 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer)
|
||||
}
|
||||
buf, _ = ExtractMetadataBytes(buf, rc)
|
||||
|
||||
metaLength := bufWithMetadataLength - len(buf)
|
||||
if metaLength < 0 {
|
||||
metaLength = 0
|
||||
}
|
||||
rc.metaLength = metaLength
|
||||
|
||||
pc.Set(renderConfigKey, rc)
|
||||
|
||||
if err := converter.Convert(buf, lw, parser.WithContext(pc)); err != nil {
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
@@ -33,9 +34,7 @@ var localMetas = map[string]string{
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setting.Init(&setting.Options{
|
||||
AllowEmpty: true,
|
||||
})
|
||||
unittest.InitSettings()
|
||||
if err := git.InitSimple(context.Background()); err != nil {
|
||||
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
|
||||
Lang string
|
||||
yamlNode *yaml.Node
|
||||
|
||||
// Used internally. Cannot be controlled by frontmatter.
|
||||
metaLength int
|
||||
}
|
||||
|
||||
func renderMetaModeFromString(s string) markup.RenderMetaMode {
|
||||
|
@@ -4,14 +4,14 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Actions settings
|
||||
var (
|
||||
Actions = struct {
|
||||
LogStorage Storage // how the created logs should be stored
|
||||
ArtifactStorage Storage // how the created artifacts should be stored
|
||||
LogStorage *Storage // how the created logs should be stored
|
||||
ArtifactStorage *Storage // how the created artifacts should be stored
|
||||
Enabled bool
|
||||
DefaultActionsURL string `ini:"DEFAULT_ACTIONS_URL"`
|
||||
}{
|
||||
@@ -20,15 +20,22 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func loadActionsFrom(rootCfg ConfigProvider) {
|
||||
func loadActionsFrom(rootCfg ConfigProvider) error {
|
||||
sec := rootCfg.Section("actions")
|
||||
if err := sec.MapTo(&Actions); err != nil {
|
||||
log.Fatal("Failed to map Actions settings: %v", err)
|
||||
err := sec.MapTo(&Actions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to map Actions settings: %v", err)
|
||||
}
|
||||
|
||||
actionsSec := rootCfg.Section("actions.artifacts")
|
||||
storageType := actionsSec.Key("STORAGE_TYPE").MustString("")
|
||||
// don't support to read configuration from [actions]
|
||||
Actions.LogStorage, err = getStorage(rootCfg, "actions_log", "", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Actions.LogStorage = getStorage(rootCfg, "actions_log", "", nil)
|
||||
Actions.ArtifactStorage = getStorage(rootCfg, "actions_artifacts", storageType, actionsSec)
|
||||
actionsSec, _ := rootCfg.GetSection("actions.artifacts")
|
||||
|
||||
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
|
||||
var Attachment = struct {
|
||||
Storage
|
||||
Storage *Storage
|
||||
AllowedTypes string
|
||||
MaxSize int64
|
||||
MaxFiles int
|
||||
Enabled bool
|
||||
}{
|
||||
Storage: Storage{
|
||||
ServeDirect: false,
|
||||
},
|
||||
AllowedTypes: "image/jpeg,image/png,application/zip,application/gzip",
|
||||
Storage: &Storage{},
|
||||
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",
|
||||
MaxSize: 4,
|
||||
MaxFiles: 5,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
func loadAttachmentFrom(rootCfg ConfigProvider) {
|
||||
sec := rootCfg.Section("attachment")
|
||||
storageType := sec.Key("STORAGE_TYPE").MustString("")
|
||||
|
||||
Attachment.Storage = getStorage(rootCfg, "attachments", storageType, sec)
|
||||
func loadAttachmentFrom(rootCfg ConfigProvider) (err error) {
|
||||
sec, _ := rootCfg.GetSection("attachment")
|
||||
if sec == nil {
|
||||
Attachment.Storage, err = getStorage(rootCfg, "attachments", "", nil)
|
||||
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.MaxSize = sec.Key("MAX_SIZE").MustInt64(4)
|
||||
Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5)
|
||||
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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -50,12 +52,18 @@ type ConfigProvider interface {
|
||||
GetSection(name string) (ConfigSection, error)
|
||||
Save() error
|
||||
SaveTo(filename string) error
|
||||
|
||||
DisableSaving()
|
||||
PrepareSaving() (ConfigProvider, error)
|
||||
IsLoadedFromEmpty() bool
|
||||
}
|
||||
|
||||
type iniConfigProvider struct {
|
||||
opts *Options
|
||||
ini *ini.File
|
||||
newFile bool // whether the file has not existed previously
|
||||
file string
|
||||
ini *ini.File
|
||||
|
||||
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 {
|
||||
@@ -95,6 +103,18 @@ func ConfigSectionKeyString(sec ConfigSection, key string, def ...string) string
|
||||
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)
|
||||
// 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.
|
||||
@@ -162,53 +182,43 @@ func NewConfigProviderFromData(configContent string) (ConfigProvider, error) {
|
||||
}
|
||||
cfg.NameMapper = ini.SnackCase
|
||||
return &iniConfigProvider{
|
||||
ini: cfg,
|
||||
newFile: true,
|
||||
ini: cfg,
|
||||
loadedFromEmpty: true,
|
||||
}, 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.
|
||||
// NOTE: do not print any log except error.
|
||||
func NewConfigProviderFromFile(opts *Options) (ConfigProvider, error) {
|
||||
cfg := ini.Empty()
|
||||
newFile := true
|
||||
func NewConfigProviderFromFile(file string, extraConfigs ...string) (ConfigProvider, error) {
|
||||
cfg := ini.Empty(ini.LoadOptions{KeyValueDelimiterOnWrite: " = "})
|
||||
loadedFromEmpty := true
|
||||
|
||||
if opts.CustomConf != "" {
|
||||
isFile, err := util.IsFile(opts.CustomConf)
|
||||
if file != "" {
|
||||
isFile, err := util.IsFile(file)
|
||||
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 err := cfg.Append(opts.CustomConf); err != nil {
|
||||
return nil, fmt.Errorf("failed to load custom conf '%s': %v", opts.CustomConf, err)
|
||||
if err = cfg.Append(file); err != nil {
|
||||
return nil, fmt.Errorf("failed to load config file %q: %v", file, err)
|
||||
}
|
||||
newFile = false
|
||||
loadedFromEmpty = false
|
||||
}
|
||||
}
|
||||
|
||||
if newFile && !opts.AllowEmpty {
|
||||
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)
|
||||
}
|
||||
|
||||
if opts.ExtraConfig != "" {
|
||||
if err := cfg.Append([]byte(opts.ExtraConfig)); err != nil {
|
||||
return nil, fmt.Errorf("unable to append more config: %v", err)
|
||||
if len(extraConfigs) > 0 {
|
||||
for _, s := range extraConfigs {
|
||||
if err := cfg.Append([]byte(s)); err != nil {
|
||||
return nil, fmt.Errorf("unable to append more config: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg.NameMapper = ini.SnackCase
|
||||
return &iniConfigProvider{
|
||||
opts: opts,
|
||||
ini: cfg,
|
||||
newFile: newFile,
|
||||
file: file,
|
||||
ini: cfg,
|
||||
loadedFromEmpty: loadedFromEmpty,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -239,22 +249,24 @@ func (p *iniConfigProvider) GetSection(name string) (ConfigSection, error) {
|
||||
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
|
||||
func (p *iniConfigProvider) Save() error {
|
||||
filename := p.opts.CustomConf
|
||||
if filename == "" {
|
||||
if !p.opts.AllowEmpty {
|
||||
return fmt.Errorf("custom config path must not be empty")
|
||||
}
|
||||
return nil
|
||||
if p.disableSaving {
|
||||
return errDisableSaving
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
@@ -272,9 +284,32 @@ func (p *iniConfigProvider) Save() error {
|
||||
}
|
||||
|
||||
func (p *iniConfigProvider) SaveTo(filename string) error {
|
||||
if p.disableSaving {
|
||||
return errDisableSaving
|
||||
}
|
||||
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) {
|
||||
if err := rootCfg.Section(sectionName).MapTo(setting); err != nil {
|
||||
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
|
||||
func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
|
||||
if rootCfg.Section(oldSection).HasKey(oldKey) {
|
||||
@@ -305,8 +346,8 @@ func NewConfigProviderForLocale(source any, others ...any) (ConfigProvider, erro
|
||||
}
|
||||
iniFile.BlockMode = false
|
||||
return &iniConfigProvider{
|
||||
ini: iniFile,
|
||||
newFile: true,
|
||||
ini: iniFile,
|
||||
loadedFromEmpty: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@@ -67,13 +67,14 @@ key = 123
|
||||
}
|
||||
|
||||
func TestNewConfigProviderFromFile(t *testing.T) {
|
||||
_, err := NewConfigProviderFromFile(&Options{CustomConf: "no-such.ini", AllowEmpty: false})
|
||||
assert.ErrorContains(t, err, "unable to find configuration file")
|
||||
cfg, err := NewConfigProviderFromFile("no-such.ini")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, cfg.IsLoadedFromEmpty())
|
||||
|
||||
// load non-existing file and save
|
||||
testFile := t.TempDir() + "/test.ini"
|
||||
testFile1 := t.TempDir() + "/test1.ini"
|
||||
cfg, err := NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true})
|
||||
cfg, err = NewConfigProviderFromFile(testFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
sec, _ := cfg.NewSection("foo")
|
||||
@@ -84,14 +85,14 @@ func TestNewConfigProviderFromFile(t *testing.T) {
|
||||
|
||||
bs, err := os.ReadFile(testFile)
|
||||
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)
|
||||
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
|
||||
cfg, err = NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true})
|
||||
cfg, err = NewConfigProviderFromFile(testFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "a", cfg.Section("foo").Key("k1").String())
|
||||
sec, _ = cfg.NewSection("bar")
|
||||
@@ -99,7 +100,7 @@ func TestNewConfigProviderFromFile(t *testing.T) {
|
||||
assert.NoError(t, cfg.Save())
|
||||
bs, err = os.ReadFile(testFile)
|
||||
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) {
|
||||
@@ -119,3 +120,27 @@ func TestNewConfigProviderForLocale(t *testing.T) {
|
||||
assert.Equal(t, "foo", cfg.Section("").Key("k1").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) {
|
||||
sec := rootCfg.Section("database")
|
||||
Database.Type = DatabaseType(sec.Key("DB_TYPE").String())
|
||||
defaultCharset := "utf8"
|
||||
|
||||
if Database.Type.IsMySQL() {
|
||||
defaultCharset = "utf8mb4"
|
||||
}
|
||||
|
||||
Database.Host = sec.Key("HOST").String()
|
||||
Database.Name = sec.Key("NAME").String()
|
||||
@@ -74,9 +69,10 @@ func loadDBSetting(rootCfg ConfigProvider) {
|
||||
}
|
||||
Database.Schema = sec.Key("SCHEMA").String()
|
||||
Database.SSLMode = sec.Key("SSL_MODE").MustString("disable")
|
||||
Database.Charset = sec.Key("CHARSET").In(defaultCharset, []string{"utf8", "utf8mb4"})
|
||||
if Database.Type.IsMySQL() && defaultCharset != "utf8mb4" {
|
||||
log.Error("Deprecated database mysql charset utf8 support, please use utf8mb4 or convert utf8 to utf8mb4.")
|
||||
|
||||
Database.Charset = sec.Key("CHARSET").MustString("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"))
|
||||
@@ -101,9 +97,9 @@ func loadDBSetting(rootCfg ConfigProvider) {
|
||||
// DBConnStr returns database connection string
|
||||
func DBConnStr() (string, error) {
|
||||
var connStr string
|
||||
Param := "?"
|
||||
if strings.Contains(Database.Name, Param) {
|
||||
Param = "&"
|
||||
paramSep := "?"
|
||||
if strings.Contains(Database.Name, paramSep) {
|
||||
paramSep = "&"
|
||||
}
|
||||
switch Database.Type {
|
||||
case "mysql":
|
||||
@@ -116,15 +112,15 @@ func DBConnStr() (string, error) {
|
||||
tls = "false"
|
||||
}
|
||||
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":
|
||||
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":
|
||||
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)
|
||||
case "sqlite3":
|
||||
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 {
|
||||
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",
|
||||
Database.Path, Database.Timeout, journalMode)
|
||||
default:
|
||||
return "", fmt.Errorf("Unknown database type: %s", Database.Type)
|
||||
return "", fmt.Errorf("unknown database type: %s", Database.Type)
|
||||
}
|
||||
|
||||
return connStr, nil
|
||||
|
@@ -5,10 +5,10 @@ package setting
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/generate"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
// LFS represents the configuration for Git LFS
|
||||
@@ -20,25 +20,27 @@ var LFS = struct {
|
||||
MaxFileSize int64 `ini:"LFS_MAX_FILE_SIZE"`
|
||||
LocksPagingNum int `ini:"LFS_LOCKS_PAGING_NUM"`
|
||||
|
||||
Storage
|
||||
Storage *Storage
|
||||
}{}
|
||||
|
||||
func loadLFSFrom(rootCfg ConfigProvider) {
|
||||
func loadLFSFrom(rootCfg ConfigProvider) error {
|
||||
sec := rootCfg.Section("server")
|
||||
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")
|
||||
storageType := lfsSec.Key("STORAGE_TYPE").MustString("")
|
||||
lfsSec, _ := rootCfg.GetSection("lfs")
|
||||
|
||||
// Specifically default PATH to LFS_CONTENT_PATH
|
||||
// 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
|
||||
deprecatedSetting(rootCfg, "server", "LFS_CONTENT_PATH", "lfs", "PATH", "v1.19.0")
|
||||
lfsSec.Key("PATH").MustString(sec.Key("LFS_CONTENT_PATH").String())
|
||||
deprecatedSettingFatal(rootCfg, "server", "LFS_CONTENT_PATH", "lfs", "PATH", "v1.19.0")
|
||||
|
||||
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
|
||||
if LFS.LocksPagingNum == 0 {
|
||||
@@ -47,23 +49,30 @@ func loadLFSFrom(rootCfg ConfigProvider) {
|
||||
|
||||
LFS.HTTPAuthExpiry = sec.Key("LFS_HTTP_AUTH_EXPIRY").MustDuration(24 * time.Hour)
|
||||
|
||||
if LFS.StartServer {
|
||||
LFS.JWTSecretBytes = make([]byte, 32)
|
||||
n, err := base64.RawURLEncoding.Decode(LFS.JWTSecretBytes, []byte(LFS.JWTSecretBase64))
|
||||
if !LFS.StartServer {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil || n != 32 {
|
||||
LFS.JWTSecretBase64, err = generate.NewJwtSecretBase64()
|
||||
if err != nil {
|
||||
log.Fatal("Error generating JWT Secret for custom config: %v", err)
|
||||
return
|
||||
}
|
||||
LFS.JWTSecretBytes = make([]byte, 32)
|
||||
n, err := base64.RawURLEncoding.Decode(LFS.JWTSecretBytes, []byte(LFS.JWTSecretBase64))
|
||||
|
||||
// Save secret
|
||||
sec.Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
|
||||
if err := rootCfg.Save(); err != nil {
|
||||
log.Fatal("Error saving JWT Secret for custom config: %v", err)
|
||||
return
|
||||
}
|
||||
if err != nil || n != 32 {
|
||||
LFS.JWTSecretBase64, err = generate.NewJwtSecretBase64()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating JWT Secret for custom config: %v", err)
|
||||
}
|
||||
|
||||
// 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
|
||||
// if these are removed, the warning will not be shown
|
||||
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
|
||||
}
|
||||
|
||||
|
@@ -120,18 +120,25 @@ func loadOAuth2From(rootCfg ConfigProvider) {
|
||||
OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile)
|
||||
}
|
||||
|
||||
key := make([]byte, 32)
|
||||
n, err := base64.RawURLEncoding.Decode(key, []byte(OAuth2.JWTSecretBase64))
|
||||
if err != nil || n != 32 {
|
||||
key, err = generate.NewJwtSecret()
|
||||
if err != nil {
|
||||
log.Fatal("error generating JWT secret: %v", err)
|
||||
}
|
||||
if InstallLock {
|
||||
key := make([]byte, 32)
|
||||
n, err := base64.RawURLEncoding.Decode(key, []byte(OAuth2.JWTSecretBase64))
|
||||
if err != nil || n != 32 {
|
||||
key, err = generate.NewJwtSecret()
|
||||
if err != nil {
|
||||
log.Fatal("error generating JWT secret: %v", err)
|
||||
}
|
||||
|
||||
secretBase64 := base64.RawURLEncoding.EncodeToString(key)
|
||||
rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64)
|
||||
if err := rootCfg.Save(); err != nil {
|
||||
log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
|
||||
secretBase64 := base64.RawURLEncoding.EncodeToString(key)
|
||||
saveCfg, err := rootCfg.PrepareSaving()
|
||||
if err != nil {
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
// Package registry settings
|
||||
var (
|
||||
Packages = struct {
|
||||
Storage
|
||||
Storage *Storage
|
||||
Enabled bool
|
||||
ChunkedUploadPath string
|
||||
RegistryHost string
|
||||
@@ -51,13 +50,21 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func loadPackagesFrom(rootCfg ConfigProvider) {
|
||||
sec := rootCfg.Section("packages")
|
||||
if err := sec.MapTo(&Packages); err != nil {
|
||||
log.Fatal("Failed to map Packages settings: %v", err)
|
||||
func loadPackagesFrom(rootCfg ConfigProvider) (err error) {
|
||||
sec, _ := rootCfg.GetSection("packages")
|
||||
if sec == nil {
|
||||
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)
|
||||
Packages.RegistryHost = appURL.Host
|
||||
@@ -68,7 +75,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) {
|
||||
}
|
||||
|
||||
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")
|
||||
@@ -93,6 +100,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) {
|
||||
Packages.LimitSizeRubyGems = mustBytes(sec, "LIMIT_SIZE_RUBYGEMS")
|
||||
Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT")
|
||||
Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT")
|
||||
return nil
|
||||
}
|
||||
|
||||
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, -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