Compare commits
82 Commits
forgejo
...
v1.19/forg
Author | SHA1 | Date |
---|---|---|
Loïc Dachary | 095a5b0c88 | |
Loïc Dachary | 7639646fb5 | |
Loïc Dachary | 9a7332c2be | |
Loïc Dachary | 9438f6e422 | |
Giteabot | e87f36e885 | |
Giteabot | b301cb17a3 | |
Giteabot | e259daeff8 | |
Giteabot | edb618c136 | |
Giteabot | 43cf04c031 | |
Giteabot | e9991b1f06 | |
Giteabot | 975785dd42 | |
Giteabot | e269e8901f | |
Giteabot | 87c31c2ffe | |
Giteabot | 54c674c936 | |
delvh | 2ba58fab22 | |
wxiaoguang | cd7bd8568c | |
Giteabot | cf80f829b4 | |
Giteabot | ed25e094ab | |
Giteabot | 10df304b2f | |
Giteabot | ecae62837c | |
sillyguodong | e8e871b44e | |
Giteabot | 6be6c19daf | |
Giteabot | 61f91bdc7e | |
Giteabot | 8ab50be000 | |
Giteabot | dfab6e2d1c | |
Giteabot | 2f7bbdf8c9 | |
Giteabot | af4767df5c | |
Giteabot | 233a399706 | |
Giteabot | dcf1717793 | |
Giteabot | b1e68f39e7 | |
Giteabot | ee3d9330a8 | |
Giteabot | d1d15306d1 | |
Giteabot | e3b1ebbbfe | |
Giteabot | 17ae7e335e | |
Giteabot | 1edb57eda9 | |
Giteabot | a2a9b0f977 | |
Giteabot | ff96f804b6 | |
Giteabot | a926994bfe | |
Giteabot | 83903535e3 | |
Giteabot | 8142408d3a | |
Giteabot | a4158d1904 | |
Giteabot | 781019216c | |
Giteabot | 1322cd7a58 | |
Giteabot | 464bbd747e | |
Giteabot | 574182e1eb | |
Giteabot | ef8209a953 | |
Giteabot | 9309098eab | |
Giteabot | 790a79b04c | |
Giteabot | f8a40dafb9 | |
Giteabot | 9843a0b741 | |
Giteabot | 085a4debd5 | |
Giteabot | 4c1e24864f | |
Giteabot | 5d5f907e7f | |
Giteabot | 39178b5756 | |
Giteabot | 3d8412dd51 | |
Yarden Shoham | ff7057a46d | |
wxiaoguang | bb8ef28913 | |
Giteabot | 13918ad344 | |
Giteabot | 7528ce60e7 | |
Yarden Shoham | 6c6a7e7d97 | |
Yarden Shoham | 111c509287 | |
Yarden Shoham | 9d7ef0ad63 | |
Yarden Shoham | 9aae54c81f | |
Yarden Shoham | 1bc4ffc337 | |
Yarden Shoham | 27879bc45e | |
Yarden Shoham | a3694b6989 | |
Yarden Shoham | 28625fba5b | |
Yarden Shoham | 7c3196ceac | |
Yarden Shoham | 80c1264f4b | |
silverwind | f0340c28f1 | |
Yarden Shoham | 5beb29ad35 | |
Yarden Shoham | 27e307142b | |
Yarden Shoham | e02e752f68 | |
Yarden Shoham | 5ddf67a9c2 | |
Yarden Shoham | 4d3e2b23b8 | |
Yarden Shoham | ddf61373f6 | |
Yarden Shoham | b4ed3f07e4 | |
HesterG | ced94f2e0d | |
Yarden Shoham | aff432b197 | |
Yarden Shoham | 0ac3be1482 | |
Yarden Shoham | 75eaf99076 | |
wxiaoguang | e67d60d336 |
101
.drone.yml
101
.drone.yml
|
@ -1016,7 +1016,7 @@ steps:
|
|||
- git fetch --tags --force
|
||||
|
||||
- name: publish
|
||||
image: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: true
|
||||
|
@ -1028,13 +1028,17 @@ steps:
|
|||
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: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
auto_tag: true
|
||||
|
@ -1046,6 +1050,10 @@ steps:
|
|||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
|
@ -1080,7 +1088,7 @@ steps:
|
|||
- git fetch --tags --force
|
||||
|
||||
- name: publish
|
||||
image: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
tags: ${DRONE_TAG##v}-linux-amd64
|
||||
|
@ -1091,13 +1099,17 @@ steps:
|
|||
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: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
tags: ${DRONE_TAG##v}-linux-amd64-rootless
|
||||
|
@ -1108,6 +1120,10 @@ steps:
|
|||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
|
@ -1142,7 +1158,7 @@ steps:
|
|||
- git fetch --tags --force
|
||||
|
||||
- name: publish
|
||||
image: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: false
|
||||
|
@ -1154,13 +1170,17 @@ steps:
|
|||
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: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
auto_tag: false
|
||||
|
@ -1172,6 +1192,10 @@ steps:
|
|||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
|
@ -1205,7 +1229,7 @@ steps:
|
|||
- git fetch --tags --force
|
||||
|
||||
- name: publish
|
||||
image: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: false
|
||||
|
@ -1217,13 +1241,17 @@ steps:
|
|||
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: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
auto_tag: false
|
||||
|
@ -1235,6 +1263,10 @@ steps:
|
|||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
|
@ -1243,7 +1275,7 @@ steps:
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-linux-arm64-dry-run
|
||||
name: docker-linux-amd64-dry-run
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
|
@ -1261,7 +1293,7 @@ trigger:
|
|||
|
||||
steps:
|
||||
- name: dryrun
|
||||
image: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
dry_run: true
|
||||
|
@ -1272,6 +1304,7 @@ steps:
|
|||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
|
@ -1308,7 +1341,7 @@ steps:
|
|||
- git fetch --tags --force
|
||||
|
||||
- name: publish
|
||||
image: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: true
|
||||
|
@ -1320,13 +1353,17 @@ steps:
|
|||
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: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
auto_tag: true
|
||||
|
@ -1338,6 +1375,10 @@ steps:
|
|||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
|
@ -1372,7 +1413,7 @@ steps:
|
|||
- git fetch --tags --force
|
||||
|
||||
- name: publish
|
||||
image: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
tags: ${DRONE_TAG##v}-linux-arm64
|
||||
|
@ -1383,13 +1424,17 @@ steps:
|
|||
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: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
tags: ${DRONE_TAG##v}-linux-arm64-rootless
|
||||
|
@ -1400,6 +1445,10 @@ steps:
|
|||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
|
@ -1434,7 +1483,7 @@ steps:
|
|||
- git fetch --tags --force
|
||||
|
||||
- name: publish
|
||||
image: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: false
|
||||
|
@ -1446,13 +1495,17 @@ steps:
|
|||
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: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
auto_tag: false
|
||||
|
@ -1464,6 +1517,10 @@ steps:
|
|||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
|
@ -1497,7 +1554,7 @@ steps:
|
|||
- git fetch --tags --force
|
||||
|
||||
- name: publish
|
||||
image: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: false
|
||||
|
@ -1509,13 +1566,17 @@ steps:
|
|||
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: techknowlogick/drone-docker:latest
|
||||
image: plugins/docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
auto_tag: false
|
||||
|
@ -1527,6 +1588,10 @@ steps:
|
|||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# Emacs
|
||||
*~
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
plugins:
|
||||
- stylelint-declaration-strict-value
|
||||
|
||||
ignoreFiles:
|
||||
- "**/*.go"
|
||||
|
||||
overrides:
|
||||
- files: ["**/*.less"]
|
||||
customSyntax: postcss-less
|
||||
- files: ["**/chroma/*", "**/codemirror/*", "**/standalone/*", "**/console/*"]
|
||||
rules:
|
||||
scale-unlimited/declaration-strict-value: null
|
||||
- files: ["**/chroma/*", "**/codemirror/*"]
|
||||
rules:
|
||||
block-no-empty: null
|
||||
|
||||
rules:
|
||||
alpha-value-notation: null
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
platform: linux/amd64
|
||||
|
||||
when:
|
||||
event: [ push, pull_request, manual ]
|
||||
branch:
|
||||
exclude: [ soft-fork/*/*, soft-fork/*/*/* ]
|
||||
|
||||
variables:
|
||||
- &golang_image 'golang:1.20'
|
||||
- &test_image 'codeberg.org/forgejo/test_env:main'
|
||||
- &goproxy_override ''
|
||||
- &goproxy_setup |-
|
||||
if [ -n "$${GOPROXY_OVERRIDE:-}" ]; then
|
||||
export GOPROXY="$${GOPROXY_OVERRIDE}";
|
||||
echo "Using goproxy from goproxy_override \"$${GOPROXY}\"";
|
||||
elif [ -n "$${GOPROXY_DEFAULT:-}" ]; then
|
||||
export GOPROXY="$${GOPROXY_DEFAULT}";
|
||||
echo "Using goproxy from goproxy_default (secret) not displaying";
|
||||
else
|
||||
export GOPROXY="https://proxy.golang.org,direct";
|
||||
echo "No goproxy overrides or defaults given, using \"$${GOPROXY}\"";
|
||||
fi
|
||||
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/codeberg/gitea
|
||||
|
||||
pipeline:
|
||||
deps-backend:
|
||||
image: *golang_image
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY_OVERRIDE: *goproxy_override
|
||||
secrets:
|
||||
- goproxy_default
|
||||
commands:
|
||||
- *goproxy_setup
|
||||
- make deps-backend
|
||||
|
||||
security-check:
|
||||
image: *golang_image
|
||||
group: checks
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY_OVERRIDE: *goproxy_override
|
||||
secrets:
|
||||
- goproxy_default
|
||||
commands:
|
||||
- *goproxy_setup
|
||||
- make security-check
|
||||
|
||||
lint-backend:
|
||||
image: *test_image
|
||||
pull: true
|
||||
group: checks
|
||||
environment:
|
||||
GOPROXY_OVERRIDE: *goproxy_override
|
||||
TAGS: 'bindata sqlite sqlite_unlock_notify'
|
||||
GOSUMDB: 'sum.golang.org'
|
||||
secrets:
|
||||
- goproxy_default
|
||||
commands:
|
||||
- *goproxy_setup
|
||||
- make lint-backend
|
||||
|
||||
checks-backend:
|
||||
image: *test_image
|
||||
group: checks
|
||||
environment:
|
||||
GOPROXY_OVERRIDE: *goproxy_override
|
||||
secrets:
|
||||
- goproxy_default
|
||||
commands:
|
||||
- *goproxy_setup
|
||||
- make --always-make checks-backend
|
|
@ -0,0 +1,141 @@
|
|||
platform: linux/amd64
|
||||
|
||||
when:
|
||||
event: [ push, pull_request, manual ]
|
||||
branch:
|
||||
exclude: [ soft-fork/*/*, soft-fork/*/*/* ]
|
||||
|
||||
depends_on:
|
||||
- compliance
|
||||
|
||||
variables:
|
||||
- &golang_image 'golang:1.20'
|
||||
- &test_image 'codeberg.org/forgejo/test_env:main'
|
||||
- &mysql_image 'mysql:8'
|
||||
- &pgsql_image 'postgres:10'
|
||||
- &goproxy_override ''
|
||||
- &goproxy_setup |-
|
||||
if [ -n "$${GOPROXY_OVERRIDE:-}" ]; then
|
||||
export GOPROXY="$${GOPROXY_OVERRIDE}";
|
||||
echo "Using goproxy from goproxy_override \"$${GOPROXY}\"";
|
||||
elif [ -n "$${GOPROXY_DEFAULT:-}" ]; then
|
||||
export GOPROXY="$${GOPROXY_DEFAULT}";
|
||||
echo "Using goproxy from goproxy_default (secret) not displaying";
|
||||
else
|
||||
export GOPROXY="https://proxy.golang.org,direct";
|
||||
echo "No goproxy overrides or defaults given, using \"$${GOPROXY}\"";
|
||||
fi
|
||||
|
||||
services:
|
||||
mysql8:
|
||||
image: *mysql_image
|
||||
pull: true
|
||||
environment:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_DATABASE: testgitea
|
||||
|
||||
pgsql:
|
||||
image: *pgsql_image
|
||||
pull: true
|
||||
environment:
|
||||
POSTGRES_DB: test
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/codeberg/gitea
|
||||
|
||||
pipeline:
|
||||
git-safe:
|
||||
image: *golang_image
|
||||
pull: true
|
||||
commands:
|
||||
- git config --add safe.directory '*'
|
||||
|
||||
deps-backend:
|
||||
image: *golang_image
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY_OVERRIDE: *goproxy_override
|
||||
secrets:
|
||||
- goproxy_default
|
||||
commands:
|
||||
- *goproxy_setup
|
||||
- make deps-backend
|
||||
|
||||
tag-pre-condition:
|
||||
image: *golang_image
|
||||
pull: true
|
||||
commands:
|
||||
- git update-ref refs/heads/tag_test ${CI_COMMIT_SHA}
|
||||
|
||||
prepare-test-env:
|
||||
image: *test_image
|
||||
pull: true
|
||||
commands:
|
||||
- ./build/test-env-prepare.sh
|
||||
|
||||
build:
|
||||
image: *test_image
|
||||
environment:
|
||||
GOSUMDB: sum.golang.org
|
||||
TAGS: bindata sqlite sqlite_unlock_notify
|
||||
GOPROXY_OVERRIDE: *goproxy_override
|
||||
secrets:
|
||||
- goproxy_default
|
||||
commands:
|
||||
- *goproxy_setup
|
||||
- su gitea -c './build/test-env-check.sh'
|
||||
- su gitea -c 'make backend'
|
||||
|
||||
unit-test:
|
||||
image: *test_image
|
||||
environment:
|
||||
TAGS: 'bindata sqlite sqlite_unlock_notify'
|
||||
RACE_ENABLED: 'true'
|
||||
GOPROXY_OVERRIDE: *goproxy_override
|
||||
secrets:
|
||||
- github_read_token
|
||||
- goproxy_default
|
||||
commands:
|
||||
- *goproxy_setup
|
||||
- su gitea -c 'make unit-test-coverage test-check'
|
||||
|
||||
test-mysql8:
|
||||
group: integration
|
||||
image: *test_image
|
||||
commands:
|
||||
- *goproxy_setup
|
||||
- su gitea -c 'timeout -s ABRT 50m make test-mysql8-migration test-mysql8'
|
||||
environment:
|
||||
TAGS: 'bindata'
|
||||
RACE_ENABLED: 'true'
|
||||
USE_REPO_TEST_DIR: '1'
|
||||
GOPROXY_OVERRIDE: *goproxy_override
|
||||
secrets:
|
||||
- goproxy_default
|
||||
|
||||
test-pgsql:
|
||||
group: integration
|
||||
image: *test_image
|
||||
commands:
|
||||
- *goproxy_setup
|
||||
- su gitea -c 'timeout -s ABRT 50m make test-pgsql-migration test-pgsql'
|
||||
environment:
|
||||
TAGS: 'bindata'
|
||||
RACE_ENABLED: 'true'
|
||||
USE_REPO_TEST_DIR: '1'
|
||||
GOPROXY_OVERRIDE: *goproxy_override
|
||||
secrets:
|
||||
- goproxy_default
|
||||
|
||||
test-sqlite:
|
||||
group: integration
|
||||
image: *test_image
|
||||
environment:
|
||||
- USE_REPO_TEST_DIR=1
|
||||
- GOPROXY=off
|
||||
- TAGS=bindata gogit sqlite sqlite_unlock_notify
|
||||
- TEST_TAGS=bindata gogit sqlite sqlite_unlock_notify
|
||||
commands:
|
||||
- su gitea -c 'timeout -s ABRT 120m make test-sqlite-migration test-sqlite'
|
|
@ -24,7 +24,7 @@ RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
|
|||
RUN go build contrib/environment-to-ini/environment-to-ini.go
|
||||
|
||||
FROM alpine:3.17
|
||||
LABEL maintainer="maintainers@gitea.io"
|
||||
LABEL maintainer="contact@forgejo.org"
|
||||
|
||||
EXPOSE 22 3000
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
|
|||
RUN go build contrib/environment-to-ini/environment-to-ini.go
|
||||
|
||||
FROM alpine:3.17
|
||||
LABEL maintainer="maintainers@gitea.io"
|
||||
LABEL maintainer="contact@forgejo.org"
|
||||
|
||||
EXPOSE 2222 3000
|
||||
|
||||
|
|
15
Makefile
15
Makefile
|
@ -83,7 +83,7 @@ ifneq ($(DRONE_TAG),)
|
|||
GITEA_VERSION ?= $(VERSION)
|
||||
else
|
||||
ifneq ($(DRONE_BRANCH),)
|
||||
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
|
||||
VERSION ?= $(shell echo $(DRONE_BRANCH) | sed -e 's|v\([0-9.][0-9.]*\)/.*|\1|')
|
||||
else
|
||||
VERSION ?= main
|
||||
endif
|
||||
|
@ -286,8 +286,7 @@ misspell-check:
|
|||
.PHONY: vet
|
||||
vet:
|
||||
@echo "Running go vet..."
|
||||
@GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet
|
||||
@$(GO) vet -vettool=gitea-vet $(GO_PACKAGES)
|
||||
@$(GO) vet $(GO_PACKAGES)
|
||||
|
||||
.PHONY: $(TAGS_EVIDENCE)
|
||||
$(TAGS_EVIDENCE):
|
||||
|
@ -751,7 +750,7 @@ $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
|
|||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
|
||||
|
||||
.PHONY: release
|
||||
release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-docs release-check
|
||||
release: frontend generate release-linux release-copy release-compress vendor release-sources release-check
|
||||
|
||||
$(DIST_DIRS):
|
||||
mkdir -p $(DIST_DIRS)
|
||||
|
@ -768,7 +767,7 @@ 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) .
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out forgejo-$(VERSION) .
|
||||
ifeq ($(CI),true)
|
||||
cp /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
@ -805,8 +804,8 @@ release-sources: | $(DIST_DIRS)
|
|||
# bsdtar needs a ^ to prevent matching subdirectories
|
||||
$(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./)
|
||||
# use transform to a add a release-folder prefix; in bsdtar the transform parameter equivalent is -s
|
||||
$(eval TRANSFORM := $(shell tar --help | grep -q bsdtar && echo "-s '/^./gitea-src-$(VERSION)/'" || echo "--transform 's|^./|gitea-src-$(VERSION)/|'"))
|
||||
tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) $(TRANSFORM) -czf $(DIST)/release/gitea-src-$(VERSION).tar.gz .
|
||||
$(eval TRANSFORM := $(shell tar --help | grep -q bsdtar && echo "-s '/^./forgejo-src-$(VERSION)/'" || echo "--transform 's|^./|forgejo-src-$(VERSION)/|'"))
|
||||
tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) $(TRANSFORM) -czf $(DIST)/release/forgejo-src-$(VERSION).tar.gz .
|
||||
rm -f $(STORED_VERSION_FILE)
|
||||
|
||||
.PHONY: release-docs
|
||||
|
@ -859,6 +858,8 @@ fomantic:
|
|||
cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
|
||||
cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
|
||||
cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build
|
||||
# fomantic uses "touchstart" as click event for some browsers, it's not ideal, so we force fomantic to always use "click" as click event
|
||||
$(SED_INPLACE) -e 's/clickEvent[ \t]*=/clickEvent = "click", unstableClickEvent =/g' $(FOMANTIC_WORK_DIR)/build/semantic.js
|
||||
$(SED_INPLACE) -e 's/\r//g' $(FOMANTIC_WORK_DIR)/build/semantic.css $(FOMANTIC_WORK_DIR)/build/semantic.js
|
||||
rm -f $(FOMANTIC_WORK_DIR)/build/*.min.*
|
||||
|
||||
|
|
11
cmd/admin.go
11
cmd/admin.go
|
@ -7,6 +7,7 @@ package cmd
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
@ -469,11 +470,19 @@ func runAddOauth(c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
config := parseOAuth2Config(c)
|
||||
if config.Provider == "openidConnect" {
|
||||
discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL)
|
||||
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
|
||||
return fmt.Errorf("invalid Auto Discovery URL: %s (this must be a valid URL starting with http:// or https://)", config.OpenIDConnectAutoDiscoveryURL)
|
||||
}
|
||||
}
|
||||
|
||||
return auth_model.CreateSource(&auth_model.Source{
|
||||
Type: auth_model.OAuth2,
|
||||
Name: c.String("name"),
|
||||
IsActive: true,
|
||||
Cfg: parseOAuth2Config(c),
|
||||
Cfg: config,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ func runConvert(ctx *cli.Context) error {
|
|||
log.Info("Log path: %s", setting.Log.RootPath)
|
||||
log.Info("Configuration file: %s", setting.CustomConf)
|
||||
|
||||
if !setting.Database.UseMySQL {
|
||||
if !setting.Database.Type.IsMySQL() {
|
||||
fmt.Println("This command can only be used with a MySQL database")
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -272,13 +272,14 @@ func runDump(ctx *cli.Context) error {
|
|||
fatal("Failed to create tmp file: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = dbDump.Close()
|
||||
if err := util.Remove(dbDump.Name()); err != nil {
|
||||
log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err)
|
||||
}
|
||||
}()
|
||||
|
||||
targetDBType := ctx.String("database")
|
||||
if len(targetDBType) > 0 && targetDBType != setting.Database.Type {
|
||||
if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() {
|
||||
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
|
||||
} else {
|
||||
log.Info("Dumping database...")
|
||||
|
|
25
cmd/serv.go
25
cmd/serv.go
|
@ -11,6 +11,7 @@ import (
|
|||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -290,17 +291,21 @@ func runServ(c *cli.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Special handle for Windows.
|
||||
if setting.IsWindows {
|
||||
verb = strings.Replace(verb, "-", " ", 1)
|
||||
}
|
||||
|
||||
var gitcmd *exec.Cmd
|
||||
verbs := strings.Split(verb, " ")
|
||||
if len(verbs) == 2 {
|
||||
gitcmd = exec.CommandContext(ctx, verbs[0], verbs[1], repoPath)
|
||||
} else {
|
||||
gitcmd = exec.CommandContext(ctx, verb, repoPath)
|
||||
gitBinPath := filepath.Dir(git.GitExecutable) // e.g. /usr/bin
|
||||
gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack
|
||||
if _, err := os.Stat(gitBinVerb); err != nil {
|
||||
// if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git
|
||||
// ps: Windows only has "git.exe" in the bin path, so Windows always uses this way
|
||||
verbFields := strings.SplitN(verb, "-", 2)
|
||||
if len(verbFields) == 2 {
|
||||
// use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ...
|
||||
gitcmd = exec.CommandContext(ctx, git.GitExecutable, verbFields[1], repoPath)
|
||||
}
|
||||
}
|
||||
if gitcmd == nil {
|
||||
// by default, use the verb (it has been checked above by allowedCommands)
|
||||
gitcmd = exec.CommandContext(ctx, gitBinVerb, repoPath)
|
||||
}
|
||||
|
||||
process.SetSysProcAttribute(gitcmd)
|
||||
|
|
|
@ -1871,6 +1871,9 @@ ROUTER = console
|
|||
;;
|
||||
;; 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
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -2552,6 +2555,9 @@ ROUTER = console
|
|||
;;
|
||||
;; 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
|
||||
|
||||
;[proxy]
|
||||
;; Enable the proxy, all requests to external via HTTP will be affected
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-rootless
|
||||
{{#if build.tags}}
|
||||
{{#unless contains "-rc" build.tag}}
|
||||
{{#unless (contains "-rc" build.tag)}}
|
||||
tags:
|
||||
{{#each build.tags}}
|
||||
- {{this}}-rootless
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}
|
||||
{{#if build.tags}}
|
||||
{{#unless contains "-rc" build.tag }}
|
||||
{{#unless (contains "-rc" build.tag)}}
|
||||
tags:
|
||||
{{#each build.tags}}
|
||||
- {{this}}
|
||||
|
|
|
@ -854,6 +854,7 @@ Default templates for project boards:
|
|||
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when STORAGE_TYPE is `minio`
|
||||
- `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio`
|
||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when STORAGE_TYPE is `minio`
|
||||
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
||||
|
||||
## Log (`log`)
|
||||
|
||||
|
@ -1268,6 +1269,7 @@ is `data/lfs` and the default of `MINIO_BASE_PATH` is `lfs/`.
|
|||
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_BASE_PATH`: **lfs/**: Minio base path on the bucket only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
||||
|
||||
## Storage (`storage`)
|
||||
|
||||
|
@ -1280,6 +1282,7 @@ Default storage configuration for attachments, lfs, avatars and etc.
|
|||
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the data only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
||||
|
||||
And you can also define a customize storage like below:
|
||||
|
||||
|
@ -1298,6 +1301,8 @@ MINIO_BUCKET = gitea
|
|||
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
|
||||
```
|
||||
|
||||
And used by `[attachment]`, `[lfs]` and etc. as `STORAGE_TYPE`.
|
||||
|
@ -1318,6 +1323,7 @@ is `data/repo-archive` and the default of `MINIO_BASE_PATH` is `repo-archive/`.
|
|||
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_BASE_PATH`: **repo-archive/**: Minio base path on the bucket only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
||||
|
||||
## Proxy (`proxy`)
|
||||
|
||||
|
|
|
@ -431,6 +431,8 @@ MINIO_BUCKET = gitea
|
|||
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
|
||||
```
|
||||
|
||||
然后你在 `[attachment]`, `[lfs]` 等中可以把这个名字用作 `STORAGE_TYPE` 的值。
|
||||
|
|
|
@ -282,9 +282,22 @@ To add custom .gitignore, add a file with existing [.gitignore rules](https://gi
|
|||
|
||||
### Labels
|
||||
|
||||
To add a custom label set, add a file that follows the [label format](https://github.com/go-gitea/gitea/blob/main/options/label/Default) to `$GITEA_CUSTOM/options/label`
|
||||
Starting with Gitea 1.19, you can add a file that follows the [YAML label format](https://github.com/go-gitea/gitea/blob/main/options/label/Advanced.yaml) to `$GITEA_CUSTOM/options/label`:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- name: "foo/bar" # name of the label that will appear in the dropdown
|
||||
exclusive: true # whether to use the exclusive namespace for scoped labels. scoped delimiter is /
|
||||
color: aabbcc # hex colour coding
|
||||
description: Some label # long description of label intent
|
||||
```
|
||||
|
||||
The [legacy file format](https://github.com/go-gitea/gitea/blob/main/options/label/Default) can still be used following the format below, however we strongly recommend using the newer YAML format instead.
|
||||
|
||||
`#hex-color label name ; label description`
|
||||
|
||||
For more information, see the [labels documentation]({{< relref "doc/usage/labels.en-us.md" >}}).
|
||||
|
||||
### Licenses
|
||||
|
||||
To add a custom license, add a file with the license text to `$GITEA_CUSTOM/options/license`
|
||||
|
|
|
@ -6,11 +6,11 @@ weight: 20
|
|||
toc: false
|
||||
draft: false
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "developers"
|
||||
name: "Guidelines for Refactoring"
|
||||
weight: 20
|
||||
identifier: "guidelines-refactoring"
|
||||
sidebar:
|
||||
parent: "developers"
|
||||
name: "Guidelines for Refactoring"
|
||||
weight: 20
|
||||
identifier: "guidelines-refactoring"
|
||||
---
|
||||
|
||||
# Guidelines for Refactoring
|
||||
|
|
|
@ -0,0 +1,349 @@
|
|||
---
|
||||
date: "2016-12-01T16:00:00+02:00"
|
||||
title: "玩转 Gitea"
|
||||
slug: "hacking-on-gitea"
|
||||
weight: 10
|
||||
toc: false
|
||||
draft: false
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "developers"
|
||||
name: "玩转 Gitea"
|
||||
weight: 10
|
||||
identifier: "hacking-on-gitea"
|
||||
---
|
||||
|
||||
# Hacking on Gitea
|
||||
|
||||
**目录**
|
||||
|
||||
{{< toc >}}
|
||||
|
||||
## 快速入门
|
||||
|
||||
要获得快速工作的开发环境,您可以使用 Gitpod。
|
||||
|
||||
[![在 Gitpod 中打开](/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
||||
|
||||
## 安装 Golang
|
||||
|
||||
您需要 [安装 go]( https://golang.org/doc/install ) 并设置您的 go 环境。
|
||||
|
||||
接下来,[使用 npm 安装 Node.js](https://nodejs.org/en/download/) ,这是构建
|
||||
JavaScript 和 CSS 文件的必要工具。最低支持的 Node.js 版本是 {{< min-node-version >}}
|
||||
并且推荐使用最新的 LTS 版本。
|
||||
|
||||
**注意** :当执行需要外部工具的 make 任务时,比如
|
||||
`make watch-backend`,Gitea 会自动下载并构建这些必要的组件。为了能够使用这些,你必须
|
||||
将 `"$GOPATH"/bin` 目录加入到可执行路径上。如果你不把go bin目录添加到可执行路径你必须手动
|
||||
指定可执行程序路径。
|
||||
|
||||
**注意2** :Go版本 {{< min-go-version >}} 或更高版本是必须的。Gitea 使用 `gofmt` 来
|
||||
格式化源代码。然而,`gofmt` 的结果可能因 `go` 的版本而有差异。因此推荐安装我们持续集成使用
|
||||
的 Go版本。截至上次更新,Go 版本应该是 {{< go-version >}}。
|
||||
|
||||
## 安装 Make
|
||||
|
||||
Gitea 大量使用 `Make` 来自动化任务和改进开发。本指南涵盖了如何安装 Make。
|
||||
|
||||
### 在 Linux 上
|
||||
|
||||
使用包管理器安装。
|
||||
|
||||
在 Ubuntu/Debian 上:
|
||||
|
||||
```bash
|
||||
sudo apt-get install make
|
||||
```
|
||||
|
||||
在 Fedora/RHEL/CentOS 上:
|
||||
|
||||
```bash
|
||||
sudo yum install make
|
||||
```
|
||||
|
||||
### 在 Windows 上
|
||||
|
||||
Make 的这三个发行版都可以在 Windows 上运行:
|
||||
|
||||
- [单个二进制构建]( http://www.equation.com/servlet/equation.cmd?fa=make )。复制到某处并添加到 `PATH`。
|
||||
- [32 位版本](http://www.equation.com/ftpdir/make/32/make.exe)
|
||||
- [64 位版本](http://www.equation.com/ftpdir/make/64/make.exe)
|
||||
- [MinGW-w64](https://www.mingw-w64.org) / [MSYS2](https://www.msys2.org/)。
|
||||
- MSYS2 是一个工具和库的集合,为您提供一个易于使用的环境来构建、安装和运行本机 Windows 软件,它包括 MinGW-w64。
|
||||
- 在 MingGW-w64 中,二进制文件称为 `mingw32-make.exe` 而不是 `make.exe`。将 `bin` 文件夹添加到 `PATH`。
|
||||
- 在 MSYS2 中,您可以直接使用 `make`。请参阅 [MSYS2 移植](https://www.msys2.org/wiki/Porting/)。
|
||||
- 要使用 CGO_ENABLED(例如:SQLite3)编译 Gitea,您可能需要使用 [tdm-gcc](https://jmeubank.github.io/tdm-gcc/) 而不是 MSYS2 gcc,因为 MSYS2 gcc 标头缺少一些 Windows -只有 CRT 函数像 _beginthread 一样。
|
||||
- [Chocolatey包管理器]( https://chocolatey.org/packages/make )。运行`choco install make`
|
||||
|
||||
**注意** :如果您尝试在 Windows 命令提示符下使用 make 进行构建,您可能会遇到问题。建议使用上述提示(Git bash 或 MinGW),但是如果您只有命令提示符(或可能是 PowerShell),则可以使用 [set](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/set_1) 命令,例如 `set TAGS=bindata`。
|
||||
|
||||
## 下载并克隆 Gitea 源代码
|
||||
|
||||
获取源代码的推荐方法是使用 `git clone`。
|
||||
|
||||
```bash
|
||||
git clone https://github.com/go-gitea/gitea
|
||||
```
|
||||
|
||||
(自从go modules出现后,不再需要构建 go 项目从 `$GOPATH` 中获取,因此不再推荐使用 `go get` 方法。)
|
||||
|
||||
## 派生 Gitea
|
||||
|
||||
如上所述下载主要的 Gitea 源代码。然后,派生 [Gitea 仓库](https://github.com/go-gitea/gitea),
|
||||
并为您的本地仓库切换 git 远程源,或添加另一个远程源:
|
||||
|
||||
```bash
|
||||
# 将原来的 Gitea origin 重命名为 upstream
|
||||
git remote rename origin upstream
|
||||
git remote add origin "git@github.com:$GITHUB_USERNAME/gitea.git"
|
||||
git fetch --all --prune
|
||||
```
|
||||
|
||||
或者:
|
||||
|
||||
```bash
|
||||
# 为我们的 fork 添加新的远程
|
||||
git remote add "$FORK_NAME" "git@github.com:$GITHUB_USERNAME/gitea.git"
|
||||
git fetch --all --prune
|
||||
```
|
||||
|
||||
为了能够创建合并请求,应将分叉存储库添加为 Gitea 本地仓库的远程,否则无法推送更改。
|
||||
|
||||
## 构建 Gitea(基本)
|
||||
|
||||
看看我们的
|
||||
<a href='{{ < relref "doc/installation/from-source.en-us.md" > }}'>说明</a>
|
||||
关于如何 <a href='{{ < relref "doc/installation/from-source.en-us.md" > }}'>从源代码构建</a> 。
|
||||
|
||||
从源代码构建的最简单推荐方法是:
|
||||
|
||||
```bash
|
||||
TAGS="bindata sqlite sqlite_unlock_notify" make build
|
||||
```
|
||||
|
||||
`build` 目标将同时执行 `frontend` 和 `backend` 子目标。如果存在 `bindata` 标签,资源文件将被编译成二进制文件。建议在进行前端开发时省略 `bindata` 标签,以便实时反映更改。
|
||||
|
||||
有关所有可用的 `make` 目标,请参阅 `make help`。另请参阅 [`.drone.yml`](https://github.com/go-gitea/gitea/blob/main/.drone.yml) 以了解我们的持续集成是如何工作的。
|
||||
|
||||
## 持续构建
|
||||
|
||||
要在源文件更改时运行并持续构建:
|
||||
|
||||
```bash
|
||||
# 对于前端和后端
|
||||
make watch
|
||||
|
||||
# 或者:只看前端文件(html/js/css)
|
||||
make watch-frontend
|
||||
|
||||
# 或者:只看后端文件 (go)
|
||||
make watch-backend
|
||||
```
|
||||
|
||||
在 macOS 上,监视所有后端源文件可能会达到默认的打开文件限制,这可以通过当前 shell 的 `ulimit -n 12288` 或所有未来 shell 的 shell 启动文件来增加。
|
||||
|
||||
### 格式化、代码分析和拼写检查
|
||||
|
||||
我们的持续集成将拒绝未通过代码检查(包括格式检查、代码分析和拼写检查)的 PR。
|
||||
|
||||
你应该格式化你的代码:
|
||||
|
||||
```bash
|
||||
make fmt
|
||||
```
|
||||
|
||||
并检查源代码:
|
||||
|
||||
```bash
|
||||
# lint 前端和后端代码
|
||||
make lint
|
||||
# 仅 lint 后端代码
|
||||
make lint-backend
|
||||
```
|
||||
|
||||
**注意** :`gofmt` 的结果取决于 `go` 的版本。您应该运行与持续集成相同的 go 版本。
|
||||
|
||||
### 处理 JS 和 CSS
|
||||
|
||||
前端开发应遵循 [Guidelines for Frontend Development]({{ < 相关参考 "doc/developers/guidelines-frontend.en-us.md" > }})
|
||||
|
||||
要使用前端资源构建,请使用上面提到的“watch-frontend”目标或只构建一次:
|
||||
|
||||
```bash
|
||||
make build && ./gitea
|
||||
```
|
||||
|
||||
在提交之前,确保 linters 通过:
|
||||
|
||||
```bash
|
||||
make lint-frontend
|
||||
```
|
||||
|
||||
### 配置本地 ElasticSearch 实例
|
||||
|
||||
使用 docker 启动本地 ElasticSearch 实例:
|
||||
|
||||
```sh
|
||||
mkdir -p $(pwd) /data/elasticsearch
|
||||
sudo chown -R 1000:1000 $(pwd) /data/elasticsearch
|
||||
docker run --rm --memory= "4g" -p 127.0.0.1:9200:9200 -p 127.0.0.1:9300:9300 -e "discovery.type=single-node" -v "$(pwd)/data /elasticsearch:/usr/share/elasticsearch/data" docker.elastic.co/elasticsearch/elasticsearch:7.16.3
|
||||
```
|
||||
|
||||
配置`app.ini`:
|
||||
|
||||
```ini
|
||||
[indexer]
|
||||
ISSUE_INDEXER_TYPE = elasticsearch
|
||||
ISSUE_INDEXER_CONN_STR = http://elastic:changeme@localhost:9200
|
||||
REPO_INDEXER_ENABLED = true
|
||||
REPO_INDEXER_TYPE = elasticsearch
|
||||
REPO_INDEXER_CONN_STR = http://elastic:changeme@localhost:9200
|
||||
```
|
||||
|
||||
### 构建和添加 SVGs
|
||||
|
||||
SVG 图标是使用 `make svg` 目标构建的,该目标将 `build/generate-svg.js` 中定义的图标源编译到输出目录 `public/img/svg` 中。可以在 `web_src/svg` 目录中添加自定义图标。
|
||||
|
||||
### 构建 Logo
|
||||
|
||||
Gitea Logo的 PNG 和 SVG 版本是使用 `TAGS="gitea" make generate-images` 目标从单个 SVG 源文件 assets/logo.svg 构建的。要运行它,Node.js 和 npm 必须可用。
|
||||
|
||||
通过更新 `assets/logo.svg` 并运行 `make generate-images`,同样的过程也可用于从 SVG 源文件生成自定义 Logo PNG。忽略 gitea 编译选项将仅更新用户指定的 LOGO 文件。
|
||||
|
||||
### 更新 API
|
||||
|
||||
创建新的 API 路由或修改现有的 API 路由时,您**必须**
|
||||
更新和/或创建 [Swagger](https://swagger.io/docs/specification/2-0/what-is-swagger/)
|
||||
这些使用 [go-swagger](https://goswagger.io/) 评论的文档。
|
||||
[规范]( https://goswagger.io/use/spec.html#annotation-syntax )中描述了这些注释的结构。
|
||||
如果您想了解更多有关 Swagger 结构的信息,可以查看
|
||||
[Swagger 2.0 文档](https://swagger.io/docs/specification/2-0/basic-structure/)
|
||||
或与添加新 API 端点的先前 PR 进行比较,例如 [PR #5483](https://github.com/go-gitea/gitea/pull/5843/files#diff-2e0a7b644cf31e1c8ef7d76b444fe3aaR20)
|
||||
|
||||
您应该注意不要破坏下游用户依赖的 API。在稳定的 API 上,一般来说添加是可以接受的,但删除
|
||||
或对 API 进行根本性更改将会被拒绝。
|
||||
|
||||
创建或更改 API 端点后,请用以下命令重新生成 Swagger 文档:
|
||||
|
||||
```bash
|
||||
make generate-swagger
|
||||
```
|
||||
|
||||
您应该验证生成的 Swagger 文件并使用以下命令对其进行拼写检查:
|
||||
|
||||
```bash
|
||||
make swagger-validate misspell-check
|
||||
```
|
||||
|
||||
您应该提交更改后的 swagger JSON 文件。持续集成服务器将使用以下方法检查是否已完成:
|
||||
|
||||
```bash
|
||||
make swagger-check
|
||||
```
|
||||
|
||||
**注意** :请注意,您应该使用 Swagger 2.0 文档,而不是 OpenAPI 3 文档。
|
||||
|
||||
### 创建新的配置选项
|
||||
|
||||
创建新的配置选项时,将它们添加到 `modules/setting` 的对应文件。您应该将信息添加到 `custom/conf/app.ini`
|
||||
并到 <a href = '{{ < relref "doc/advanced/config-cheat-sheet.en-us.md" > }}'>配置备忘单</a>
|
||||
在 `docs/content/doc/advanced/config-cheat-sheet.en-us.md` 中找到
|
||||
|
||||
### 更改Logo
|
||||
|
||||
更改 Gitea Logo SVG 时,您将需要运行并提交结果的:
|
||||
|
||||
```bash
|
||||
make generate-images
|
||||
```
|
||||
|
||||
这将创建必要的 Gitea 图标和其他图标。
|
||||
|
||||
### 数据库迁移
|
||||
|
||||
如果您对数据库中的任何数据库持久结构进行重大更改
|
||||
`models/` 目录,您将需要进行新的迁移。可以找到这些
|
||||
在 `models/migrations/` 中。您可以确保您的迁移适用于主要
|
||||
数据库类型使用:
|
||||
|
||||
```bash
|
||||
make test-sqlite-migration # 将 SQLite 切换为适当的数据库
|
||||
```
|
||||
|
||||
## 测试
|
||||
|
||||
Gitea 运行两种类型的测试:单元测试和集成测试。
|
||||
|
||||
### 单元测试
|
||||
|
||||
`go test` 系统中的`*_test.go` 涵盖了单元测试。
|
||||
您可以设置环境变量 `GITEA_UNIT_TESTS_LOG_SQL=1` 以在详细模式下运行测试时显示所有 SQL 语句(即设置`GOTESTFLAGS=-v` 时)。
|
||||
|
||||
```bash
|
||||
TAGS="bindata sqlite sqlite_unlock_notify" make test # Runs the unit tests
|
||||
```
|
||||
|
||||
### 集成测试
|
||||
|
||||
单元测试不会也不能完全单独测试 Gitea。因此,我们编写了集成测试;但是,这些依赖于数据库。
|
||||
|
||||
```bash
|
||||
TAGS="bindata sqlite sqlite_unlock_notify" make build test-sqlite
|
||||
```
|
||||
|
||||
将在 SQLite 环境中运行集成测试。集成测试需要安装 `git lfs`。其他数据库测试可用,但
|
||||
可能需要适应当地环境。
|
||||
|
||||
看看 [`tests/integration/README.md`](https://github.com/go-gitea/gitea/blob/main/tests/integration/README.md) 有关更多信息以及如何运行单个测试。
|
||||
|
||||
### 测试 PR
|
||||
|
||||
我们的持续集成将测试代码是否通过了单元测试,并且所有支持的数据库都将在 Docker 环境中通过集成测试。
|
||||
还将测试从几个最新版本的 Gitea 迁移。
|
||||
|
||||
请在PR中附带提交适当的单元测试和集成测试。
|
||||
|
||||
## 网站文档
|
||||
|
||||
该网站的文档位于 `docs/` 中。如果你改变了文档内容,你可以使用以下测试方法进行持续集成:
|
||||
|
||||
```bash
|
||||
# 来自 Gitea 中的 docs 目录
|
||||
make trans-copy clean build
|
||||
```
|
||||
|
||||
运行此任务依赖于 [Hugo](https://gohugo.io/)。请注意:这可能会生成一些未跟踪的 Git 对象,
|
||||
需要被清理干净。
|
||||
|
||||
## Visual Studio Code
|
||||
|
||||
`contrib/ide/vscode` 中为 Visual Studio Code 提供了 `launch.json` 和 `tasks.json`。查看
|
||||
[`contrib/ide/README.md`](https://github.com/go-gitea/gitea/blob/main/contrib/ide/README.md) 了解更多信息。
|
||||
|
||||
## Goland
|
||||
|
||||
单击 `/main.go` 中函数 `func main()` 上的 `Run Application` 箭头
|
||||
可以快速启动一个可调试的 Gitea 实例。
|
||||
|
||||
`Run/Debug Configuration` 中的 `Output Directory` 必须设置为
|
||||
gitea 项目目录(包含 `main.go` 和 `go.mod`),
|
||||
否则,启动实例的工作目录是 GoLand 的临时目录
|
||||
并防止 Gitea 在开发环境中加载动态资源(例如:模板)。
|
||||
|
||||
要在 GoLand 中使用 SQLite 运行单元测试,请设置 `-tags sqlite,sqlite_unlock_notify`
|
||||
在 `运行/调试配置` 的 `Go 工具参数` 中。
|
||||
|
||||
## 提交 PR
|
||||
|
||||
对更改感到满意后,将它们推送并打开拉取请求。它建议您允许 Gitea Managers 和 Owners 修改您的 PR
|
||||
分支,因为我们需要在合并之前将其更新为 main 和/或可能是能够直接帮助解决问题。
|
||||
|
||||
任何 PR 都需要 Gitea 维护者的两次批准,并且需要通过持续集成。看看我们的
|
||||
[CONTRIBUTING.md](https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md)
|
||||
文档。
|
||||
|
||||
如果您需要更多帮助,请访问 [Discord](https://discord.gg/gitea) #Develop 频道
|
||||
并在那里聊天。
|
||||
|
||||
现在,您已准备好 Hacking Gitea。
|
|
@ -9,7 +9,7 @@ menu:
|
|||
parent: "packages"
|
||||
name: "Overview"
|
||||
weight: 1
|
||||
identifier: "overview"
|
||||
identifier: "packages-overview"
|
||||
---
|
||||
|
||||
# Package Registry
|
||||
|
|
|
@ -9,7 +9,7 @@ menu:
|
|||
parent: "secrets"
|
||||
name: "Overview"
|
||||
weight: 1
|
||||
identifier: "overview"
|
||||
identifier: "secrets-overview"
|
||||
---
|
||||
|
||||
# Secrets
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
date: "2023-03-04T19:00:00+00:00"
|
||||
title: "Usage: Labels"
|
||||
slug: "labels"
|
||||
weight: 13
|
||||
toc: false
|
||||
draft: false
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "usage"
|
||||
name: "Labels"
|
||||
weight: 13
|
||||
identifier: "labels"
|
||||
---
|
||||
|
||||
# Labels
|
||||
|
||||
You can use labels to classify issues and pull requests and to improve your overview over them.
|
||||
|
||||
## Creating Labels
|
||||
|
||||
For repositories, labels can be created by going to `Issues` and clicking on `Labels`.
|
||||
|
||||
For organizations, you can define organization-wide labels that are shared with all organization repositories, including both already-existing repositories as well as newly created ones. Organization-wide labels can be created in the organization `Settings`.
|
||||
|
||||
Labels have a mandatory name, a mandatory color, an optional description, and must either be exclusive or not (see `Scoped labels` below).
|
||||
|
||||
When you create a repository, you can ensure certain labels exist by using the `Issue Labels` option. This option lists a number of available label sets that are [configured globally on your instance](../customizing-gitea/#labels). Its contained labels will all be created as well while creating the repository.
|
||||
|
||||
## Scoped Labels
|
||||
|
||||
A scoped label is a label that contains `/` in its name (not at either end of the name). For example labels `kind/bug` and `kind/enhancement` both have scope `kind`. Such labels will display the scope with slightly darker color.
|
||||
|
||||
The scope of a label is determined based on the **last** `/`, so for example the scope of label `scope/subscope/item` is `scope/subscope`.
|
||||
|
||||
Scoped labels can be marked as exclusive. This ensures at most a single label with the same scope is assigned to an issue or pull request. For example, if `kind/bug` and `kind/enhancement` are marked exclusive, an issue can only be classified as a bug or an enhancement.
|
||||
|
||||
## Filtering by Label
|
||||
|
||||
Issue and pull request lists can be filtered by label. Selecting multiple labels shows issues and pull requests that have all selected labels assigned.
|
||||
|
||||
By holding alt to click the label, issues and pull requests with the chosen label are excluded from the list.
|
|
@ -194,6 +194,7 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
|
|||
if len(needs) > 0 {
|
||||
status = StatusBlocked
|
||||
}
|
||||
job.Name, _ = util.SplitStringAtByteN(job.Name, 255)
|
||||
runJobs = append(runJobs, &ActionRunJob{
|
||||
RunID: run.ID,
|
||||
RepoID: run.RepoID,
|
||||
|
|
|
@ -298,8 +298,9 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
|
|||
if len(workflowJob.Steps) > 0 {
|
||||
steps := make([]*ActionTaskStep, len(workflowJob.Steps))
|
||||
for i, v := range workflowJob.Steps {
|
||||
name, _ := util.SplitStringAtByteN(v.String(), 255)
|
||||
steps[i] = &ActionTaskStep{
|
||||
Name: v.String(),
|
||||
Name: name,
|
||||
TaskID: task.ID,
|
||||
Index: int64(i),
|
||||
RepoID: task.RepoID,
|
||||
|
|
|
@ -99,7 +99,7 @@ func (a *Action) TableIndices() []*schemas.Index {
|
|||
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
||||
|
||||
indices := []*schemas.Index{actUserIndex, repoIndex}
|
||||
if setting.Database.UsePostgreSQL {
|
||||
if setting.Database.Type.IsPostgreSQL() {
|
||||
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
||||
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
||||
indices = append(indices, cudIndex)
|
||||
|
@ -640,7 +640,7 @@ func DeleteIssueActions(ctx context.Context, repoID, issueID int64) error {
|
|||
|
||||
// CountActionCreatedUnixString count actions where created_unix is an empty string
|
||||
func CountActionCreatedUnixString(ctx context.Context) (int64, error) {
|
||||
if setting.Database.UseSQLite3 {
|
||||
if setting.Database.Type.IsSQLite3() {
|
||||
return db.GetEngine(ctx).Where(`created_unix = ""`).Count(new(Action))
|
||||
}
|
||||
return 0, nil
|
||||
|
@ -648,7 +648,7 @@ func CountActionCreatedUnixString(ctx context.Context) (int64, error) {
|
|||
|
||||
// FixActionCreatedUnixString set created_unix to zero if it is an empty string
|
||||
func FixActionCreatedUnixString(ctx context.Context) (int64, error) {
|
||||
if setting.Database.UseSQLite3 {
|
||||
if setting.Database.Type.IsSQLite3() {
|
||||
res, err := db.GetEngine(ctx).Exec(`UPDATE action SET created_unix = 0 WHERE created_unix = ""`)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
|
|
@ -234,7 +234,7 @@ func TestGetFeedsCorrupted(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConsistencyUpdateAction(t *testing.T) {
|
||||
if !setting.Database.UseSQLite3 {
|
||||
if !setting.Database.Type.IsSQLite3() {
|
||||
t.Skip("Test is only for SQLite database.")
|
||||
}
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
|
|
@ -39,9 +39,9 @@ func getUserHeatmapData(user *user_model.User, team *organization.Team, doer *us
|
|||
groupBy := "created_unix / 900 * 900"
|
||||
groupByName := "timestamp" // We need this extra case because mssql doesn't allow grouping by alias
|
||||
switch {
|
||||
case setting.Database.UseMySQL:
|
||||
case setting.Database.Type.IsMySQL():
|
||||
groupBy = "created_unix DIV 900 * 900"
|
||||
case setting.Database.UseMSSQL:
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
groupByName = groupBy
|
||||
}
|
||||
|
||||
|
|
|
@ -153,7 +153,7 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final
|
|||
return DefaultAvatarLink()
|
||||
}
|
||||
|
||||
enableFederatedAvatar := system_model.GetSettingBool(ctx, system_model.KeyPictureEnableFederatedAvatar)
|
||||
enableFederatedAvatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureEnableFederatedAvatar)
|
||||
|
||||
var err error
|
||||
if enableFederatedAvatar && system_model.LibravatarService != nil {
|
||||
|
@ -174,7 +174,7 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final
|
|||
return urlStr
|
||||
}
|
||||
|
||||
disableGravatar := system_model.GetSettingBool(ctx, system_model.KeyPictureDisableGravatar)
|
||||
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
|
||||
if !disableGravatar {
|
||||
// copy GravatarSourceURL, because we will modify its Path.
|
||||
avatarURLCopy := *system_model.GravatarSourceURL
|
||||
|
|
|
@ -28,7 +28,7 @@ func enableGravatar(t *testing.T) {
|
|||
err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "false")
|
||||
assert.NoError(t, err)
|
||||
setting.GravatarSource = gravatarSource
|
||||
err = system_model.Init()
|
||||
err = system_model.Init(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
// BuildCaseInsensitiveLike returns a condition to check if the given value is like the given key case-insensitively.
|
||||
// Handles especially SQLite correctly as UPPER there only transforms ASCII letters.
|
||||
func BuildCaseInsensitiveLike(key, value string) builder.Cond {
|
||||
if setting.Database.UseSQLite3 {
|
||||
if setting.Database.Type.IsSQLite3() {
|
||||
return builder.Like{"UPPER(" + key + ")", util.ToUpperASCII(value)}
|
||||
}
|
||||
return builder.Like{"UPPER(" + key + ")", strings.ToUpper(value)}
|
||||
|
|
|
@ -100,12 +100,12 @@ func newXORMEngine() (*xorm.Engine, error) {
|
|||
|
||||
var engine *xorm.Engine
|
||||
|
||||
if setting.Database.UsePostgreSQL && len(setting.Database.Schema) > 0 {
|
||||
if setting.Database.Type.IsPostgreSQL() && len(setting.Database.Schema) > 0 {
|
||||
// OK whilst we sort out our schema issues - create a schema aware postgres
|
||||
registerPostgresSchemaDriver()
|
||||
engine, err = xorm.NewEngine("postgresschema", connStr)
|
||||
} else {
|
||||
engine, err = xorm.NewEngine(setting.Database.Type, connStr)
|
||||
engine, err = xorm.NewEngine(setting.Database.Type.String(), connStr)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -73,7 +73,7 @@ func postgresGetNextResourceIndex(ctx context.Context, tableName string, groupID
|
|||
|
||||
// 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) {
|
||||
if setting.Database.UsePostgreSQL {
|
||||
if setting.Database.Type.IsPostgreSQL() {
|
||||
return postgresGetNextResourceIndex(ctx, tableName, groupID)
|
||||
}
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ func Find[T any](ctx context.Context, opts FindOptions, objects *[]T) error {
|
|||
if !opts.IsListAll() {
|
||||
sess.Limit(opts.GetSkipTake())
|
||||
}
|
||||
return sess.Find(&objects)
|
||||
return sess.Find(objects)
|
||||
}
|
||||
|
||||
// Count represents a common count function which accept an options interface
|
||||
|
@ -148,5 +148,5 @@ func FindAndCount[T any](ctx context.Context, opts FindOptions, objects *[]T) (i
|
|||
if !opts.IsListAll() {
|
||||
sess.Limit(opts.GetSkipTake())
|
||||
}
|
||||
return sess.FindAndCount(&objects)
|
||||
return sess.FindAndCount(objects)
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package db_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type mockListOptions struct {
|
||||
db.ListOptions
|
||||
}
|
||||
|
||||
func (opts *mockListOptions) IsListAll() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (opts *mockListOptions) ToConds() builder.Cond {
|
||||
return builder.NewCond()
|
||||
}
|
||||
|
||||
func TestFind(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
xe := unittest.GetXORMEngine()
|
||||
assert.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
|
||||
|
||||
opts := mockListOptions{}
|
||||
var repoUnits []repo_model.RepoUnit
|
||||
err := db.Find(db.DefaultContext, &opts, &repoUnits)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 83, len(repoUnits))
|
||||
|
||||
cnt, err := db.Count(db.DefaultContext, &opts, new(repo_model.RepoUnit))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 83, cnt)
|
||||
|
||||
repoUnits = make([]repo_model.RepoUnit, 0, 10)
|
||||
newCnt, err := db.FindAndCount(db.DefaultContext, &opts, &repoUnits)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, cnt, newCnt)
|
||||
}
|
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
// CountBadSequences looks for broken sequences from recreate-table mistakes
|
||||
func CountBadSequences(_ context.Context) (int64, error) {
|
||||
if !setting.Database.UsePostgreSQL {
|
||||
if !setting.Database.Type.IsPostgreSQL() {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ func CountBadSequences(_ context.Context) (int64, error) {
|
|||
|
||||
// FixBadSequences fixes for broken sequences from recreate-table mistakes
|
||||
func FixBadSequences(_ context.Context) error {
|
||||
if !setting.Database.UsePostgreSQL {
|
||||
if !setting.Database.Type.IsPostgreSQL() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
fork_id: 0
|
||||
is_template: false
|
||||
template_id: 0
|
||||
size: 6708
|
||||
size: 7028
|
||||
is_fsck_enabled: true
|
||||
close_issues_via_commit_in_any_branch: false
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ func postgresGetCommitStatusIndex(ctx context.Context, repoID int64, sha string)
|
|||
|
||||
// GetNextCommitStatusIndex retried 3 times to generate a resource index
|
||||
func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
|
||||
if setting.Database.UsePostgreSQL {
|
||||
if setting.Database.Type.IsPostgreSQL() {
|
||||
return postgresGetCommitStatusIndex(ctx, repoID, sha)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,12 +7,12 @@ package issues
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/label"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
|
@ -78,9 +78,6 @@ func (err ErrLabelNotExist) Unwrap() error {
|
|||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
// LabelColorPattern is a regexp witch can validate LabelColor
|
||||
var LabelColorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")
|
||||
|
||||
// Label represents a label of repository for issues.
|
||||
type Label struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
|
@ -109,12 +106,12 @@ func init() {
|
|||
}
|
||||
|
||||
// CalOpenIssues sets the number of open issues of a label based on the already stored number of closed issues.
|
||||
func (label *Label) CalOpenIssues() {
|
||||
label.NumOpenIssues = label.NumIssues - label.NumClosedIssues
|
||||
func (l *Label) CalOpenIssues() {
|
||||
l.NumOpenIssues = l.NumIssues - l.NumClosedIssues
|
||||
}
|
||||
|
||||
// CalOpenOrgIssues calculates the open issues of a label for a specific repo
|
||||
func (label *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
|
||||
func (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
|
||||
counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{
|
||||
RepoID: repoID,
|
||||
LabelIDs: []int64{labelID},
|
||||
|
@ -122,22 +119,22 @@ func (label *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64)
|
|||
})
|
||||
|
||||
for _, count := range counts {
|
||||
label.NumOpenRepoIssues += count
|
||||
l.NumOpenRepoIssues += count
|
||||
}
|
||||
}
|
||||
|
||||
// LoadSelectedLabelsAfterClick calculates the set of selected labels when a label is clicked
|
||||
func (label *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64, currentSelectedExclusiveScopes []string) {
|
||||
func (l *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64, currentSelectedExclusiveScopes []string) {
|
||||
var labelQuerySlice []string
|
||||
labelSelected := false
|
||||
labelID := strconv.FormatInt(label.ID, 10)
|
||||
labelScope := label.ExclusiveScope()
|
||||
labelID := strconv.FormatInt(l.ID, 10)
|
||||
labelScope := l.ExclusiveScope()
|
||||
for i, s := range currentSelectedLabels {
|
||||
if s == label.ID {
|
||||
if s == l.ID {
|
||||
labelSelected = true
|
||||
} else if -s == label.ID {
|
||||
} else if -s == l.ID {
|
||||
labelSelected = true
|
||||
label.IsExcluded = true
|
||||
l.IsExcluded = true
|
||||
} else if s != 0 {
|
||||
// Exclude other labels in the same scope from selection
|
||||
if s < 0 || labelScope == "" || labelScope != currentSelectedExclusiveScopes[i] {
|
||||
|
@ -148,23 +145,23 @@ func (label *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64,
|
|||
if !labelSelected {
|
||||
labelQuerySlice = append(labelQuerySlice, labelID)
|
||||
}
|
||||
label.IsSelected = labelSelected
|
||||
label.QueryString = strings.Join(labelQuerySlice, ",")
|
||||
l.IsSelected = labelSelected
|
||||
l.QueryString = strings.Join(labelQuerySlice, ",")
|
||||
}
|
||||
|
||||
// BelongsToOrg returns true if label is an organization label
|
||||
func (label *Label) BelongsToOrg() bool {
|
||||
return label.OrgID > 0
|
||||
func (l *Label) BelongsToOrg() bool {
|
||||
return l.OrgID > 0
|
||||
}
|
||||
|
||||
// BelongsToRepo returns true if label is a repository label
|
||||
func (label *Label) BelongsToRepo() bool {
|
||||
return label.RepoID > 0
|
||||
func (l *Label) BelongsToRepo() bool {
|
||||
return l.RepoID > 0
|
||||
}
|
||||
|
||||
// Get color as RGB values in 0..255 range
|
||||
func (label *Label) ColorRGB() (float64, float64, float64, error) {
|
||||
color, err := strconv.ParseUint(label.Color[1:], 16, 64)
|
||||
func (l *Label) ColorRGB() (float64, float64, float64, error) {
|
||||
color, err := strconv.ParseUint(l.Color[1:], 16, 64)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
@ -176,9 +173,9 @@ func (label *Label) ColorRGB() (float64, float64, float64, error) {
|
|||
}
|
||||
|
||||
// Determine if label text should be light or dark to be readable on background color
|
||||
func (label *Label) UseLightTextColor() bool {
|
||||
if strings.HasPrefix(label.Color, "#") {
|
||||
if r, g, b, err := label.ColorRGB(); err == nil {
|
||||
func (l *Label) UseLightTextColor() bool {
|
||||
if strings.HasPrefix(l.Color, "#") {
|
||||
if r, g, b, err := l.ColorRGB(); err == nil {
|
||||
// Perceived brightness from: https://www.w3.org/TR/AERT/#color-contrast
|
||||
// In the future WCAG 3 APCA may be a better solution
|
||||
brightness := (0.299*r + 0.587*g + 0.114*b) / 255
|
||||
|
@ -190,40 +187,26 @@ func (label *Label) UseLightTextColor() bool {
|
|||
}
|
||||
|
||||
// Return scope substring of label name, or empty string if none exists
|
||||
func (label *Label) ExclusiveScope() string {
|
||||
if !label.Exclusive {
|
||||
func (l *Label) ExclusiveScope() string {
|
||||
if !l.Exclusive {
|
||||
return ""
|
||||
}
|
||||
lastIndex := strings.LastIndex(label.Name, "/")
|
||||
if lastIndex == -1 || lastIndex == 0 || lastIndex == len(label.Name)-1 {
|
||||
lastIndex := strings.LastIndex(l.Name, "/")
|
||||
if lastIndex == -1 || lastIndex == 0 || lastIndex == len(l.Name)-1 {
|
||||
return ""
|
||||
}
|
||||
return label.Name[:lastIndex]
|
||||
return l.Name[:lastIndex]
|
||||
}
|
||||
|
||||
// NewLabel creates a new label
|
||||
func NewLabel(ctx context.Context, label *Label) error {
|
||||
if !LabelColorPattern.MatchString(label.Color) {
|
||||
return fmt.Errorf("bad color code: %s", label.Color)
|
||||
func NewLabel(ctx context.Context, l *Label) error {
|
||||
color, err := label.NormalizeColor(l.Color)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.Color = color
|
||||
|
||||
// normalize case
|
||||
label.Color = strings.ToLower(label.Color)
|
||||
|
||||
// add leading hash
|
||||
if label.Color[0] != '#' {
|
||||
label.Color = "#" + label.Color
|
||||
}
|
||||
|
||||
// convert 3-character shorthand into 6-character version
|
||||
if len(label.Color) == 4 {
|
||||
r := label.Color[1]
|
||||
g := label.Color[2]
|
||||
b := label.Color[3]
|
||||
label.Color = fmt.Sprintf("#%c%c%c%c%c%c", r, r, g, g, b, b)
|
||||
}
|
||||
|
||||
return db.Insert(ctx, label)
|
||||
return db.Insert(ctx, l)
|
||||
}
|
||||
|
||||
// NewLabels creates new labels
|
||||
|
@ -234,11 +217,14 @@ func NewLabels(labels ...*Label) error {
|
|||
}
|
||||
defer committer.Close()
|
||||
|
||||
for _, label := range labels {
|
||||
if !LabelColorPattern.MatchString(label.Color) {
|
||||
return fmt.Errorf("bad color code: %s", label.Color)
|
||||
for _, l := range labels {
|
||||
color, err := label.NormalizeColor(l.Color)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.Insert(ctx, label); err != nil {
|
||||
l.Color = color
|
||||
|
||||
if err := db.Insert(ctx, l); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -247,15 +233,18 @@ func NewLabels(labels ...*Label) error {
|
|||
|
||||
// UpdateLabel updates label information.
|
||||
func UpdateLabel(l *Label) error {
|
||||
if !LabelColorPattern.MatchString(l.Color) {
|
||||
return fmt.Errorf("bad color code: %s", l.Color)
|
||||
color, err := label.NormalizeColor(l.Color)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.Color = color
|
||||
|
||||
return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive")
|
||||
}
|
||||
|
||||
// DeleteLabel delete a label
|
||||
func DeleteLabel(id, labelID int64) error {
|
||||
label, err := GetLabelByID(db.DefaultContext, labelID)
|
||||
l, err := GetLabelByID(db.DefaultContext, labelID)
|
||||
if err != nil {
|
||||
if IsErrLabelNotExist(err) {
|
||||
return nil
|
||||
|
@ -271,10 +260,10 @@ func DeleteLabel(id, labelID int64) error {
|
|||
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
if label.BelongsToOrg() && label.OrgID != id {
|
||||
if l.BelongsToOrg() && l.OrgID != id {
|
||||
return nil
|
||||
}
|
||||
if label.BelongsToRepo() && label.RepoID != id {
|
||||
if l.BelongsToRepo() && l.RepoID != id {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -682,14 +671,14 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us
|
|||
if err = issue.LoadRepo(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, label := range labels {
|
||||
for _, l := range labels {
|
||||
// Don't add already present labels and invalid labels
|
||||
if HasIssueLabel(ctx, issue.ID, label.ID) ||
|
||||
(label.RepoID != issue.RepoID && label.OrgID != issue.Repo.OwnerID) {
|
||||
if HasIssueLabel(ctx, issue.ID, l.ID) ||
|
||||
(l.RepoID != issue.RepoID && l.OrgID != issue.Repo.OwnerID) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = newIssueLabel(ctx, issue, label, doer); err != nil {
|
||||
if err = newIssueLabel(ctx, issue, l, doer); err != nil {
|
||||
return fmt.Errorf("newIssueLabel: %w", err)
|
||||
}
|
||||
}
|
||||
|
@ -778,7 +767,7 @@ func CountOrphanedLabels(ctx context.Context) (int64, error) {
|
|||
norepo, err := db.GetEngine(ctx).Table("label").
|
||||
Where(builder.And(
|
||||
builder.Gt{"repo_id": 0},
|
||||
builder.NotIn("repo_id", builder.Select("id").From("repository")),
|
||||
builder.NotIn("repo_id", builder.Select("id").From("`repository`")),
|
||||
)).
|
||||
Count()
|
||||
if err != nil {
|
||||
|
@ -788,7 +777,7 @@ func CountOrphanedLabels(ctx context.Context) (int64, error) {
|
|||
noorg, err := db.GetEngine(ctx).Table("label").
|
||||
Where(builder.And(
|
||||
builder.Gt{"org_id": 0},
|
||||
builder.NotIn("org_id", builder.Select("id").From("user")),
|
||||
builder.NotIn("org_id", builder.Select("id").From("`user`")),
|
||||
)).
|
||||
Count()
|
||||
if err != nil {
|
||||
|
@ -809,7 +798,7 @@ func DeleteOrphanedLabels(ctx context.Context) error {
|
|||
if _, err := db.GetEngine(ctx).
|
||||
Where(builder.And(
|
||||
builder.Gt{"repo_id": 0},
|
||||
builder.NotIn("repo_id", builder.Select("id").From("repository")),
|
||||
builder.NotIn("repo_id", builder.Select("id").From("`repository`")),
|
||||
)).
|
||||
Delete(Label{}); err != nil {
|
||||
return err
|
||||
|
@ -819,7 +808,7 @@ func DeleteOrphanedLabels(ctx context.Context) error {
|
|||
if _, err := db.GetEngine(ctx).
|
||||
Where(builder.And(
|
||||
builder.Gt{"org_id": 0},
|
||||
builder.NotIn("org_id", builder.Select("id").From("user")),
|
||||
builder.NotIn("org_id", builder.Select("id").From("`user`")),
|
||||
)).
|
||||
Delete(Label{}); err != nil {
|
||||
return err
|
||||
|
|
|
@ -15,8 +15,6 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TODO TestGetLabelTemplateFile
|
||||
|
||||
func TestLabel_CalOpenIssues(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
|
||||
|
|
|
@ -52,13 +52,16 @@ func listPullRequestStatement(baseRepoID int64, opts *PullRequestsOptions) (*xor
|
|||
|
||||
// GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged
|
||||
// by given head information (repo and branch).
|
||||
func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) {
|
||||
// arg `includeClosed` controls whether the SQL returns closed PRs
|
||||
func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string, includeClosed bool) ([]*PullRequest, error) {
|
||||
prs := make([]*PullRequest, 0, 2)
|
||||
return prs, db.GetEngine(db.DefaultContext).
|
||||
Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ? AND flow = ?",
|
||||
repoID, branch, false, false, PullRequestFlowGithub).
|
||||
sess := db.GetEngine(db.DefaultContext).
|
||||
Join("INNER", "issue", "issue.id = pull_request.issue_id").
|
||||
Find(&prs)
|
||||
Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND flow = ?", repoID, branch, false, PullRequestFlowGithub)
|
||||
if !includeClosed {
|
||||
sess.Where("issue.is_closed = ?", false)
|
||||
}
|
||||
return prs, sess.Find(&prs)
|
||||
}
|
||||
|
||||
// CanMaintainerWriteToBranch check whether user is a maintainer and could write to the branch
|
||||
|
@ -71,7 +74,7 @@ func CanMaintainerWriteToBranch(p access_model.Permission, branch string, user *
|
|||
return false
|
||||
}
|
||||
|
||||
prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch)
|
||||
prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch, false)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
@ -111,6 +114,7 @@ func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequ
|
|||
return prs, db.GetEngine(db.DefaultContext).
|
||||
Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
|
||||
repoID, branch, false, false).
|
||||
OrderBy("issue.updated_unix DESC").
|
||||
Join("INNER", "issue", "issue.id=pull_request.issue_id").
|
||||
Find(&prs)
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ func TestHasUnmergedPullRequestsByHeadInfo(t *testing.T) {
|
|||
|
||||
func TestGetUnmergedPullRequestsByHeadInfo(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(1, "branch2")
|
||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(1, "branch2", false)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, prs, 1)
|
||||
for _, pr := range prs {
|
||||
|
|
|
@ -89,7 +89,7 @@ func RecreateTable(sess *xorm.Session, bean interface{}) error {
|
|||
hasID = hasID || (column.IsPrimaryKey && column.IsAutoIncrement)
|
||||
}
|
||||
|
||||
if hasID && setting.Database.UseMSSQL {
|
||||
if hasID && setting.Database.Type.IsMSSQL() {
|
||||
if _, err := sess.Exec(fmt.Sprintf("SET IDENTITY_INSERT `%s` ON", tempTableName)); err != nil {
|
||||
log.Error("Unable to set identity insert for table %s. Error: %v", tempTableName, err)
|
||||
return err
|
||||
|
@ -143,7 +143,7 @@ func RecreateTable(sess *xorm.Session, bean interface{}) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if hasID && setting.Database.UseMSSQL {
|
||||
if hasID && setting.Database.Type.IsMSSQL() {
|
||||
if _, err := sess.Exec(fmt.Sprintf("SET IDENTITY_INSERT `%s` OFF", tempTableName)); err != nil {
|
||||
log.Error("Unable to switch off identity insert for table %s. Error: %v", tempTableName, err)
|
||||
return err
|
||||
|
@ -151,7 +151,7 @@ func RecreateTable(sess *xorm.Session, bean interface{}) error {
|
|||
}
|
||||
|
||||
switch {
|
||||
case setting.Database.UseSQLite3:
|
||||
case setting.Database.Type.IsSQLite3():
|
||||
// SQLite will drop all the constraints on the old table
|
||||
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
|
||||
log.Error("Unable to drop old table %s. Error: %v", tableName, err)
|
||||
|
@ -178,7 +178,7 @@ func RecreateTable(sess *xorm.Session, bean interface{}) error {
|
|||
return err
|
||||
}
|
||||
|
||||
case setting.Database.UseMySQL:
|
||||
case setting.Database.Type.IsMySQL():
|
||||
// MySQL will drop all the constraints on the old table
|
||||
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
|
||||
log.Error("Unable to drop old table %s. Error: %v", tableName, err)
|
||||
|
@ -205,7 +205,7 @@ func RecreateTable(sess *xorm.Session, bean interface{}) error {
|
|||
log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err)
|
||||
return err
|
||||
}
|
||||
case setting.Database.UsePostgreSQL:
|
||||
case setting.Database.Type.IsPostgreSQL():
|
||||
var originalSequences []string
|
||||
type sequenceData struct {
|
||||
LastValue int `xorm:"'last_value'"`
|
||||
|
@ -296,7 +296,7 @@ func RecreateTable(sess *xorm.Session, bean interface{}) error {
|
|||
|
||||
}
|
||||
|
||||
case setting.Database.UseMSSQL:
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
// MSSQL will drop all the constraints on the old table
|
||||
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
|
||||
log.Error("Unable to drop old table %s. Error: %v", tableName, err)
|
||||
|
@ -323,7 +323,7 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
|
|||
// TODO: This will not work if there are foreign keys
|
||||
|
||||
switch {
|
||||
case setting.Database.UseSQLite3:
|
||||
case setting.Database.Type.IsSQLite3():
|
||||
// First drop the indexes on the columns
|
||||
res, errIndex := sess.Query(fmt.Sprintf("PRAGMA index_list(`%s`)", tableName))
|
||||
if errIndex != nil {
|
||||
|
@ -405,7 +405,7 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
|
|||
return err
|
||||
}
|
||||
|
||||
case setting.Database.UsePostgreSQL:
|
||||
case setting.Database.Type.IsPostgreSQL():
|
||||
cols := ""
|
||||
for _, col := range columnNames {
|
||||
if cols != "" {
|
||||
|
@ -416,7 +416,7 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
|
|||
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
|
||||
return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
|
||||
}
|
||||
case setting.Database.UseMySQL:
|
||||
case setting.Database.Type.IsMySQL():
|
||||
// Drop indexes on columns first
|
||||
sql := fmt.Sprintf("SHOW INDEX FROM %s WHERE column_name IN ('%s')", tableName, strings.Join(columnNames, "','"))
|
||||
res, err := sess.Query(sql)
|
||||
|
@ -444,7 +444,7 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
|
|||
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
|
||||
return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
|
||||
}
|
||||
case setting.Database.UseMSSQL:
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
cols := ""
|
||||
for _, col := range columnNames {
|
||||
if cols != "" {
|
||||
|
@ -543,13 +543,13 @@ func newXORMEngine() (*xorm.Engine, error) {
|
|||
|
||||
func deleteDB() error {
|
||||
switch {
|
||||
case setting.Database.UseSQLite3:
|
||||
case setting.Database.Type.IsSQLite3():
|
||||
if err := util.Remove(setting.Database.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(path.Dir(setting.Database.Path), os.ModePerm)
|
||||
|
||||
case setting.Database.UseMySQL:
|
||||
case setting.Database.Type.IsMySQL():
|
||||
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/",
|
||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host))
|
||||
if err != nil {
|
||||
|
@ -565,7 +565,7 @@ func deleteDB() error {
|
|||
return err
|
||||
}
|
||||
return nil
|
||||
case setting.Database.UsePostgreSQL:
|
||||
case setting.Database.Type.IsPostgreSQL():
|
||||
db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/?sslmode=%s",
|
||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.SSLMode))
|
||||
if err != nil {
|
||||
|
@ -612,7 +612,7 @@ func deleteDB() error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
case setting.Database.UseMSSQL:
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
host, port := setting.ParseMSSQLHostPort(setting.Database.Host)
|
||||
db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;",
|
||||
host, port, "master", setting.Database.User, setting.Database.Passwd))
|
||||
|
|
|
@ -13,9 +13,9 @@ func PrependRefsHeadsToIssueRefs(x *xorm.Engine) error {
|
|||
var query string
|
||||
|
||||
switch {
|
||||
case setting.Database.UseMSSQL:
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
query = "UPDATE `issue` SET `ref` = 'refs/heads/' + `ref` WHERE `ref` IS NOT NULL AND `ref` <> '' AND `ref` NOT LIKE 'refs/%'"
|
||||
case setting.Database.UseMySQL:
|
||||
case setting.Database.Type.IsMySQL():
|
||||
query = "UPDATE `issue` SET `ref` = CONCAT('refs/heads/', `ref`) WHERE `ref` IS NOT NULL AND `ref` <> '' AND `ref` NOT LIKE 'refs/%';"
|
||||
default:
|
||||
query = "UPDATE `issue` SET `ref` = 'refs/heads/' || `ref` WHERE `ref` IS NOT NULL AND `ref` <> '' AND `ref` NOT LIKE 'refs/%'"
|
||||
|
|
|
@ -41,7 +41,7 @@ func FixLanguageStatsToSaveSize(x *xorm.Engine) error {
|
|||
|
||||
// Delete language stat statuses
|
||||
truncExpr := "TRUNCATE TABLE"
|
||||
if setting.Database.UseSQLite3 {
|
||||
if setting.Database.Type.IsSQLite3() {
|
||||
truncExpr = "DELETE FROM"
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ func IncreaseLanguageField(x *xorm.Engine) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if setting.Database.UseSQLite3 {
|
||||
if setting.Database.Type.IsSQLite3() {
|
||||
// SQLite maps VARCHAR to TEXT without size so we're done
|
||||
return nil
|
||||
}
|
||||
|
@ -41,11 +41,11 @@ func IncreaseLanguageField(x *xorm.Engine) error {
|
|||
}
|
||||
|
||||
switch {
|
||||
case setting.Database.UseMySQL:
|
||||
case setting.Database.Type.IsMySQL():
|
||||
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE language_stat MODIFY COLUMN language %s", sqlType)); err != nil {
|
||||
return err
|
||||
}
|
||||
case setting.Database.UseMSSQL:
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
// Yet again MSSQL just has to be awkward.
|
||||
// Here we have to drop the constraints first and then rebuild them
|
||||
constraints := make([]string, 0)
|
||||
|
@ -71,7 +71,7 @@ func IncreaseLanguageField(x *xorm.Engine) error {
|
|||
if err := sess.CreateUniques(new(LanguageStat)); err != nil {
|
||||
return err
|
||||
}
|
||||
case setting.Database.UsePostgreSQL:
|
||||
case setting.Database.Type.IsPostgreSQL():
|
||||
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE language_stat ALTER COLUMN language TYPE %s", sqlType)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -17,13 +17,13 @@ import (
|
|||
|
||||
func SetDefaultPasswordToArgon2(x *xorm.Engine) error {
|
||||
switch {
|
||||
case setting.Database.UseMySQL:
|
||||
case setting.Database.Type.IsMySQL():
|
||||
_, err := x.Exec("ALTER TABLE `user` ALTER passwd_hash_algo SET DEFAULT 'argon2';")
|
||||
return err
|
||||
case setting.Database.UsePostgreSQL:
|
||||
case setting.Database.Type.IsPostgreSQL():
|
||||
_, err := x.Exec("ALTER TABLE `user` ALTER COLUMN passwd_hash_algo SET DEFAULT 'argon2';")
|
||||
return err
|
||||
case setting.Database.UseMSSQL:
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
// need to find the constraint and drop it, then recreate it.
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
@ -53,7 +53,7 @@ func SetDefaultPasswordToArgon2(x *xorm.Engine) error {
|
|||
}
|
||||
return sess.Commit()
|
||||
|
||||
case setting.Database.UseSQLite3:
|
||||
case setting.Database.Type.IsSQLite3():
|
||||
// drop through
|
||||
default:
|
||||
log.Fatal("Unrecognized DB")
|
||||
|
|
|
@ -62,7 +62,7 @@ func UpdateCodeCommentReplies(x *xorm.Engine) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if setting.Database.UseMSSQL {
|
||||
if setting.Database.Type.IsMSSQL() {
|
||||
if _, err := sess.Exec(sqlSelect + " INTO #temp_comments" + sqlTail); err != nil {
|
||||
log.Error("unable to create temporary table")
|
||||
return err
|
||||
|
@ -72,13 +72,13 @@ func UpdateCodeCommentReplies(x *xorm.Engine) error {
|
|||
comments := make([]*Comment, 0, batchSize)
|
||||
|
||||
switch {
|
||||
case setting.Database.UseMySQL:
|
||||
case setting.Database.Type.IsMySQL():
|
||||
sqlCmd = sqlSelect + sqlTail + " LIMIT " + strconv.Itoa(batchSize) + ", " + strconv.Itoa(start)
|
||||
case setting.Database.UsePostgreSQL:
|
||||
case setting.Database.Type.IsPostgreSQL():
|
||||
fallthrough
|
||||
case setting.Database.UseSQLite3:
|
||||
case setting.Database.Type.IsSQLite3():
|
||||
sqlCmd = sqlSelect + sqlTail + " LIMIT " + strconv.Itoa(batchSize) + " OFFSET " + strconv.Itoa(start)
|
||||
case setting.Database.UseMSSQL:
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
sqlCmd = "SELECT TOP " + strconv.Itoa(batchSize) + " * FROM #temp_comments WHERE " +
|
||||
"(id NOT IN ( SELECT TOP " + strconv.Itoa(start) + " id FROM #temp_comments ORDER BY id )) ORDER BY id"
|
||||
default:
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
)
|
||||
|
||||
func FixPostgresIDSequences(x *xorm.Engine) error {
|
||||
if !setting.Database.UsePostgreSQL {
|
||||
if !setting.Database.Type.IsPostgreSQL() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -54,11 +54,11 @@ func RenameTaskErrorsToMessage(x *xorm.Engine) error {
|
|||
}
|
||||
|
||||
switch {
|
||||
case setting.Database.UseMySQL:
|
||||
case setting.Database.Type.IsMySQL():
|
||||
if _, err := sess.Exec("ALTER TABLE `task` CHANGE errors message text"); err != nil {
|
||||
return err
|
||||
}
|
||||
case setting.Database.UseMSSQL:
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
if _, err := sess.Exec("sp_rename 'task.errors', 'message', 'COLUMN'"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ func AlterIssueAndCommentTextFieldsToLongText(x *xorm.Engine) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if setting.Database.UseMySQL {
|
||||
if setting.Database.Type.IsMySQL() {
|
||||
if _, err := sess.Exec("ALTER TABLE `issue` CHANGE `content` `content` LONGTEXT"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ func AlterHookTaskTextFieldsToLongText(x *xorm.Engine) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if setting.Database.UseMySQL {
|
||||
if setting.Database.Type.IsMySQL() {
|
||||
if _, err := sess.Exec("ALTER TABLE `hook_task` CHANGE `payload_content` `payload_content` LONGTEXT, CHANGE `request_content` `request_content` LONGTEXT, change `response_content` `response_content` LONGTEXT"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ func (*improveActionTableIndicesAction) TableIndices() []*schemas.Index {
|
|||
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
|
||||
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
||||
indices := []*schemas.Index{actUserIndex, repoIndex}
|
||||
if setting.Database.UsePostgreSQL {
|
||||
if setting.Database.Type.IsPostgreSQL() {
|
||||
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
||||
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
||||
indices = append(indices, cudIndex)
|
||||
|
|
|
@ -65,11 +65,11 @@ func RenameCredentialIDBytes(x *xorm.Engine) error {
|
|||
}
|
||||
|
||||
switch {
|
||||
case setting.Database.UseMySQL:
|
||||
case setting.Database.Type.IsMySQL():
|
||||
if _, err := sess.Exec("ALTER TABLE `webauthn_credential` CHANGE credential_id_bytes credential_id VARBINARY(1024)"); err != nil {
|
||||
return err
|
||||
}
|
||||
case setting.Database.UseMSSQL:
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
if _, err := sess.Exec("sp_rename 'webauthn_credential.credential_id_bytes', 'credential_id', 'COLUMN'"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ func AlterPublicGPGKeyContentFieldsToMediumText(x *xorm.Engine) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if setting.Database.UseMySQL {
|
||||
if setting.Database.Type.IsMySQL() {
|
||||
if _, err := sess.Exec("ALTER TABLE `gpg_key` CHANGE `content` `content` MEDIUMTEXT"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ func AlterPackageVersionMetadataToLongText(x *xorm.Engine) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if setting.Database.UseMySQL {
|
||||
if setting.Database.Type.IsMySQL() {
|
||||
if _, err := sess.Exec("ALTER TABLE `package_version` MODIFY COLUMN `metadata_json` LONGTEXT"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ func AlterPublicGPGKeyImportContentFieldToMediumText(x *xorm.Engine) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if setting.Database.UseMySQL {
|
||||
if setting.Database.Type.IsMySQL() {
|
||||
if _, err := sess.Exec("ALTER TABLE `gpg_key_import` CHANGE `content` `content` MEDIUMTEXT"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -239,6 +239,32 @@ func (org *Organization) CustomAvatarRelativePath() string {
|
|||
return org.Avatar
|
||||
}
|
||||
|
||||
// UnitPermission returns unit permission
|
||||
func (org *Organization) UnitPermission(ctx context.Context, doer *user_model.User, unitType unit.Type) perm.AccessMode {
|
||||
if doer != nil {
|
||||
teams, err := GetUserOrgTeams(ctx, org.ID, doer.ID)
|
||||
if err != nil {
|
||||
log.Error("GetUserOrgTeams: %v", err)
|
||||
return perm.AccessModeNone
|
||||
}
|
||||
|
||||
if err := teams.LoadUnits(ctx); err != nil {
|
||||
log.Error("LoadUnits: %v", err)
|
||||
return perm.AccessModeNone
|
||||
}
|
||||
|
||||
if len(teams) > 0 {
|
||||
return teams.UnitMaxAccess(unitType)
|
||||
}
|
||||
}
|
||||
|
||||
if org.Visibility.IsPublic() {
|
||||
return perm.AccessModeRead
|
||||
}
|
||||
|
||||
return perm.AccessModeNone
|
||||
}
|
||||
|
||||
// CreateOrganization creates record of a new organization.
|
||||
func CreateOrganization(org *Organization, owner *user_model.User) (err error) {
|
||||
if !owner.CanCreateOrganization() {
|
||||
|
|
|
@ -416,7 +416,7 @@ func DeleteProjectByID(ctx context.Context, id int64) error {
|
|||
|
||||
func DeleteProjectByRepoID(ctx context.Context, repoID int64) error {
|
||||
switch {
|
||||
case setting.Database.UseSQLite3:
|
||||
case setting.Database.Type.IsSQLite3():
|
||||
if _, err := db.GetEngine(ctx).Exec("DELETE FROM project_issue WHERE project_issue.id IN (SELECT project_issue.id FROM project_issue INNER JOIN project WHERE project.id = project_issue.project_id AND project.repo_id = ?)", repoID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -426,7 +426,7 @@ func DeleteProjectByRepoID(ctx context.Context, repoID int64) error {
|
|||
if _, err := db.GetEngine(ctx).Table("project").Where("repo_id = ? ", repoID).Delete(&Project{}); err != nil {
|
||||
return err
|
||||
}
|
||||
case setting.Database.UsePostgreSQL:
|
||||
case setting.Database.Type.IsPostgreSQL():
|
||||
if _, err := db.GetEngine(ctx).Exec("DELETE FROM project_issue USING project WHERE project.id = project_issue.project_id AND project.repo_id = ? ", repoID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -39,9 +39,9 @@ import (
|
|||
var ItemsPerPage = 40
|
||||
|
||||
// Init initialize model
|
||||
func Init() error {
|
||||
func Init(ctx context.Context) error {
|
||||
unit.LoadUnitConfig()
|
||||
return system_model.Init()
|
||||
return system_model.Init(ctx)
|
||||
}
|
||||
|
||||
// DeleteRepository deletes a repository for a user or organization.
|
||||
|
|
|
@ -498,7 +498,7 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
|
|||
subQueryCond := builder.NewCond()
|
||||
|
||||
// Topic checking. Topics are present.
|
||||
if setting.Database.UsePostgreSQL { // postgres stores the topics as json and not as text
|
||||
if setting.Database.Type.IsPostgreSQL() { // postgres stores the topics as json and not as text
|
||||
subQueryCond = subQueryCond.Or(builder.And(builder.NotNull{"topics"}, builder.Neq{"(topics)::text": "[]"}))
|
||||
} else {
|
||||
subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"}))
|
||||
|
|
|
@ -79,8 +79,8 @@ func IsErrDataExpired(err error) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
// GetSettingNoCache returns specific setting without using the cache
|
||||
func GetSettingNoCache(ctx context.Context, key string) (*Setting, error) {
|
||||
// GetSetting returns specific setting without using the cache
|
||||
func GetSetting(ctx context.Context, key string) (*Setting, error) {
|
||||
v, err := GetSettings(ctx, []string{key})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -93,11 +93,11 @@ func GetSettingNoCache(ctx context.Context, key string) (*Setting, error) {
|
|||
|
||||
const contextCacheKey = "system_setting"
|
||||
|
||||
// GetSetting returns the setting value via the key
|
||||
func GetSetting(ctx context.Context, key string) (string, error) {
|
||||
// GetSettingWithCache returns the setting value via the key
|
||||
func GetSettingWithCache(ctx context.Context, key string) (string, error) {
|
||||
return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) {
|
||||
return cache.GetString(genSettingCacheKey(key), func() (string, error) {
|
||||
res, err := GetSettingNoCache(ctx, key)
|
||||
res, err := GetSetting(ctx, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -110,6 +110,15 @@ func GetSetting(ctx context.Context, key string) (string, error) {
|
|||
// none existing keys and errors are ignored and result in false
|
||||
func GetSettingBool(ctx context.Context, key string) bool {
|
||||
s, _ := GetSetting(ctx, key)
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
v, _ := strconv.ParseBool(s.SettingValue)
|
||||
return v
|
||||
}
|
||||
|
||||
func GetSettingWithCacheBool(ctx context.Context, key string) bool {
|
||||
s, _ := GetSettingWithCache(ctx, key)
|
||||
v, _ := strconv.ParseBool(s)
|
||||
return v
|
||||
}
|
||||
|
@ -120,7 +129,7 @@ func GetSettings(ctx context.Context, keys []string) (map[string]*Setting, error
|
|||
keys[i] = strings.ToLower(keys[i])
|
||||
}
|
||||
settings := make([]*Setting, 0, len(keys))
|
||||
if err := db.GetEngine(db.DefaultContext).
|
||||
if err := db.GetEngine(ctx).
|
||||
Where(builder.In("setting_key", keys)).
|
||||
Find(&settings); err != nil {
|
||||
return nil, err
|
||||
|
@ -151,9 +160,9 @@ func (settings AllSettings) GetVersion(key string) int {
|
|||
}
|
||||
|
||||
// GetAllSettings returns all settings from user
|
||||
func GetAllSettings() (AllSettings, error) {
|
||||
func GetAllSettings(ctx context.Context) (AllSettings, error) {
|
||||
settings := make([]*Setting, 0, 5)
|
||||
if err := db.GetEngine(db.DefaultContext).
|
||||
if err := db.GetEngine(ctx).
|
||||
Find(&settings); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -168,12 +177,12 @@ func GetAllSettings() (AllSettings, error) {
|
|||
func DeleteSetting(ctx context.Context, setting *Setting) error {
|
||||
cache.RemoveContextData(ctx, contextCacheKey, setting.SettingKey)
|
||||
cache.Remove(genSettingCacheKey(setting.SettingKey))
|
||||
_, err := db.GetEngine(db.DefaultContext).Delete(setting)
|
||||
_, err := db.GetEngine(ctx).Delete(setting)
|
||||
return err
|
||||
}
|
||||
|
||||
func SetSettingNoVersion(ctx context.Context, key, value string) error {
|
||||
s, err := GetSettingNoCache(ctx, key)
|
||||
s, err := GetSetting(ctx, key)
|
||||
if IsErrSettingIsNotExist(err) {
|
||||
return SetSetting(ctx, &Setting{
|
||||
SettingKey: key,
|
||||
|
@ -189,7 +198,7 @@ func SetSettingNoVersion(ctx context.Context, key, value string) error {
|
|||
|
||||
// SetSetting updates a users' setting for a specific key
|
||||
func SetSetting(ctx context.Context, setting *Setting) error {
|
||||
if err := upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
|
||||
if err := upsertSettingValue(ctx, strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -205,8 +214,8 @@ func SetSetting(ctx context.Context, setting *Setting) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func upsertSettingValue(key, value string, version int) error {
|
||||
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
||||
func upsertSettingValue(parentCtx context.Context, key, value string, version int) error {
|
||||
return db.WithTx(parentCtx, func(ctx context.Context) error {
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
// here we use a general method to do a safe upsert for different databases (and most transaction levels)
|
||||
|
@ -249,9 +258,9 @@ var (
|
|||
LibravatarService *libravatar.Libravatar
|
||||
)
|
||||
|
||||
func Init() error {
|
||||
func Init(ctx context.Context) error {
|
||||
var disableGravatar bool
|
||||
disableGravatarSetting, err := GetSettingNoCache(db.DefaultContext, KeyPictureDisableGravatar)
|
||||
disableGravatarSetting, err := GetSetting(ctx, KeyPictureDisableGravatar)
|
||||
if IsErrSettingIsNotExist(err) {
|
||||
disableGravatar = setting_module.GetDefaultDisableGravatar()
|
||||
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
|
||||
|
@ -262,7 +271,7 @@ func Init() error {
|
|||
}
|
||||
|
||||
var enableFederatedAvatar bool
|
||||
enableFederatedAvatarSetting, err := GetSettingNoCache(db.DefaultContext, KeyPictureEnableFederatedAvatar)
|
||||
enableFederatedAvatarSetting, err := GetSetting(ctx, KeyPictureEnableFederatedAvatar)
|
||||
if IsErrSettingIsNotExist(err) {
|
||||
enableFederatedAvatar = setting_module.GetDefaultEnableFederatedAvatar(disableGravatar)
|
||||
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
|
||||
|
@ -275,13 +284,13 @@ func Init() error {
|
|||
if setting_module.OfflineMode {
|
||||
disableGravatar = true
|
||||
enableFederatedAvatar = false
|
||||
if !GetSettingBool(db.DefaultContext, KeyPictureDisableGravatar) {
|
||||
if err := SetSettingNoVersion(db.DefaultContext, KeyPictureDisableGravatar, "true"); err != nil {
|
||||
if !GetSettingBool(ctx, KeyPictureDisableGravatar) {
|
||||
if err := SetSettingNoVersion(ctx, KeyPictureDisableGravatar, "true"); err != nil {
|
||||
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureDisableGravatar, err)
|
||||
}
|
||||
}
|
||||
if GetSettingBool(db.DefaultContext, KeyPictureEnableFederatedAvatar) {
|
||||
if err := SetSettingNoVersion(db.DefaultContext, KeyPictureEnableFederatedAvatar, "false"); err != nil {
|
||||
if GetSettingBool(ctx, KeyPictureEnableFederatedAvatar) {
|
||||
if err := SetSettingNoVersion(ctx, KeyPictureEnableFederatedAvatar, "false"); err != nil {
|
||||
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,10 +40,10 @@ func TestSettings(t *testing.T) {
|
|||
|
||||
value, err := system.GetSetting(db.DefaultContext, keyName)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, updatedSetting.SettingValue, value)
|
||||
assert.EqualValues(t, updatedSetting.SettingValue, value.SettingValue)
|
||||
|
||||
// get all settings
|
||||
settings, err = system.GetAllSettings()
|
||||
settings, err = system.GetAllSettings(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, settings, 3)
|
||||
assert.EqualValues(t, updatedSetting.SettingValue, settings[strings.ToLower(updatedSetting.SettingKey)].SettingValue)
|
||||
|
@ -51,7 +51,7 @@ func TestSettings(t *testing.T) {
|
|||
// delete setting
|
||||
err = system.DeleteSetting(db.DefaultContext, &system.Setting{SettingKey: strings.ToLower(keyName)})
|
||||
assert.NoError(t, err)
|
||||
settings, err = system.GetAllSettings()
|
||||
settings, err = system.GetAllSettings(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, settings, 2)
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
|
|||
setting.SSH.BuiltinServerUser = "builtinuser"
|
||||
setting.SSH.Port = 3000
|
||||
setting.SSH.Domain = "try.gitea.io"
|
||||
setting.Database.UseSQLite3 = true
|
||||
setting.Database.Type = "sqlite3"
|
||||
setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master"
|
||||
repoRootPath, err := os.MkdirTemp(os.TempDir(), "repos")
|
||||
if err != nil {
|
||||
|
@ -113,7 +113,7 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
|
|||
if err = storage.Init(); err != nil {
|
||||
fatalTestError("storage.Init: %v\n", err)
|
||||
}
|
||||
if err = system_model.Init(); err != nil {
|
||||
if err = system_model.Init(db.DefaultContext); err != nil {
|
||||
fatalTestError("models.Init: %v\n", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
|||
useLocalAvatar := false
|
||||
autoGenerateAvatar := false
|
||||
|
||||
disableGravatar := system_model.GetSettingBool(ctx, system_model.KeyPictureDisableGravatar)
|
||||
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
|
||||
|
||||
switch {
|
||||
case u.UseCustomAvatar:
|
||||
|
|
|
@ -393,6 +393,11 @@ func (u *User) IsOrganization() bool {
|
|||
return u.Type == UserTypeOrganization
|
||||
}
|
||||
|
||||
// IsIndividual returns true if user is actually a individual user.
|
||||
func (u *User) IsIndividual() bool {
|
||||
return u.Type == UserTypeIndividual
|
||||
}
|
||||
|
||||
// DisplayName returns full name if it's not empty,
|
||||
// returns username otherwise.
|
||||
func (u *User) DisplayName() string {
|
||||
|
|
|
@ -41,9 +41,8 @@ var RecommendedHashAlgorithms = []string{
|
|||
"pbkdf2_hi",
|
||||
}
|
||||
|
||||
// SetDefaultPasswordHashAlgorithm will take a provided algorithmName and dealias it to
|
||||
// a complete algorithm specification.
|
||||
func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHashAlgorithm) {
|
||||
// hashAlgorithmToSpec converts an algorithm name or a specification to a full algorithm specification
|
||||
func hashAlgorithmToSpec(algorithmName string) string {
|
||||
if algorithmName == "" {
|
||||
algorithmName = DefaultHashAlgorithmName
|
||||
}
|
||||
|
@ -52,10 +51,26 @@ func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHas
|
|||
algorithmName = alias
|
||||
alias, has = aliasAlgorithmNames[algorithmName]
|
||||
}
|
||||
|
||||
// algorithmName should now be a full algorithm specification
|
||||
// e.g. pbkdf2$50000$50 rather than pbdkf2
|
||||
DefaultHashAlgorithm = Parse(algorithmName)
|
||||
|
||||
return algorithmName, DefaultHashAlgorithm
|
||||
return algorithmName
|
||||
}
|
||||
|
||||
// SetDefaultPasswordHashAlgorithm will take a provided algorithmName and de-alias it to
|
||||
// a complete algorithm specification.
|
||||
func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHashAlgorithm) {
|
||||
algoSpec := hashAlgorithmToSpec(algorithmName)
|
||||
// now we get a full specification, e.g. pbkdf2$50000$50 rather than pbdkf2
|
||||
DefaultHashAlgorithm = Parse(algoSpec)
|
||||
return algoSpec, DefaultHashAlgorithm
|
||||
}
|
||||
|
||||
// ConfigHashAlgorithm will try to find a "recommended algorithm name" defined by RecommendedHashAlgorithms for config
|
||||
// This function is not fast and is only used for the installation page
|
||||
func ConfigHashAlgorithm(algorithm string) string {
|
||||
algorithm = hashAlgorithmToSpec(algorithm)
|
||||
for _, recommAlgo := range RecommendedHashAlgorithms {
|
||||
if algorithm == hashAlgorithmToSpec(recommAlgo) {
|
||||
return recommAlgo
|
||||
}
|
||||
}
|
||||
return algorithm
|
||||
}
|
||||
|
|
|
@ -244,7 +244,7 @@ func APIContexter() func(http.Handler) http.Handler {
|
|||
}
|
||||
}
|
||||
|
||||
httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 0, "no-transform")
|
||||
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform")
|
||||
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||
|
||||
ctx.Data["Context"] = &ctx
|
||||
|
|
|
@ -388,7 +388,7 @@ func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
|
|||
if duration == 0 {
|
||||
duration = 5 * time.Minute
|
||||
}
|
||||
httpcache.AddCacheControlToHeader(header, duration)
|
||||
httpcache.SetCacheControlInHeader(header, duration)
|
||||
|
||||
if !opts.LastModified.IsZero() {
|
||||
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
|
||||
|
@ -753,7 +753,7 @@ func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
|
|||
}
|
||||
}
|
||||
|
||||
httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 0, "no-transform")
|
||||
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform")
|
||||
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||
|
||||
ctx.Data["CsrfToken"] = ctx.csrf.GetToken()
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"code.gitea.io/gitea/models/perm"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
@ -31,29 +30,34 @@ type Organization struct {
|
|||
}
|
||||
|
||||
func (org *Organization) CanWriteUnit(ctx *Context, unitType unit.Type) bool {
|
||||
if ctx.Doer == nil {
|
||||
return false
|
||||
}
|
||||
return org.UnitPermission(ctx, ctx.Doer.ID, unitType) >= perm.AccessModeWrite
|
||||
return org.Organization.UnitPermission(ctx, ctx.Doer, unitType) >= perm.AccessModeWrite
|
||||
}
|
||||
|
||||
func (org *Organization) UnitPermission(ctx *Context, doerID int64, unitType unit.Type) perm.AccessMode {
|
||||
if doerID > 0 {
|
||||
teams, err := organization.GetUserOrgTeams(ctx, org.Organization.ID, doerID)
|
||||
if err != nil {
|
||||
log.Error("GetUserOrgTeams: %v", err)
|
||||
return perm.AccessModeNone
|
||||
}
|
||||
if len(teams) > 0 {
|
||||
return teams.UnitMaxAccess(unitType)
|
||||
}
|
||||
}
|
||||
func (org *Organization) CanReadUnit(ctx *Context, unitType unit.Type) bool {
|
||||
return org.Organization.UnitPermission(ctx, ctx.Doer, unitType) >= perm.AccessModeRead
|
||||
}
|
||||
|
||||
if org.Organization.Visibility == structs.VisibleTypePublic {
|
||||
return perm.AccessModeRead
|
||||
}
|
||||
func GetOrganizationByParams(ctx *Context) {
|
||||
orgName := ctx.Params(":org")
|
||||
|
||||
return perm.AccessModeNone
|
||||
var err error
|
||||
|
||||
ctx.Org.Organization, err = organization.GetOrgByName(ctx, orgName)
|
||||
if err != nil {
|
||||
if organization.IsErrOrgNotExist(err) {
|
||||
redirectUserID, err := user_model.LookupUserRedirect(orgName)
|
||||
if err == nil {
|
||||
RedirectToUser(ctx, orgName, redirectUserID)
|
||||
} else if user_model.IsErrUserRedirectNotExist(err) {
|
||||
ctx.NotFound("GetUserByName", err)
|
||||
} else {
|
||||
ctx.ServerError("LookupUserRedirect", err)
|
||||
}
|
||||
} else {
|
||||
ctx.ServerError("GetUserByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// HandleOrgAssignment handles organization assignment
|
||||
|
@ -77,25 +81,26 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
|
|||
requireTeamAdmin = args[3]
|
||||
}
|
||||
|
||||
orgName := ctx.Params(":org")
|
||||
|
||||
var err error
|
||||
ctx.Org.Organization, err = organization.GetOrgByName(ctx, orgName)
|
||||
if err != nil {
|
||||
if organization.IsErrOrgNotExist(err) {
|
||||
redirectUserID, err := user_model.LookupUserRedirect(orgName)
|
||||
if err == nil {
|
||||
RedirectToUser(ctx, orgName, redirectUserID)
|
||||
} else if user_model.IsErrUserRedirectNotExist(err) {
|
||||
ctx.NotFound("GetUserByName", err)
|
||||
} else {
|
||||
ctx.ServerError("LookupUserRedirect", err)
|
||||
|
||||
if ctx.ContextUser == nil {
|
||||
// if Organization is not defined, get it from params
|
||||
if ctx.Org.Organization == nil {
|
||||
GetOrganizationByParams(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ctx.ServerError("GetUserByName", err)
|
||||
}
|
||||
} else if ctx.ContextUser.IsOrganization() {
|
||||
if ctx.Org == nil {
|
||||
ctx.Org = &Organization{}
|
||||
}
|
||||
ctx.Org.Organization = (*organization.Organization)(ctx.ContextUser)
|
||||
} else {
|
||||
// ContextUser is an individual User
|
||||
return
|
||||
}
|
||||
|
||||
org := ctx.Org.Organization
|
||||
|
||||
// Handle Visibility
|
||||
|
@ -156,6 +161,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
|
|||
}
|
||||
ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner
|
||||
ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember
|
||||
ctx.Data["IsProjectEnabled"] = true
|
||||
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
|
||||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
||||
ctx.Data["IsPublicMember"] = func(uid int64) bool {
|
||||
|
@ -231,6 +237,10 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["CanReadProjects"] = ctx.Org.CanReadUnit(ctx, unit.TypeProjects)
|
||||
ctx.Data["CanReadPackages"] = ctx.Org.CanReadUnit(ctx, unit.TypePackages)
|
||||
ctx.Data["CanReadCode"] = ctx.Org.CanReadUnit(ctx, unit.TypeCode)
|
||||
}
|
||||
|
||||
// OrgAssignment returns a middleware to handle organization assignment
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -229,7 +230,10 @@ John Doe john@doe.com This,note,had,a,lot,of,commas,to,test,delimiters`,
|
|||
}
|
||||
|
||||
for n, c := range cases {
|
||||
delimiter := determineDelimiter(&markup.RenderContext{RelativePath: c.filename}, []byte(decodeSlashes(t, c.csv)))
|
||||
delimiter := determineDelimiter(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
RelativePath: c.filename,
|
||||
}, []byte(decodeSlashes(t, c.csv)))
|
||||
assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
|
|||
|
||||
// TODO: function to recalc all counters
|
||||
|
||||
if setting.Database.UsePostgreSQL {
|
||||
if setting.Database.Type.IsPostgreSQL() {
|
||||
consistencyChecks = append(consistencyChecks, consistencyCheck{
|
||||
Name: "Sequence values",
|
||||
Counter: db.CountBadSequences,
|
||||
|
|
|
@ -312,7 +312,7 @@ func CheckGitVersionAtLeast(atLeast string) error {
|
|||
}
|
||||
|
||||
func configSet(key, value string) error {
|
||||
stdout, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||
if err != nil && !err.IsExitCode(1) {
|
||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
||||
}
|
||||
|
@ -331,7 +331,7 @@ func configSet(key, value string) error {
|
|||
}
|
||||
|
||||
func configSetNonExist(key, value string) error {
|
||||
_, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||
if err == nil {
|
||||
// already exist
|
||||
return nil
|
||||
|
@ -349,7 +349,7 @@ func configSetNonExist(key, value string) error {
|
|||
}
|
||||
|
||||
func configAddNonExist(key, value string) error {
|
||||
_, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
|
||||
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
|
||||
if err == nil {
|
||||
// already exist
|
||||
return nil
|
||||
|
@ -366,7 +366,7 @@ func configAddNonExist(key, value string) error {
|
|||
}
|
||||
|
||||
func configUnsetAll(key, value string) error {
|
||||
_, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||
if err == nil {
|
||||
// exist, need to remove
|
||||
_, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
|
||||
|
|
|
@ -42,7 +42,10 @@ func RevListObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync.
|
|||
defer revListWriter.Close()
|
||||
stderr := new(bytes.Buffer)
|
||||
var errbuf strings.Builder
|
||||
cmd := git.NewCommand(ctx, "rev-list", "--objects").AddDynamicArguments(headSHA).AddArguments("--not").AddDynamicArguments(baseSHA)
|
||||
cmd := git.NewCommand(ctx, "rev-list", "--objects").AddDynamicArguments(headSHA)
|
||||
if baseSHA != "" {
|
||||
cmd = cmd.AddArguments("--not").AddDynamicArguments(baseSHA)
|
||||
}
|
||||
if err := cmd.Run(&git.RunOpts{
|
||||
Dir: tmpBasePath,
|
||||
Stdout: revListWriter,
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
|
@ -67,38 +66,6 @@ func (repo *Repository) IsCommitExist(name string) bool {
|
|||
return err == nil
|
||||
}
|
||||
|
||||
func convertPGPSignatureForTag(t *object.Tag) *CommitGPGSignature {
|
||||
if t.PGPSignature == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var w strings.Builder
|
||||
var err error
|
||||
|
||||
if _, err = fmt.Fprintf(&w,
|
||||
"object %s\ntype %s\ntag %s\ntagger ",
|
||||
t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = t.Tagger.Encode(&w); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err = fmt.Fprintf(&w, "\n\n"); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err = fmt.Fprintf(&w, t.Message); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &CommitGPGSignature{
|
||||
Signature: t.PGPSignature,
|
||||
Payload: strings.TrimSpace(w.String()) + "\n",
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
|
||||
var tagObject *object.Tag
|
||||
|
||||
|
@ -122,12 +89,6 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
|
|||
commit := convertCommit(gogitCommit)
|
||||
commit.repo = repo
|
||||
|
||||
if tagObject != nil {
|
||||
commit.CommitMessage = strings.TrimSpace(tagObject.Message)
|
||||
commit.Author = &tagObject.Tagger
|
||||
commit.Signature = convertPGPSignatureForTag(tagObject)
|
||||
}
|
||||
|
||||
tree, err := gogitCommit.Tree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -107,10 +107,6 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co
|
|||
return nil, err
|
||||
}
|
||||
|
||||
commit.CommitMessage = strings.TrimSpace(tag.Message)
|
||||
commit.Author = tag.Tagger
|
||||
commit.Signature = tag.Signature
|
||||
|
||||
return commit, nil
|
||||
case "commit":
|
||||
commit, err := CommitFromReader(repo, id, io.LimitReader(rd, size))
|
||||
|
|
|
@ -43,12 +43,13 @@ func TestGetTagCommitWithSignature(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
defer bareRepo1.Close()
|
||||
|
||||
commit, err := bareRepo1.GetCommit("3ad28a9149a2864384548f3d17ed7f38014c9e8a")
|
||||
// both the tag and the commit are signed here, this validates only the commit signature
|
||||
commit, err := bareRepo1.GetCommit("28b55526e7100924d864dd89e35c1ea62e7a5a32")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, commit)
|
||||
assert.NotNil(t, commit.Signature)
|
||||
// test that signature is not in message
|
||||
assert.Equal(t, "tag", commit.CommitMessage)
|
||||
assert.Equal(t, "signed-commit\n", commit.CommitMessage)
|
||||
}
|
||||
|
||||
func TestGetCommitWithBadCommitID(t *testing.T) {
|
||||
|
|
|
@ -277,11 +277,18 @@ func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
|
|||
|
||||
// GetFilesChangedBetween returns a list of all files that have been changed between the given commits
|
||||
func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, error) {
|
||||
stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only").AddDynamicArguments(base + ".." + head).RunStdString(&RunOpts{Dir: repo.Path})
|
||||
stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", "-z").AddDynamicArguments(base + ".." + head).RunStdString(&RunOpts{Dir: repo.Path})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return strings.Split(stdout, "\n"), err
|
||||
split := strings.Split(stdout, "\000")
|
||||
|
||||
// Because Git will always emit filenames with a terminal NUL ignore the last entry in the split - which will always be empty.
|
||||
if len(split) > 0 {
|
||||
split = split[:len(split)-1]
|
||||
}
|
||||
|
||||
return split, err
|
||||
}
|
||||
|
||||
// GetDiffFromMergeBase generates and return patch data from merge base to head
|
||||
|
|
|
@ -19,13 +19,14 @@ func TestRepository_GetRefs(t *testing.T) {
|
|||
refs, err := bareRepo1.GetRefs()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, refs, 5)
|
||||
assert.Len(t, refs, 6)
|
||||
|
||||
expectedRefs := []string{
|
||||
BranchPrefix + "branch1",
|
||||
BranchPrefix + "branch2",
|
||||
BranchPrefix + "master",
|
||||
TagPrefix + "test",
|
||||
TagPrefix + "signed-tag",
|
||||
NotesRef,
|
||||
}
|
||||
|
||||
|
@ -43,9 +44,12 @@ func TestRepository_GetRefsFiltered(t *testing.T) {
|
|||
refs, err := bareRepo1.GetRefsFiltered(TagPrefix)
|
||||
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, refs, 1) {
|
||||
assert.Equal(t, TagPrefix+"test", refs[0].Name)
|
||||
if assert.Len(t, refs, 2) {
|
||||
assert.Equal(t, TagPrefix+"signed-tag", refs[0].Name)
|
||||
assert.Equal(t, "tag", refs[0].Type)
|
||||
assert.Equal(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", refs[0].Object.String())
|
||||
assert.Equal(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", refs[0].Object.String())
|
||||
assert.Equal(t, TagPrefix+"test", refs[1].Name)
|
||||
assert.Equal(t, "tag", refs[1].Type)
|
||||
assert.Equal(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", refs[1].Object.String())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,9 +24,9 @@ func TestRepository_GetCodeActivityStats(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.NotNil(t, code)
|
||||
|
||||
assert.EqualValues(t, 9, code.CommitCount)
|
||||
assert.EqualValues(t, 10, code.CommitCount)
|
||||
assert.EqualValues(t, 3, code.AuthorCount)
|
||||
assert.EqualValues(t, 9, code.CommitCountInAllBranches)
|
||||
assert.EqualValues(t, 10, code.CommitCountInAllBranches)
|
||||
assert.EqualValues(t, 10, code.Additions)
|
||||
assert.EqualValues(t, 1, code.Deletions)
|
||||
assert.Len(t, code.Authors, 3)
|
||||
|
|
|
@ -25,11 +25,14 @@ func TestRepository_GetTags(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
return
|
||||
}
|
||||
assert.Len(t, tags, 1)
|
||||
assert.Len(t, tags, 2)
|
||||
assert.Equal(t, len(tags), total)
|
||||
assert.EqualValues(t, "test", tags[0].Name)
|
||||
assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[0].ID.String())
|
||||
assert.EqualValues(t, "signed-tag", tags[0].Name)
|
||||
assert.EqualValues(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", tags[0].ID.String())
|
||||
assert.EqualValues(t, "tag", tags[0].Type)
|
||||
assert.EqualValues(t, "test", tags[1].Name)
|
||||
assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[1].ID.String())
|
||||
assert.EqualValues(t, "tag", tags[1].Type)
|
||||
}
|
||||
|
||||
func TestRepository_GetTag(t *testing.T) {
|
||||
|
|
|
@ -14,10 +14,10 @@ func TestGetLatestCommitTime(t *testing.T) {
|
|||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
lct, err := GetLatestCommitTime(DefaultContext, bareRepo1Path)
|
||||
assert.NoError(t, err)
|
||||
// Time is Sun Jul 21 22:43:13 2019 +0200
|
||||
// Time is Sun Nov 13 16:40:14 2022 +0100
|
||||
// which is the time of commit
|
||||
// feaf4ba6bc635fec442f46ddd4512416ec43c2c2 (refs/heads/master)
|
||||
assert.EqualValues(t, 1563741793, lct.Unix())
|
||||
// ce064814f4a0d337b333e646ece456cd39fab612 (refs/heads/master)
|
||||
assert.EqualValues(t, 1668354014, lct.Unix())
|
||||
}
|
||||
|
||||
func TestRepoIsEmpty(t *testing.T) {
|
||||
|
|
Binary file not shown.
|
@ -1 +1,2 @@
|
|||
37991dec2c8e592043f47155ce4808d4580f9123 feaf4ba6bc635fec442f46ddd4512416ec43c2c2 silverwind <me@silverwind.io> 1563741799 +0200 push
|
||||
feaf4ba6bc635fec442f46ddd4512416ec43c2c2 ce064814f4a0d337b333e646ece456cd39fab612 silverwind <me@silverwind.io> 1668354026 +0100 push
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
37991dec2c8e592043f47155ce4808d4580f9123 feaf4ba6bc635fec442f46ddd4512416ec43c2c2 silverwind <me@silverwind.io> 1563741799 +0200 push
|
||||
feaf4ba6bc635fec442f46ddd4512416ec43c2c2 ce064814f4a0d337b333e646ece456cd39fab612 silverwind <me@silverwind.io> 1668354026 +0100 push
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1 +1 @@
|
|||
feaf4ba6bc635fec442f46ddd4512416ec43c2c2
|
||||
ce064814f4a0d337b333e646ece456cd39fab612
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
36f97d9a96457e2bab511db30fe2db03893ebc64
|
|
@ -15,8 +15,8 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// AddCacheControlToHeader adds suitable cache-control headers to response
|
||||
func AddCacheControlToHeader(h http.Header, maxAge time.Duration, additionalDirectives ...string) {
|
||||
// SetCacheControlInHeader sets suitable cache-control headers in the response
|
||||
func SetCacheControlInHeader(h http.Header, maxAge time.Duration, additionalDirectives ...string) {
|
||||
directives := make([]string, 0, 2+len(additionalDirectives))
|
||||
|
||||
// "max-age=0 + must-revalidate" (aka "no-cache") is preferred instead of "no-store"
|
||||
|
@ -31,7 +31,7 @@ func AddCacheControlToHeader(h http.Header, maxAge time.Duration, additionalDire
|
|||
directives = append(directives, "max-age=0", "private", "must-revalidate")
|
||||
|
||||
// to remind users they are using non-prod setting.
|
||||
h.Add("X-Gitea-Debug", "RUN_MODE="+setting.RunMode)
|
||||
h.Set("X-Gitea-Debug", "RUN_MODE="+setting.RunMode)
|
||||
}
|
||||
|
||||
h.Set("Cache-Control", strings.Join(append(directives, additionalDirectives...), ", "))
|
||||
|
@ -50,7 +50,7 @@ func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (
|
|||
|
||||
// HandleGenericTimeCache handles time-based caching for a HTTP request
|
||||
func HandleGenericTimeCache(req *http.Request, w http.ResponseWriter, lastModified time.Time) (handled bool) {
|
||||
AddCacheControlToHeader(w.Header(), setting.StaticCacheTime)
|
||||
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
|
||||
|
||||
ifModifiedSince := req.Header.Get("If-Modified-Since")
|
||||
if ifModifiedSince != "" {
|
||||
|
@ -81,7 +81,7 @@ func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag strin
|
|||
return true
|
||||
}
|
||||
}
|
||||
AddCacheControlToHeader(w.Header(), setting.StaticCacheTime)
|
||||
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -125,6 +125,6 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s
|
|||
}
|
||||
}
|
||||
}
|
||||
AddCacheControlToHeader(w.Header(), setting.StaticCacheTime)
|
||||
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package label
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// colorPattern is a regexp which can validate label color
|
||||
var colorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")
|
||||
|
||||
// Label represents label information loaded from template
|
||||
type Label struct {
|
||||
Name string `yaml:"name"`
|
||||
Color string `yaml:"color"`
|
||||
Description string `yaml:"description,omitempty"`
|
||||
Exclusive bool `yaml:"exclusive,omitempty"`
|
||||
}
|
||||
|
||||
// NormalizeColor normalizes a color string to a 6-character hex code
|
||||
func NormalizeColor(color string) (string, error) {
|
||||
// normalize case
|
||||
color = strings.TrimSpace(strings.ToLower(color))
|
||||
|
||||
// add leading hash
|
||||
if len(color) == 6 || len(color) == 3 {
|
||||
color = "#" + color
|
||||
}
|
||||
|
||||
if !colorPattern.MatchString(color) {
|
||||
return "", fmt.Errorf("bad color code: %s", color)
|
||||
}
|
||||
|
||||
// convert 3-character shorthand into 6-character version
|
||||
if len(color) == 4 {
|
||||
r := color[1]
|
||||
g := color[2]
|
||||
b := color[3]
|
||||
color = fmt.Sprintf("#%c%c%c%c%c%c", r, r, g, g, b, b)
|
||||
}
|
||||
|
||||
return color, nil
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package label
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/options"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type labelFile struct {
|
||||
Labels []*Label `yaml:"labels"`
|
||||
}
|
||||
|
||||
// ErrTemplateLoad represents a "ErrTemplateLoad" kind of error.
|
||||
type ErrTemplateLoad struct {
|
||||
TemplateFile string
|
||||
OriginalError error
|
||||
}
|
||||
|
||||
// IsErrTemplateLoad checks if an error is a ErrTemplateLoad.
|
||||
func IsErrTemplateLoad(err error) bool {
|
||||
_, ok := err.(ErrTemplateLoad)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrTemplateLoad) Error() string {
|
||||
return fmt.Sprintf("Failed to load label template file '%s': %v", err.TemplateFile, err.OriginalError)
|
||||
}
|
||||
|
||||
// GetTemplateFile loads the label template file by given name,
|
||||
// then parses and returns a list of name-color pairs and optionally description.
|
||||
func GetTemplateFile(name string) ([]*Label, error) {
|
||||
data, err := options.GetRepoInitFile("label", name+".yaml")
|
||||
if err == nil && len(data) > 0 {
|
||||
return parseYamlFormat(name+".yaml", data)
|
||||
}
|
||||
|
||||
data, err = options.GetRepoInitFile("label", name+".yml")
|
||||
if err == nil && len(data) > 0 {
|
||||
return parseYamlFormat(name+".yml", data)
|
||||
}
|
||||
|
||||
data, err = options.GetRepoInitFile("label", name)
|
||||
if err != nil {
|
||||
return nil, ErrTemplateLoad{name, fmt.Errorf("GetRepoInitFile: %w", err)}
|
||||
}
|
||||
|
||||
return parseLegacyFormat(name, data)
|
||||
}
|
||||
|
||||
func parseYamlFormat(name string, data []byte) ([]*Label, error) {
|
||||
lf := &labelFile{}
|
||||
|
||||
if err := yaml.Unmarshal(data, lf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate label data and fix colors
|
||||
for _, l := range lf.Labels {
|
||||
l.Color = strings.TrimSpace(l.Color)
|
||||
if len(l.Name) == 0 || len(l.Color) == 0 {
|
||||
return nil, ErrTemplateLoad{name, errors.New("label name and color are required fields")}
|
||||
}
|
||||
color, err := NormalizeColor(l.Color)
|
||||
if err != nil {
|
||||
return nil, ErrTemplateLoad{name, fmt.Errorf("bad HTML color code '%s' in label: %s", l.Color, l.Name)}
|
||||
}
|
||||
l.Color = color
|
||||
}
|
||||
|
||||
return lf.Labels, nil
|
||||
}
|
||||
|
||||
func parseLegacyFormat(name string, data []byte) ([]*Label, error) {
|
||||
lines := strings.Split(string(data), "\n")
|
||||
list := make([]*Label, 0, len(lines))
|
||||
for i := 0; i < len(lines); i++ {
|
||||
line := strings.TrimSpace(lines[i])
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
parts, description, _ := strings.Cut(line, ";")
|
||||
|
||||
color, name, ok := strings.Cut(parts, " ")
|
||||
if !ok {
|
||||
return nil, ErrTemplateLoad{name, fmt.Errorf("line is malformed: %s", line)}
|
||||
}
|
||||
|
||||
color, err := NormalizeColor(color)
|
||||
if err != nil {
|
||||
return nil, ErrTemplateLoad{name, fmt.Errorf("bad HTML color code '%s' in line: %s", color, line)}
|
||||
}
|
||||
|
||||
list = append(list, &Label{
|
||||
Name: strings.TrimSpace(name),
|
||||
Color: color,
|
||||
Description: strings.TrimSpace(description),
|
||||
})
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// LoadFormatted loads the labels' list of a template file as a string separated by comma
|
||||
func LoadFormatted(name string) (string, error) {
|
||||
var buf strings.Builder
|
||||
list, err := GetTemplateFile(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for i := 0; i < len(list); i++ {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(list[i].Name)
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package label
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestYamlParser(t *testing.T) {
|
||||
data := []byte(`labels:
|
||||
- name: priority/low
|
||||
exclusive: true
|
||||
color: "#0000ee"
|
||||
description: "Low priority"
|
||||
- name: priority/medium
|
||||
exclusive: true
|
||||
color: "0e0"
|
||||
description: "Medium priority"
|
||||
- name: priority/high
|
||||
exclusive: true
|
||||
color: "#ee0000"
|
||||
description: "High priority"
|
||||
- name: type/bug
|
||||
color: "#f00"
|
||||
description: "Bug"`)
|
||||
|
||||
labels, err := parseYamlFormat("test", data)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, labels, 4)
|
||||
assert.Equal(t, "priority/low", labels[0].Name)
|
||||
assert.True(t, labels[0].Exclusive)
|
||||
assert.Equal(t, "#0000ee", labels[0].Color)
|
||||
assert.Equal(t, "Low priority", labels[0].Description)
|
||||
assert.Equal(t, "priority/medium", labels[1].Name)
|
||||
assert.True(t, labels[1].Exclusive)
|
||||
assert.Equal(t, "#00ee00", labels[1].Color)
|
||||
assert.Equal(t, "Medium priority", labels[1].Description)
|
||||
assert.Equal(t, "priority/high", labels[2].Name)
|
||||
assert.True(t, labels[2].Exclusive)
|
||||
assert.Equal(t, "#ee0000", labels[2].Color)
|
||||
assert.Equal(t, "High priority", labels[2].Description)
|
||||
assert.Equal(t, "type/bug", labels[3].Name)
|
||||
assert.False(t, labels[3].Exclusive)
|
||||
assert.Equal(t, "#ff0000", labels[3].Color)
|
||||
assert.Equal(t, "Bug", labels[3].Description)
|
||||
}
|
||||
|
||||
func TestLegacyParser(t *testing.T) {
|
||||
data := []byte(`#ee0701 bug ; Something is not working
|
||||
#cccccc duplicate ; This issue or pull request already exists
|
||||
#84b6eb enhancement`)
|
||||
|
||||
labels, err := parseLegacyFormat("test", data)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, labels, 3)
|
||||
assert.Equal(t, "bug", labels[0].Name)
|
||||
assert.False(t, labels[0].Exclusive)
|
||||
assert.Equal(t, "#ee0701", labels[0].Color)
|
||||
assert.Equal(t, "Something is not working", labels[0].Description)
|
||||
assert.Equal(t, "duplicate", labels[1].Name)
|
||||
assert.False(t, labels[1].Exclusive)
|
||||
assert.Equal(t, "#cccccc", labels[1].Color)
|
||||
assert.Equal(t, "This issue or pull request already exists", labels[1].Description)
|
||||
assert.Equal(t, "enhancement", labels[2].Name)
|
||||
assert.False(t, labels[2].Exclusive)
|
||||
assert.Equal(t, "#84b6eb", labels[2].Color)
|
||||
assert.Empty(t, labels[2].Description)
|
||||
}
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -23,7 +24,8 @@ func TestRenderConsole(t *testing.T) {
|
|||
canRender := render.CanRender("test", strings.NewReader(k))
|
||||
assert.True(t, canRender)
|
||||
|
||||
err := render.Render(&markup.RenderContext{}, strings.NewReader(k), &buf)
|
||||
err := render.Render(&markup.RenderContext{Ctx: git.DefaultContext},
|
||||
strings.NewReader(k), &buf)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, v, buf.String())
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -23,7 +24,8 @@ func TestRenderCSV(t *testing.T) {
|
|||
|
||||
for k, v := range kases {
|
||||
var buf strings.Builder
|
||||
err := render.Render(&markup.RenderContext{}, strings.NewReader(k), &buf)
|
||||
err := render.Render(&markup.RenderContext{Ctx: git.DefaultContext},
|
||||
strings.NewReader(k), &buf)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, v, buf.String())
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue