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
|
- git fetch --tags --force
|
||||||
|
|
||||||
- name: publish
|
- name: publish
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
pull: always
|
pull: always
|
||||||
settings:
|
settings:
|
||||||
auto_tag: true
|
auto_tag: true
|
||||||
|
@ -1028,13 +1028,17 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
- name: publish-rootless
|
- name: publish-rootless
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
settings:
|
settings:
|
||||||
dockerfile: Dockerfile.rootless
|
dockerfile: Dockerfile.rootless
|
||||||
auto_tag: true
|
auto_tag: true
|
||||||
|
@ -1046,6 +1050,10 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
|
@ -1080,7 +1088,7 @@ steps:
|
||||||
- git fetch --tags --force
|
- git fetch --tags --force
|
||||||
|
|
||||||
- name: publish
|
- name: publish
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
pull: always
|
pull: always
|
||||||
settings:
|
settings:
|
||||||
tags: ${DRONE_TAG##v}-linux-amd64
|
tags: ${DRONE_TAG##v}-linux-amd64
|
||||||
|
@ -1091,13 +1099,17 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
- name: publish-rootless
|
- name: publish-rootless
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
settings:
|
settings:
|
||||||
dockerfile: Dockerfile.rootless
|
dockerfile: Dockerfile.rootless
|
||||||
tags: ${DRONE_TAG##v}-linux-amd64-rootless
|
tags: ${DRONE_TAG##v}-linux-amd64-rootless
|
||||||
|
@ -1108,6 +1120,10 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
|
@ -1142,7 +1158,7 @@ steps:
|
||||||
- git fetch --tags --force
|
- git fetch --tags --force
|
||||||
|
|
||||||
- name: publish
|
- name: publish
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
pull: always
|
pull: always
|
||||||
settings:
|
settings:
|
||||||
auto_tag: false
|
auto_tag: false
|
||||||
|
@ -1154,13 +1170,17 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
- name: publish-rootless
|
- name: publish-rootless
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
settings:
|
settings:
|
||||||
dockerfile: Dockerfile.rootless
|
dockerfile: Dockerfile.rootless
|
||||||
auto_tag: false
|
auto_tag: false
|
||||||
|
@ -1172,6 +1192,10 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
|
@ -1205,7 +1229,7 @@ steps:
|
||||||
- git fetch --tags --force
|
- git fetch --tags --force
|
||||||
|
|
||||||
- name: publish
|
- name: publish
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
pull: always
|
pull: always
|
||||||
settings:
|
settings:
|
||||||
auto_tag: false
|
auto_tag: false
|
||||||
|
@ -1217,13 +1241,17 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
- name: publish-rootless
|
- name: publish-rootless
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
settings:
|
settings:
|
||||||
dockerfile: Dockerfile.rootless
|
dockerfile: Dockerfile.rootless
|
||||||
auto_tag: false
|
auto_tag: false
|
||||||
|
@ -1235,6 +1263,10 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
|
@ -1243,7 +1275,7 @@ steps:
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: docker-linux-arm64-dry-run
|
name: docker-linux-amd64-dry-run
|
||||||
|
|
||||||
platform:
|
platform:
|
||||||
os: linux
|
os: linux
|
||||||
|
@ -1261,7 +1293,7 @@ trigger:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: dryrun
|
- name: dryrun
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
pull: always
|
pull: always
|
||||||
settings:
|
settings:
|
||||||
dry_run: true
|
dry_run: true
|
||||||
|
@ -1272,6 +1304,7 @@ steps:
|
||||||
environment:
|
environment:
|
||||||
PLUGIN_MIRROR:
|
PLUGIN_MIRROR:
|
||||||
from_secret: plugin_mirror
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
@ -1308,7 +1341,7 @@ steps:
|
||||||
- git fetch --tags --force
|
- git fetch --tags --force
|
||||||
|
|
||||||
- name: publish
|
- name: publish
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
pull: always
|
pull: always
|
||||||
settings:
|
settings:
|
||||||
auto_tag: true
|
auto_tag: true
|
||||||
|
@ -1320,13 +1353,17 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
- name: publish-rootless
|
- name: publish-rootless
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
settings:
|
settings:
|
||||||
dockerfile: Dockerfile.rootless
|
dockerfile: Dockerfile.rootless
|
||||||
auto_tag: true
|
auto_tag: true
|
||||||
|
@ -1338,6 +1375,10 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
|
@ -1372,7 +1413,7 @@ steps:
|
||||||
- git fetch --tags --force
|
- git fetch --tags --force
|
||||||
|
|
||||||
- name: publish
|
- name: publish
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
pull: always
|
pull: always
|
||||||
settings:
|
settings:
|
||||||
tags: ${DRONE_TAG##v}-linux-arm64
|
tags: ${DRONE_TAG##v}-linux-arm64
|
||||||
|
@ -1383,13 +1424,17 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
- name: publish-rootless
|
- name: publish-rootless
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
settings:
|
settings:
|
||||||
dockerfile: Dockerfile.rootless
|
dockerfile: Dockerfile.rootless
|
||||||
tags: ${DRONE_TAG##v}-linux-arm64-rootless
|
tags: ${DRONE_TAG##v}-linux-arm64-rootless
|
||||||
|
@ -1400,6 +1445,10 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
|
@ -1434,7 +1483,7 @@ steps:
|
||||||
- git fetch --tags --force
|
- git fetch --tags --force
|
||||||
|
|
||||||
- name: publish
|
- name: publish
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
pull: always
|
pull: always
|
||||||
settings:
|
settings:
|
||||||
auto_tag: false
|
auto_tag: false
|
||||||
|
@ -1446,13 +1495,17 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
- name: publish-rootless
|
- name: publish-rootless
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
settings:
|
settings:
|
||||||
dockerfile: Dockerfile.rootless
|
dockerfile: Dockerfile.rootless
|
||||||
auto_tag: false
|
auto_tag: false
|
||||||
|
@ -1464,6 +1517,10 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
|
@ -1497,7 +1554,7 @@ steps:
|
||||||
- git fetch --tags --force
|
- git fetch --tags --force
|
||||||
|
|
||||||
- name: publish
|
- name: publish
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
pull: always
|
pull: always
|
||||||
settings:
|
settings:
|
||||||
auto_tag: false
|
auto_tag: false
|
||||||
|
@ -1509,13 +1566,17 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
- name: publish-rootless
|
- name: publish-rootless
|
||||||
image: techknowlogick/drone-docker:latest
|
image: plugins/docker:latest
|
||||||
settings:
|
settings:
|
||||||
dockerfile: Dockerfile.rootless
|
dockerfile: Dockerfile.rootless
|
||||||
auto_tag: false
|
auto_tag: false
|
||||||
|
@ -1527,6 +1588,10 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
environment:
|
||||||
|
PLUGIN_MIRROR:
|
||||||
|
from_secret: plugin_mirror
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
exclude:
|
exclude:
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# Emacs
|
||||||
|
*~
|
||||||
|
|
||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
*.o
|
*.o
|
||||||
*.a
|
*.a
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
plugins:
|
plugins:
|
||||||
- stylelint-declaration-strict-value
|
- stylelint-declaration-strict-value
|
||||||
|
|
||||||
|
ignoreFiles:
|
||||||
|
- "**/*.go"
|
||||||
|
|
||||||
overrides:
|
overrides:
|
||||||
- files: ["**/*.less"]
|
- files: ["**/*.less"]
|
||||||
customSyntax: postcss-less
|
customSyntax: postcss-less
|
||||||
- files: ["**/chroma/*", "**/codemirror/*", "**/standalone/*", "**/console/*"]
|
- files: ["**/chroma/*", "**/codemirror/*", "**/standalone/*", "**/console/*"]
|
||||||
rules:
|
rules:
|
||||||
scale-unlimited/declaration-strict-value: null
|
scale-unlimited/declaration-strict-value: null
|
||||||
|
- files: ["**/chroma/*", "**/codemirror/*"]
|
||||||
|
rules:
|
||||||
|
block-no-empty: null
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
alpha-value-notation: null
|
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
|
RUN go build contrib/environment-to-ini/environment-to-ini.go
|
||||||
|
|
||||||
FROM alpine:3.17
|
FROM alpine:3.17
|
||||||
LABEL maintainer="maintainers@gitea.io"
|
LABEL maintainer="contact@forgejo.org"
|
||||||
|
|
||||||
EXPOSE 22 3000
|
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
|
RUN go build contrib/environment-to-ini/environment-to-ini.go
|
||||||
|
|
||||||
FROM alpine:3.17
|
FROM alpine:3.17
|
||||||
LABEL maintainer="maintainers@gitea.io"
|
LABEL maintainer="contact@forgejo.org"
|
||||||
|
|
||||||
EXPOSE 2222 3000
|
EXPOSE 2222 3000
|
||||||
|
|
||||||
|
|
15
Makefile
15
Makefile
|
@ -83,7 +83,7 @@ ifneq ($(DRONE_TAG),)
|
||||||
GITEA_VERSION ?= $(VERSION)
|
GITEA_VERSION ?= $(VERSION)
|
||||||
else
|
else
|
||||||
ifneq ($(DRONE_BRANCH),)
|
ifneq ($(DRONE_BRANCH),)
|
||||||
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
|
VERSION ?= $(shell echo $(DRONE_BRANCH) | sed -e 's|v\([0-9.][0-9.]*\)/.*|\1|')
|
||||||
else
|
else
|
||||||
VERSION ?= main
|
VERSION ?= main
|
||||||
endif
|
endif
|
||||||
|
@ -286,8 +286,7 @@ misspell-check:
|
||||||
.PHONY: vet
|
.PHONY: vet
|
||||||
vet:
|
vet:
|
||||||
@echo "Running go vet..."
|
@echo "Running go vet..."
|
||||||
@GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet
|
@$(GO) vet $(GO_PACKAGES)
|
||||||
@$(GO) vet -vettool=gitea-vet $(GO_PACKAGES)
|
|
||||||
|
|
||||||
.PHONY: $(TAGS_EVIDENCE)
|
.PHONY: $(TAGS_EVIDENCE)
|
||||||
$(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 $@
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
|
||||||
|
|
||||||
.PHONY: release
|
.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):
|
$(DIST_DIRS):
|
||||||
mkdir -p $(DIST_DIRS)
|
mkdir -p $(DIST_DIRS)
|
||||||
|
@ -768,7 +767,7 @@ endif
|
||||||
|
|
||||||
.PHONY: release-linux
|
.PHONY: release-linux
|
||||||
release-linux: | $(DIST_DIRS)
|
release-linux: | $(DIST_DIRS)
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out forgejo-$(VERSION) .
|
||||||
ifeq ($(CI),true)
|
ifeq ($(CI),true)
|
||||||
cp /build/* $(DIST)/binaries
|
cp /build/* $(DIST)/binaries
|
||||||
endif
|
endif
|
||||||
|
@ -805,8 +804,8 @@ release-sources: | $(DIST_DIRS)
|
||||||
# bsdtar needs a ^ to prevent matching subdirectories
|
# bsdtar needs a ^ to prevent matching subdirectories
|
||||||
$(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./)
|
$(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
|
# 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)/|'"))
|
$(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/gitea-src-$(VERSION).tar.gz .
|
tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) $(TRANSFORM) -czf $(DIST)/release/forgejo-src-$(VERSION).tar.gz .
|
||||||
rm -f $(STORED_VERSION_FILE)
|
rm -f $(STORED_VERSION_FILE)
|
||||||
|
|
||||||
.PHONY: release-docs
|
.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 -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
|
||||||
cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
|
cp -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
|
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
|
$(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.*
|
rm -f $(FOMANTIC_WORK_DIR)/build/*.min.*
|
||||||
|
|
||||||
|
|
11
cmd/admin.go
11
cmd/admin.go
|
@ -7,6 +7,7 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
@ -469,11 +470,19 @@ func runAddOauth(c *cli.Context) error {
|
||||||
return err
|
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{
|
return auth_model.CreateSource(&auth_model.Source{
|
||||||
Type: auth_model.OAuth2,
|
Type: auth_model.OAuth2,
|
||||||
Name: c.String("name"),
|
Name: c.String("name"),
|
||||||
IsActive: true,
|
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("Log path: %s", setting.Log.RootPath)
|
||||||
log.Info("Configuration file: %s", setting.CustomConf)
|
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")
|
fmt.Println("This command can only be used with a MySQL database")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -272,13 +272,14 @@ func runDump(ctx *cli.Context) error {
|
||||||
fatal("Failed to create tmp file: %v", err)
|
fatal("Failed to create tmp file: %v", err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
|
_ = dbDump.Close()
|
||||||
if err := util.Remove(dbDump.Name()); err != nil {
|
if err := util.Remove(dbDump.Name()); err != nil {
|
||||||
log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err)
|
log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
targetDBType := ctx.String("database")
|
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)
|
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
|
||||||
} else {
|
} else {
|
||||||
log.Info("Dumping database...")
|
log.Info("Dumping database...")
|
||||||
|
|
25
cmd/serv.go
25
cmd/serv.go
|
@ -11,6 +11,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -290,17 +291,21 @@ func runServ(c *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special handle for Windows.
|
|
||||||
if setting.IsWindows {
|
|
||||||
verb = strings.Replace(verb, "-", " ", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var gitcmd *exec.Cmd
|
var gitcmd *exec.Cmd
|
||||||
verbs := strings.Split(verb, " ")
|
gitBinPath := filepath.Dir(git.GitExecutable) // e.g. /usr/bin
|
||||||
if len(verbs) == 2 {
|
gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack
|
||||||
gitcmd = exec.CommandContext(ctx, verbs[0], verbs[1], repoPath)
|
if _, err := os.Stat(gitBinVerb); err != nil {
|
||||||
} else {
|
// if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git
|
||||||
gitcmd = exec.CommandContext(ctx, verb, repoPath)
|
// 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)
|
process.SetSysProcAttribute(gitcmd)
|
||||||
|
|
|
@ -1871,6 +1871,9 @@ ROUTER = console
|
||||||
;;
|
;;
|
||||||
;; Minio enabled ssl only available when STORAGE_TYPE is `minio`
|
;; Minio enabled ssl only available when STORAGE_TYPE is `minio`
|
||||||
;MINIO_USE_SSL = false
|
;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 enabled ssl only available when STORAGE_TYPE is `minio`
|
||||||
;MINIO_USE_SSL = false
|
;MINIO_USE_SSL = false
|
||||||
|
;;
|
||||||
|
;; Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
||||||
|
;MINIO_INSECURE_SKIP_VERIFY = false
|
||||||
|
|
||||||
;[proxy]
|
;[proxy]
|
||||||
;; Enable the proxy, all requests to external via HTTP will be affected
|
;; 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
|
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}}
|
{{#if build.tags}}
|
||||||
{{#unless contains "-rc" build.tag}}
|
{{#unless (contains "-rc" build.tag)}}
|
||||||
tags:
|
tags:
|
||||||
{{#each build.tags}}
|
{{#each build.tags}}
|
||||||
- {{this}}-rootless
|
- {{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}}
|
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}}
|
{{#if build.tags}}
|
||||||
{{#unless contains "-rc" build.tag }}
|
{{#unless (contains "-rc" build.tag)}}
|
||||||
tags:
|
tags:
|
||||||
{{#each build.tags}}
|
{{#each build.tags}}
|
||||||
- {{this}}
|
- {{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_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_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_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`)
|
## 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_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_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_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`)
|
## 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_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_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_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:
|
And you can also define a customize storage like below:
|
||||||
|
|
||||||
|
@ -1298,6 +1301,8 @@ MINIO_BUCKET = gitea
|
||||||
MINIO_LOCATION = us-east-1
|
MINIO_LOCATION = us-east-1
|
||||||
; Minio enabled ssl only available when STORAGE_TYPE is `minio`
|
; Minio enabled ssl only available when STORAGE_TYPE is `minio`
|
||||||
MINIO_USE_SSL = false
|
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`.
|
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_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_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_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`)
|
## Proxy (`proxy`)
|
||||||
|
|
||||||
|
|
|
@ -431,6 +431,8 @@ MINIO_BUCKET = gitea
|
||||||
MINIO_LOCATION = us-east-1
|
MINIO_LOCATION = us-east-1
|
||||||
; Minio enabled ssl only available when STORAGE_TYPE is `minio`
|
; Minio enabled ssl only available when STORAGE_TYPE is `minio`
|
||||||
MINIO_USE_SSL = false
|
MINIO_USE_SSL = false
|
||||||
|
; Minio skip SSL verification available when STORAGE_TYPE is `minio`
|
||||||
|
MINIO_INSECURE_SKIP_VERIFY = false
|
||||||
```
|
```
|
||||||
|
|
||||||
然后你在 `[attachment]`, `[lfs]` 等中可以把这个名字用作 `STORAGE_TYPE` 的值。
|
然后你在 `[attachment]`, `[lfs]` 等中可以把这个名字用作 `STORAGE_TYPE` 的值。
|
||||||
|
|
|
@ -282,9 +282,22 @@ To add custom .gitignore, add a file with existing [.gitignore rules](https://gi
|
||||||
|
|
||||||
### Labels
|
### 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`
|
`#hex-color label name ; label description`
|
||||||
|
|
||||||
|
For more information, see the [labels documentation]({{< relref "doc/usage/labels.en-us.md" >}}).
|
||||||
|
|
||||||
### Licenses
|
### Licenses
|
||||||
|
|
||||||
To add a custom license, add a file with the license text to `$GITEA_CUSTOM/options/license`
|
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
|
toc: false
|
||||||
draft: false
|
draft: false
|
||||||
menu:
|
menu:
|
||||||
sidebar:
|
sidebar:
|
||||||
parent: "developers"
|
parent: "developers"
|
||||||
name: "Guidelines for Refactoring"
|
name: "Guidelines for Refactoring"
|
||||||
weight: 20
|
weight: 20
|
||||||
identifier: "guidelines-refactoring"
|
identifier: "guidelines-refactoring"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Guidelines for 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"
|
parent: "packages"
|
||||||
name: "Overview"
|
name: "Overview"
|
||||||
weight: 1
|
weight: 1
|
||||||
identifier: "overview"
|
identifier: "packages-overview"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Package Registry
|
# Package Registry
|
||||||
|
|
|
@ -9,7 +9,7 @@ menu:
|
||||||
parent: "secrets"
|
parent: "secrets"
|
||||||
name: "Overview"
|
name: "Overview"
|
||||||
weight: 1
|
weight: 1
|
||||||
identifier: "overview"
|
identifier: "secrets-overview"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Secrets
|
# 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 {
|
if len(needs) > 0 {
|
||||||
status = StatusBlocked
|
status = StatusBlocked
|
||||||
}
|
}
|
||||||
|
job.Name, _ = util.SplitStringAtByteN(job.Name, 255)
|
||||||
runJobs = append(runJobs, &ActionRunJob{
|
runJobs = append(runJobs, &ActionRunJob{
|
||||||
RunID: run.ID,
|
RunID: run.ID,
|
||||||
RepoID: run.RepoID,
|
RepoID: run.RepoID,
|
||||||
|
|
|
@ -298,8 +298,9 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
|
||||||
if len(workflowJob.Steps) > 0 {
|
if len(workflowJob.Steps) > 0 {
|
||||||
steps := make([]*ActionTaskStep, len(workflowJob.Steps))
|
steps := make([]*ActionTaskStep, len(workflowJob.Steps))
|
||||||
for i, v := range workflowJob.Steps {
|
for i, v := range workflowJob.Steps {
|
||||||
|
name, _ := util.SplitStringAtByteN(v.String(), 255)
|
||||||
steps[i] = &ActionTaskStep{
|
steps[i] = &ActionTaskStep{
|
||||||
Name: v.String(),
|
Name: name,
|
||||||
TaskID: task.ID,
|
TaskID: task.ID,
|
||||||
Index: int64(i),
|
Index: int64(i),
|
||||||
RepoID: task.RepoID,
|
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")
|
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
||||||
|
|
||||||
indices := []*schemas.Index{actUserIndex, repoIndex}
|
indices := []*schemas.Index{actUserIndex, repoIndex}
|
||||||
if setting.Database.UsePostgreSQL {
|
if setting.Database.Type.IsPostgreSQL() {
|
||||||
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
||||||
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
||||||
indices = append(indices, cudIndex)
|
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
|
// CountActionCreatedUnixString count actions where created_unix is an empty string
|
||||||
func CountActionCreatedUnixString(ctx context.Context) (int64, error) {
|
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 db.GetEngine(ctx).Where(`created_unix = ""`).Count(new(Action))
|
||||||
}
|
}
|
||||||
return 0, nil
|
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
|
// FixActionCreatedUnixString set created_unix to zero if it is an empty string
|
||||||
func FixActionCreatedUnixString(ctx context.Context) (int64, error) {
|
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 = ""`)
|
res, err := db.GetEngine(ctx).Exec(`UPDATE action SET created_unix = 0 WHERE created_unix = ""`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
|
@ -234,7 +234,7 @@ func TestGetFeedsCorrupted(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConsistencyUpdateAction(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.")
|
t.Skip("Test is only for SQLite database.")
|
||||||
}
|
}
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
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"
|
groupBy := "created_unix / 900 * 900"
|
||||||
groupByName := "timestamp" // We need this extra case because mssql doesn't allow grouping by alias
|
groupByName := "timestamp" // We need this extra case because mssql doesn't allow grouping by alias
|
||||||
switch {
|
switch {
|
||||||
case setting.Database.UseMySQL:
|
case setting.Database.Type.IsMySQL():
|
||||||
groupBy = "created_unix DIV 900 * 900"
|
groupBy = "created_unix DIV 900 * 900"
|
||||||
case setting.Database.UseMSSQL:
|
case setting.Database.Type.IsMSSQL():
|
||||||
groupByName = groupBy
|
groupByName = groupBy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,7 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final
|
||||||
return DefaultAvatarLink()
|
return DefaultAvatarLink()
|
||||||
}
|
}
|
||||||
|
|
||||||
enableFederatedAvatar := system_model.GetSettingBool(ctx, system_model.KeyPictureEnableFederatedAvatar)
|
enableFederatedAvatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureEnableFederatedAvatar)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if enableFederatedAvatar && system_model.LibravatarService != nil {
|
if enableFederatedAvatar && system_model.LibravatarService != nil {
|
||||||
|
@ -174,7 +174,7 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final
|
||||||
return urlStr
|
return urlStr
|
||||||
}
|
}
|
||||||
|
|
||||||
disableGravatar := system_model.GetSettingBool(ctx, system_model.KeyPictureDisableGravatar)
|
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
|
||||||
if !disableGravatar {
|
if !disableGravatar {
|
||||||
// copy GravatarSourceURL, because we will modify its Path.
|
// copy GravatarSourceURL, because we will modify its Path.
|
||||||
avatarURLCopy := *system_model.GravatarSourceURL
|
avatarURLCopy := *system_model.GravatarSourceURL
|
||||||
|
|
|
@ -28,7 +28,7 @@ func enableGravatar(t *testing.T) {
|
||||||
err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "false")
|
err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "false")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
setting.GravatarSource = gravatarSource
|
setting.GravatarSource = gravatarSource
|
||||||
err = system_model.Init()
|
err = system_model.Init(db.DefaultContext)
|
||||||
assert.NoError(t, err)
|
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.
|
// 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.
|
// Handles especially SQLite correctly as UPPER there only transforms ASCII letters.
|
||||||
func BuildCaseInsensitiveLike(key, value string) builder.Cond {
|
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 + ")", util.ToUpperASCII(value)}
|
||||||
}
|
}
|
||||||
return builder.Like{"UPPER(" + key + ")", strings.ToUpper(value)}
|
return builder.Like{"UPPER(" + key + ")", strings.ToUpper(value)}
|
||||||
|
|
|
@ -100,12 +100,12 @@ func newXORMEngine() (*xorm.Engine, error) {
|
||||||
|
|
||||||
var engine *xorm.Engine
|
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
|
// OK whilst we sort out our schema issues - create a schema aware postgres
|
||||||
registerPostgresSchemaDriver()
|
registerPostgresSchemaDriver()
|
||||||
engine, err = xorm.NewEngine("postgresschema", connStr)
|
engine, err = xorm.NewEngine("postgresschema", connStr)
|
||||||
} else {
|
} else {
|
||||||
engine, err = xorm.NewEngine(setting.Database.Type, connStr)
|
engine, err = xorm.NewEngine(setting.Database.Type.String(), connStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
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
|
// GetNextResourceIndex generates a resource index, it must run in the same transaction where the resource is created
|
||||||
func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
|
func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
|
||||||
if setting.Database.UsePostgreSQL {
|
if setting.Database.Type.IsPostgreSQL() {
|
||||||
return postgresGetNextResourceIndex(ctx, tableName, groupID)
|
return postgresGetNextResourceIndex(ctx, tableName, groupID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -134,7 +134,7 @@ func Find[T any](ctx context.Context, opts FindOptions, objects *[]T) error {
|
||||||
if !opts.IsListAll() {
|
if !opts.IsListAll() {
|
||||||
sess.Limit(opts.GetSkipTake())
|
sess.Limit(opts.GetSkipTake())
|
||||||
}
|
}
|
||||||
return sess.Find(&objects)
|
return sess.Find(objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count represents a common count function which accept an options interface
|
// 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() {
|
if !opts.IsListAll() {
|
||||||
sess.Limit(opts.GetSkipTake())
|
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
|
// CountBadSequences looks for broken sequences from recreate-table mistakes
|
||||||
func CountBadSequences(_ context.Context) (int64, error) {
|
func CountBadSequences(_ context.Context) (int64, error) {
|
||||||
if !setting.Database.UsePostgreSQL {
|
if !setting.Database.Type.IsPostgreSQL() {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func CountBadSequences(_ context.Context) (int64, error) {
|
||||||
|
|
||||||
// FixBadSequences fixes for broken sequences from recreate-table mistakes
|
// FixBadSequences fixes for broken sequences from recreate-table mistakes
|
||||||
func FixBadSequences(_ context.Context) error {
|
func FixBadSequences(_ context.Context) error {
|
||||||
if !setting.Database.UsePostgreSQL {
|
if !setting.Database.Type.IsPostgreSQL() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
fork_id: 0
|
fork_id: 0
|
||||||
is_template: false
|
is_template: false
|
||||||
template_id: 0
|
template_id: 0
|
||||||
size: 6708
|
size: 7028
|
||||||
is_fsck_enabled: true
|
is_fsck_enabled: true
|
||||||
close_issues_via_commit_in_any_branch: false
|
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
|
// GetNextCommitStatusIndex retried 3 times to generate a resource index
|
||||||
func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
|
func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
|
||||||
if setting.Database.UsePostgreSQL {
|
if setting.Database.Type.IsPostgreSQL() {
|
||||||
return postgresGetCommitStatusIndex(ctx, repoID, sha)
|
return postgresGetCommitStatusIndex(ctx, repoID, sha)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,12 @@ package issues
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
@ -78,9 +78,6 @@ func (err ErrLabelNotExist) Unwrap() error {
|
||||||
return util.ErrNotExist
|
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.
|
// Label represents a label of repository for issues.
|
||||||
type Label struct {
|
type Label struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
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.
|
// CalOpenIssues sets the number of open issues of a label based on the already stored number of closed issues.
|
||||||
func (label *Label) CalOpenIssues() {
|
func (l *Label) CalOpenIssues() {
|
||||||
label.NumOpenIssues = label.NumIssues - label.NumClosedIssues
|
l.NumOpenIssues = l.NumIssues - l.NumClosedIssues
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalOpenOrgIssues calculates the open issues of a label for a specific repo
|
// 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{
|
counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{
|
||||||
RepoID: repoID,
|
RepoID: repoID,
|
||||||
LabelIDs: []int64{labelID},
|
LabelIDs: []int64{labelID},
|
||||||
|
@ -122,22 +119,22 @@ func (label *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64)
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, count := range counts {
|
for _, count := range counts {
|
||||||
label.NumOpenRepoIssues += count
|
l.NumOpenRepoIssues += count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadSelectedLabelsAfterClick calculates the set of selected labels when a label is clicked
|
// 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
|
var labelQuerySlice []string
|
||||||
labelSelected := false
|
labelSelected := false
|
||||||
labelID := strconv.FormatInt(label.ID, 10)
|
labelID := strconv.FormatInt(l.ID, 10)
|
||||||
labelScope := label.ExclusiveScope()
|
labelScope := l.ExclusiveScope()
|
||||||
for i, s := range currentSelectedLabels {
|
for i, s := range currentSelectedLabels {
|
||||||
if s == label.ID {
|
if s == l.ID {
|
||||||
labelSelected = true
|
labelSelected = true
|
||||||
} else if -s == label.ID {
|
} else if -s == l.ID {
|
||||||
labelSelected = true
|
labelSelected = true
|
||||||
label.IsExcluded = true
|
l.IsExcluded = true
|
||||||
} else if s != 0 {
|
} else if s != 0 {
|
||||||
// Exclude other labels in the same scope from selection
|
// Exclude other labels in the same scope from selection
|
||||||
if s < 0 || labelScope == "" || labelScope != currentSelectedExclusiveScopes[i] {
|
if s < 0 || labelScope == "" || labelScope != currentSelectedExclusiveScopes[i] {
|
||||||
|
@ -148,23 +145,23 @@ func (label *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64,
|
||||||
if !labelSelected {
|
if !labelSelected {
|
||||||
labelQuerySlice = append(labelQuerySlice, labelID)
|
labelQuerySlice = append(labelQuerySlice, labelID)
|
||||||
}
|
}
|
||||||
label.IsSelected = labelSelected
|
l.IsSelected = labelSelected
|
||||||
label.QueryString = strings.Join(labelQuerySlice, ",")
|
l.QueryString = strings.Join(labelQuerySlice, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
// BelongsToOrg returns true if label is an organization label
|
// BelongsToOrg returns true if label is an organization label
|
||||||
func (label *Label) BelongsToOrg() bool {
|
func (l *Label) BelongsToOrg() bool {
|
||||||
return label.OrgID > 0
|
return l.OrgID > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// BelongsToRepo returns true if label is a repository label
|
// BelongsToRepo returns true if label is a repository label
|
||||||
func (label *Label) BelongsToRepo() bool {
|
func (l *Label) BelongsToRepo() bool {
|
||||||
return label.RepoID > 0
|
return l.RepoID > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get color as RGB values in 0..255 range
|
// Get color as RGB values in 0..255 range
|
||||||
func (label *Label) ColorRGB() (float64, float64, float64, error) {
|
func (l *Label) ColorRGB() (float64, float64, float64, error) {
|
||||||
color, err := strconv.ParseUint(label.Color[1:], 16, 64)
|
color, err := strconv.ParseUint(l.Color[1:], 16, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, err
|
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
|
// Determine if label text should be light or dark to be readable on background color
|
||||||
func (label *Label) UseLightTextColor() bool {
|
func (l *Label) UseLightTextColor() bool {
|
||||||
if strings.HasPrefix(label.Color, "#") {
|
if strings.HasPrefix(l.Color, "#") {
|
||||||
if r, g, b, err := label.ColorRGB(); err == nil {
|
if r, g, b, err := l.ColorRGB(); err == nil {
|
||||||
// Perceived brightness from: https://www.w3.org/TR/AERT/#color-contrast
|
// Perceived brightness from: https://www.w3.org/TR/AERT/#color-contrast
|
||||||
// In the future WCAG 3 APCA may be a better solution
|
// In the future WCAG 3 APCA may be a better solution
|
||||||
brightness := (0.299*r + 0.587*g + 0.114*b) / 255
|
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
|
// Return scope substring of label name, or empty string if none exists
|
||||||
func (label *Label) ExclusiveScope() string {
|
func (l *Label) ExclusiveScope() string {
|
||||||
if !label.Exclusive {
|
if !l.Exclusive {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
lastIndex := strings.LastIndex(label.Name, "/")
|
lastIndex := strings.LastIndex(l.Name, "/")
|
||||||
if lastIndex == -1 || lastIndex == 0 || lastIndex == len(label.Name)-1 {
|
if lastIndex == -1 || lastIndex == 0 || lastIndex == len(l.Name)-1 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return label.Name[:lastIndex]
|
return l.Name[:lastIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLabel creates a new label
|
// NewLabel creates a new label
|
||||||
func NewLabel(ctx context.Context, label *Label) error {
|
func NewLabel(ctx context.Context, l *Label) error {
|
||||||
if !LabelColorPattern.MatchString(label.Color) {
|
color, err := label.NormalizeColor(l.Color)
|
||||||
return fmt.Errorf("bad color code: %s", label.Color)
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
l.Color = color
|
||||||
|
|
||||||
// normalize case
|
return db.Insert(ctx, l)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLabels creates new labels
|
// NewLabels creates new labels
|
||||||
|
@ -234,11 +217,14 @@ func NewLabels(labels ...*Label) error {
|
||||||
}
|
}
|
||||||
defer committer.Close()
|
defer committer.Close()
|
||||||
|
|
||||||
for _, label := range labels {
|
for _, l := range labels {
|
||||||
if !LabelColorPattern.MatchString(label.Color) {
|
color, err := label.NormalizeColor(l.Color)
|
||||||
return fmt.Errorf("bad color code: %s", label.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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,15 +233,18 @@ func NewLabels(labels ...*Label) error {
|
||||||
|
|
||||||
// UpdateLabel updates label information.
|
// UpdateLabel updates label information.
|
||||||
func UpdateLabel(l *Label) error {
|
func UpdateLabel(l *Label) error {
|
||||||
if !LabelColorPattern.MatchString(l.Color) {
|
color, err := label.NormalizeColor(l.Color)
|
||||||
return fmt.Errorf("bad color code: %s", l.Color)
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
l.Color = color
|
||||||
|
|
||||||
return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive")
|
return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive")
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteLabel delete a label
|
// DeleteLabel delete a label
|
||||||
func DeleteLabel(id, labelID int64) error {
|
func DeleteLabel(id, labelID int64) error {
|
||||||
label, err := GetLabelByID(db.DefaultContext, labelID)
|
l, err := GetLabelByID(db.DefaultContext, labelID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if IsErrLabelNotExist(err) {
|
if IsErrLabelNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
|
@ -271,10 +260,10 @@ func DeleteLabel(id, labelID int64) error {
|
||||||
|
|
||||||
sess := db.GetEngine(ctx)
|
sess := db.GetEngine(ctx)
|
||||||
|
|
||||||
if label.BelongsToOrg() && label.OrgID != id {
|
if l.BelongsToOrg() && l.OrgID != id {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if label.BelongsToRepo() && label.RepoID != id {
|
if l.BelongsToRepo() && l.RepoID != id {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -682,14 +671,14 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us
|
||||||
if err = issue.LoadRepo(ctx); err != nil {
|
if err = issue.LoadRepo(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, label := range labels {
|
for _, l := range labels {
|
||||||
// Don't add already present labels and invalid labels
|
// Don't add already present labels and invalid labels
|
||||||
if HasIssueLabel(ctx, issue.ID, label.ID) ||
|
if HasIssueLabel(ctx, issue.ID, l.ID) ||
|
||||||
(label.RepoID != issue.RepoID && label.OrgID != issue.Repo.OwnerID) {
|
(l.RepoID != issue.RepoID && l.OrgID != issue.Repo.OwnerID) {
|
||||||
continue
|
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)
|
return fmt.Errorf("newIssueLabel: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -778,7 +767,7 @@ func CountOrphanedLabels(ctx context.Context) (int64, error) {
|
||||||
norepo, err := db.GetEngine(ctx).Table("label").
|
norepo, err := db.GetEngine(ctx).Table("label").
|
||||||
Where(builder.And(
|
Where(builder.And(
|
||||||
builder.Gt{"repo_id": 0},
|
builder.Gt{"repo_id": 0},
|
||||||
builder.NotIn("repo_id", builder.Select("id").From("repository")),
|
builder.NotIn("repo_id", builder.Select("id").From("`repository`")),
|
||||||
)).
|
)).
|
||||||
Count()
|
Count()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -788,7 +777,7 @@ func CountOrphanedLabels(ctx context.Context) (int64, error) {
|
||||||
noorg, err := db.GetEngine(ctx).Table("label").
|
noorg, err := db.GetEngine(ctx).Table("label").
|
||||||
Where(builder.And(
|
Where(builder.And(
|
||||||
builder.Gt{"org_id": 0},
|
builder.Gt{"org_id": 0},
|
||||||
builder.NotIn("org_id", builder.Select("id").From("user")),
|
builder.NotIn("org_id", builder.Select("id").From("`user`")),
|
||||||
)).
|
)).
|
||||||
Count()
|
Count()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -809,7 +798,7 @@ func DeleteOrphanedLabels(ctx context.Context) error {
|
||||||
if _, err := db.GetEngine(ctx).
|
if _, err := db.GetEngine(ctx).
|
||||||
Where(builder.And(
|
Where(builder.And(
|
||||||
builder.Gt{"repo_id": 0},
|
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 {
|
Delete(Label{}); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -819,7 +808,7 @@ func DeleteOrphanedLabels(ctx context.Context) error {
|
||||||
if _, err := db.GetEngine(ctx).
|
if _, err := db.GetEngine(ctx).
|
||||||
Where(builder.And(
|
Where(builder.And(
|
||||||
builder.Gt{"org_id": 0},
|
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 {
|
Delete(Label{}); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -15,8 +15,6 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO TestGetLabelTemplateFile
|
|
||||||
|
|
||||||
func TestLabel_CalOpenIssues(t *testing.T) {
|
func TestLabel_CalOpenIssues(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
|
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
|
// GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged
|
||||||
// by given head information (repo and branch).
|
// 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)
|
prs := make([]*PullRequest, 0, 2)
|
||||||
return prs, db.GetEngine(db.DefaultContext).
|
sess := 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).
|
|
||||||
Join("INNER", "issue", "issue.id = pull_request.issue_id").
|
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
|
// 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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch)
|
prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -111,6 +114,7 @@ func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequ
|
||||||
return prs, db.GetEngine(db.DefaultContext).
|
return prs, db.GetEngine(db.DefaultContext).
|
||||||
Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
|
Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
|
||||||
repoID, branch, false, false).
|
repoID, branch, false, false).
|
||||||
|
OrderBy("issue.updated_unix DESC").
|
||||||
Join("INNER", "issue", "issue.id=pull_request.issue_id").
|
Join("INNER", "issue", "issue.id=pull_request.issue_id").
|
||||||
Find(&prs)
|
Find(&prs)
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ func TestHasUnmergedPullRequestsByHeadInfo(t *testing.T) {
|
||||||
|
|
||||||
func TestGetUnmergedPullRequestsByHeadInfo(t *testing.T) {
|
func TestGetUnmergedPullRequestsByHeadInfo(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
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.NoError(t, err)
|
||||||
assert.Len(t, prs, 1)
|
assert.Len(t, prs, 1)
|
||||||
for _, pr := range prs {
|
for _, pr := range prs {
|
||||||
|
|
|
@ -89,7 +89,7 @@ func RecreateTable(sess *xorm.Session, bean interface{}) error {
|
||||||
hasID = hasID || (column.IsPrimaryKey && column.IsAutoIncrement)
|
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 {
|
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)
|
log.Error("Unable to set identity insert for table %s. Error: %v", tempTableName, err)
|
||||||
return err
|
return err
|
||||||
|
@ -143,7 +143,7 @@ func RecreateTable(sess *xorm.Session, bean interface{}) error {
|
||||||
return err
|
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 {
|
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)
|
log.Error("Unable to switch off identity insert for table %s. Error: %v", tempTableName, err)
|
||||||
return err
|
return err
|
||||||
|
@ -151,7 +151,7 @@ func RecreateTable(sess *xorm.Session, bean interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case setting.Database.UseSQLite3:
|
case setting.Database.Type.IsSQLite3():
|
||||||
// SQLite will drop all the constraints on the old table
|
// SQLite will drop all the constraints on the old table
|
||||||
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
|
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
|
||||||
log.Error("Unable to drop old table %s. Error: %v", tableName, err)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case setting.Database.UseMySQL:
|
case setting.Database.Type.IsMySQL():
|
||||||
// MySQL will drop all the constraints on the old table
|
// MySQL will drop all the constraints on the old table
|
||||||
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
|
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
|
||||||
log.Error("Unable to drop old table %s. Error: %v", tableName, err)
|
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)
|
log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case setting.Database.UsePostgreSQL:
|
case setting.Database.Type.IsPostgreSQL():
|
||||||
var originalSequences []string
|
var originalSequences []string
|
||||||
type sequenceData struct {
|
type sequenceData struct {
|
||||||
LastValue int `xorm:"'last_value'"`
|
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
|
// MSSQL will drop all the constraints on the old table
|
||||||
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
|
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
|
||||||
log.Error("Unable to drop old table %s. Error: %v", tableName, err)
|
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
|
// TODO: This will not work if there are foreign keys
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case setting.Database.UseSQLite3:
|
case setting.Database.Type.IsSQLite3():
|
||||||
// First drop the indexes on the columns
|
// First drop the indexes on the columns
|
||||||
res, errIndex := sess.Query(fmt.Sprintf("PRAGMA index_list(`%s`)", tableName))
|
res, errIndex := sess.Query(fmt.Sprintf("PRAGMA index_list(`%s`)", tableName))
|
||||||
if errIndex != nil {
|
if errIndex != nil {
|
||||||
|
@ -405,7 +405,7 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case setting.Database.UsePostgreSQL:
|
case setting.Database.Type.IsPostgreSQL():
|
||||||
cols := ""
|
cols := ""
|
||||||
for _, col := range columnNames {
|
for _, col := range columnNames {
|
||||||
if cols != "" {
|
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 {
|
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)
|
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
|
// Drop indexes on columns first
|
||||||
sql := fmt.Sprintf("SHOW INDEX FROM %s WHERE column_name IN ('%s')", tableName, strings.Join(columnNames, "','"))
|
sql := fmt.Sprintf("SHOW INDEX FROM %s WHERE column_name IN ('%s')", tableName, strings.Join(columnNames, "','"))
|
||||||
res, err := sess.Query(sql)
|
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 {
|
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)
|
return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
|
||||||
}
|
}
|
||||||
case setting.Database.UseMSSQL:
|
case setting.Database.Type.IsMSSQL():
|
||||||
cols := ""
|
cols := ""
|
||||||
for _, col := range columnNames {
|
for _, col := range columnNames {
|
||||||
if cols != "" {
|
if cols != "" {
|
||||||
|
@ -543,13 +543,13 @@ func newXORMEngine() (*xorm.Engine, error) {
|
||||||
|
|
||||||
func deleteDB() error {
|
func deleteDB() error {
|
||||||
switch {
|
switch {
|
||||||
case setting.Database.UseSQLite3:
|
case setting.Database.Type.IsSQLite3():
|
||||||
if err := util.Remove(setting.Database.Path); err != nil {
|
if err := util.Remove(setting.Database.Path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return os.MkdirAll(path.Dir(setting.Database.Path), os.ModePerm)
|
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)/",
|
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/",
|
||||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host))
|
setting.Database.User, setting.Database.Passwd, setting.Database.Host))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -565,7 +565,7 @@ func deleteDB() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case setting.Database.UsePostgreSQL:
|
case setting.Database.Type.IsPostgreSQL():
|
||||||
db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/?sslmode=%s",
|
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))
|
setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.SSLMode))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -612,7 +612,7 @@ func deleteDB() error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case setting.Database.UseMSSQL:
|
case setting.Database.Type.IsMSSQL():
|
||||||
host, port := setting.ParseMSSQLHostPort(setting.Database.Host)
|
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;",
|
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))
|
host, port, "master", setting.Database.User, setting.Database.Passwd))
|
||||||
|
|
|
@ -13,9 +13,9 @@ func PrependRefsHeadsToIssueRefs(x *xorm.Engine) error {
|
||||||
var query string
|
var query string
|
||||||
|
|
||||||
switch {
|
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/%'"
|
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/%';"
|
query = "UPDATE `issue` SET `ref` = CONCAT('refs/heads/', `ref`) WHERE `ref` IS NOT NULL AND `ref` <> '' AND `ref` NOT LIKE 'refs/%';"
|
||||||
default:
|
default:
|
||||||
query = "UPDATE `issue` SET `ref` = 'refs/heads/' || `ref` WHERE `ref` IS NOT NULL AND `ref` <> '' AND `ref` NOT LIKE 'refs/%'"
|
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
|
// Delete language stat statuses
|
||||||
truncExpr := "TRUNCATE TABLE"
|
truncExpr := "TRUNCATE TABLE"
|
||||||
if setting.Database.UseSQLite3 {
|
if setting.Database.Type.IsSQLite3() {
|
||||||
truncExpr = "DELETE FROM"
|
truncExpr = "DELETE FROM"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ func IncreaseLanguageField(x *xorm.Engine) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.Database.UseSQLite3 {
|
if setting.Database.Type.IsSQLite3() {
|
||||||
// SQLite maps VARCHAR to TEXT without size so we're done
|
// SQLite maps VARCHAR to TEXT without size so we're done
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -41,11 +41,11 @@ func IncreaseLanguageField(x *xorm.Engine) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
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 {
|
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE language_stat MODIFY COLUMN language %s", sqlType)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case setting.Database.UseMSSQL:
|
case setting.Database.Type.IsMSSQL():
|
||||||
// Yet again MSSQL just has to be awkward.
|
// Yet again MSSQL just has to be awkward.
|
||||||
// Here we have to drop the constraints first and then rebuild them
|
// Here we have to drop the constraints first and then rebuild them
|
||||||
constraints := make([]string, 0)
|
constraints := make([]string, 0)
|
||||||
|
@ -71,7 +71,7 @@ func IncreaseLanguageField(x *xorm.Engine) error {
|
||||||
if err := sess.CreateUniques(new(LanguageStat)); err != nil {
|
if err := sess.CreateUniques(new(LanguageStat)); err != nil {
|
||||||
return err
|
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 {
|
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE language_stat ALTER COLUMN language TYPE %s", sqlType)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,13 @@ import (
|
||||||
|
|
||||||
func SetDefaultPasswordToArgon2(x *xorm.Engine) error {
|
func SetDefaultPasswordToArgon2(x *xorm.Engine) error {
|
||||||
switch {
|
switch {
|
||||||
case setting.Database.UseMySQL:
|
case setting.Database.Type.IsMySQL():
|
||||||
_, err := x.Exec("ALTER TABLE `user` ALTER passwd_hash_algo SET DEFAULT 'argon2';")
|
_, err := x.Exec("ALTER TABLE `user` ALTER passwd_hash_algo SET DEFAULT 'argon2';")
|
||||||
return err
|
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';")
|
_, err := x.Exec("ALTER TABLE `user` ALTER COLUMN passwd_hash_algo SET DEFAULT 'argon2';")
|
||||||
return err
|
return err
|
||||||
case setting.Database.UseMSSQL:
|
case setting.Database.Type.IsMSSQL():
|
||||||
// need to find the constraint and drop it, then recreate it.
|
// need to find the constraint and drop it, then recreate it.
|
||||||
sess := x.NewSession()
|
sess := x.NewSession()
|
||||||
defer sess.Close()
|
defer sess.Close()
|
||||||
|
@ -53,7 +53,7 @@ func SetDefaultPasswordToArgon2(x *xorm.Engine) error {
|
||||||
}
|
}
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
|
|
||||||
case setting.Database.UseSQLite3:
|
case setting.Database.Type.IsSQLite3():
|
||||||
// drop through
|
// drop through
|
||||||
default:
|
default:
|
||||||
log.Fatal("Unrecognized DB")
|
log.Fatal("Unrecognized DB")
|
||||||
|
|
|
@ -62,7 +62,7 @@ func UpdateCodeCommentReplies(x *xorm.Engine) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.Database.UseMSSQL {
|
if setting.Database.Type.IsMSSQL() {
|
||||||
if _, err := sess.Exec(sqlSelect + " INTO #temp_comments" + sqlTail); err != nil {
|
if _, err := sess.Exec(sqlSelect + " INTO #temp_comments" + sqlTail); err != nil {
|
||||||
log.Error("unable to create temporary table")
|
log.Error("unable to create temporary table")
|
||||||
return err
|
return err
|
||||||
|
@ -72,13 +72,13 @@ func UpdateCodeCommentReplies(x *xorm.Engine) error {
|
||||||
comments := make([]*Comment, 0, batchSize)
|
comments := make([]*Comment, 0, batchSize)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case setting.Database.UseMySQL:
|
case setting.Database.Type.IsMySQL():
|
||||||
sqlCmd = sqlSelect + sqlTail + " LIMIT " + strconv.Itoa(batchSize) + ", " + strconv.Itoa(start)
|
sqlCmd = sqlSelect + sqlTail + " LIMIT " + strconv.Itoa(batchSize) + ", " + strconv.Itoa(start)
|
||||||
case setting.Database.UsePostgreSQL:
|
case setting.Database.Type.IsPostgreSQL():
|
||||||
fallthrough
|
fallthrough
|
||||||
case setting.Database.UseSQLite3:
|
case setting.Database.Type.IsSQLite3():
|
||||||
sqlCmd = sqlSelect + sqlTail + " LIMIT " + strconv.Itoa(batchSize) + " OFFSET " + strconv.Itoa(start)
|
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 " +
|
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"
|
"(id NOT IN ( SELECT TOP " + strconv.Itoa(start) + " id FROM #temp_comments ORDER BY id )) ORDER BY id"
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func FixPostgresIDSequences(x *xorm.Engine) error {
|
func FixPostgresIDSequences(x *xorm.Engine) error {
|
||||||
if !setting.Database.UsePostgreSQL {
|
if !setting.Database.Type.IsPostgreSQL() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,11 +54,11 @@ func RenameTaskErrorsToMessage(x *xorm.Engine) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case setting.Database.UseMySQL:
|
case setting.Database.Type.IsMySQL():
|
||||||
if _, err := sess.Exec("ALTER TABLE `task` CHANGE errors message text"); err != nil {
|
if _, err := sess.Exec("ALTER TABLE `task` CHANGE errors message text"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case setting.Database.UseMSSQL:
|
case setting.Database.Type.IsMSSQL():
|
||||||
if _, err := sess.Exec("sp_rename 'task.errors', 'message', 'COLUMN'"); err != nil {
|
if _, err := sess.Exec("sp_rename 'task.errors', 'message', 'COLUMN'"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ func AlterIssueAndCommentTextFieldsToLongText(x *xorm.Engine) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.Database.UseMySQL {
|
if setting.Database.Type.IsMySQL() {
|
||||||
if _, err := sess.Exec("ALTER TABLE `issue` CHANGE `content` `content` LONGTEXT"); err != nil {
|
if _, err := sess.Exec("ALTER TABLE `issue` CHANGE `content` `content` LONGTEXT"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ func AlterHookTaskTextFieldsToLongText(x *xorm.Engine) error {
|
||||||
return err
|
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 {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func (*improveActionTableIndicesAction) TableIndices() []*schemas.Index {
|
||||||
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
|
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
|
||||||
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
||||||
indices := []*schemas.Index{actUserIndex, repoIndex}
|
indices := []*schemas.Index{actUserIndex, repoIndex}
|
||||||
if setting.Database.UsePostgreSQL {
|
if setting.Database.Type.IsPostgreSQL() {
|
||||||
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
||||||
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
||||||
indices = append(indices, cudIndex)
|
indices = append(indices, cudIndex)
|
||||||
|
|
|
@ -65,11 +65,11 @@ func RenameCredentialIDBytes(x *xorm.Engine) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
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 {
|
if _, err := sess.Exec("ALTER TABLE `webauthn_credential` CHANGE credential_id_bytes credential_id VARBINARY(1024)"); err != nil {
|
||||||
return err
|
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 {
|
if _, err := sess.Exec("sp_rename 'webauthn_credential.credential_id_bytes', 'credential_id', 'COLUMN'"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ func AlterPublicGPGKeyContentFieldsToMediumText(x *xorm.Engine) error {
|
||||||
return err
|
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 {
|
if _, err := sess.Exec("ALTER TABLE `gpg_key` CHANGE `content` `content` MEDIUMTEXT"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ func AlterPackageVersionMetadataToLongText(x *xorm.Engine) error {
|
||||||
return err
|
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 {
|
if _, err := sess.Exec("ALTER TABLE `package_version` MODIFY COLUMN `metadata_json` LONGTEXT"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ func AlterPublicGPGKeyImportContentFieldToMediumText(x *xorm.Engine) error {
|
||||||
return err
|
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 {
|
if _, err := sess.Exec("ALTER TABLE `gpg_key_import` CHANGE `content` `content` MEDIUMTEXT"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,6 +239,32 @@ func (org *Organization) CustomAvatarRelativePath() string {
|
||||||
return org.Avatar
|
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.
|
// CreateOrganization creates record of a new organization.
|
||||||
func CreateOrganization(org *Organization, owner *user_model.User) (err error) {
|
func CreateOrganization(org *Organization, owner *user_model.User) (err error) {
|
||||||
if !owner.CanCreateOrganization() {
|
if !owner.CanCreateOrganization() {
|
||||||
|
|
|
@ -416,7 +416,7 @@ func DeleteProjectByID(ctx context.Context, id int64) error {
|
||||||
|
|
||||||
func DeleteProjectByRepoID(ctx context.Context, repoID int64) error {
|
func DeleteProjectByRepoID(ctx context.Context, repoID int64) error {
|
||||||
switch {
|
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 {
|
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
|
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 {
|
if _, err := db.GetEngine(ctx).Table("project").Where("repo_id = ? ", repoID).Delete(&Project{}); err != nil {
|
||||||
return err
|
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 {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,9 +39,9 @@ import (
|
||||||
var ItemsPerPage = 40
|
var ItemsPerPage = 40
|
||||||
|
|
||||||
// Init initialize model
|
// Init initialize model
|
||||||
func Init() error {
|
func Init(ctx context.Context) error {
|
||||||
unit.LoadUnitConfig()
|
unit.LoadUnitConfig()
|
||||||
return system_model.Init()
|
return system_model.Init(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRepository deletes a repository for a user or organization.
|
// DeleteRepository deletes a repository for a user or organization.
|
||||||
|
|
|
@ -498,7 +498,7 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
|
||||||
subQueryCond := builder.NewCond()
|
subQueryCond := builder.NewCond()
|
||||||
|
|
||||||
// Topic checking. Topics are present.
|
// 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": "[]"}))
|
subQueryCond = subQueryCond.Or(builder.And(builder.NotNull{"topics"}, builder.Neq{"(topics)::text": "[]"}))
|
||||||
} else {
|
} else {
|
||||||
subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"}))
|
subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"}))
|
||||||
|
|
|
@ -79,8 +79,8 @@ func IsErrDataExpired(err error) bool {
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSettingNoCache returns specific setting without using the cache
|
// GetSetting returns specific setting without using the cache
|
||||||
func GetSettingNoCache(ctx context.Context, key string) (*Setting, error) {
|
func GetSetting(ctx context.Context, key string) (*Setting, error) {
|
||||||
v, err := GetSettings(ctx, []string{key})
|
v, err := GetSettings(ctx, []string{key})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -93,11 +93,11 @@ func GetSettingNoCache(ctx context.Context, key string) (*Setting, error) {
|
||||||
|
|
||||||
const contextCacheKey = "system_setting"
|
const contextCacheKey = "system_setting"
|
||||||
|
|
||||||
// GetSetting returns the setting value via the key
|
// GetSettingWithCache returns the setting value via the key
|
||||||
func GetSetting(ctx context.Context, key string) (string, error) {
|
func GetSettingWithCache(ctx context.Context, key string) (string, error) {
|
||||||
return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) {
|
return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) {
|
||||||
return cache.GetString(genSettingCacheKey(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 {
|
if err != nil {
|
||||||
return "", err
|
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
|
// none existing keys and errors are ignored and result in false
|
||||||
func GetSettingBool(ctx context.Context, key string) bool {
|
func GetSettingBool(ctx context.Context, key string) bool {
|
||||||
s, _ := GetSetting(ctx, key)
|
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)
|
v, _ := strconv.ParseBool(s)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
@ -120,7 +129,7 @@ func GetSettings(ctx context.Context, keys []string) (map[string]*Setting, error
|
||||||
keys[i] = strings.ToLower(keys[i])
|
keys[i] = strings.ToLower(keys[i])
|
||||||
}
|
}
|
||||||
settings := make([]*Setting, 0, len(keys))
|
settings := make([]*Setting, 0, len(keys))
|
||||||
if err := db.GetEngine(db.DefaultContext).
|
if err := db.GetEngine(ctx).
|
||||||
Where(builder.In("setting_key", keys)).
|
Where(builder.In("setting_key", keys)).
|
||||||
Find(&settings); err != nil {
|
Find(&settings); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -151,9 +160,9 @@ func (settings AllSettings) GetVersion(key string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllSettings returns all settings from user
|
// GetAllSettings returns all settings from user
|
||||||
func GetAllSettings() (AllSettings, error) {
|
func GetAllSettings(ctx context.Context) (AllSettings, error) {
|
||||||
settings := make([]*Setting, 0, 5)
|
settings := make([]*Setting, 0, 5)
|
||||||
if err := db.GetEngine(db.DefaultContext).
|
if err := db.GetEngine(ctx).
|
||||||
Find(&settings); err != nil {
|
Find(&settings); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -168,12 +177,12 @@ func GetAllSettings() (AllSettings, error) {
|
||||||
func DeleteSetting(ctx context.Context, setting *Setting) error {
|
func DeleteSetting(ctx context.Context, setting *Setting) error {
|
||||||
cache.RemoveContextData(ctx, contextCacheKey, setting.SettingKey)
|
cache.RemoveContextData(ctx, contextCacheKey, setting.SettingKey)
|
||||||
cache.Remove(genSettingCacheKey(setting.SettingKey))
|
cache.Remove(genSettingCacheKey(setting.SettingKey))
|
||||||
_, err := db.GetEngine(db.DefaultContext).Delete(setting)
|
_, err := db.GetEngine(ctx).Delete(setting)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetSettingNoVersion(ctx context.Context, key, value string) error {
|
func SetSettingNoVersion(ctx context.Context, key, value string) error {
|
||||||
s, err := GetSettingNoCache(ctx, key)
|
s, err := GetSetting(ctx, key)
|
||||||
if IsErrSettingIsNotExist(err) {
|
if IsErrSettingIsNotExist(err) {
|
||||||
return SetSetting(ctx, &Setting{
|
return SetSetting(ctx, &Setting{
|
||||||
SettingKey: key,
|
SettingKey: key,
|
||||||
|
@ -189,7 +198,7 @@ func SetSettingNoVersion(ctx context.Context, key, value string) error {
|
||||||
|
|
||||||
// SetSetting updates a users' setting for a specific key
|
// SetSetting updates a users' setting for a specific key
|
||||||
func SetSetting(ctx context.Context, setting *Setting) error {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,8 +214,8 @@ func SetSetting(ctx context.Context, setting *Setting) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func upsertSettingValue(key, value string, version int) error {
|
func upsertSettingValue(parentCtx context.Context, key, value string, version int) error {
|
||||||
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
return db.WithTx(parentCtx, func(ctx context.Context) error {
|
||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
|
|
||||||
// here we use a general method to do a safe upsert for different databases (and most transaction levels)
|
// 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
|
LibravatarService *libravatar.Libravatar
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init() error {
|
func Init(ctx context.Context) error {
|
||||||
var disableGravatar bool
|
var disableGravatar bool
|
||||||
disableGravatarSetting, err := GetSettingNoCache(db.DefaultContext, KeyPictureDisableGravatar)
|
disableGravatarSetting, err := GetSetting(ctx, KeyPictureDisableGravatar)
|
||||||
if IsErrSettingIsNotExist(err) {
|
if IsErrSettingIsNotExist(err) {
|
||||||
disableGravatar = setting_module.GetDefaultDisableGravatar()
|
disableGravatar = setting_module.GetDefaultDisableGravatar()
|
||||||
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
|
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
|
||||||
|
@ -262,7 +271,7 @@ func Init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var enableFederatedAvatar bool
|
var enableFederatedAvatar bool
|
||||||
enableFederatedAvatarSetting, err := GetSettingNoCache(db.DefaultContext, KeyPictureEnableFederatedAvatar)
|
enableFederatedAvatarSetting, err := GetSetting(ctx, KeyPictureEnableFederatedAvatar)
|
||||||
if IsErrSettingIsNotExist(err) {
|
if IsErrSettingIsNotExist(err) {
|
||||||
enableFederatedAvatar = setting_module.GetDefaultEnableFederatedAvatar(disableGravatar)
|
enableFederatedAvatar = setting_module.GetDefaultEnableFederatedAvatar(disableGravatar)
|
||||||
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
|
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
|
||||||
|
@ -275,13 +284,13 @@ func Init() error {
|
||||||
if setting_module.OfflineMode {
|
if setting_module.OfflineMode {
|
||||||
disableGravatar = true
|
disableGravatar = true
|
||||||
enableFederatedAvatar = false
|
enableFederatedAvatar = false
|
||||||
if !GetSettingBool(db.DefaultContext, KeyPictureDisableGravatar) {
|
if !GetSettingBool(ctx, KeyPictureDisableGravatar) {
|
||||||
if err := SetSettingNoVersion(db.DefaultContext, KeyPictureDisableGravatar, "true"); err != nil {
|
if err := SetSettingNoVersion(ctx, KeyPictureDisableGravatar, "true"); err != nil {
|
||||||
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureDisableGravatar, err)
|
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureDisableGravatar, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if GetSettingBool(db.DefaultContext, KeyPictureEnableFederatedAvatar) {
|
if GetSettingBool(ctx, KeyPictureEnableFederatedAvatar) {
|
||||||
if err := SetSettingNoVersion(db.DefaultContext, KeyPictureEnableFederatedAvatar, "false"); err != nil {
|
if err := SetSettingNoVersion(ctx, KeyPictureEnableFederatedAvatar, "false"); err != nil {
|
||||||
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err)
|
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)
|
value, err := system.GetSetting(db.DefaultContext, keyName)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, updatedSetting.SettingValue, value)
|
assert.EqualValues(t, updatedSetting.SettingValue, value.SettingValue)
|
||||||
|
|
||||||
// get all settings
|
// get all settings
|
||||||
settings, err = system.GetAllSettings()
|
settings, err = system.GetAllSettings(db.DefaultContext)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, settings, 3)
|
assert.Len(t, settings, 3)
|
||||||
assert.EqualValues(t, updatedSetting.SettingValue, settings[strings.ToLower(updatedSetting.SettingKey)].SettingValue)
|
assert.EqualValues(t, updatedSetting.SettingValue, settings[strings.ToLower(updatedSetting.SettingKey)].SettingValue)
|
||||||
|
@ -51,7 +51,7 @@ func TestSettings(t *testing.T) {
|
||||||
// delete setting
|
// delete setting
|
||||||
err = system.DeleteSetting(db.DefaultContext, &system.Setting{SettingKey: strings.ToLower(keyName)})
|
err = system.DeleteSetting(db.DefaultContext, &system.Setting{SettingKey: strings.ToLower(keyName)})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
settings, err = system.GetAllSettings()
|
settings, err = system.GetAllSettings(db.DefaultContext)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, settings, 2)
|
assert.Len(t, settings, 2)
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
|
||||||
setting.SSH.BuiltinServerUser = "builtinuser"
|
setting.SSH.BuiltinServerUser = "builtinuser"
|
||||||
setting.SSH.Port = 3000
|
setting.SSH.Port = 3000
|
||||||
setting.SSH.Domain = "try.gitea.io"
|
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"
|
setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master"
|
||||||
repoRootPath, err := os.MkdirTemp(os.TempDir(), "repos")
|
repoRootPath, err := os.MkdirTemp(os.TempDir(), "repos")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -113,7 +113,7 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
|
||||||
if err = storage.Init(); err != nil {
|
if err = storage.Init(); err != nil {
|
||||||
fatalTestError("storage.Init: %v\n", err)
|
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)
|
fatalTestError("models.Init: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
||||||
useLocalAvatar := false
|
useLocalAvatar := false
|
||||||
autoGenerateAvatar := false
|
autoGenerateAvatar := false
|
||||||
|
|
||||||
disableGravatar := system_model.GetSettingBool(ctx, system_model.KeyPictureDisableGravatar)
|
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case u.UseCustomAvatar:
|
case u.UseCustomAvatar:
|
||||||
|
|
|
@ -393,6 +393,11 @@ func (u *User) IsOrganization() bool {
|
||||||
return u.Type == UserTypeOrganization
|
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,
|
// DisplayName returns full name if it's not empty,
|
||||||
// returns username otherwise.
|
// returns username otherwise.
|
||||||
func (u *User) DisplayName() string {
|
func (u *User) DisplayName() string {
|
||||||
|
|
|
@ -41,9 +41,8 @@ var RecommendedHashAlgorithms = []string{
|
||||||
"pbkdf2_hi",
|
"pbkdf2_hi",
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaultPasswordHashAlgorithm will take a provided algorithmName and dealias it to
|
// hashAlgorithmToSpec converts an algorithm name or a specification to a full algorithm specification
|
||||||
// a complete algorithm specification.
|
func hashAlgorithmToSpec(algorithmName string) string {
|
||||||
func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHashAlgorithm) {
|
|
||||||
if algorithmName == "" {
|
if algorithmName == "" {
|
||||||
algorithmName = DefaultHashAlgorithmName
|
algorithmName = DefaultHashAlgorithmName
|
||||||
}
|
}
|
||||||
|
@ -52,10 +51,26 @@ func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHas
|
||||||
algorithmName = alias
|
algorithmName = alias
|
||||||
alias, has = aliasAlgorithmNames[algorithmName]
|
alias, has = aliasAlgorithmNames[algorithmName]
|
||||||
}
|
}
|
||||||
|
return algorithmName
|
||||||
// algorithmName should now be a full algorithm specification
|
}
|
||||||
// e.g. pbkdf2$50000$50 rather than pbdkf2
|
|
||||||
DefaultHashAlgorithm = Parse(algorithmName)
|
// SetDefaultPasswordHashAlgorithm will take a provided algorithmName and de-alias it to
|
||||||
|
// a complete algorithm specification.
|
||||||
return algorithmName, DefaultHashAlgorithm
|
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.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||||
|
|
||||||
ctx.Data["Context"] = &ctx
|
ctx.Data["Context"] = &ctx
|
||||||
|
|
|
@ -388,7 +388,7 @@ func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
|
||||||
if duration == 0 {
|
if duration == 0 {
|
||||||
duration = 5 * time.Minute
|
duration = 5 * time.Minute
|
||||||
}
|
}
|
||||||
httpcache.AddCacheControlToHeader(header, duration)
|
httpcache.SetCacheControlInHeader(header, duration)
|
||||||
|
|
||||||
if !opts.LastModified.IsZero() {
|
if !opts.LastModified.IsZero() {
|
||||||
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
|
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.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||||
|
|
||||||
ctx.Data["CsrfToken"] = ctx.csrf.GetToken()
|
ctx.Data["CsrfToken"] = ctx.csrf.GetToken()
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
)
|
)
|
||||||
|
@ -31,29 +30,34 @@ type Organization struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (org *Organization) CanWriteUnit(ctx *Context, unitType unit.Type) bool {
|
func (org *Organization) CanWriteUnit(ctx *Context, unitType unit.Type) bool {
|
||||||
if ctx.Doer == nil {
|
return org.Organization.UnitPermission(ctx, ctx.Doer, unitType) >= perm.AccessModeWrite
|
||||||
return false
|
|
||||||
}
|
|
||||||
return org.UnitPermission(ctx, ctx.Doer.ID, unitType) >= perm.AccessModeWrite
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (org *Organization) UnitPermission(ctx *Context, doerID int64, unitType unit.Type) perm.AccessMode {
|
func (org *Organization) CanReadUnit(ctx *Context, unitType unit.Type) bool {
|
||||||
if doerID > 0 {
|
return org.Organization.UnitPermission(ctx, ctx.Doer, unitType) >= perm.AccessModeRead
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if org.Organization.Visibility == structs.VisibleTypePublic {
|
func GetOrganizationByParams(ctx *Context) {
|
||||||
return perm.AccessModeRead
|
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
|
// HandleOrgAssignment handles organization assignment
|
||||||
|
@ -77,25 +81,26 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
|
||||||
requireTeamAdmin = args[3]
|
requireTeamAdmin = args[3]
|
||||||
}
|
}
|
||||||
|
|
||||||
orgName := ctx.Params(":org")
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
ctx.Org.Organization, err = organization.GetOrgByName(ctx, orgName)
|
|
||||||
if err != nil {
|
if ctx.ContextUser == nil {
|
||||||
if organization.IsErrOrgNotExist(err) {
|
// if Organization is not defined, get it from params
|
||||||
redirectUserID, err := user_model.LookupUserRedirect(orgName)
|
if ctx.Org.Organization == nil {
|
||||||
if err == nil {
|
GetOrganizationByParams(ctx)
|
||||||
RedirectToUser(ctx, orgName, redirectUserID)
|
if ctx.Written() {
|
||||||
} else if user_model.IsErrUserRedirectNotExist(err) {
|
return
|
||||||
ctx.NotFound("GetUserByName", err)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("LookupUserRedirect", err)
|
|
||||||
}
|
}
|
||||||
} 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
org := ctx.Org.Organization
|
org := ctx.Org.Organization
|
||||||
|
|
||||||
// Handle Visibility
|
// Handle Visibility
|
||||||
|
@ -156,6 +161,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
|
||||||
}
|
}
|
||||||
ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner
|
ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner
|
||||||
ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember
|
ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember
|
||||||
|
ctx.Data["IsProjectEnabled"] = true
|
||||||
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
|
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
|
||||||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
||||||
ctx.Data["IsPublicMember"] = func(uid int64) bool {
|
ctx.Data["IsPublicMember"] = func(uid int64) bool {
|
||||||
|
@ -231,6 +237,10 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
|
||||||
return
|
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
|
// OrgAssignment returns a middleware to handle organization assignment
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"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 {
|
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)
|
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
|
// TODO: function to recalc all counters
|
||||||
|
|
||||||
if setting.Database.UsePostgreSQL {
|
if setting.Database.Type.IsPostgreSQL() {
|
||||||
consistencyChecks = append(consistencyChecks, consistencyCheck{
|
consistencyChecks = append(consistencyChecks, consistencyCheck{
|
||||||
Name: "Sequence values",
|
Name: "Sequence values",
|
||||||
Counter: db.CountBadSequences,
|
Counter: db.CountBadSequences,
|
||||||
|
|
|
@ -312,7 +312,7 @@ func CheckGitVersionAtLeast(atLeast string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func configSet(key, value 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) {
|
if err != nil && !err.IsExitCode(1) {
|
||||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
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 {
|
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 {
|
if err == nil {
|
||||||
// already exist
|
// already exist
|
||||||
return nil
|
return nil
|
||||||
|
@ -349,7 +349,7 @@ func configSetNonExist(key, value string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func configAddNonExist(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 {
|
if err == nil {
|
||||||
// already exist
|
// already exist
|
||||||
return nil
|
return nil
|
||||||
|
@ -366,7 +366,7 @@ func configAddNonExist(key, value string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func configUnsetAll(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 {
|
if err == nil {
|
||||||
// exist, need to remove
|
// exist, need to remove
|
||||||
_, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
|
_, _, 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()
|
defer revListWriter.Close()
|
||||||
stderr := new(bytes.Buffer)
|
stderr := new(bytes.Buffer)
|
||||||
var errbuf strings.Builder
|
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{
|
if err := cmd.Run(&git.RunOpts{
|
||||||
Dir: tmpBasePath,
|
Dir: tmpBasePath,
|
||||||
Stdout: revListWriter,
|
Stdout: revListWriter,
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
@ -67,38 +66,6 @@ func (repo *Repository) IsCommitExist(name string) bool {
|
||||||
return err == nil
|
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) {
|
func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
|
||||||
var tagObject *object.Tag
|
var tagObject *object.Tag
|
||||||
|
|
||||||
|
@ -122,12 +89,6 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
|
||||||
commit := convertCommit(gogitCommit)
|
commit := convertCommit(gogitCommit)
|
||||||
commit.repo = repo
|
commit.repo = repo
|
||||||
|
|
||||||
if tagObject != nil {
|
|
||||||
commit.CommitMessage = strings.TrimSpace(tagObject.Message)
|
|
||||||
commit.Author = &tagObject.Tagger
|
|
||||||
commit.Signature = convertPGPSignatureForTag(tagObject)
|
|
||||||
}
|
|
||||||
|
|
||||||
tree, err := gogitCommit.Tree()
|
tree, err := gogitCommit.Tree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -107,10 +107,6 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
commit.CommitMessage = strings.TrimSpace(tag.Message)
|
|
||||||
commit.Author = tag.Tagger
|
|
||||||
commit.Signature = tag.Signature
|
|
||||||
|
|
||||||
return commit, nil
|
return commit, nil
|
||||||
case "commit":
|
case "commit":
|
||||||
commit, err := CommitFromReader(repo, id, io.LimitReader(rd, size))
|
commit, err := CommitFromReader(repo, id, io.LimitReader(rd, size))
|
||||||
|
|
|
@ -43,12 +43,13 @@ func TestGetTagCommitWithSignature(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
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.NoError(t, err)
|
||||||
assert.NotNil(t, commit)
|
assert.NotNil(t, commit)
|
||||||
assert.NotNil(t, commit.Signature)
|
assert.NotNil(t, commit.Signature)
|
||||||
// test that signature is not in message
|
// 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) {
|
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
|
// GetFilesChangedBetween returns a list of all files that have been changed between the given commits
|
||||||
func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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()
|
refs, err := bareRepo1.GetRefs()
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, refs, 5)
|
assert.Len(t, refs, 6)
|
||||||
|
|
||||||
expectedRefs := []string{
|
expectedRefs := []string{
|
||||||
BranchPrefix + "branch1",
|
BranchPrefix + "branch1",
|
||||||
BranchPrefix + "branch2",
|
BranchPrefix + "branch2",
|
||||||
BranchPrefix + "master",
|
BranchPrefix + "master",
|
||||||
TagPrefix + "test",
|
TagPrefix + "test",
|
||||||
|
TagPrefix + "signed-tag",
|
||||||
NotesRef,
|
NotesRef,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,9 +44,12 @@ func TestRepository_GetRefsFiltered(t *testing.T) {
|
||||||
refs, err := bareRepo1.GetRefsFiltered(TagPrefix)
|
refs, err := bareRepo1.GetRefsFiltered(TagPrefix)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
if assert.Len(t, refs, 1) {
|
if assert.Len(t, refs, 2) {
|
||||||
assert.Equal(t, TagPrefix+"test", refs[0].Name)
|
assert.Equal(t, TagPrefix+"signed-tag", refs[0].Name)
|
||||||
assert.Equal(t, "tag", refs[0].Type)
|
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.NoError(t, err)
|
||||||
assert.NotNil(t, code)
|
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, 3, code.AuthorCount)
|
||||||
assert.EqualValues(t, 9, code.CommitCountInAllBranches)
|
assert.EqualValues(t, 10, code.CommitCountInAllBranches)
|
||||||
assert.EqualValues(t, 10, code.Additions)
|
assert.EqualValues(t, 10, code.Additions)
|
||||||
assert.EqualValues(t, 1, code.Deletions)
|
assert.EqualValues(t, 1, code.Deletions)
|
||||||
assert.Len(t, code.Authors, 3)
|
assert.Len(t, code.Authors, 3)
|
||||||
|
|
|
@ -25,11 +25,14 @@ func TestRepository_GetTags(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
assert.Len(t, tags, 1)
|
assert.Len(t, tags, 2)
|
||||||
assert.Equal(t, len(tags), total)
|
assert.Equal(t, len(tags), total)
|
||||||
assert.EqualValues(t, "test", tags[0].Name)
|
assert.EqualValues(t, "signed-tag", tags[0].Name)
|
||||||
assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[0].ID.String())
|
assert.EqualValues(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", tags[0].ID.String())
|
||||||
assert.EqualValues(t, "tag", tags[0].Type)
|
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) {
|
func TestRepository_GetTag(t *testing.T) {
|
||||||
|
|
|
@ -14,10 +14,10 @@ func TestGetLatestCommitTime(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
lct, err := GetLatestCommitTime(DefaultContext, bareRepo1Path)
|
lct, err := GetLatestCommitTime(DefaultContext, bareRepo1Path)
|
||||||
assert.NoError(t, err)
|
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
|
// which is the time of commit
|
||||||
// feaf4ba6bc635fec442f46ddd4512416ec43c2c2 (refs/heads/master)
|
// ce064814f4a0d337b333e646ece456cd39fab612 (refs/heads/master)
|
||||||
assert.EqualValues(t, 1563741793, lct.Unix())
|
assert.EqualValues(t, 1668354014, lct.Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepoIsEmpty(t *testing.T) {
|
func TestRepoIsEmpty(t *testing.T) {
|
||||||
|
|
Binary file not shown.
|
@ -1 +1,2 @@
|
||||||
37991dec2c8e592043f47155ce4808d4580f9123 feaf4ba6bc635fec442f46ddd4512416ec43c2c2 silverwind <me@silverwind.io> 1563741799 +0200 push
|
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
|
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"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddCacheControlToHeader adds suitable cache-control headers to response
|
// SetCacheControlInHeader sets suitable cache-control headers in the response
|
||||||
func AddCacheControlToHeader(h http.Header, maxAge time.Duration, additionalDirectives ...string) {
|
func SetCacheControlInHeader(h http.Header, maxAge time.Duration, additionalDirectives ...string) {
|
||||||
directives := make([]string, 0, 2+len(additionalDirectives))
|
directives := make([]string, 0, 2+len(additionalDirectives))
|
||||||
|
|
||||||
// "max-age=0 + must-revalidate" (aka "no-cache") is preferred instead of "no-store"
|
// "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")
|
directives = append(directives, "max-age=0", "private", "must-revalidate")
|
||||||
|
|
||||||
// to remind users they are using non-prod setting.
|
// 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...), ", "))
|
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
|
// HandleGenericTimeCache handles time-based caching for a HTTP request
|
||||||
func HandleGenericTimeCache(req *http.Request, w http.ResponseWriter, lastModified time.Time) (handled bool) {
|
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")
|
ifModifiedSince := req.Header.Get("If-Modified-Since")
|
||||||
if ifModifiedSince != "" {
|
if ifModifiedSince != "" {
|
||||||
|
@ -81,7 +81,7 @@ func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag strin
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddCacheControlToHeader(w.Header(), setting.StaticCacheTime)
|
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
|
||||||
return false
|
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
|
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"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -23,7 +24,8 @@ func TestRenderConsole(t *testing.T) {
|
||||||
canRender := render.CanRender("test", strings.NewReader(k))
|
canRender := render.CanRender("test", strings.NewReader(k))
|
||||||
assert.True(t, canRender)
|
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.NoError(t, err)
|
||||||
assert.EqualValues(t, v, buf.String())
|
assert.EqualValues(t, v, buf.String())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -23,7 +24,8 @@ func TestRenderCSV(t *testing.T) {
|
||||||
|
|
||||||
for k, v := range kases {
|
for k, v := range kases {
|
||||||
var buf strings.Builder
|
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.NoError(t, err)
|
||||||
assert.EqualValues(t, v, buf.String())
|
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