mirror of
https://github.com/go-gitea/gitea
synced 2025-02-24 21:29:17 +01:00
Merge branch 'main' into lunny/issue_dev
This commit is contained in:
commit
3d8ed0e853
@ -642,7 +642,7 @@ rules:
|
|||||||
no-this-before-super: [2]
|
no-this-before-super: [2]
|
||||||
no-throw-literal: [2]
|
no-throw-literal: [2]
|
||||||
no-undef-init: [2]
|
no-undef-init: [2]
|
||||||
no-undef: [2, {typeof: true}]
|
no-undef: [2, {typeof: true}] # TODO: disable this rule after tsc passes
|
||||||
no-undefined: [0]
|
no-undefined: [0]
|
||||||
no-underscore-dangle: [0]
|
no-underscore-dangle: [0]
|
||||||
no-unexpected-multiline: [2]
|
no-unexpected-multiline: [2]
|
||||||
|
8
.github/workflows/pull-compliance.yml
vendored
8
.github/workflows/pull-compliance.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
|||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: pip install poetry
|
- run: pip install poetry
|
||||||
@ -66,7 +66,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
@ -137,7 +137,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
@ -186,7 +186,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
|
10
.github/workflows/pull-db-tests.yml
vendored
10
.github/workflows/pull-db-tests.yml
vendored
@ -154,12 +154,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:8.0
|
# the bitnami mysql image has more options than the official one, it's easier to customize
|
||||||
|
image: bitnami/mysql:8.0
|
||||||
env:
|
env:
|
||||||
MYSQL_ALLOW_EMPTY_PASSWORD: true
|
ALLOW_EMPTY_PASSWORD: true
|
||||||
MYSQL_DATABASE: testgitea
|
MYSQL_DATABASE: testgitea
|
||||||
ports:
|
ports:
|
||||||
- "3306:3306"
|
- "3306:3306"
|
||||||
|
options: >-
|
||||||
|
--mount type=tmpfs,destination=/bitnami/mysql/data
|
||||||
elasticsearch:
|
elasticsearch:
|
||||||
image: elasticsearch:7.5.0
|
image: elasticsearch:7.5.0
|
||||||
env:
|
env:
|
||||||
@ -188,7 +191,8 @@ jobs:
|
|||||||
- name: run migration tests
|
- name: run migration tests
|
||||||
run: make test-mysql-migration
|
run: make test-mysql-migration
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: make integration-test-coverage
|
# run: make integration-test-coverage (at the moment, no coverage is really handled)
|
||||||
|
run: make test-mysql
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
RACE_ENABLED: true
|
RACE_ENABLED: true
|
||||||
|
2
.github/workflows/pull-e2e-tests.yml
vendored
2
.github/workflows/pull-e2e-tests.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend frontend deps-backend
|
- run: make deps-frontend frontend deps-backend
|
||||||
|
2
.github/workflows/release-nightly.yml
vendored
2
.github/workflows/release-nightly.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend deps-backend
|
- run: make deps-frontend deps-backend
|
||||||
|
2
.github/workflows/release-tag-rc.yml
vendored
2
.github/workflows/release-tag-rc.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend deps-backend
|
- run: make deps-frontend deps-backend
|
||||||
|
2
.github/workflows/release-tag-version.yml
vendored
2
.github/workflows/release-tag-version.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend deps-backend
|
- run: make deps-frontend deps-backend
|
||||||
|
@ -63,3 +63,4 @@ Tim-Niclas Oelschläger <zokki.softwareschmiede@gmail.com> (@zokkis)
|
|||||||
Yu Liu <1240335630@qq.com> (@HEREYUA)
|
Yu Liu <1240335630@qq.com> (@HEREYUA)
|
||||||
Kemal Zebari <kemalzebra@gmail.com> (@kemzeb)
|
Kemal Zebari <kemalzebra@gmail.com> (@kemzeb)
|
||||||
Rowan Bohde <rowan.bohde@gmail.com> (@bohde)
|
Rowan Bohde <rowan.bohde@gmail.com> (@bohde)
|
||||||
|
hiifong <i@hiif.ong> (@hiifong)
|
||||||
|
8
Makefile
8
Makefile
@ -377,12 +377,12 @@ lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig
|
|||||||
.PHONY: lint-js
|
.PHONY: lint-js
|
||||||
lint-js: node_modules
|
lint-js: node_modules
|
||||||
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
|
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
|
||||||
# npx tsc
|
# npx vue-tsc
|
||||||
|
|
||||||
.PHONY: lint-js-fix
|
.PHONY: lint-js-fix
|
||||||
lint-js-fix: node_modules
|
lint-js-fix: node_modules
|
||||||
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
|
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
|
||||||
# npx tsc
|
# npx vue-tsc
|
||||||
|
|
||||||
.PHONY: lint-css
|
.PHONY: lint-css
|
||||||
lint-css: node_modules
|
lint-css: node_modules
|
||||||
@ -451,6 +451,10 @@ lint-templates: .venv node_modules
|
|||||||
lint-yaml: .venv
|
lint-yaml: .venv
|
||||||
@poetry run yamllint .
|
@poetry run yamllint .
|
||||||
|
|
||||||
|
.PHONY: tsc
|
||||||
|
tsc:
|
||||||
|
npx vue-tsc
|
||||||
|
|
||||||
.PHONY: watch
|
.PHONY: watch
|
||||||
watch:
|
watch:
|
||||||
@bash tools/watch.sh
|
@bash tools/watch.sh
|
||||||
|
4
assets/go-licenses.json
generated
4
assets/go-licenses.json
generated
@ -1090,8 +1090,8 @@
|
|||||||
"licenseText": "MIT License\n\nCopyright (c) 2017 Asher\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
"licenseText": "MIT License\n\nCopyright (c) 2017 Asher\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "github.com/stretchr/testify/assert",
|
"name": "github.com/stretchr/testify",
|
||||||
"path": "github.com/stretchr/testify/assert/LICENSE",
|
"path": "github.com/stretchr/testify/LICENSE",
|
||||||
"licenseText": "MIT License\n\nCopyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
"licenseText": "MIT License\n\nCopyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -111,13 +111,11 @@ func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error
|
|||||||
if !setting.IsProd {
|
if !setting.IsProd {
|
||||||
_, _ = fmt.Fprintln(os.Stderr, "Gitea:", logMsg)
|
_, _ = fmt.Fprintln(os.Stderr, "Gitea:", logMsg)
|
||||||
}
|
}
|
||||||
if userMessage != "" {
|
|
||||||
if unicode.IsPunct(rune(userMessage[len(userMessage)-1])) {
|
if unicode.IsPunct(rune(userMessage[len(userMessage)-1])) {
|
||||||
logMsg = userMessage + " " + logMsg
|
logMsg = userMessage + " " + logMsg
|
||||||
} else {
|
} else {
|
||||||
logMsg = userMessage + ". " + logMsg
|
logMsg = userMessage + ". " + logMsg
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ = private.SSHLog(ctx, true, logMsg)
|
_ = private.SSHLog(ctx, true, logMsg)
|
||||||
}
|
}
|
||||||
return cli.Exit("", 1)
|
return cli.Exit("", 1)
|
||||||
@ -288,10 +286,10 @@ func runServ(c *cli.Context) error {
|
|||||||
if allowedCommands.Contains(verb) {
|
if allowedCommands.Contains(verb) {
|
||||||
if allowedCommandsLfs.Contains(verb) {
|
if allowedCommandsLfs.Contains(verb) {
|
||||||
if !setting.LFS.StartServer {
|
if !setting.LFS.StartServer {
|
||||||
return fail(ctx, "Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled")
|
return fail(ctx, "LFS Server is not enabled", "")
|
||||||
}
|
}
|
||||||
if verb == verbLfsTransfer && !setting.LFS.AllowPureSSH {
|
if verb == verbLfsTransfer && !setting.LFS.AllowPureSSH {
|
||||||
return fail(ctx, "Unknown git command", "LFS SSH transfer connection denied, pure SSH protocol is disabled")
|
return fail(ctx, "LFS SSH transfer is not enabled", "")
|
||||||
}
|
}
|
||||||
if len(words) > 2 {
|
if len(words) > 2 {
|
||||||
lfsVerb = words[2]
|
lfsVerb = words[2]
|
||||||
|
@ -1007,6 +1007,14 @@ LEVEL = Info
|
|||||||
;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS.
|
;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS.
|
||||||
;DEFAULT_FORK_REPO_UNITS = repo.code,repo.pulls
|
;DEFAULT_FORK_REPO_UNITS = repo.code,repo.pulls
|
||||||
;;
|
;;
|
||||||
|
;; Comma separated list of default mirror repo units.
|
||||||
|
;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS.
|
||||||
|
;DEFAULT_MIRROR_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.wiki,repo.projects,repo.packages
|
||||||
|
;;
|
||||||
|
;; Comma separated list of default template repo units.
|
||||||
|
;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS.
|
||||||
|
;DEFAULT_TEMPLATE_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects,repo.packages
|
||||||
|
;;
|
||||||
;; Prefix archive files by placing them in a directory named after the repository
|
;; Prefix archive files by placing them in a directory named after the repository
|
||||||
;PREFIX_ARCHIVE_FILES = true
|
;PREFIX_ARCHIVE_FILES = true
|
||||||
;;
|
;;
|
||||||
@ -1904,7 +1912,7 @@ LEVEL = Info
|
|||||||
;ENABLED = true
|
;ENABLED = true
|
||||||
;;
|
;;
|
||||||
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||||
;ALLOWED_TYPES = .csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip
|
;ALLOWED_TYPES = .avif,.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip
|
||||||
;;
|
;;
|
||||||
;; Max size of each file. Defaults to 2048MB
|
;; Max size of each file. Defaults to 2048MB
|
||||||
;MAX_SIZE = 2048
|
;MAX_SIZE = 2048
|
||||||
@ -1936,6 +1944,13 @@ LEVEL = Info
|
|||||||
;; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
|
;; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
|
||||||
;MINIO_SECRET_ACCESS_KEY =
|
;MINIO_SECRET_ACCESS_KEY =
|
||||||
;;
|
;;
|
||||||
|
;; Preferred IAM Endpoint to override Minio's default IAM Endpoint resolution only available when STORAGE_TYPE is `minio`.
|
||||||
|
;; If not provided and STORAGE_TYPE is `minio`, will search for and derive endpoint from known environment variables
|
||||||
|
;; (AWS_CONTAINER_AUTHORIZATION_TOKEN, AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,
|
||||||
|
;; AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_WEB_IDENTITY_TOKEN_FILE, AWS_ROLE_ARN, AWS_ROLE_SESSION_NAME, AWS_REGION),
|
||||||
|
;; or the DefaultIAMRoleEndpoint if not provided otherwise.
|
||||||
|
;MINIO_IAM_ENDPOINT =
|
||||||
|
;;
|
||||||
;; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio`
|
;; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio`
|
||||||
;MINIO_BUCKET = gitea
|
;MINIO_BUCKET = gitea
|
||||||
;;
|
;;
|
||||||
@ -2680,6 +2695,13 @@ LEVEL = Info
|
|||||||
;; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
|
;; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
|
||||||
;MINIO_SECRET_ACCESS_KEY =
|
;MINIO_SECRET_ACCESS_KEY =
|
||||||
;;
|
;;
|
||||||
|
;; Preferred IAM Endpoint to override Minio's default IAM Endpoint resolution only available when STORAGE_TYPE is `minio`.
|
||||||
|
;; If not provided and STORAGE_TYPE is `minio`, will search for and derive endpoint from known environment variables
|
||||||
|
;; (AWS_CONTAINER_AUTHORIZATION_TOKEN, AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,
|
||||||
|
;; AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_WEB_IDENTITY_TOKEN_FILE, AWS_ROLE_ARN, AWS_ROLE_SESSION_NAME, AWS_REGION),
|
||||||
|
;; or the DefaultIAMRoleEndpoint if not provided otherwise.
|
||||||
|
;MINIO_IAM_ENDPOINT =
|
||||||
|
;;
|
||||||
;; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio`
|
;; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio`
|
||||||
;MINIO_BUCKET = gitea
|
;MINIO_BUCKET = gitea
|
||||||
;;
|
;;
|
||||||
|
12
flake.lock
generated
12
flake.lock
generated
@ -5,11 +5,11 @@
|
|||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1710146030,
|
"lastModified": 1726560853,
|
||||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1720542800,
|
"lastModified": 1731139594,
|
||||||
"narHash": "sha256-ZgnNHuKV6h2+fQ5LuqnUaqZey1Lqqt5dTUAiAnqH0QQ=",
|
"narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "feb2849fdeb70028c70d73b848214b00d324a497",
|
"rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -22,14 +22,13 @@
|
|||||||
gzip
|
gzip
|
||||||
|
|
||||||
# frontend
|
# frontend
|
||||||
nodejs_20
|
nodejs_22
|
||||||
|
|
||||||
# linting
|
# linting
|
||||||
python312
|
python312
|
||||||
poetry
|
poetry
|
||||||
|
|
||||||
# backend
|
# backend
|
||||||
go_1_22
|
|
||||||
gofumpt
|
gofumpt
|
||||||
sqlite
|
sqlite
|
||||||
];
|
];
|
||||||
|
5
go.mod
5
go.mod
@ -88,7 +88,7 @@ require (
|
|||||||
github.com/markbates/goth v1.80.0
|
github.com/markbates/goth v1.80.0
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/mattn/go-sqlite3 v1.14.24
|
github.com/mattn/go-sqlite3 v1.14.24
|
||||||
github.com/meilisearch/meilisearch-go v0.29.0
|
github.com/meilisearch/meilisearch-go v0.29.1-0.20241106140435-0bf60fad690a
|
||||||
github.com/mholt/archiver/v3 v3.5.1
|
github.com/mholt/archiver/v3 v3.5.1
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27
|
github.com/microcosm-cc/bluemonday v1.0.27
|
||||||
github.com/microsoft/go-mssqldb v1.7.2
|
github.com/microsoft/go-mssqldb v1.7.2
|
||||||
@ -222,7 +222,7 @@ require (
|
|||||||
github.com/go-openapi/validate v0.24.0 // indirect
|
github.com/go-openapi/validate v0.24.0 // indirect
|
||||||
github.com/go-webauthn/x v0.1.15 // indirect
|
github.com/go-webauthn/x v0.1.15 // indirect
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
|
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
|
||||||
@ -330,6 +330,7 @@ replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142
|
|||||||
|
|
||||||
replace github.com/nektos/act => gitea.com/gitea/act v0.261.3
|
replace github.com/nektos/act => gitea.com/gitea/act v0.261.3
|
||||||
|
|
||||||
|
// TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why
|
||||||
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0
|
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0
|
||||||
|
|
||||||
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
|
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
|
||||||
|
9
go.sum
9
go.sum
@ -98,7 +98,6 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1L
|
|||||||
github.com/anchore/archiver/v3 v3.5.2 h1:Bjemm2NzuRhmHy3m0lRe5tNoClB9A4zYyDV58PaB6aA=
|
github.com/anchore/archiver/v3 v3.5.2 h1:Bjemm2NzuRhmHy3m0lRe5tNoClB9A4zYyDV58PaB6aA=
|
||||||
github.com/anchore/archiver/v3 v3.5.2/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
github.com/anchore/archiver/v3 v3.5.2/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
|
||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||||
@ -391,8 +390,8 @@ github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7w
|
|||||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
|
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
|
||||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
|
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||||
@ -603,8 +602,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
|
|||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/meilisearch/meilisearch-go v0.29.0 h1:HZ9NEKN59USINQ/DXJge/aaXq8IrsKbXGTdAoBaaDz4=
|
github.com/meilisearch/meilisearch-go v0.29.1-0.20241106140435-0bf60fad690a h1:F0y+3QtCG00mr4KueQWuHv1tlIQeNXhH+XAKYLhb3X4=
|
||||||
github.com/meilisearch/meilisearch-go v0.29.0/go.mod h1:2cRCAn4ddySUsFfNDLVPod/plRibQsJkXF/4gLhxbOk=
|
github.com/meilisearch/meilisearch-go v0.29.1-0.20241106140435-0bf60fad690a/go.mod h1:NYOgjEGt/+oExD+NixreBMqxtIB0kCndXOOgpGhoqEs=
|
||||||
github.com/mholt/acmez/v2 v2.0.3 h1:CgDBlEwg3QBp6s45tPQmFIBrkRIkBT4rW4orMM6p4sw=
|
github.com/mholt/acmez/v2 v2.0.3 h1:CgDBlEwg3QBp6s45tPQmFIBrkRIkBT4rW4orMM6p4sw=
|
||||||
github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw=
|
github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||||
|
@ -261,6 +261,7 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InsertRun inserts a run
|
// InsertRun inserts a run
|
||||||
|
// The title will be cut off at 255 characters if it's longer than 255 characters.
|
||||||
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
|
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -273,6 +274,7 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
run.Index = index
|
run.Index = index
|
||||||
|
run.Title, _ = util.SplitStringAtByteN(run.Title, 255)
|
||||||
|
|
||||||
if err := db.Insert(ctx, run); err != nil {
|
if err := db.Insert(ctx, run); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -399,6 +401,7 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
|
|||||||
if len(cols) > 0 {
|
if len(cols) > 0 {
|
||||||
sess.Cols(cols...)
|
sess.Cols(cols...)
|
||||||
}
|
}
|
||||||
|
run.Title, _ = util.SplitStringAtByteN(run.Title, 255)
|
||||||
affected, err := sess.Update(run)
|
affected, err := sess.Update(run)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -252,6 +252,7 @@ func GetRunnerByID(ctx context.Context, id int64) (*ActionRunner, error) {
|
|||||||
// UpdateRunner updates runner's information.
|
// UpdateRunner updates runner's information.
|
||||||
func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error {
|
func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error {
|
||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
|
r.Name, _ = util.SplitStringAtByteN(r.Name, 255)
|
||||||
var err error
|
var err error
|
||||||
if len(cols) == 0 {
|
if len(cols) == 0 {
|
||||||
_, err = e.ID(r.ID).AllCols().Update(r)
|
_, err = e.ID(r.ID).AllCols().Update(r)
|
||||||
@ -278,6 +279,7 @@ func CreateRunner(ctx context.Context, t *ActionRunner) error {
|
|||||||
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
|
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
|
||||||
t.OwnerID = 0
|
t.OwnerID = 0
|
||||||
}
|
}
|
||||||
|
t.Name, _ = util.SplitStringAtByteN(t.Name, 255)
|
||||||
return db.Insert(ctx, t)
|
return db.Insert(ctx, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -67,6 +68,7 @@ func CreateScheduleTask(ctx context.Context, rows []*ActionSchedule) error {
|
|||||||
|
|
||||||
// Loop through each schedule row
|
// Loop through each schedule row
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
|
row.Title, _ = util.SplitStringAtByteN(row.Title, 255)
|
||||||
// Create new schedule row
|
// Create new schedule row
|
||||||
if err = db.Insert(ctx, row); err != nil {
|
if err = db.Insert(ctx, row); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -341,7 +341,7 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error {
|
|||||||
// UpdateTaskByState updates the task by the state.
|
// UpdateTaskByState updates the task by the state.
|
||||||
// It will always update the task if the state is not final, even there is no change.
|
// It will always update the task if the state is not final, even there is no change.
|
||||||
// So it will update ActionTask.Updated to avoid the task being judged as a zombie task.
|
// So it will update ActionTask.Updated to avoid the task being judged as a zombie task.
|
||||||
func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionTask, error) {
|
func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.TaskState) (*ActionTask, error) {
|
||||||
stepStates := map[int64]*runnerv1.StepState{}
|
stepStates := map[int64]*runnerv1.StepState{}
|
||||||
for _, v := range state.Steps {
|
for _, v := range state.Steps {
|
||||||
stepStates[v.Id] = v
|
stepStates[v.Id] = v
|
||||||
@ -360,6 +360,8 @@ func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionT
|
|||||||
return nil, err
|
return nil, err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
return nil, util.ErrNotExist
|
return nil, util.ErrNotExist
|
||||||
|
} else if runnerID != task.RunnerID {
|
||||||
|
return nil, fmt.Errorf("invalid runner for task")
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.Status.IsDone() {
|
if task.Status.IsDone() {
|
||||||
|
@ -200,7 +200,7 @@ func (a *Action) LoadActUser(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Action) loadRepo(ctx context.Context) {
|
func (a *Action) LoadRepo(ctx context.Context) {
|
||||||
if a.Repo != nil {
|
if a.Repo != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -250,7 +250,10 @@ func (a *Action) GetActDisplayNameTitle(ctx context.Context) string {
|
|||||||
|
|
||||||
// GetRepoUserName returns the name of the action repository owner.
|
// GetRepoUserName returns the name of the action repository owner.
|
||||||
func (a *Action) GetRepoUserName(ctx context.Context) string {
|
func (a *Action) GetRepoUserName(ctx context.Context) string {
|
||||||
a.loadRepo(ctx)
|
a.LoadRepo(ctx)
|
||||||
|
if a.Repo == nil {
|
||||||
|
return "(non-existing-repo)"
|
||||||
|
}
|
||||||
return a.Repo.OwnerName
|
return a.Repo.OwnerName
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,7 +265,10 @@ func (a *Action) ShortRepoUserName(ctx context.Context) string {
|
|||||||
|
|
||||||
// GetRepoName returns the name of the action repository.
|
// GetRepoName returns the name of the action repository.
|
||||||
func (a *Action) GetRepoName(ctx context.Context) string {
|
func (a *Action) GetRepoName(ctx context.Context) string {
|
||||||
a.loadRepo(ctx)
|
a.LoadRepo(ctx)
|
||||||
|
if a.Repo == nil {
|
||||||
|
return "(non-existing-repo)"
|
||||||
|
}
|
||||||
return a.Repo.Name
|
return a.Repo.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -638,7 +644,7 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if repoChanged {
|
if repoChanged {
|
||||||
act.loadRepo(ctx)
|
act.LoadRepo(ctx)
|
||||||
repo = act.Repo
|
repo = act.Repo
|
||||||
|
|
||||||
// check repo owner exist.
|
// check repo owner exist.
|
||||||
@ -764,7 +770,7 @@ func DeleteIssueActions(ctx context.Context, repoID, issueID, issueIndex int64)
|
|||||||
// 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.Type.IsSQLite3() {
|
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
|
||||||
}
|
}
|
||||||
@ -772,7 +778,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.Type.IsSQLite3() {
|
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
|
||||||
}
|
}
|
||||||
|
@ -256,7 +256,7 @@ func TestConsistencyUpdateAction(t *testing.T) {
|
|||||||
unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
|
unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
|
||||||
ID: int64(id),
|
ID: int64(id),
|
||||||
})
|
})
|
||||||
_, err := db.GetEngine(db.DefaultContext).Exec(`UPDATE action SET created_unix = "" WHERE id = ?`, id)
|
_, err := db.GetEngine(db.DefaultContext).Exec(`UPDATE action SET created_unix = '' WHERE id = ?`, id)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
actions := make([]*activities_model.Action, 0, 1)
|
actions := make([]*activities_model.Action, 0, 1)
|
||||||
//
|
//
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
|
"xorm.io/xorm/schemas"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -50,25 +51,64 @@ const (
|
|||||||
// Notification represents a notification
|
// Notification represents a notification
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
UserID int64 `xorm:"INDEX NOT NULL"`
|
UserID int64 `xorm:"NOT NULL"`
|
||||||
RepoID int64 `xorm:"INDEX NOT NULL"`
|
RepoID int64 `xorm:"NOT NULL"`
|
||||||
|
|
||||||
Status NotificationStatus `xorm:"SMALLINT INDEX NOT NULL"`
|
Status NotificationStatus `xorm:"SMALLINT NOT NULL"`
|
||||||
Source NotificationSource `xorm:"SMALLINT INDEX NOT NULL"`
|
Source NotificationSource `xorm:"SMALLINT NOT NULL"`
|
||||||
|
|
||||||
IssueID int64 `xorm:"INDEX NOT NULL"`
|
IssueID int64 `xorm:"NOT NULL"`
|
||||||
CommitID string `xorm:"INDEX"`
|
CommitID string
|
||||||
CommentID int64
|
CommentID int64
|
||||||
|
|
||||||
UpdatedBy int64 `xorm:"INDEX NOT NULL"`
|
UpdatedBy int64 `xorm:"NOT NULL"`
|
||||||
|
|
||||||
Issue *issues_model.Issue `xorm:"-"`
|
Issue *issues_model.Issue `xorm:"-"`
|
||||||
Repository *repo_model.Repository `xorm:"-"`
|
Repository *repo_model.Repository `xorm:"-"`
|
||||||
Comment *issues_model.Comment `xorm:"-"`
|
Comment *issues_model.Comment `xorm:"-"`
|
||||||
User *user_model.User `xorm:"-"`
|
User *user_model.User `xorm:"-"`
|
||||||
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"`
|
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated INDEX NOT NULL"`
|
UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableIndices implements xorm's TableIndices interface
|
||||||
|
func (n *Notification) TableIndices() []*schemas.Index {
|
||||||
|
indices := make([]*schemas.Index, 0, 8)
|
||||||
|
usuuIndex := schemas.NewIndex("u_s_uu", schemas.IndexType)
|
||||||
|
usuuIndex.AddColumn("user_id", "status", "updated_unix")
|
||||||
|
indices = append(indices, usuuIndex)
|
||||||
|
|
||||||
|
// Add the individual indices that were previously defined in struct tags
|
||||||
|
userIDIndex := schemas.NewIndex("idx_notification_user_id", schemas.IndexType)
|
||||||
|
userIDIndex.AddColumn("user_id")
|
||||||
|
indices = append(indices, userIDIndex)
|
||||||
|
|
||||||
|
repoIDIndex := schemas.NewIndex("idx_notification_repo_id", schemas.IndexType)
|
||||||
|
repoIDIndex.AddColumn("repo_id")
|
||||||
|
indices = append(indices, repoIDIndex)
|
||||||
|
|
||||||
|
statusIndex := schemas.NewIndex("idx_notification_status", schemas.IndexType)
|
||||||
|
statusIndex.AddColumn("status")
|
||||||
|
indices = append(indices, statusIndex)
|
||||||
|
|
||||||
|
sourceIndex := schemas.NewIndex("idx_notification_source", schemas.IndexType)
|
||||||
|
sourceIndex.AddColumn("source")
|
||||||
|
indices = append(indices, sourceIndex)
|
||||||
|
|
||||||
|
issueIDIndex := schemas.NewIndex("idx_notification_issue_id", schemas.IndexType)
|
||||||
|
issueIDIndex.AddColumn("issue_id")
|
||||||
|
indices = append(indices, issueIDIndex)
|
||||||
|
|
||||||
|
commitIDIndex := schemas.NewIndex("idx_notification_commit_id", schemas.IndexType)
|
||||||
|
commitIDIndex.AddColumn("commit_id")
|
||||||
|
indices = append(indices, commitIDIndex)
|
||||||
|
|
||||||
|
updatedByIndex := schemas.NewIndex("idx_notification_updated_by", schemas.IndexType)
|
||||||
|
updatedByIndex.AddColumn("updated_by")
|
||||||
|
indices = append(indices, updatedByIndex)
|
||||||
|
|
||||||
|
return indices
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -89,14 +90,33 @@ func (cred *WebAuthnCredential) AfterLoad() {
|
|||||||
// WebAuthnCredentialList is a list of *WebAuthnCredential
|
// WebAuthnCredentialList is a list of *WebAuthnCredential
|
||||||
type WebAuthnCredentialList []*WebAuthnCredential
|
type WebAuthnCredentialList []*WebAuthnCredential
|
||||||
|
|
||||||
|
// newCredentialFlagsFromAuthenticatorFlags is copied from https://github.com/go-webauthn/webauthn/pull/337
|
||||||
|
// to convert protocol.AuthenticatorFlags to webauthn.CredentialFlags
|
||||||
|
func newCredentialFlagsFromAuthenticatorFlags(flags protocol.AuthenticatorFlags) webauthn.CredentialFlags {
|
||||||
|
return webauthn.CredentialFlags{
|
||||||
|
UserPresent: flags.HasUserPresent(),
|
||||||
|
UserVerified: flags.HasUserVerified(),
|
||||||
|
BackupEligible: flags.HasBackupEligible(),
|
||||||
|
BackupState: flags.HasBackupState(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ToCredentials will convert all WebAuthnCredentials to webauthn.Credentials
|
// ToCredentials will convert all WebAuthnCredentials to webauthn.Credentials
|
||||||
func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential {
|
func (list WebAuthnCredentialList) ToCredentials(defaultAuthFlags ...protocol.AuthenticatorFlags) []webauthn.Credential {
|
||||||
|
// TODO: at the moment, Gitea doesn't store or check the flags
|
||||||
|
// so we need to use the default flags from the authenticator to make the login validation pass
|
||||||
|
// In the future, we should:
|
||||||
|
// 1. store the flags when registering the credential
|
||||||
|
// 2. provide the stored flags when converting the credentials (for login)
|
||||||
|
// 3. for old users, still use this fallback to the default flags
|
||||||
|
defAuthFlags := util.OptionalArg(defaultAuthFlags)
|
||||||
creds := make([]webauthn.Credential, 0, len(list))
|
creds := make([]webauthn.Credential, 0, len(list))
|
||||||
for _, cred := range list {
|
for _, cred := range list {
|
||||||
creds = append(creds, webauthn.Credential{
|
creds = append(creds, webauthn.Credential{
|
||||||
ID: cred.CredentialID,
|
ID: cred.CredentialID,
|
||||||
PublicKey: cred.PublicKey,
|
PublicKey: cred.PublicKey,
|
||||||
AttestationType: cred.AttestationType,
|
AttestationType: cred.AttestationType,
|
||||||
|
Flags: newCredentialFlagsFromAuthenticatorFlags(defAuthFlags),
|
||||||
Authenticator: webauthn.Authenticator{
|
Authenticator: webauthn.Authenticator{
|
||||||
AAGUID: cred.AAGUID,
|
AAGUID: cred.AAGUID,
|
||||||
SignCount: cred.SignCount,
|
SignCount: cred.SignCount,
|
||||||
|
@ -68,7 +68,8 @@ func CheckCollations(x *xorm.Engine) (*CheckCollationsResult, error) {
|
|||||||
|
|
||||||
var candidateCollations []string
|
var candidateCollations []string
|
||||||
if x.Dialect().URI().DBType == schemas.MYSQL {
|
if x.Dialect().URI().DBType == schemas.MYSQL {
|
||||||
if _, err = x.SQL("SELECT @@collation_database").Get(&res.DatabaseCollation); err != nil {
|
_, err = x.SQL("SELECT DEFAULT_COLLATION_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", setting.Database.Name).Get(&res.DatabaseCollation)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res.IsCollationCaseSensitive = func(s string) bool {
|
res.IsCollationCaseSensitive = func(s string) bool {
|
||||||
|
@ -134,6 +134,9 @@ func SyncAllTables() error {
|
|||||||
func InitEngine(ctx context.Context) error {
|
func InitEngine(ctx context.Context) error {
|
||||||
xormEngine, err := newXORMEngine()
|
xormEngine, err := newXORMEngine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "SQLite3 support") {
|
||||||
|
return fmt.Errorf(`sqlite3 requires: -tags sqlite,sqlite_unlock_notify%s%w`, "\n", err)
|
||||||
|
}
|
||||||
return fmt.Errorf("failed to connect to database: %w", err)
|
return fmt.Errorf("failed to connect to database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,22 @@
|
|||||||
|
-
|
||||||
|
id: 46
|
||||||
|
attempt: 3
|
||||||
|
runner_id: 1
|
||||||
|
status: 3 # 3 is the status code for "cancelled"
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
repo_id: 4
|
||||||
|
owner_id: 1
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
token_hash: 6d8ef48297195edcc8e22c70b3020eaa06c52976db67d39b4260c64a69a2cc1508825121b7b8394e48e00b1bf8718b2aaaaa
|
||||||
|
token_salt: eeeeeeee
|
||||||
|
token_last_eight: eeeeeeee
|
||||||
|
log_filename: artifact-test2/2f/47.log
|
||||||
|
log_in_storage: 1
|
||||||
|
log_length: 707
|
||||||
|
log_size: 90179
|
||||||
|
log_expired: 0
|
||||||
-
|
-
|
||||||
id: 47
|
id: 47
|
||||||
job_id: 192
|
job_id: 192
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
id: 2
|
id: 2
|
||||||
oid: 2eccdb43825d2a49d99d542daa20075cff1d97d9d2349a8977efe9c03661737c
|
oid: 2eccdb43825d2a49d99d542daa20075cff1d97d9d2349a8977efe9c03661737c
|
||||||
size: 107
|
size: 2048
|
||||||
repository_id: 54
|
repository_id: 54
|
||||||
created_unix: 1671607299
|
created_unix: 1671607299
|
||||||
|
|
||||||
|
@ -129,3 +129,9 @@
|
|||||||
uid: 2
|
uid: 2
|
||||||
org_id: 35
|
org_id: 35
|
||||||
is_public: true
|
is_public: true
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 23
|
||||||
|
uid: 20
|
||||||
|
org_id: 17
|
||||||
|
is_public: false
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
fork_id: 0
|
fork_id: 0
|
||||||
is_template: false
|
is_template: false
|
||||||
template_id: 0
|
template_id: 0
|
||||||
size: 8478
|
size: 0
|
||||||
is_fsck_enabled: true
|
is_fsck_enabled: true
|
||||||
close_issues_via_commit_in_any_branch: false
|
close_issues_via_commit_in_any_branch: false
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
-
|
-
|
||||||
id: 1
|
id: 1
|
||||||
setting_key: 'picture.disable_gravatar'
|
setting_key: 'picture.disable_gravatar'
|
||||||
setting_value: 'false'
|
setting_value: 'true'
|
||||||
version: 1
|
version: 1
|
||||||
created: 1653533198
|
created: 1653533198
|
||||||
updated: 1653533198
|
updated: 1653533198
|
||||||
|
@ -23,9 +23,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar1
|
avatar: ""
|
||||||
avatar_email: user1@example.com
|
avatar_email: user1@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -60,8 +60,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar2
|
avatar: ""
|
||||||
avatar_email: user2@example.com
|
avatar_email: user2@example.com
|
||||||
|
# cause a random avatar to be generated when referenced for test purposes
|
||||||
use_custom_avatar: false
|
use_custom_avatar: false
|
||||||
num_followers: 2
|
num_followers: 2
|
||||||
num_following: 1
|
num_following: 1
|
||||||
@ -97,9 +98,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar3
|
avatar: ""
|
||||||
avatar_email: org3@example.com
|
avatar_email: org3@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -134,9 +135,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar4
|
avatar: ""
|
||||||
avatar_email: user4@example.com
|
avatar_email: user4@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 1
|
num_following: 1
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -171,9 +172,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: false
|
allow_create_organization: false
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar5
|
avatar: ""
|
||||||
avatar_email: user5@example.com
|
avatar_email: user5@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -208,9 +209,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar6
|
avatar: ""
|
||||||
avatar_email: org6@example.com
|
avatar_email: org6@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -245,9 +246,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar7
|
avatar: ""
|
||||||
avatar_email: org7@example.com
|
avatar_email: org7@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -282,9 +283,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar8
|
avatar: ""
|
||||||
avatar_email: user8@example.com
|
avatar_email: user8@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 1
|
num_followers: 1
|
||||||
num_following: 1
|
num_following: 1
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -319,9 +320,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar9
|
avatar: ""
|
||||||
avatar_email: user9@example.com
|
avatar_email: user9@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -332,6 +333,7 @@
|
|||||||
repo_admin_change_team_access: false
|
repo_admin_change_team_access: false
|
||||||
theme: ""
|
theme: ""
|
||||||
keep_activity_private: false
|
keep_activity_private: false
|
||||||
|
created_unix: 1730468968
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 10
|
id: 10
|
||||||
@ -356,9 +358,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar10
|
avatar: ""
|
||||||
avatar_email: user10@example.com
|
avatar_email: user10@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 2
|
num_stars: 2
|
||||||
@ -393,9 +395,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar11
|
avatar: ""
|
||||||
avatar_email: user11@example.com
|
avatar_email: user11@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -430,9 +432,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar12
|
avatar: ""
|
||||||
avatar_email: user12@example.com
|
avatar_email: user12@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -467,9 +469,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar13
|
avatar: ""
|
||||||
avatar_email: user13@example.com
|
avatar_email: user13@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -504,9 +506,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar14
|
avatar: ""
|
||||||
avatar_email: user13@example.com
|
avatar_email: user13@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -541,9 +543,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar15
|
avatar: ""
|
||||||
avatar_email: user15@example.com
|
avatar_email: user15@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -578,9 +580,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar16
|
avatar: ""
|
||||||
avatar_email: user16@example.com
|
avatar_email: user16@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -615,15 +617,15 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar17
|
avatar: ""
|
||||||
avatar_email: org17@example.com
|
avatar_email: org17@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_repos: 2
|
num_repos: 2
|
||||||
num_teams: 3
|
num_teams: 3
|
||||||
num_members: 4
|
num_members: 5
|
||||||
visibility: 0
|
visibility: 0
|
||||||
repo_admin_change_team_access: false
|
repo_admin_change_team_access: false
|
||||||
theme: ""
|
theme: ""
|
||||||
@ -652,9 +654,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar18
|
avatar: ""
|
||||||
avatar_email: user18@example.com
|
avatar_email: user18@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -689,9 +691,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar19
|
avatar: ""
|
||||||
avatar_email: org19@example.com
|
avatar_email: org19@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -726,9 +728,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar20
|
avatar: ""
|
||||||
avatar_email: user20@example.com
|
avatar_email: user20@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -763,9 +765,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar21
|
avatar: ""
|
||||||
avatar_email: user21@example.com
|
avatar_email: user21@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -800,9 +802,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar22
|
avatar: ""
|
||||||
avatar_email: limited_org@example.com
|
avatar_email: limited_org@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -837,9 +839,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar23
|
avatar: ""
|
||||||
avatar_email: privated_org@example.com
|
avatar_email: privated_org@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -874,9 +876,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar24
|
avatar: ""
|
||||||
avatar_email: user24@example.com
|
avatar_email: user24@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -911,9 +913,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar25
|
avatar: ""
|
||||||
avatar_email: org25@example.com
|
avatar_email: org25@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -948,9 +950,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar26
|
avatar: ""
|
||||||
avatar_email: org26@example.com
|
avatar_email: org26@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -985,9 +987,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar27
|
avatar: ""
|
||||||
avatar_email: user27@example.com
|
avatar_email: user27@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1022,9 +1024,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar28
|
avatar: ""
|
||||||
avatar_email: user28@example.com
|
avatar_email: user28@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1059,9 +1061,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar29
|
avatar: ""
|
||||||
avatar_email: user29@example.com
|
avatar_email: user29@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1096,9 +1098,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar29
|
avatar: ""
|
||||||
avatar_email: user30@example.com
|
avatar_email: user30@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1133,9 +1135,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar31
|
avatar: ""
|
||||||
avatar_email: user31@example.com
|
avatar_email: user31@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 1
|
num_following: 1
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1170,9 +1172,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar32
|
avatar: ""
|
||||||
avatar_email: user30@example.com
|
avatar_email: user30@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1207,9 +1209,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar33
|
avatar: ""
|
||||||
avatar_email: user33@example.com
|
avatar_email: user33@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 1
|
num_followers: 1
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1245,7 +1247,7 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: false
|
allow_create_organization: false
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar34
|
avatar: ""
|
||||||
avatar_email: user34@example.com
|
avatar_email: user34@example.com
|
||||||
use_custom_avatar: true
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
@ -1282,9 +1284,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar35
|
avatar: ""
|
||||||
avatar_email: private_org35@example.com
|
avatar_email: private_org35@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1319,9 +1321,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar22
|
avatar: ""
|
||||||
avatar_email: abcde@gitea.com
|
avatar_email: abcde@gitea.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1356,9 +1358,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: true
|
prohibit_login: true
|
||||||
avatar: avatar29
|
avatar: ""
|
||||||
avatar_email: user37@example.com
|
avatar_email: user37@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1393,9 +1395,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar38
|
avatar: ""
|
||||||
avatar_email: user38@example.com
|
avatar_email: user38@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1430,9 +1432,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar39
|
avatar: ""
|
||||||
avatar_email: user39@example.com
|
avatar_email: user39@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1467,9 +1469,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar40
|
avatar: ""
|
||||||
avatar_email: user40@example.com
|
avatar_email: user40@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1504,9 +1506,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar41
|
avatar: ""
|
||||||
avatar_email: org41@example.com
|
avatar_email: org41@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1541,9 +1543,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar42
|
avatar: ""
|
||||||
avatar_email: org42@example.com
|
avatar_email: org42@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
|
@ -34,6 +34,7 @@ type ProtectedBranch struct {
|
|||||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||||
Repo *repo_model.Repository `xorm:"-"`
|
Repo *repo_model.Repository `xorm:"-"`
|
||||||
RuleName string `xorm:"'branch_name' UNIQUE(s)"` // a branch name or a glob match to branch name
|
RuleName string `xorm:"'branch_name' UNIQUE(s)"` // a branch name or a glob match to branch name
|
||||||
|
Priority int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||||
globRule glob.Glob `xorm:"-"`
|
globRule glob.Glob `xorm:"-"`
|
||||||
isPlainName bool `xorm:"-"`
|
isPlainName bool `xorm:"-"`
|
||||||
CanPush bool `xorm:"NOT NULL DEFAULT false"`
|
CanPush bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
@ -413,14 +414,27 @@ func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, prote
|
|||||||
}
|
}
|
||||||
protectBranch.ApprovalsWhitelistTeamIDs = whitelist
|
protectBranch.ApprovalsWhitelistTeamIDs = whitelist
|
||||||
|
|
||||||
// Make sure protectBranch.ID is not 0 for whitelists
|
// Looks like it's a new rule
|
||||||
if protectBranch.ID == 0 {
|
if protectBranch.ID == 0 {
|
||||||
|
// as it's a new rule and if priority was not set, we need to calc it.
|
||||||
|
if protectBranch.Priority == 0 {
|
||||||
|
var lowestPrio int64
|
||||||
|
// because of mssql we can not use builder or save xorm syntax, so raw sql it is
|
||||||
|
if _, err := db.GetEngine(ctx).SQL(`SELECT MAX(priority) FROM protected_branch WHERE repo_id = ?`, protectBranch.RepoID).
|
||||||
|
Get(&lowestPrio); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Trace("Create new ProtectedBranch at repo[%d] and detect current lowest priority '%d'", protectBranch.RepoID, lowestPrio)
|
||||||
|
protectBranch.Priority = lowestPrio + 1
|
||||||
|
}
|
||||||
|
|
||||||
if _, err = db.GetEngine(ctx).Insert(protectBranch); err != nil {
|
if _, err = db.GetEngine(ctx).Insert(protectBranch); err != nil {
|
||||||
return fmt.Errorf("Insert: %v", err)
|
return fmt.Errorf("Insert: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update the rule
|
||||||
if _, err = db.GetEngine(ctx).ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil {
|
if _, err = db.GetEngine(ctx).ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil {
|
||||||
return fmt.Errorf("Update: %v", err)
|
return fmt.Errorf("Update: %v", err)
|
||||||
}
|
}
|
||||||
@ -428,6 +442,24 @@ func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, prote
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateProtectBranchPriorities(ctx context.Context, repo *repo_model.Repository, ids []int64) error {
|
||||||
|
prio := int64(1)
|
||||||
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
for _, id := range ids {
|
||||||
|
if _, err := db.GetEngine(ctx).
|
||||||
|
ID(id).Where("repo_id = ?", repo.ID).
|
||||||
|
Cols("priority").
|
||||||
|
Update(&ProtectedBranch{
|
||||||
|
Priority: prio,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
prio++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// updateApprovalWhitelist checks whether the user whitelist changed and returns a whitelist with
|
// updateApprovalWhitelist checks whether the user whitelist changed and returns a whitelist with
|
||||||
// the users from newWhitelist which have explicit read or write access to the repo.
|
// the users from newWhitelist which have explicit read or write access to the repo.
|
||||||
func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
|
func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
|
||||||
|
@ -28,6 +28,13 @@ func (rules ProtectedBranchRules) sort() {
|
|||||||
sort.Slice(rules, func(i, j int) bool {
|
sort.Slice(rules, func(i, j int) bool {
|
||||||
rules[i].loadGlob()
|
rules[i].loadGlob()
|
||||||
rules[j].loadGlob()
|
rules[j].loadGlob()
|
||||||
|
|
||||||
|
// if priority differ, use that to sort
|
||||||
|
if rules[i].Priority != rules[j].Priority {
|
||||||
|
return rules[i].Priority < rules[j].Priority
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we sort the old way
|
||||||
if rules[i].isPlainName != rules[j].isPlainName {
|
if rules[i].isPlainName != rules[j].isPlainName {
|
||||||
return rules[i].isPlainName // plain name comes first, so plain name means "less"
|
return rules[i].isPlainName // plain name comes first, so plain name means "less"
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ func TestBranchRuleMatchPriority(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBranchRuleSort(t *testing.T) {
|
func TestBranchRuleSortLegacy(t *testing.T) {
|
||||||
in := []*ProtectedBranch{{
|
in := []*ProtectedBranch{{
|
||||||
RuleName: "b",
|
RuleName: "b",
|
||||||
CreatedUnix: 1,
|
CreatedUnix: 1,
|
||||||
@ -103,3 +103,37 @@ func TestBranchRuleSort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.Equal(t, expect, got)
|
assert.Equal(t, expect, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBranchRuleSortPriority(t *testing.T) {
|
||||||
|
in := []*ProtectedBranch{{
|
||||||
|
RuleName: "b",
|
||||||
|
CreatedUnix: 1,
|
||||||
|
Priority: 4,
|
||||||
|
}, {
|
||||||
|
RuleName: "b/*",
|
||||||
|
CreatedUnix: 3,
|
||||||
|
Priority: 2,
|
||||||
|
}, {
|
||||||
|
RuleName: "a/*",
|
||||||
|
CreatedUnix: 2,
|
||||||
|
Priority: 1,
|
||||||
|
}, {
|
||||||
|
RuleName: "c",
|
||||||
|
CreatedUnix: 0,
|
||||||
|
Priority: 0,
|
||||||
|
}, {
|
||||||
|
RuleName: "a",
|
||||||
|
CreatedUnix: 4,
|
||||||
|
Priority: 3,
|
||||||
|
}}
|
||||||
|
expect := []string{"c", "a/*", "b/*", "a", "b"}
|
||||||
|
|
||||||
|
pbr := ProtectedBranchRules(in)
|
||||||
|
pbr.sort()
|
||||||
|
|
||||||
|
var got []string
|
||||||
|
for i := range pbr {
|
||||||
|
got = append(got, pbr[i].RuleName)
|
||||||
|
}
|
||||||
|
assert.Equal(t, expect, got)
|
||||||
|
}
|
||||||
|
@ -7,6 +7,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"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"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -76,3 +80,77 @@ func TestBranchRuleMatch(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUpdateProtectBranchPriorities(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
|
||||||
|
// Create some test protected branches with initial priorities
|
||||||
|
protectedBranches := []*ProtectedBranch{
|
||||||
|
{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
RuleName: "master",
|
||||||
|
Priority: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
RuleName: "develop",
|
||||||
|
Priority: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
RuleName: "feature/*",
|
||||||
|
Priority: 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pb := range protectedBranches {
|
||||||
|
_, err := db.GetEngine(db.DefaultContext).Insert(pb)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test updating priorities
|
||||||
|
newPriorities := []int64{protectedBranches[2].ID, protectedBranches[0].ID, protectedBranches[1].ID}
|
||||||
|
err := UpdateProtectBranchPriorities(db.DefaultContext, repo, newPriorities)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify new priorities
|
||||||
|
pbs, err := FindRepoProtectedBranchRules(db.DefaultContext, repo.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
expectedPriorities := map[string]int64{
|
||||||
|
"feature/*": 1,
|
||||||
|
"master": 2,
|
||||||
|
"develop": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pb := range pbs {
|
||||||
|
assert.Equal(t, expectedPriorities[pb.RuleName], pb.Priority)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewProtectBranchPriority(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
|
||||||
|
err := UpdateProtectBranch(db.DefaultContext, repo, &ProtectedBranch{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
RuleName: "branch-1",
|
||||||
|
Priority: 1,
|
||||||
|
}, WhitelistOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
newPB := &ProtectedBranch{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
RuleName: "branch-2",
|
||||||
|
// Priority intentionally omitted
|
||||||
|
}
|
||||||
|
|
||||||
|
err = UpdateProtectBranch(db.DefaultContext, repo, newPB, WhitelistOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
savedPB2, err := GetFirstMatchProtectedBranchRule(db.DefaultContext, repo.ID, "branch-2")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(2), savedPB2.Priority)
|
||||||
|
}
|
||||||
|
@ -1108,7 +1108,7 @@ func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList,
|
|||||||
sess.Join("INNER", "issue", "issue.id = comment.issue_id")
|
sess.Join("INNER", "issue", "issue.id = comment.issue_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Page != 0 {
|
if opts.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, opts)
|
sess = db.SetSessionPagination(sess, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
@ -112,14 +112,8 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo)
|
||||||
Ctx: ctx,
|
if comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content); err != nil {
|
||||||
Repo: issue.Repo,
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: issue.Repo.Link(),
|
|
||||||
},
|
|
||||||
Metas: issue.Repo.ComposeMetas(ctx),
|
|
||||||
}, comment.Content); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -641,7 +641,7 @@ func (issue *Issue) BlockedByDependencies(ctx context.Context, opts db.ListOptio
|
|||||||
Where("issue_id = ?", issue.ID).
|
Where("issue_id = ?", issue.ID).
|
||||||
// sort by repo id then created date, with the issues of the same repo at the beginning of the list
|
// sort by repo id then created date, with the issues of the same repo at the beginning of the list
|
||||||
OrderBy("CASE WHEN issue.repo_id = ? THEN 0 ELSE issue.repo_id END, issue.created_unix DESC", issue.RepoID)
|
OrderBy("CASE WHEN issue.repo_id = ? THEN 0 ELSE issue.repo_id END, issue.created_unix DESC", issue.RepoID)
|
||||||
if opts.Page != 0 {
|
if opts.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &opts)
|
sess = db.SetSessionPagination(sess, &opts)
|
||||||
}
|
}
|
||||||
err = sess.Find(&issueDeps)
|
err = sess.Find(&issueDeps)
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/references"
|
"code.gitea.io/gitea/modules/references"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
@ -138,6 +139,7 @@ func ChangeIssueTitle(ctx context.Context, issue *Issue, doer *user_model.User,
|
|||||||
}
|
}
|
||||||
defer committer.Close()
|
defer committer.Close()
|
||||||
|
|
||||||
|
issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255)
|
||||||
if err = UpdateIssueCols(ctx, issue, "name"); err != nil {
|
if err = UpdateIssueCols(ctx, issue, "name"); err != nil {
|
||||||
return fmt.Errorf("updateIssueCols: %w", err)
|
return fmt.Errorf("updateIssueCols: %w", err)
|
||||||
}
|
}
|
||||||
@ -386,6 +388,7 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewIssue creates new issue with labels for repository.
|
// NewIssue creates new issue with labels for repository.
|
||||||
|
// The title will be cut off at 255 characters if it's longer than 255 characters.
|
||||||
func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
|
func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -399,6 +402,7 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, la
|
|||||||
}
|
}
|
||||||
|
|
||||||
issue.Index = idx
|
issue.Index = idx
|
||||||
|
issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255)
|
||||||
|
|
||||||
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
|
@ -105,7 +105,7 @@ func GetIssueWatchers(ctx context.Context, issueID int64, listOptions db.ListOpt
|
|||||||
And("`user`.prohibit_login = ?", false).
|
And("`user`.prohibit_login = ?", false).
|
||||||
Join("INNER", "`user`", "`user`.id = `issue_watch`.user_id")
|
Join("INNER", "`user`", "`user`.id = `issue_watch`.user_id")
|
||||||
|
|
||||||
if listOptions.Page != 0 {
|
if listOptions.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
sess = db.SetSessionPagination(sess, &listOptions)
|
||||||
watches := make([]*IssueWatch, 0, listOptions.PageSize)
|
watches := make([]*IssueWatch, 0, listOptions.PageSize)
|
||||||
return watches, sess.Find(&watches)
|
return watches, sess.Find(&watches)
|
||||||
|
@ -390,7 +390,7 @@ func GetLabelsByRepoID(ctx context.Context, repoID int64, sortType string, listO
|
|||||||
sess.Asc("name")
|
sess.Asc("name")
|
||||||
}
|
}
|
||||||
|
|
||||||
if listOptions.Page != 0 {
|
if listOptions.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
sess = db.SetSessionPagination(sess, &listOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -462,7 +462,7 @@ func GetLabelsByOrgID(ctx context.Context, orgID int64, sortType string, listOpt
|
|||||||
sess.Asc("name")
|
sess.Asc("name")
|
||||||
}
|
}
|
||||||
|
|
||||||
if listOptions.Page != 0 {
|
if listOptions.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
sess = db.SetSessionPagination(sess, &listOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,7 +406,7 @@ func TestDeleteIssueLabel(t *testing.T) {
|
|||||||
PosterID: doerID,
|
PosterID: doerID,
|
||||||
IssueID: issueID,
|
IssueID: issueID,
|
||||||
LabelID: labelID,
|
LabelID: labelID,
|
||||||
}, `content=""`)
|
}, `content=''`)
|
||||||
label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: labelID})
|
label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: labelID})
|
||||||
assert.EqualValues(t, expectedNumIssues, label.NumIssues)
|
assert.EqualValues(t, expectedNumIssues, label.NumIssues)
|
||||||
assert.EqualValues(t, expectedNumClosedIssues, label.NumClosedIssues)
|
assert.EqualValues(t, expectedNumClosedIssues, label.NumClosedIssues)
|
||||||
|
@ -572,6 +572,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Iss
|
|||||||
}
|
}
|
||||||
|
|
||||||
issue.Index = idx
|
issue.Index = idx
|
||||||
|
issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255)
|
||||||
|
|
||||||
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
|
@ -163,7 +163,7 @@ func FindReactions(ctx context.Context, opts FindReactionsOptions) (ReactionList
|
|||||||
Where(opts.toConds()).
|
Where(opts.toConds()).
|
||||||
In("reaction.`type`", setting.UI.Reactions).
|
In("reaction.`type`", setting.UI.Reactions).
|
||||||
Asc("reaction.issue_id", "reaction.comment_id", "reaction.created_unix", "reaction.id")
|
Asc("reaction.issue_id", "reaction.comment_id", "reaction.created_unix", "reaction.id")
|
||||||
if opts.Page != 0 {
|
if opts.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &opts)
|
sess = db.SetSessionPagination(sess, &opts)
|
||||||
|
|
||||||
reactions := make([]*Reaction, 0, opts.PageSize)
|
reactions := make([]*Reaction, 0, opts.PageSize)
|
||||||
|
@ -96,7 +96,7 @@ func GetUIDsAndStopwatch(ctx context.Context) ([]*UserStopwatch, error) {
|
|||||||
func GetUserStopwatches(ctx context.Context, userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) {
|
func GetUserStopwatches(ctx context.Context, userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) {
|
||||||
sws := make([]*Stopwatch, 0, 8)
|
sws := make([]*Stopwatch, 0, 8)
|
||||||
sess := db.GetEngine(ctx).Where("stopwatch.user_id = ?", userID)
|
sess := db.GetEngine(ctx).Where("stopwatch.user_id = ?", userID)
|
||||||
if listOptions.Page != 0 {
|
if listOptions.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
sess = db.SetSessionPagination(sess, &listOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ func (opts *FindTrackedTimesOptions) toSession(e db.Engine) db.Engine {
|
|||||||
|
|
||||||
sess = sess.Where(opts.ToConds())
|
sess = sess.Where(opts.ToConds())
|
||||||
|
|
||||||
if opts.Page != 0 {
|
if opts.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, opts)
|
sess = db.SetSessionPagination(sess, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
@ -16,11 +15,10 @@ import (
|
|||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/testlogger"
|
"code.gitea.io/gitea/modules/testlogger"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,35 +33,15 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu
|
|||||||
ourSkip := 2
|
ourSkip := 2
|
||||||
ourSkip += skip
|
ourSkip += skip
|
||||||
deferFn := testlogger.PrintCurrentTest(t, ourSkip)
|
deferFn := testlogger.PrintCurrentTest(t, ourSkip)
|
||||||
assert.NoError(t, os.RemoveAll(setting.RepoRootPath))
|
require.NoError(t, unittest.SyncDirs(filepath.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath))
|
||||||
assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath))
|
|
||||||
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
|
||||||
if err != nil {
|
|
||||||
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
|
|
||||||
}
|
|
||||||
for _, ownerDir := range ownerDirs {
|
|
||||||
if !ownerDir.Type().IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
|
|
||||||
if err != nil {
|
|
||||||
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
|
|
||||||
}
|
|
||||||
for _, repoDir := range repoDirs {
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := deleteDB(); err != nil {
|
if err := deleteDB(); err != nil {
|
||||||
t.Errorf("unable to reset database: %v", err)
|
t.Fatalf("unable to reset database: %v", err)
|
||||||
return nil, deferFn
|
return nil, deferFn
|
||||||
}
|
}
|
||||||
|
|
||||||
x, err := newXORMEngine()
|
x, err := newXORMEngine()
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if x != nil {
|
if x != nil {
|
||||||
oldDefer := deferFn
|
oldDefer := deferFn
|
||||||
deferFn = func() {
|
deferFn = func() {
|
||||||
@ -112,39 +90,36 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MainTest(m *testing.M) {
|
func MainTest(m *testing.M) {
|
||||||
log.RegisterEventWriter("test", testlogger.NewTestLoggerWriter)
|
testlogger.Init()
|
||||||
|
|
||||||
giteaRoot := base.SetupGiteaRoot()
|
giteaRoot := base.SetupGiteaRoot()
|
||||||
if giteaRoot == "" {
|
if giteaRoot == "" {
|
||||||
fmt.Println("Environment variable $GITEA_ROOT not set")
|
testlogger.Fatalf("Environment variable $GITEA_ROOT not set\n")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
giteaBinary := "gitea"
|
giteaBinary := "gitea"
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
giteaBinary += ".exe"
|
giteaBinary += ".exe"
|
||||||
}
|
}
|
||||||
setting.AppPath = path.Join(giteaRoot, giteaBinary)
|
setting.AppPath = filepath.Join(giteaRoot, giteaBinary)
|
||||||
if _, err := os.Stat(setting.AppPath); err != nil {
|
if _, err := os.Stat(setting.AppPath); err != nil {
|
||||||
fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath)
|
testlogger.Fatalf("Could not find gitea binary at %s\n", setting.AppPath)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
giteaConf := os.Getenv("GITEA_CONF")
|
giteaConf := os.Getenv("GITEA_CONF")
|
||||||
if giteaConf == "" {
|
if giteaConf == "" {
|
||||||
giteaConf = path.Join(filepath.Dir(setting.AppPath), "tests/sqlite.ini")
|
giteaConf = filepath.Join(filepath.Dir(setting.AppPath), "tests/sqlite.ini")
|
||||||
fmt.Printf("Environment variable $GITEA_CONF not set - defaulting to %s\n", giteaConf)
|
fmt.Printf("Environment variable $GITEA_CONF not set - defaulting to %s\n", giteaConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !path.IsAbs(giteaConf) {
|
if !filepath.IsAbs(giteaConf) {
|
||||||
setting.CustomConf = path.Join(giteaRoot, giteaConf)
|
setting.CustomConf = filepath.Join(giteaRoot, giteaConf)
|
||||||
} else {
|
} else {
|
||||||
setting.CustomConf = giteaConf
|
setting.CustomConf = giteaConf
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpDataPath, err := os.MkdirTemp("", "data")
|
tmpDataPath, err := os.MkdirTemp("", "data")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Unable to create temporary data path %v\n", err)
|
testlogger.Fatalf("Unable to create temporary data path %v\n", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
|
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
|
||||||
@ -152,8 +127,7 @@ func MainTest(m *testing.M) {
|
|||||||
|
|
||||||
unittest.InitSettings()
|
unittest.InitSettings()
|
||||||
if err = git.InitFull(context.Background()); err != nil {
|
if err = git.InitFull(context.Background()); err != nil {
|
||||||
fmt.Printf("Unable to InitFull: %v\n", err)
|
testlogger.Fatalf("Unable to InitFull: %v\n", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
setting.LoadDBSetting()
|
setting.LoadDBSetting()
|
||||||
setting.InitLoggersForTest()
|
setting.InitLoggersForTest()
|
||||||
|
@ -366,7 +366,9 @@ func prepareMigrationTasks() []*migration {
|
|||||||
newMigration(306, "Add BlockAdminMergeOverride to ProtectedBranch", v1_23.AddBlockAdminMergeOverrideBranchProtection),
|
newMigration(306, "Add BlockAdminMergeOverride to ProtectedBranch", v1_23.AddBlockAdminMergeOverrideBranchProtection),
|
||||||
newMigration(307, "Fix milestone deadline_unix when there is no due date", v1_23.FixMilestoneNoDueDate),
|
newMigration(307, "Fix milestone deadline_unix when there is no due date", v1_23.FixMilestoneNoDueDate),
|
||||||
newMigration(308, "Add index(user_id, is_deleted) for action table", v1_23.AddNewIndexForUserDashboard),
|
newMigration(308, "Add index(user_id, is_deleted) for action table", v1_23.AddNewIndexForUserDashboard),
|
||||||
newMigration(309, "Add table issue_dev_link", v1_23.CreateTableIssueDevLink),
|
newMigration(309, "Improve Notification table indices", v1_23.ImproveNotificationTableIndices),
|
||||||
|
newMigration(310, "Add Priority to ProtectedBranch", v1_23.AddPriorityToProtectedBranch),
|
||||||
|
newMigration(311, "Add table issue_dev_link", v1_23.CreateTableIssueDevLink),
|
||||||
}
|
}
|
||||||
return preparedMigrations
|
return preparedMigrations
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
giturl "code.gitea.io/gitea/modules/git/url"
|
giturl "code.gitea.io/gitea/modules/git/url"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
@ -163,7 +164,9 @@ func migratePushMirrors(x *xorm.Engine) error {
|
|||||||
|
|
||||||
func getRemoteAddress(ownerName, repoName, remoteName string) (string, error) {
|
func getRemoteAddress(ownerName, repoName, remoteName string) (string, error) {
|
||||||
repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(ownerName), strings.ToLower(repoName)+".git")
|
repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(ownerName), strings.ToLower(repoName)+".git")
|
||||||
|
if exist, _ := util.IsExist(repoPath); !exist {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
remoteURL, err := git.GetRemoteAddress(context.Background(), repoPath, remoteName)
|
remoteURL, err := git.GetRemoteAddress(context.Background(), repoPath, remoteName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("get remote %s's address of %s/%s failed: %v", remoteName, ownerName, repoName, err)
|
return "", fmt.Errorf("get remote %s's address of %s/%s failed: %v", remoteName, ownerName, repoName, err)
|
||||||
|
@ -7,16 +7,71 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
"xorm.io/xorm/schemas"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateTableIssueDevLink(x *xorm.Engine) error {
|
type improveNotificationTableIndicesAction struct {
|
||||||
type IssueDevLink struct {
|
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
IssueID int64 `xorm:"INDEX"`
|
UserID int64 `xorm:"NOT NULL"`
|
||||||
LinkType int
|
RepoID int64 `xorm:"NOT NULL"`
|
||||||
LinkedRepoID int64 `xorm:"INDEX"` // it can link to self repo or other repo
|
|
||||||
LinkIndex string // branch name, pull request number or commit sha
|
Status uint8 `xorm:"SMALLINT NOT NULL"`
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
Source uint8 `xorm:"SMALLINT NOT NULL"`
|
||||||
}
|
|
||||||
return x.Sync(new(IssueDevLink))
|
IssueID int64 `xorm:"NOT NULL"`
|
||||||
|
CommitID string
|
||||||
|
CommentID int64
|
||||||
|
|
||||||
|
UpdatedBy int64 `xorm:"NOT NULL"`
|
||||||
|
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
|
||||||
|
UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName sets the name of this table
|
||||||
|
func (*improveNotificationTableIndicesAction) TableName() string {
|
||||||
|
return "notification"
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableIndices implements xorm's TableIndices interface
|
||||||
|
func (*improveNotificationTableIndicesAction) TableIndices() []*schemas.Index {
|
||||||
|
indices := make([]*schemas.Index, 0, 8)
|
||||||
|
usuuIndex := schemas.NewIndex("u_s_uu", schemas.IndexType)
|
||||||
|
usuuIndex.AddColumn("user_id", "status", "updated_unix")
|
||||||
|
indices = append(indices, usuuIndex)
|
||||||
|
|
||||||
|
// Add the individual indices that were previously defined in struct tags
|
||||||
|
userIDIndex := schemas.NewIndex("idx_notification_user_id", schemas.IndexType)
|
||||||
|
userIDIndex.AddColumn("user_id")
|
||||||
|
indices = append(indices, userIDIndex)
|
||||||
|
|
||||||
|
repoIDIndex := schemas.NewIndex("idx_notification_repo_id", schemas.IndexType)
|
||||||
|
repoIDIndex.AddColumn("repo_id")
|
||||||
|
indices = append(indices, repoIDIndex)
|
||||||
|
|
||||||
|
statusIndex := schemas.NewIndex("idx_notification_status", schemas.IndexType)
|
||||||
|
statusIndex.AddColumn("status")
|
||||||
|
indices = append(indices, statusIndex)
|
||||||
|
|
||||||
|
sourceIndex := schemas.NewIndex("idx_notification_source", schemas.IndexType)
|
||||||
|
sourceIndex.AddColumn("source")
|
||||||
|
indices = append(indices, sourceIndex)
|
||||||
|
|
||||||
|
issueIDIndex := schemas.NewIndex("idx_notification_issue_id", schemas.IndexType)
|
||||||
|
issueIDIndex.AddColumn("issue_id")
|
||||||
|
indices = append(indices, issueIDIndex)
|
||||||
|
|
||||||
|
commitIDIndex := schemas.NewIndex("idx_notification_commit_id", schemas.IndexType)
|
||||||
|
commitIDIndex.AddColumn("commit_id")
|
||||||
|
indices = append(indices, commitIDIndex)
|
||||||
|
|
||||||
|
updatedByIndex := schemas.NewIndex("idx_notification_updated_by", schemas.IndexType)
|
||||||
|
updatedByIndex.AddColumn("updated_by")
|
||||||
|
indices = append(indices, updatedByIndex)
|
||||||
|
|
||||||
|
return indices
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImproveNotificationTableIndices(x *xorm.Engine) error {
|
||||||
|
return x.Sync(&improveNotificationTableIndicesAction{})
|
||||||
}
|
}
|
||||||
|
16
models/migrations/v1_23/v310.go
Normal file
16
models/migrations/v1_23/v310.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_23 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddPriorityToProtectedBranch(x *xorm.Engine) error {
|
||||||
|
type ProtectedBranch struct {
|
||||||
|
Priority int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.Sync(new(ProtectedBranch))
|
||||||
|
}
|
22
models/migrations/v1_23/v311.go
Normal file
22
models/migrations/v1_23/v311.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_23 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateTableIssueDevLink(x *xorm.Engine) error {
|
||||||
|
type IssueDevLink struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
IssueID int64 `xorm:"INDEX"`
|
||||||
|
LinkType int
|
||||||
|
LinkedRepoID int64 `xorm:"INDEX"` // it can link to self repo or other repo
|
||||||
|
LinkIndex string // branch name, pull request number or commit sha
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
|
}
|
||||||
|
return x.Sync(new(IssueDevLink))
|
||||||
|
}
|
@ -1,78 +0,0 @@
|
|||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package organization
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
|
||||||
"code.gitea.io/gitea/models/unit"
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
|
||||||
|
|
||||||
"xorm.io/builder"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MinimalOrg represents a simple organization with only the needed columns
|
|
||||||
type MinimalOrg = Organization
|
|
||||||
|
|
||||||
// GetUserOrgsList returns all organizations the given user has access to
|
|
||||||
func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, error) {
|
|
||||||
schema, err := db.TableInfo(new(user_model.User))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
outputCols := []string{
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"full_name",
|
|
||||||
"visibility",
|
|
||||||
"avatar",
|
|
||||||
"avatar_email",
|
|
||||||
"use_custom_avatar",
|
|
||||||
}
|
|
||||||
|
|
||||||
groupByCols := &strings.Builder{}
|
|
||||||
for _, col := range outputCols {
|
|
||||||
fmt.Fprintf(groupByCols, "`%s`.%s,", schema.Name, col)
|
|
||||||
}
|
|
||||||
groupByStr := groupByCols.String()
|
|
||||||
groupByStr = groupByStr[0 : len(groupByStr)-1]
|
|
||||||
|
|
||||||
sess := db.GetEngine(ctx)
|
|
||||||
sess = sess.Select(groupByStr+", count(distinct repo_id) as org_count").
|
|
||||||
Table("user").
|
|
||||||
Join("INNER", "team", "`team`.org_id = `user`.id").
|
|
||||||
Join("INNER", "team_user", "`team`.id = `team_user`.team_id").
|
|
||||||
Join("LEFT", builder.
|
|
||||||
Select("id as repo_id, owner_id as repo_owner_id").
|
|
||||||
From("repository").
|
|
||||||
Where(repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid)), "`repository`.repo_owner_id = `team`.org_id").
|
|
||||||
Where("`team_user`.uid = ?", user.ID).
|
|
||||||
GroupBy(groupByStr)
|
|
||||||
|
|
||||||
type OrgCount struct {
|
|
||||||
Organization `xorm:"extends"`
|
|
||||||
OrgCount int
|
|
||||||
}
|
|
||||||
|
|
||||||
orgCounts := make([]*OrgCount, 0, 10)
|
|
||||||
|
|
||||||
if err := sess.
|
|
||||||
Asc("`user`.name").
|
|
||||||
Find(&orgCounts); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
orgs := make([]*MinimalOrg, len(orgCounts))
|
|
||||||
for i, orgCount := range orgCounts {
|
|
||||||
orgCount.Organization.NumRepos = orgCount.OrgCount
|
|
||||||
orgs[i] = &orgCount.Organization
|
|
||||||
}
|
|
||||||
|
|
||||||
return orgs, nil
|
|
||||||
}
|
|
@ -22,15 +22,9 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ________ .__ __ .__
|
|
||||||
// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____
|
|
||||||
// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \
|
|
||||||
// / | \ | \/ /_/ > __ \| | \ |/ / / __ \| | | ( <_> ) | \
|
|
||||||
// \_______ /__| \___ (____ /___| /__/_____ \(____ /__| |__|\____/|___| /
|
|
||||||
// \/ /_____/ \/ \/ \/ \/ \/
|
|
||||||
|
|
||||||
// ErrOrgNotExist represents a "OrgNotExist" kind of error.
|
// ErrOrgNotExist represents a "OrgNotExist" kind of error.
|
||||||
type ErrOrgNotExist struct {
|
type ErrOrgNotExist struct {
|
||||||
ID int64
|
ID int64
|
||||||
@ -141,8 +135,9 @@ func (org *Organization) LoadTeams(ctx context.Context) ([]*Team, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetMembers returns all members of organization.
|
// GetMembers returns all members of organization.
|
||||||
func (org *Organization) GetMembers(ctx context.Context) (user_model.UserList, map[int64]bool, error) {
|
func (org *Organization) GetMembers(ctx context.Context, doer *user_model.User) (user_model.UserList, map[int64]bool, error) {
|
||||||
return FindOrgMembers(ctx, &FindOrgMembersOpts{
|
return FindOrgMembers(ctx, &FindOrgMembersOpts{
|
||||||
|
Doer: doer,
|
||||||
OrgID: org.ID,
|
OrgID: org.ID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -195,16 +190,39 @@ func (org *Organization) CanCreateRepo() bool {
|
|||||||
// FindOrgMembersOpts represensts find org members conditions
|
// FindOrgMembersOpts represensts find org members conditions
|
||||||
type FindOrgMembersOpts struct {
|
type FindOrgMembersOpts struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
|
Doer *user_model.User
|
||||||
|
IsDoerMember bool
|
||||||
OrgID int64
|
OrgID int64
|
||||||
PublicOnly bool
|
}
|
||||||
|
|
||||||
|
func (opts FindOrgMembersOpts) PublicOnly() bool {
|
||||||
|
return opts.Doer == nil || !(opts.IsDoerMember || opts.Doer.IsAdmin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyTeamMatesOnlyFilter make sure restricted users only see public team members and there own team mates
|
||||||
|
func (opts FindOrgMembersOpts) applyTeamMatesOnlyFilter(sess *xorm.Session) {
|
||||||
|
if opts.Doer != nil && opts.IsDoerMember && opts.Doer.IsRestricted {
|
||||||
|
teamMates := builder.Select("DISTINCT team_user.uid").
|
||||||
|
From("team_user").
|
||||||
|
Where(builder.In("team_user.team_id", getUserTeamIDsQueryBuilder(opts.OrgID, opts.Doer.ID))).
|
||||||
|
And(builder.Eq{"team_user.org_id": opts.OrgID})
|
||||||
|
|
||||||
|
sess.And(
|
||||||
|
builder.In("org_user.uid", teamMates).
|
||||||
|
Or(builder.Eq{"org_user.is_public": true}),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountOrgMembers counts the organization's members
|
// CountOrgMembers counts the organization's members
|
||||||
func CountOrgMembers(ctx context.Context, opts *FindOrgMembersOpts) (int64, error) {
|
func CountOrgMembers(ctx context.Context, opts *FindOrgMembersOpts) (int64, error) {
|
||||||
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
|
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
|
||||||
if opts.PublicOnly {
|
if opts.PublicOnly() {
|
||||||
sess.And("is_public = ?", true)
|
sess = sess.And("is_public = ?", true)
|
||||||
|
} else {
|
||||||
|
opts.applyTeamMatesOnlyFilter(sess)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sess.Count(new(OrgUser))
|
return sess.Count(new(OrgUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -440,42 +458,6 @@ func GetUsersWhoCanCreateOrgRepo(ctx context.Context, orgID int64) (map[int64]*u
|
|||||||
And("team_user.org_id = ?", orgID).Find(&users)
|
And("team_user.org_id = ?", orgID).Find(&users)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchOrganizationsOptions options to filter organizations
|
|
||||||
type SearchOrganizationsOptions struct {
|
|
||||||
db.ListOptions
|
|
||||||
All bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindOrgOptions finds orgs options
|
|
||||||
type FindOrgOptions struct {
|
|
||||||
db.ListOptions
|
|
||||||
UserID int64
|
|
||||||
IncludePrivate bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
|
|
||||||
cond := builder.Eq{"uid": userID}
|
|
||||||
if !includePrivate {
|
|
||||||
cond["is_public"] = true
|
|
||||||
}
|
|
||||||
return builder.Select("org_id").From("org_user").Where(cond)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts FindOrgOptions) ToConds() builder.Cond {
|
|
||||||
var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization}
|
|
||||||
if opts.UserID > 0 {
|
|
||||||
cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate)))
|
|
||||||
}
|
|
||||||
if !opts.IncludePrivate {
|
|
||||||
cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})
|
|
||||||
}
|
|
||||||
return cond
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts FindOrgOptions) ToOrders() string {
|
|
||||||
return "`user`.name ASC"
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasOrgOrUserVisible tells if the given user can see the given org or user
|
// HasOrgOrUserVisible tells if the given user can see the given org or user
|
||||||
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
|
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
|
||||||
// If user is nil, it's an anonymous user/request.
|
// If user is nil, it's an anonymous user/request.
|
||||||
@ -508,26 +490,15 @@ func HasOrgsVisible(ctx context.Context, orgs []*Organization, user *user_model.
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID
|
|
||||||
// are allowed to create repos.
|
|
||||||
func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) {
|
|
||||||
orgs := make([]*Organization, 0, 10)
|
|
||||||
|
|
||||||
return orgs, db.GetEngine(ctx).Where(builder.In("id", builder.Select("`user`.id").From("`user`").
|
|
||||||
Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id").
|
|
||||||
Join("INNER", "`team`", "`team`.id = `team_user`.team_id").
|
|
||||||
Where(builder.Eq{"`team_user`.uid": userID}).
|
|
||||||
And(builder.Eq{"`team`.authorize": perm.AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})))).
|
|
||||||
Asc("`user`.name").
|
|
||||||
Find(&orgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOrgUsersByOrgID returns all organization-user relations by organization ID.
|
// GetOrgUsersByOrgID returns all organization-user relations by organization ID.
|
||||||
func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUser, error) {
|
func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUser, error) {
|
||||||
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
|
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
|
||||||
if opts.PublicOnly {
|
if opts.PublicOnly() {
|
||||||
sess.And("is_public = ?", true)
|
sess = sess.And("is_public = ?", true)
|
||||||
|
} else {
|
||||||
|
opts.applyTeamMatesOnlyFilter(sess)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.ListOptions.PageSize > 0 {
|
if opts.ListOptions.PageSize > 0 {
|
||||||
sess = db.SetSessionPagination(sess, opts)
|
sess = db.SetSessionPagination(sess, opts)
|
||||||
|
|
||||||
@ -656,6 +627,15 @@ func (org *Organization) getUserTeamIDs(ctx context.Context, userID int64) ([]in
|
|||||||
Find(&teamIDs)
|
Find(&teamIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getUserTeamIDsQueryBuilder(orgID, userID int64) *builder.Builder {
|
||||||
|
return builder.Select("team.id").From("team").
|
||||||
|
InnerJoin("team_user", "team_user.team_id = team.id").
|
||||||
|
Where(builder.Eq{
|
||||||
|
"team_user.org_id": orgID,
|
||||||
|
"team_user.uid": userID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// TeamsWithAccessToRepo returns all teams that have given access level to the repository.
|
// TeamsWithAccessToRepo returns all teams that have given access level to the repository.
|
||||||
func (org *Organization) TeamsWithAccessToRepo(ctx context.Context, repoID int64, mode perm.AccessMode) ([]*Team, error) {
|
func (org *Organization) TeamsWithAccessToRepo(ctx context.Context, repoID int64, mode perm.AccessMode) ([]*Team, error) {
|
||||||
return GetTeamsWithAccessToRepo(ctx, org.ID, repoID, mode)
|
return GetTeamsWithAccessToRepo(ctx, org.ID, repoID, mode)
|
||||||
|
163
models/organization/org_list.go
Normal file
163
models/organization/org_list.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package organization
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrgList []*Organization
|
||||||
|
|
||||||
|
func (orgs OrgList) LoadTeams(ctx context.Context) (map[int64]TeamList, error) {
|
||||||
|
if len(orgs) == 0 {
|
||||||
|
return map[int64]TeamList{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
orgIDs := make([]int64, len(orgs))
|
||||||
|
for i, org := range orgs {
|
||||||
|
orgIDs[i] = org.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
teams, err := GetTeamsByOrgIDs(ctx, orgIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
teamMap := make(map[int64]TeamList, len(orgs))
|
||||||
|
for _, team := range teams {
|
||||||
|
teamMap[team.OrgID] = append(teamMap[team.OrgID], team)
|
||||||
|
}
|
||||||
|
|
||||||
|
return teamMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchOrganizationsOptions options to filter organizations
|
||||||
|
type SearchOrganizationsOptions struct {
|
||||||
|
db.ListOptions
|
||||||
|
All bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOrgOptions finds orgs options
|
||||||
|
type FindOrgOptions struct {
|
||||||
|
db.ListOptions
|
||||||
|
UserID int64
|
||||||
|
IncludePrivate bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
|
||||||
|
cond := builder.Eq{"uid": userID}
|
||||||
|
if !includePrivate {
|
||||||
|
cond["is_public"] = true
|
||||||
|
}
|
||||||
|
return builder.Select("org_id").From("org_user").Where(cond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts FindOrgOptions) ToConds() builder.Cond {
|
||||||
|
var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization}
|
||||||
|
if opts.UserID > 0 {
|
||||||
|
cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate)))
|
||||||
|
}
|
||||||
|
if !opts.IncludePrivate {
|
||||||
|
cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})
|
||||||
|
}
|
||||||
|
return cond
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts FindOrgOptions) ToOrders() string {
|
||||||
|
return "`user`.lower_name ASC"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID
|
||||||
|
// are allowed to create repos.
|
||||||
|
func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) {
|
||||||
|
orgs := make([]*Organization, 0, 10)
|
||||||
|
|
||||||
|
return orgs, db.GetEngine(ctx).Where(builder.In("id", builder.Select("`user`.id").From("`user`").
|
||||||
|
Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id").
|
||||||
|
Join("INNER", "`team`", "`team`.id = `team_user`.team_id").
|
||||||
|
Where(builder.Eq{"`team_user`.uid": userID}).
|
||||||
|
And(builder.Eq{"`team`.authorize": perm.AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})))).
|
||||||
|
Asc("`user`.name").
|
||||||
|
Find(&orgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinimalOrg represents a simple organization with only the needed columns
|
||||||
|
type MinimalOrg = Organization
|
||||||
|
|
||||||
|
// GetUserOrgsList returns all organizations the given user has access to
|
||||||
|
func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, error) {
|
||||||
|
schema, err := db.TableInfo(new(user_model.User))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outputCols := []string{
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"full_name",
|
||||||
|
"visibility",
|
||||||
|
"avatar",
|
||||||
|
"avatar_email",
|
||||||
|
"use_custom_avatar",
|
||||||
|
}
|
||||||
|
|
||||||
|
selectColumns := &strings.Builder{}
|
||||||
|
for i, col := range outputCols {
|
||||||
|
fmt.Fprintf(selectColumns, "`%s`.%s", schema.Name, col)
|
||||||
|
if i < len(outputCols)-1 {
|
||||||
|
selectColumns.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
columnsStr := selectColumns.String()
|
||||||
|
|
||||||
|
var orgs []*MinimalOrg
|
||||||
|
if err := db.GetEngine(ctx).Select(columnsStr).
|
||||||
|
Table("user").
|
||||||
|
Where(builder.In("`user`.`id`", queryUserOrgIDs(user.ID, true))).
|
||||||
|
Find(&orgs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type orgCount struct {
|
||||||
|
OrgID int64
|
||||||
|
RepoCount int
|
||||||
|
}
|
||||||
|
var orgCounts []orgCount
|
||||||
|
if err := db.GetEngine(ctx).
|
||||||
|
Select("owner_id AS org_id, COUNT(DISTINCT(repository.id)) as repo_count").
|
||||||
|
Table("repository").
|
||||||
|
Join("INNER", "org_user", "owner_id = org_user.org_id").
|
||||||
|
Where("org_user.uid = ?", user.ID).
|
||||||
|
And(builder.Or(
|
||||||
|
builder.Eq{"repository.is_private": false},
|
||||||
|
builder.In("repository.id", builder.Select("repo_id").From("team_repo").
|
||||||
|
InnerJoin("team_user", "team_user.team_id = team_repo.team_id").
|
||||||
|
Where(builder.Eq{"team_user.uid": user.ID})),
|
||||||
|
builder.In("repository.id", builder.Select("repo_id").From("collaboration").
|
||||||
|
Where(builder.Eq{"user_id": user.ID})),
|
||||||
|
)).
|
||||||
|
GroupBy("owner_id").Find(&orgCounts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
orgCountMap := make(map[int64]int, len(orgCounts))
|
||||||
|
for _, orgCount := range orgCounts {
|
||||||
|
orgCountMap[orgCount.OrgID] = orgCount.RepoCount
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, org := range orgs {
|
||||||
|
org.NumRepos = orgCountMap[org.ID]
|
||||||
|
}
|
||||||
|
|
||||||
|
return orgs, nil
|
||||||
|
}
|
73
models/organization/org_list_test.go
Normal file
73
models/organization/org_list_test.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package organization_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCountOrganizations(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludePrivate: true})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expected, cnt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindOrgs(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||||
|
UserID: 4,
|
||||||
|
IncludePrivate: true,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.Len(t, orgs, 1) {
|
||||||
|
assert.EqualValues(t, 3, orgs[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
orgs, err = db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||||
|
UserID: 4,
|
||||||
|
IncludePrivate: false,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, orgs, 0)
|
||||||
|
|
||||||
|
total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||||
|
UserID: 4,
|
||||||
|
IncludePrivate: true,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserOrgsList(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.Len(t, orgs, 1) {
|
||||||
|
assert.EqualValues(t, 3, orgs[0].ID)
|
||||||
|
// repo_id: 3 is in the team, 32 is public, 5 is private with no team
|
||||||
|
assert.EqualValues(t, 2, orgs[0].NumRepos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadOrgListTeams(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, orgs, 1)
|
||||||
|
teamsMap, err := organization.OrgList(orgs).LoadTeams(db.DefaultContext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, teamsMap, 1)
|
||||||
|
assert.Len(t, teamsMap[3], 5)
|
||||||
|
}
|
@ -4,6 +4,8 @@
|
|||||||
package organization_test
|
package organization_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
@ -103,7 +105,7 @@ func TestUser_GetTeams(t *testing.T) {
|
|||||||
func TestUser_GetMembers(t *testing.T) {
|
func TestUser_GetMembers(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||||
members, _, err := org.GetMembers(db.DefaultContext)
|
members, _, err := org.GetMembers(db.DefaultContext, &user_model.User{IsAdmin: true})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
if assert.Len(t, members, 3) {
|
if assert.Len(t, members, 3) {
|
||||||
assert.Equal(t, int64(2), members[0].ID)
|
assert.Equal(t, int64(2), members[0].ID)
|
||||||
@ -127,15 +129,6 @@ func TestGetOrgByName(t *testing.T) {
|
|||||||
assert.True(t, organization.IsErrOrgNotExist(err))
|
assert.True(t, organization.IsErrOrgNotExist(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCountOrganizations(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludePrivate: true})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, expected, cnt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsOrganizationOwner(t *testing.T) {
|
func TestIsOrganizationOwner(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
test := func(orgID, userID int64, expected bool) {
|
test := func(orgID, userID int64, expected bool) {
|
||||||
@ -180,67 +173,114 @@ func TestIsPublicMembership(t *testing.T) {
|
|||||||
test(unittest.NonexistentID, unittest.NonexistentID, false)
|
test(unittest.NonexistentID, unittest.NonexistentID, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindOrgs(t *testing.T) {
|
func TestRestrictedUserOrgMembers(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{
|
||||||
UserID: 4,
|
ID: 29,
|
||||||
IncludePrivate: true,
|
IsRestricted: true,
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
if !assert.True(t, restrictedUser.IsRestricted) {
|
||||||
if assert.Len(t, orgs, 1) {
|
return // ensure fixtures return restricted user
|
||||||
assert.EqualValues(t, 3, orgs[0].ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
orgs, err = db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
testCases := []struct {
|
||||||
UserID: 4,
|
name string
|
||||||
IncludePrivate: false,
|
opts *organization.FindOrgMembersOpts
|
||||||
})
|
expectedUIDs []int64
|
||||||
assert.NoError(t, err)
|
}{
|
||||||
assert.Len(t, orgs, 0)
|
{
|
||||||
|
name: "restricted user sees public members and teammates",
|
||||||
|
opts: &organization.FindOrgMembersOpts{
|
||||||
|
OrgID: 17, // org17 where user29 is in team9
|
||||||
|
Doer: restrictedUser,
|
||||||
|
IsDoerMember: true,
|
||||||
|
},
|
||||||
|
expectedUIDs: []int64{2, 15, 20, 29}, // Public members (2) + teammates in team9 (15, 20, 29)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "restricted user sees only public members when not member",
|
||||||
|
opts: &organization.FindOrgMembersOpts{
|
||||||
|
OrgID: 3, // org3 where user29 is not a member
|
||||||
|
Doer: restrictedUser,
|
||||||
|
},
|
||||||
|
expectedUIDs: []int64{2, 28}, // Only public members
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non logged in only shows public members",
|
||||||
|
opts: &organization.FindOrgMembersOpts{
|
||||||
|
OrgID: 3,
|
||||||
|
},
|
||||||
|
expectedUIDs: []int64{2, 28}, // Only public members
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non restricted user sees all members",
|
||||||
|
opts: &organization.FindOrgMembersOpts{
|
||||||
|
OrgID: 17,
|
||||||
|
Doer: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}),
|
||||||
|
IsDoerMember: true,
|
||||||
|
},
|
||||||
|
expectedUIDs: []int64{2, 15, 18, 20, 29}, // All members
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
for _, tc := range testCases {
|
||||||
UserID: 4,
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
IncludePrivate: true,
|
count, err := organization.CountOrgMembers(db.DefaultContext, tc.opts)
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 1, total)
|
assert.EqualValues(t, len(tc.expectedUIDs), count)
|
||||||
|
|
||||||
|
members, err := organization.GetOrgUsersByOrgID(db.DefaultContext, tc.opts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
memberUIDs := make([]int64, 0, len(members))
|
||||||
|
for _, member := range members {
|
||||||
|
memberUIDs = append(memberUIDs, member.UID)
|
||||||
|
}
|
||||||
|
slices.Sort(memberUIDs)
|
||||||
|
assert.EqualValues(t, tc.expectedUIDs, memberUIDs)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetOrgUsersByOrgID(t *testing.T) {
|
func TestGetOrgUsersByOrgID(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
orgUsers, err := organization.GetOrgUsersByOrgID(db.DefaultContext, &organization.FindOrgMembersOpts{
|
opts := &organization.FindOrgMembersOpts{
|
||||||
ListOptions: db.ListOptions{},
|
Doer: &user_model.User{IsAdmin: true},
|
||||||
OrgID: 3,
|
OrgID: 3,
|
||||||
PublicOnly: false,
|
}
|
||||||
})
|
assert.False(t, opts.PublicOnly())
|
||||||
|
orgUsers, err := organization.GetOrgUsersByOrgID(db.DefaultContext, opts)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
if assert.Len(t, orgUsers, 3) {
|
sort.Slice(orgUsers, func(i, j int) bool {
|
||||||
assert.Equal(t, organization.OrgUser{
|
return orgUsers[i].ID < orgUsers[j].ID
|
||||||
ID: orgUsers[0].ID,
|
})
|
||||||
|
assert.EqualValues(t, []*organization.OrgUser{{
|
||||||
|
ID: 1,
|
||||||
OrgID: 3,
|
OrgID: 3,
|
||||||
UID: 2,
|
UID: 2,
|
||||||
IsPublic: true,
|
IsPublic: true,
|
||||||
}, *orgUsers[0])
|
}, {
|
||||||
assert.Equal(t, organization.OrgUser{
|
ID: 2,
|
||||||
ID: orgUsers[1].ID,
|
|
||||||
OrgID: 3,
|
OrgID: 3,
|
||||||
UID: 4,
|
UID: 4,
|
||||||
IsPublic: false,
|
IsPublic: false,
|
||||||
}, *orgUsers[1])
|
}, {
|
||||||
assert.Equal(t, organization.OrgUser{
|
ID: 9,
|
||||||
ID: orgUsers[2].ID,
|
|
||||||
OrgID: 3,
|
OrgID: 3,
|
||||||
UID: 28,
|
UID: 28,
|
||||||
IsPublic: true,
|
IsPublic: true,
|
||||||
}, *orgUsers[2])
|
}}, orgUsers)
|
||||||
}
|
|
||||||
|
opts = &organization.FindOrgMembersOpts{OrgID: 3}
|
||||||
|
assert.True(t, opts.PublicOnly())
|
||||||
|
orgUsers, err = organization.GetOrgUsersByOrgID(db.DefaultContext, opts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, orgUsers, 2)
|
||||||
|
|
||||||
orgUsers, err = organization.GetOrgUsersByOrgID(db.DefaultContext, &organization.FindOrgMembersOpts{
|
orgUsers, err = organization.GetOrgUsersByOrgID(db.DefaultContext, &organization.FindOrgMembersOpts{
|
||||||
ListOptions: db.ListOptions{},
|
ListOptions: db.ListOptions{},
|
||||||
OrgID: unittest.NonexistentID,
|
OrgID: unittest.NonexistentID,
|
||||||
PublicOnly: false,
|
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, orgUsers, 0)
|
assert.Len(t, orgUsers, 0)
|
||||||
|
@ -94,7 +94,7 @@ func TestUserListIsPublicMember(t *testing.T) {
|
|||||||
func testUserListIsPublicMember(t *testing.T, orgID int64, expected map[int64]bool) {
|
func testUserListIsPublicMember(t *testing.T, orgID int64, expected map[int64]bool) {
|
||||||
org, err := organization.GetOrgByID(db.DefaultContext, orgID)
|
org, err := organization.GetOrgByID(db.DefaultContext, orgID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
_, membersIsPublic, err := org.GetMembers(db.DefaultContext)
|
_, membersIsPublic, err := org.GetMembers(db.DefaultContext, &user_model.User{IsAdmin: true})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expected, membersIsPublic)
|
assert.Equal(t, expected, membersIsPublic)
|
||||||
}
|
}
|
||||||
@ -121,7 +121,7 @@ func TestUserListIsUserOrgOwner(t *testing.T) {
|
|||||||
func testUserListIsUserOrgOwner(t *testing.T, orgID int64, expected map[int64]bool) {
|
func testUserListIsUserOrgOwner(t *testing.T, orgID int64, expected map[int64]bool) {
|
||||||
org, err := organization.GetOrgByID(db.DefaultContext, orgID)
|
org, err := organization.GetOrgByID(db.DefaultContext, orgID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
members, _, err := org.GetMembers(db.DefaultContext)
|
members, _, err := org.GetMembers(db.DefaultContext, &user_model.User{IsAdmin: true})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expected, organization.IsUserOrgOwner(db.DefaultContext, members, orgID))
|
assert.Equal(t, expected, organization.IsUserOrgOwner(db.DefaultContext, members, orgID))
|
||||||
}
|
}
|
||||||
|
@ -126,3 +126,8 @@ func GetUserRepoTeams(ctx context.Context, orgID, userID, repoID int64) (teams T
|
|||||||
And("team_repo.repo_id=?", repoID).
|
And("team_repo.repo_id=?", repoID).
|
||||||
Find(&teams)
|
Find(&teams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetTeamsByOrgIDs(ctx context.Context, orgIDs []int64) (TeamList, error) {
|
||||||
|
teams := make([]*Team, 0, 10)
|
||||||
|
return teams, db.GetEngine(ctx).Where(builder.In("org_id", orgIDs)).Find(&teams)
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
@ -83,3 +84,16 @@ func GetTeamsWithAccessToRepo(ctx context.Context, orgID, repoID int64, mode per
|
|||||||
OrderBy("name").
|
OrderBy("name").
|
||||||
Find(&teams)
|
Find(&teams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTeamsWithAccessToRepoUnit returns all teams in an organization that have given access level to the repository special unit.
|
||||||
|
func GetTeamsWithAccessToRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type) ([]*Team, error) {
|
||||||
|
teams := make([]*Team, 0, 5)
|
||||||
|
return teams, db.GetEngine(ctx).Where("team_unit.access_mode >= ?", mode).
|
||||||
|
Join("INNER", "team_repo", "team_repo.team_id = team.id").
|
||||||
|
Join("INNER", "team_unit", "team_unit.team_id = team.id").
|
||||||
|
And("team_repo.org_id = ?", orgID).
|
||||||
|
And("team_repo.repo_id = ?", repoID).
|
||||||
|
And("team_unit.type = ?", unitType).
|
||||||
|
OrderBy("name").
|
||||||
|
Find(&teams)
|
||||||
|
}
|
||||||
|
31
models/organization/team_repo_test.go
Normal file
31
models/organization/team_repo_test.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package organization_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
"code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetTeamsWithAccessToRepoUnit(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
org41 := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 41})
|
||||||
|
repo61 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 61})
|
||||||
|
|
||||||
|
teams, err := organization.GetTeamsWithAccessToRepoUnit(db.DefaultContext, org41.ID, repo61.ID, perm.AccessModeRead, unit.TypePullRequests)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.Len(t, teams, 2) {
|
||||||
|
assert.EqualValues(t, 21, teams[0].ID)
|
||||||
|
assert.EqualValues(t, 22, teams[1].ID)
|
||||||
|
}
|
||||||
|
}
|
@ -197,3 +197,8 @@ func TestUsersInTeamsCount(t *testing.T) {
|
|||||||
test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) // userid 2,4
|
test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) // userid 2,4
|
||||||
test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5
|
test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsUsableTeamName(t *testing.T) {
|
||||||
|
assert.NoError(t, organization.IsUsableTeamName("usable"))
|
||||||
|
assert.True(t, db.IsErrNameReserved(organization.IsUsableTeamName("new")))
|
||||||
|
}
|
||||||
|
@ -60,3 +60,6 @@ func ParseAccessMode(permission string, allowed ...AccessMode) AccessMode {
|
|||||||
}
|
}
|
||||||
return util.Iif(slices.Contains(allowed, m), m, AccessModeNone)
|
return util.Iif(slices.Contains(allowed, m), m, AccessModeNone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrInvalidAccessMode is returned when an invalid access mode is used
|
||||||
|
var ErrInvalidAccessMode = util.NewInvalidArgumentErrorf("Invalid access mode")
|
||||||
|
@ -242,6 +242,7 @@ func GetSearchOrderByBySortType(sortType string) db.SearchOrderBy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewProject creates a new Project
|
// NewProject creates a new Project
|
||||||
|
// The title will be cut off at 255 characters if it's longer than 255 characters.
|
||||||
func NewProject(ctx context.Context, p *Project) error {
|
func NewProject(ctx context.Context, p *Project) error {
|
||||||
if !IsTemplateTypeValid(p.TemplateType) {
|
if !IsTemplateTypeValid(p.TemplateType) {
|
||||||
p.TemplateType = TemplateTypeNone
|
p.TemplateType = TemplateTypeNone
|
||||||
@ -255,6 +256,8 @@ func NewProject(ctx context.Context, p *Project) error {
|
|||||||
return util.NewInvalidArgumentErrorf("project type is not valid")
|
return util.NewInvalidArgumentErrorf("project type is not valid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.Title, _ = util.SplitStringAtByteN(p.Title, 255)
|
||||||
|
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
if err := db.Insert(ctx, p); err != nil {
|
if err := db.Insert(ctx, p); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -308,6 +311,7 @@ func UpdateProject(ctx context.Context, p *Project) error {
|
|||||||
p.CardType = CardTypeTextOnly
|
p.CardType = CardTypeTextOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.Title, _ = util.SplitStringAtByteN(p.Title, 255)
|
||||||
_, err := db.GetEngine(ctx).ID(p.ID).Cols(
|
_, err := db.GetEngine(ctx).ID(p.ID).Cols(
|
||||||
"title",
|
"title",
|
||||||
"description",
|
"description",
|
||||||
|
53
models/renderhelper/commit_checker.go
Normal file
53
models/renderhelper/commit_checker.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package renderhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type commitChecker struct {
|
||||||
|
ctx context.Context
|
||||||
|
commitCache map[string]bool
|
||||||
|
gitRepoFacade gitrepo.Repository
|
||||||
|
|
||||||
|
gitRepo *git.Repository
|
||||||
|
gitRepoCloser io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommitChecker(ctx context.Context, gitRepo gitrepo.Repository) *commitChecker {
|
||||||
|
return &commitChecker{ctx: ctx, commitCache: make(map[string]bool), gitRepoFacade: gitRepo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commitChecker) Close() error {
|
||||||
|
if c != nil && c.gitRepoCloser != nil {
|
||||||
|
return c.gitRepoCloser.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commitChecker) IsCommitIDExisting(commitID string) bool {
|
||||||
|
exist, inCache := c.commitCache[commitID]
|
||||||
|
if inCache {
|
||||||
|
return exist
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.gitRepo == nil {
|
||||||
|
r, closer, err := gitrepo.RepositoryFromContextOrOpen(c.ctx, c.gitRepoFacade)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("unable to open repository: %s Error: %v", gitrepo.RepoGitURL(c.gitRepoFacade), err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c.gitRepo, c.gitRepoCloser = r, closer
|
||||||
|
}
|
||||||
|
|
||||||
|
exist = c.gitRepo.IsReferenceExist(commitID) // Don't use IsObjectExist since it doesn't support short hashs with gogit edition.
|
||||||
|
c.commitCache[commitID] = exist
|
||||||
|
return exist
|
||||||
|
}
|
27
models/renderhelper/main_test.go
Normal file
27
models/renderhelper/main_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package renderhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
unittest.MainTest(m, &unittest.TestOptions{
|
||||||
|
FixtureFiles: []string{"repository.yml", "user.yml"},
|
||||||
|
SetUp: func() error {
|
||||||
|
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
||||||
|
markup.Init(&markup.RenderHelperFuncs{
|
||||||
|
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
||||||
|
return username == "user2"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
73
models/renderhelper/repo_comment.go
Normal file
73
models/renderhelper/repo_comment.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package renderhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RepoComment struct {
|
||||||
|
ctx *markup.RenderContext
|
||||||
|
opts RepoCommentOptions
|
||||||
|
|
||||||
|
commitChecker *commitChecker
|
||||||
|
repoLink string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepoComment) CleanUp() {
|
||||||
|
_ = r.commitChecker.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepoComment) IsCommitIDExisting(commitID string) bool {
|
||||||
|
return r.commitChecker.IsCommitIDExisting(commitID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepoComment) ResolveLink(link string, likeType markup.LinkType) (finalLink string) {
|
||||||
|
switch likeType {
|
||||||
|
case markup.LinkTypeApp:
|
||||||
|
finalLink = r.ctx.ResolveLinkApp(link)
|
||||||
|
default:
|
||||||
|
finalLink = r.ctx.ResolveLinkRelative(r.repoLink, r.opts.CurrentRefPath, link)
|
||||||
|
}
|
||||||
|
return finalLink
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ markup.RenderHelper = (*RepoComment)(nil)
|
||||||
|
|
||||||
|
type RepoCommentOptions struct {
|
||||||
|
DeprecatedRepoName string // it is only a patch for the non-standard "markup" api
|
||||||
|
DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api
|
||||||
|
CurrentRefPath string // eg: "branch/main" or "commit/11223344"
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRenderContextRepoComment(ctx context.Context, repo *repo_model.Repository, opts ...RepoCommentOptions) *markup.RenderContext {
|
||||||
|
helper := &RepoComment{
|
||||||
|
repoLink: repo.Link(),
|
||||||
|
opts: util.OptionalArg(opts),
|
||||||
|
}
|
||||||
|
rctx := markup.NewRenderContext(ctx)
|
||||||
|
helper.ctx = rctx
|
||||||
|
if repo != nil {
|
||||||
|
helper.repoLink = repo.Link()
|
||||||
|
helper.commitChecker = newCommitChecker(ctx, repo)
|
||||||
|
rctx = rctx.WithMetas(repo.ComposeMetas(ctx))
|
||||||
|
} else {
|
||||||
|
// this is almost dead code, only to pass the incorrect tests
|
||||||
|
helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName)
|
||||||
|
rctx = rctx.WithMetas(map[string]string{
|
||||||
|
"user": helper.opts.DeprecatedOwnerName,
|
||||||
|
"repo": helper.opts.DeprecatedRepoName,
|
||||||
|
|
||||||
|
"markdownLineBreakStyle": "comment",
|
||||||
|
"markupAllowShortIssuePattern": "true",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
rctx = rctx.WithHelper(helper)
|
||||||
|
return rctx
|
||||||
|
}
|
76
models/renderhelper/repo_comment_test.go
Normal file
76
models/renderhelper/repo_comment_test.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package renderhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepoComment(t *testing.T) {
|
||||||
|
unittest.PrepareTestEnv(t)
|
||||||
|
|
||||||
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
|
||||||
|
t.Run("AutoLink", func(t *testing.T) {
|
||||||
|
rctx := NewRenderContextRepoComment(context.Background(), repo1).WithMarkupType(markdown.MarkupName)
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
65f1bf27bc3bf70f64657658635e66094edbcb4d
|
||||||
|
#1
|
||||||
|
@user2
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t,
|
||||||
|
`<p><a href="/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d" rel="nofollow"><code>65f1bf27bc</code></a><br/>
|
||||||
|
<a href="/user2/repo1/issues/1" class="ref-issue" rel="nofollow">#1</a><br/>
|
||||||
|
<a href="/user2" rel="nofollow">@user2</a></p>
|
||||||
|
`, rendered)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("AbsoluteAndRelative", func(t *testing.T) {
|
||||||
|
rctx := NewRenderContextRepoComment(context.Background(), repo1).WithMarkupType(markdown.MarkupName)
|
||||||
|
|
||||||
|
// It is Gitea's old behavior, the relative path is resolved to the repo path
|
||||||
|
// It is different from GitHub, GitHub resolves relative links to current page's path
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
[/test](/test)
|
||||||
|
[./test](./test)
|
||||||
|

|
||||||
|

|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t,
|
||||||
|
`<p><a href="/user2/repo1/test" rel="nofollow">/test</a><br/>
|
||||||
|
<a href="/user2/repo1/test" rel="nofollow">./test</a><br/>
|
||||||
|
<a href="/user2/repo1/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/image" alt="/image"/></a><br/>
|
||||||
|
<a href="/user2/repo1/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/image" alt="./image"/></a></p>
|
||||||
|
`, rendered)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithCurrentRefPath", func(t *testing.T) {
|
||||||
|
rctx := NewRenderContextRepoComment(context.Background(), repo1, RepoCommentOptions{CurrentRefPath: "/commit/1234"}).
|
||||||
|
WithMarkupType(markdown.MarkupName)
|
||||||
|
|
||||||
|
// the ref path is only used to render commit message: a commit message is rendered at the commit page with its commit ID path
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
[/test](/test)
|
||||||
|
[./test](./test)
|
||||||
|

|
||||||
|

|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, `<p><a href="/user2/repo1/test" rel="nofollow">/test</a><br/>
|
||||||
|
<a href="/user2/repo1/commit/1234/test" rel="nofollow">./test</a><br/>
|
||||||
|
<a href="/user2/repo1/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/image" alt="/image"/></a><br/>
|
||||||
|
<a href="/user2/repo1/commit/1234/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/commit/1234/image" alt="./image"/></a></p>
|
||||||
|
`, rendered)
|
||||||
|
})
|
||||||
|
}
|
77
models/renderhelper/repo_file.go
Normal file
77
models/renderhelper/repo_file.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package renderhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RepoFile struct {
|
||||||
|
ctx *markup.RenderContext
|
||||||
|
opts RepoFileOptions
|
||||||
|
|
||||||
|
commitChecker *commitChecker
|
||||||
|
repoLink string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepoFile) CleanUp() {
|
||||||
|
_ = r.commitChecker.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepoFile) IsCommitIDExisting(commitID string) bool {
|
||||||
|
return r.commitChecker.IsCommitIDExisting(commitID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepoFile) ResolveLink(link string, likeType markup.LinkType) string {
|
||||||
|
finalLink := link
|
||||||
|
switch likeType {
|
||||||
|
case markup.LinkTypeApp:
|
||||||
|
finalLink = r.ctx.ResolveLinkApp(link)
|
||||||
|
case markup.LinkTypeDefault:
|
||||||
|
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "src", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link)
|
||||||
|
case markup.LinkTypeRaw:
|
||||||
|
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "raw", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link)
|
||||||
|
case markup.LinkTypeMedia:
|
||||||
|
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "media", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link)
|
||||||
|
}
|
||||||
|
return finalLink
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ markup.RenderHelper = (*RepoFile)(nil)
|
||||||
|
|
||||||
|
type RepoFileOptions struct {
|
||||||
|
DeprecatedRepoName string // it is only a patch for the non-standard "markup" api
|
||||||
|
DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api
|
||||||
|
|
||||||
|
CurrentRefPath string // eg: "branch/main"
|
||||||
|
CurrentTreePath string // eg: "path/to/file" in the repo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRenderContextRepoFile(ctx context.Context, repo *repo_model.Repository, opts ...RepoFileOptions) *markup.RenderContext {
|
||||||
|
helper := &RepoFile{opts: util.OptionalArg(opts)}
|
||||||
|
rctx := markup.NewRenderContext(ctx)
|
||||||
|
helper.ctx = rctx
|
||||||
|
if repo != nil {
|
||||||
|
helper.repoLink = repo.Link()
|
||||||
|
helper.commitChecker = newCommitChecker(ctx, repo)
|
||||||
|
rctx = rctx.WithMetas(repo.ComposeDocumentMetas(ctx))
|
||||||
|
} else {
|
||||||
|
// this is almost dead code, only to pass the incorrect tests
|
||||||
|
helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName)
|
||||||
|
rctx = rctx.WithMetas(map[string]string{
|
||||||
|
"user": helper.opts.DeprecatedOwnerName,
|
||||||
|
"repo": helper.opts.DeprecatedRepoName,
|
||||||
|
|
||||||
|
"markdownLineBreakStyle": "document",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
rctx = rctx.WithHelper(helper)
|
||||||
|
return rctx
|
||||||
|
}
|
83
models/renderhelper/repo_file_test.go
Normal file
83
models/renderhelper/repo_file_test.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package renderhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepoFile(t *testing.T) {
|
||||||
|
unittest.PrepareTestEnv(t)
|
||||||
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
|
||||||
|
t.Run("AutoLink", func(t *testing.T) {
|
||||||
|
rctx := NewRenderContextRepoFile(context.Background(), repo1).WithMarkupType(markdown.MarkupName)
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
65f1bf27bc3bf70f64657658635e66094edbcb4d
|
||||||
|
#1
|
||||||
|
@user2
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t,
|
||||||
|
`<p><a href="/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d" rel="nofollow"><code>65f1bf27bc</code></a>
|
||||||
|
#1
|
||||||
|
<a href="/user2" rel="nofollow">@user2</a></p>
|
||||||
|
`, rendered)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("AbsoluteAndRelative", func(t *testing.T) {
|
||||||
|
rctx := NewRenderContextRepoFile(context.Background(), repo1, RepoFileOptions{CurrentRefPath: "branch/main"}).
|
||||||
|
WithMarkupType(markdown.MarkupName)
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
[/test](/test)
|
||||||
|
[./test](./test)
|
||||||
|

|
||||||
|

|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t,
|
||||||
|
`<p><a href="/user2/repo1/src/branch/main/test" rel="nofollow">/test</a>
|
||||||
|
<a href="/user2/repo1/src/branch/main/test" rel="nofollow">./test</a>
|
||||||
|
<a href="/user2/repo1/media/branch/main/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/media/branch/main/image" alt="/image"/></a>
|
||||||
|
<a href="/user2/repo1/media/branch/main/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/media/branch/main/image" alt="./image"/></a></p>
|
||||||
|
`, rendered)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithCurrentRefPath", func(t *testing.T) {
|
||||||
|
rctx := NewRenderContextRepoFile(context.Background(), repo1, RepoFileOptions{CurrentRefPath: "/commit/1234"}).
|
||||||
|
WithMarkupType(markdown.MarkupName)
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
[/test](/test)
|
||||||
|

|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, `<p><a href="/user2/repo1/src/commit/1234/test" rel="nofollow">/test</a>
|
||||||
|
<a href="/user2/repo1/media/commit/1234/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/media/commit/1234/image" alt="/image"/></a></p>
|
||||||
|
`, rendered)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WithCurrentRefPathByTag", func(t *testing.T) {
|
||||||
|
rctx := NewRenderContextRepoFile(context.Background(), repo1, RepoFileOptions{
|
||||||
|
CurrentRefPath: "/commit/1234",
|
||||||
|
CurrentTreePath: "my-dir",
|
||||||
|
}).
|
||||||
|
WithMarkupType(markdown.MarkupName)
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
<img src="LINK">
|
||||||
|
<video src="LINK">
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, `<a href="/user2/repo1/media/commit/1234/my-dir/LINK" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/media/commit/1234/my-dir/LINK"/></a>
|
||||||
|
<video src="/user2/repo1/media/commit/1234/my-dir/LINK">
|
||||||
|
</video>`, rendered)
|
||||||
|
})
|
||||||
|
}
|
80
models/renderhelper/repo_wiki.go
Normal file
80
models/renderhelper/repo_wiki.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package renderhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RepoWiki struct {
|
||||||
|
ctx *markup.RenderContext
|
||||||
|
opts RepoWikiOptions
|
||||||
|
|
||||||
|
commitChecker *commitChecker
|
||||||
|
repoLink string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepoWiki) CleanUp() {
|
||||||
|
_ = r.commitChecker.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepoWiki) IsCommitIDExisting(commitID string) bool {
|
||||||
|
return r.commitChecker.IsCommitIDExisting(commitID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepoWiki) ResolveLink(link string, likeType markup.LinkType) string {
|
||||||
|
finalLink := link
|
||||||
|
switch likeType {
|
||||||
|
case markup.LinkTypeApp:
|
||||||
|
finalLink = r.ctx.ResolveLinkApp(link)
|
||||||
|
case markup.LinkTypeDefault:
|
||||||
|
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "wiki", r.opts.currentRefPath), r.opts.currentTreePath, link)
|
||||||
|
case markup.LinkTypeMedia:
|
||||||
|
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "wiki/raw", r.opts.currentRefPath), r.opts.currentTreePath, link)
|
||||||
|
case markup.LinkTypeRaw: // wiki doesn't use it
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalLink
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ markup.RenderHelper = (*RepoWiki)(nil)
|
||||||
|
|
||||||
|
type RepoWikiOptions struct {
|
||||||
|
DeprecatedRepoName string // it is only a patch for the non-standard "markup" api
|
||||||
|
DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api
|
||||||
|
|
||||||
|
// these options are not used at the moment because Wiki doesn't support sub-path, nor branch
|
||||||
|
currentRefPath string // eg: "branch/main"
|
||||||
|
currentTreePath string // eg: "path/to/file" in the repo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRenderContextRepoWiki(ctx context.Context, repo *repo_model.Repository, opts ...RepoWikiOptions) *markup.RenderContext {
|
||||||
|
helper := &RepoWiki{opts: util.OptionalArg(opts)}
|
||||||
|
rctx := markup.NewRenderContext(ctx).WithMarkupType(markdown.MarkupName)
|
||||||
|
if repo != nil {
|
||||||
|
helper.repoLink = repo.Link()
|
||||||
|
helper.commitChecker = newCommitChecker(ctx, repo)
|
||||||
|
rctx = rctx.WithMetas(repo.ComposeWikiMetas(ctx))
|
||||||
|
} else {
|
||||||
|
// this is almost dead code, only to pass the incorrect tests
|
||||||
|
helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName)
|
||||||
|
rctx = rctx.WithMetas(map[string]string{
|
||||||
|
"user": helper.opts.DeprecatedOwnerName,
|
||||||
|
"repo": helper.opts.DeprecatedRepoName,
|
||||||
|
|
||||||
|
"markdownLineBreakStyle": "document",
|
||||||
|
"markupAllowShortIssuePattern": "true",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
rctx = rctx.WithHelper(helper)
|
||||||
|
helper.ctx = rctx
|
||||||
|
return rctx
|
||||||
|
}
|
65
models/renderhelper/repo_wiki_test.go
Normal file
65
models/renderhelper/repo_wiki_test.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package renderhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepoWiki(t *testing.T) {
|
||||||
|
unittest.PrepareTestEnv(t)
|
||||||
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
|
||||||
|
t.Run("AutoLink", func(t *testing.T) {
|
||||||
|
rctx := NewRenderContextRepoWiki(context.Background(), repo1).WithMarkupType(markdown.MarkupName)
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
65f1bf27bc3bf70f64657658635e66094edbcb4d
|
||||||
|
#1
|
||||||
|
@user2
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t,
|
||||||
|
`<p><a href="/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d" rel="nofollow"><code>65f1bf27bc</code></a>
|
||||||
|
<a href="/user2/repo1/issues/1" class="ref-issue" rel="nofollow">#1</a>
|
||||||
|
<a href="/user2" rel="nofollow">@user2</a></p>
|
||||||
|
`, rendered)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("AbsoluteAndRelative", func(t *testing.T) {
|
||||||
|
rctx := NewRenderContextRepoWiki(context.Background(), repo1).WithMarkupType(markdown.MarkupName)
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
[/test](/test)
|
||||||
|
[./test](./test)
|
||||||
|

|
||||||
|

|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t,
|
||||||
|
`<p><a href="/user2/repo1/wiki/test" rel="nofollow">/test</a>
|
||||||
|
<a href="/user2/repo1/wiki/test" rel="nofollow">./test</a>
|
||||||
|
<a href="/user2/repo1/wiki/raw/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/wiki/raw/image" alt="/image"/></a>
|
||||||
|
<a href="/user2/repo1/wiki/raw/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/wiki/raw/image" alt="./image"/></a></p>
|
||||||
|
`, rendered)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PathInTag", func(t *testing.T) {
|
||||||
|
rctx := NewRenderContextRepoWiki(context.Background(), repo1).WithMarkupType(markdown.MarkupName)
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
<img src="LINK">
|
||||||
|
<video src="LINK">
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, `<a href="/user2/repo1/wiki/raw/LINK" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/wiki/raw/LINK"/></a>
|
||||||
|
<video src="/user2/repo1/wiki/raw/LINK">
|
||||||
|
</video>`, rendered)
|
||||||
|
})
|
||||||
|
}
|
29
models/renderhelper/simple_document.go
Normal file
29
models/renderhelper/simple_document.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package renderhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SimpleDocument struct {
|
||||||
|
*markup.SimpleRenderHelper
|
||||||
|
ctx *markup.RenderContext
|
||||||
|
baseLink string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SimpleDocument) ResolveLink(link string, likeType markup.LinkType) string {
|
||||||
|
return r.ctx.ResolveLinkRelative(r.baseLink, "", link)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ markup.RenderHelper = (*SimpleDocument)(nil)
|
||||||
|
|
||||||
|
func NewRenderContextSimpleDocument(ctx context.Context, baseLink string) *markup.RenderContext {
|
||||||
|
helper := &SimpleDocument{baseLink: baseLink}
|
||||||
|
rctx := markup.NewRenderContext(ctx).WithHelper(helper).WithMetas(markup.ComposeSimpleDocumentMetas())
|
||||||
|
helper.ctx = rctx
|
||||||
|
return rctx
|
||||||
|
}
|
40
models/renderhelper/simple_document_test.go
Normal file
40
models/renderhelper/simple_document_test.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package renderhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSimpleDocument(t *testing.T) {
|
||||||
|
unittest.PrepareTestEnv(t)
|
||||||
|
rctx := NewRenderContextSimpleDocument(context.Background(), "/base").WithMarkupType(markdown.MarkupName)
|
||||||
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
65f1bf27bc3bf70f64657658635e66094edbcb4d
|
||||||
|
#1
|
||||||
|
@user2
|
||||||
|
|
||||||
|
[/test](/test)
|
||||||
|
[./test](./test)
|
||||||
|

|
||||||
|

|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t,
|
||||||
|
`<p>65f1bf27bc3bf70f64657658635e66094edbcb4d
|
||||||
|
#1
|
||||||
|
<a href="/base/user2" rel="nofollow">@user2</a></p>
|
||||||
|
<p><a href="/base/test" rel="nofollow">/test</a>
|
||||||
|
<a href="/base/test" rel="nofollow">./test</a>
|
||||||
|
<a href="/base/image" target="_blank" rel="nofollow noopener"><img src="/base/image" alt="/image"/></a>
|
||||||
|
<a href="/base/image" target="_blank" rel="nofollow noopener"><img src="/base/image" alt="./image"/></a></p>
|
||||||
|
`, rendered)
|
||||||
|
}
|
@ -54,21 +54,6 @@ func GetUserFork(ctx context.Context, repoID, userID int64) (*Repository, error)
|
|||||||
return &forkedRepo, nil
|
return &forkedRepo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetForks returns all the forks of the repository
|
|
||||||
func GetForks(ctx context.Context, repo *Repository, listOptions db.ListOptions) ([]*Repository, error) {
|
|
||||||
sess := db.GetEngine(ctx)
|
|
||||||
|
|
||||||
var forks []*Repository
|
|
||||||
if listOptions.Page == 0 {
|
|
||||||
forks = make([]*Repository, 0, repo.NumForks)
|
|
||||||
} else {
|
|
||||||
forks = make([]*Repository, 0, listOptions.PageSize)
|
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
return forks, sess.Find(&forks, &Repository{ForkID: repo.ID})
|
|
||||||
}
|
|
||||||
|
|
||||||
// IncrementRepoForkNum increment repository fork number
|
// IncrementRepoForkNum increment repository fork number
|
||||||
func IncrementRepoForkNum(ctx context.Context, repoID int64) error {
|
func IncrementRepoForkNum(ctx context.Context, repoID int64) error {
|
||||||
_, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID)
|
_, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID)
|
||||||
|
@ -9,15 +9,13 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrPushMirrorNotExist mirror does not exist error
|
|
||||||
var ErrPushMirrorNotExist = util.NewNotExistErrorf("PushMirror does not exist")
|
|
||||||
|
|
||||||
// PushMirror represents mirror information of a repository.
|
// PushMirror represents mirror information of a repository.
|
||||||
type PushMirror struct {
|
type PushMirror struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
@ -96,26 +94,46 @@ func DeletePushMirrors(ctx context.Context, opts PushMirrorOptions) error {
|
|||||||
return util.NewInvalidArgumentErrorf("repoID required and must be set")
|
return util.NewInvalidArgumentErrorf("repoID required and must be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type findPushMirrorOptions struct {
|
||||||
|
db.ListOptions
|
||||||
|
RepoID int64
|
||||||
|
SyncOnCommit optional.Option[bool]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts findPushMirrorOptions) ToConds() builder.Cond {
|
||||||
|
cond := builder.NewCond()
|
||||||
|
if opts.RepoID > 0 {
|
||||||
|
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||||
|
}
|
||||||
|
if opts.SyncOnCommit.Has() {
|
||||||
|
cond = cond.And(builder.Eq{"sync_on_commit": opts.SyncOnCommit.Value()})
|
||||||
|
}
|
||||||
|
return cond
|
||||||
|
}
|
||||||
|
|
||||||
// GetPushMirrorsByRepoID returns push-mirror information of a repository.
|
// GetPushMirrorsByRepoID returns push-mirror information of a repository.
|
||||||
func GetPushMirrorsByRepoID(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*PushMirror, int64, error) {
|
func GetPushMirrorsByRepoID(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*PushMirror, int64, error) {
|
||||||
sess := db.GetEngine(ctx).Where("repo_id = ?", repoID)
|
return db.FindAndCount[PushMirror](ctx, findPushMirrorOptions{
|
||||||
if listOptions.Page != 0 {
|
ListOptions: listOptions,
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
RepoID: repoID,
|
||||||
mirrors := make([]*PushMirror, 0, listOptions.PageSize)
|
})
|
||||||
count, err := sess.FindAndCount(&mirrors)
|
}
|
||||||
return mirrors, count, err
|
|
||||||
|
func GetPushMirrorByIDAndRepoID(ctx context.Context, id, repoID int64) (*PushMirror, bool, error) {
|
||||||
|
var pushMirror PushMirror
|
||||||
|
has, err := db.GetEngine(ctx).Where("id = ?", id).And("repo_id = ?", repoID).Get(&pushMirror)
|
||||||
|
if !has || err != nil {
|
||||||
|
return nil, has, err
|
||||||
}
|
}
|
||||||
mirrors := make([]*PushMirror, 0, 10)
|
return &pushMirror, true, nil
|
||||||
count, err := sess.FindAndCount(&mirrors)
|
|
||||||
return mirrors, count, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPushMirrorsSyncedOnCommit returns push-mirrors for this repo that should be updated by new commits
|
// GetPushMirrorsSyncedOnCommit returns push-mirrors for this repo that should be updated by new commits
|
||||||
func GetPushMirrorsSyncedOnCommit(ctx context.Context, repoID int64) ([]*PushMirror, error) {
|
func GetPushMirrorsSyncedOnCommit(ctx context.Context, repoID int64) ([]*PushMirror, error) {
|
||||||
mirrors := make([]*PushMirror, 0, 10)
|
return db.Find[PushMirror](ctx, findPushMirrorOptions{
|
||||||
return mirrors, db.GetEngine(ctx).
|
RepoID: repoID,
|
||||||
Where("repo_id = ? AND sync_on_commit = ?", repoID, true).
|
SyncOnCommit: optional.Some(true),
|
||||||
Find(&mirrors)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushMirrorsIterate iterates all push-mirror repositories.
|
// PushMirrorsIterate iterates all push-mirror repositories.
|
||||||
|
@ -156,6 +156,7 @@ func IsReleaseExist(ctx context.Context, repoID int64, tagName string) (bool, er
|
|||||||
|
|
||||||
// UpdateRelease updates all columns of a release
|
// UpdateRelease updates all columns of a release
|
||||||
func UpdateRelease(ctx context.Context, rel *Release) error {
|
func UpdateRelease(ctx context.Context, rel *Release) error {
|
||||||
|
rel.Title, _ = util.SplitStringAtByteN(rel.Title, 255)
|
||||||
_, err := db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel)
|
_, err := db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -165,8 +166,8 @@ type Repository struct {
|
|||||||
|
|
||||||
Status RepositoryStatus `xorm:"NOT NULL DEFAULT 0"`
|
Status RepositoryStatus `xorm:"NOT NULL DEFAULT 0"`
|
||||||
|
|
||||||
RenderingMetas map[string]string `xorm:"-"`
|
commonRenderingMetas map[string]string `xorm:"-"`
|
||||||
DocumentRenderingMetas map[string]string `xorm:"-"`
|
|
||||||
Units []*RepoUnit `xorm:"-"`
|
Units []*RepoUnit `xorm:"-"`
|
||||||
PrimaryLanguage *LanguageStat `xorm:"-"`
|
PrimaryLanguage *LanguageStat `xorm:"-"`
|
||||||
|
|
||||||
@ -473,13 +474,11 @@ func (repo *Repository) MustOwner(ctx context.Context) *user_model.User {
|
|||||||
return repo.Owner
|
return repo.Owner
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers.
|
func (repo *Repository) composeCommonMetas(ctx context.Context) map[string]string {
|
||||||
func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string {
|
if len(repo.commonRenderingMetas) == 0 {
|
||||||
if len(repo.RenderingMetas) == 0 {
|
|
||||||
metas := map[string]string{
|
metas := map[string]string{
|
||||||
"user": repo.OwnerName,
|
"user": repo.OwnerName,
|
||||||
"repo": repo.Name,
|
"repo": repo.Name,
|
||||||
"mode": "comment",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unit, err := repo.GetUnit(ctx, unit.TypeExternalTracker)
|
unit, err := repo.GetUnit(ctx, unit.TypeExternalTracker)
|
||||||
@ -509,22 +508,34 @@ func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string {
|
|||||||
metas["org"] = strings.ToLower(repo.OwnerName)
|
metas["org"] = strings.ToLower(repo.OwnerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.RenderingMetas = metas
|
repo.commonRenderingMetas = metas
|
||||||
}
|
}
|
||||||
return repo.RenderingMetas
|
return repo.commonRenderingMetas
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComposeDocumentMetas composes a map of metas for properly rendering documents
|
// ComposeMetas composes a map of metas for properly rendering comments or comment-like contents (commit message)
|
||||||
|
func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string {
|
||||||
|
metas := maps.Clone(repo.composeCommonMetas(ctx))
|
||||||
|
metas["markdownLineBreakStyle"] = "comment"
|
||||||
|
metas["markupAllowShortIssuePattern"] = "true"
|
||||||
|
return metas
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComposeWikiMetas composes a map of metas for properly rendering wikis
|
||||||
|
func (repo *Repository) ComposeWikiMetas(ctx context.Context) map[string]string {
|
||||||
|
// does wiki need the "teams" and "org" from common metas?
|
||||||
|
metas := maps.Clone(repo.composeCommonMetas(ctx))
|
||||||
|
metas["markdownLineBreakStyle"] = "document"
|
||||||
|
metas["markupAllowShortIssuePattern"] = "true"
|
||||||
|
return metas
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComposeDocumentMetas composes a map of metas for properly rendering documents (repo files)
|
||||||
func (repo *Repository) ComposeDocumentMetas(ctx context.Context) map[string]string {
|
func (repo *Repository) ComposeDocumentMetas(ctx context.Context) map[string]string {
|
||||||
if len(repo.DocumentRenderingMetas) == 0 {
|
// does document(file) need the "teams" and "org" from common metas?
|
||||||
metas := map[string]string{}
|
metas := maps.Clone(repo.composeCommonMetas(ctx))
|
||||||
for k, v := range repo.ComposeMetas(ctx) {
|
metas["markdownLineBreakStyle"] = "document"
|
||||||
metas[k] = v
|
return metas
|
||||||
}
|
|
||||||
metas["mode"] = "document"
|
|
||||||
repo.DocumentRenderingMetas = metas
|
|
||||||
}
|
|
||||||
return repo.DocumentRenderingMetas
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBaseRepo populates repo.BaseRepo for a fork repository and
|
// GetBaseRepo populates repo.BaseRepo for a fork repository and
|
||||||
@ -606,10 +617,7 @@ func (repo *Repository) CanEnableEditor() bool {
|
|||||||
|
|
||||||
// DescriptionHTML does special handles to description and return HTML string.
|
// DescriptionHTML does special handles to description and return HTML string.
|
||||||
func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
|
func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
|
||||||
desc, err := markup.RenderDescriptionHTML(&markup.RenderContext{
|
desc, err := markup.RenderDescriptionHTML(markup.NewRenderContext(ctx), repo.Description)
|
||||||
Ctx: ctx,
|
|
||||||
// Don't use Metas to speedup requests
|
|
||||||
}, repo.Description)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
|
log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
|
||||||
return template.HTML(markup.SanitizeDescription(repo.Description))
|
return template.HTML(markup.SanitizeDescription(repo.Description))
|
||||||
|
@ -98,8 +98,7 @@ func (repos RepositoryList) IDs() []int64 {
|
|||||||
return repoIDs
|
return repoIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAttributes loads the attributes for the given RepositoryList
|
func (repos RepositoryList) LoadOwners(ctx context.Context) error {
|
||||||
func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
|
|
||||||
if len(repos) == 0 {
|
if len(repos) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -107,10 +106,6 @@ func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
|
|||||||
userIDs := container.FilterSlice(repos, func(repo *Repository) (int64, bool) {
|
userIDs := container.FilterSlice(repos, func(repo *Repository) (int64, bool) {
|
||||||
return repo.OwnerID, true
|
return repo.OwnerID, true
|
||||||
})
|
})
|
||||||
repoIDs := make([]int64, len(repos))
|
|
||||||
for i := range repos {
|
|
||||||
repoIDs[i] = repos[i].ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load owners.
|
// Load owners.
|
||||||
users := make(map[int64]*user_model.User, len(userIDs))
|
users := make(map[int64]*user_model.User, len(userIDs))
|
||||||
@ -123,12 +118,19 @@ func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
|
|||||||
for i := range repos {
|
for i := range repos {
|
||||||
repos[i].Owner = users[repos[i].OwnerID]
|
repos[i].Owner = users[repos[i].OwnerID]
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repos RepositoryList) LoadLanguageStats(ctx context.Context) error {
|
||||||
|
if len(repos) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Load primary language.
|
// Load primary language.
|
||||||
stats := make(LanguageStatList, 0, len(repos))
|
stats := make(LanguageStatList, 0, len(repos))
|
||||||
if err := db.GetEngine(ctx).
|
if err := db.GetEngine(ctx).
|
||||||
Where("`is_primary` = ? AND `language` != ?", true, "other").
|
Where("`is_primary` = ? AND `language` != ?", true, "other").
|
||||||
In("`repo_id`", repoIDs).
|
In("`repo_id`", repos.IDs()).
|
||||||
Find(&stats); err != nil {
|
Find(&stats); err != nil {
|
||||||
return fmt.Errorf("find primary languages: %w", err)
|
return fmt.Errorf("find primary languages: %w", err)
|
||||||
}
|
}
|
||||||
@ -141,10 +143,18 @@ func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadAttributes loads the attributes for the given RepositoryList
|
||||||
|
func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
|
||||||
|
if err := repos.LoadOwners(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return repos.LoadLanguageStats(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// SearchRepoOptions holds the search options
|
// SearchRepoOptions holds the search options
|
||||||
type SearchRepoOptions struct {
|
type SearchRepoOptions struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package repo_test
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@ -20,18 +19,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
countRepospts = repo_model.CountRepositoryOptions{OwnerID: 10}
|
countRepospts = CountRepositoryOptions{OwnerID: 10}
|
||||||
countReposptsPublic = repo_model.CountRepositoryOptions{OwnerID: 10, Private: optional.Some(false)}
|
countReposptsPublic = CountRepositoryOptions{OwnerID: 10, Private: optional.Some(false)}
|
||||||
countReposptsPrivate = repo_model.CountRepositoryOptions{OwnerID: 10, Private: optional.Some(true)}
|
countReposptsPrivate = CountRepositoryOptions{OwnerID: 10, Private: optional.Some(true)}
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetRepositoryCount(t *testing.T) {
|
func TestGetRepositoryCount(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
ctx := db.DefaultContext
|
ctx := db.DefaultContext
|
||||||
count, err1 := repo_model.CountRepositories(ctx, countRepospts)
|
count, err1 := CountRepositories(ctx, countRepospts)
|
||||||
privateCount, err2 := repo_model.CountRepositories(ctx, countReposptsPrivate)
|
privateCount, err2 := CountRepositories(ctx, countReposptsPrivate)
|
||||||
publicCount, err3 := repo_model.CountRepositories(ctx, countReposptsPublic)
|
publicCount, err3 := CountRepositories(ctx, countReposptsPublic)
|
||||||
assert.NoError(t, err1)
|
assert.NoError(t, err1)
|
||||||
assert.NoError(t, err2)
|
assert.NoError(t, err2)
|
||||||
assert.NoError(t, err3)
|
assert.NoError(t, err3)
|
||||||
@ -42,7 +41,7 @@ func TestGetRepositoryCount(t *testing.T) {
|
|||||||
func TestGetPublicRepositoryCount(t *testing.T) {
|
func TestGetPublicRepositoryCount(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
count, err := repo_model.CountRepositories(db.DefaultContext, countReposptsPublic)
|
count, err := CountRepositories(db.DefaultContext, countReposptsPublic)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(1), count)
|
assert.Equal(t, int64(1), count)
|
||||||
}
|
}
|
||||||
@ -50,14 +49,14 @@ func TestGetPublicRepositoryCount(t *testing.T) {
|
|||||||
func TestGetPrivateRepositoryCount(t *testing.T) {
|
func TestGetPrivateRepositoryCount(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
count, err := repo_model.CountRepositories(db.DefaultContext, countReposptsPrivate)
|
count, err := CountRepositories(db.DefaultContext, countReposptsPrivate)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(2), count)
|
assert.Equal(t, int64(2), count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepoAPIURL(t *testing.T) {
|
func TestRepoAPIURL(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
|
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 10})
|
||||||
|
|
||||||
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user12/repo10", repo.APIURL())
|
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user12/repo10", repo.APIURL())
|
||||||
}
|
}
|
||||||
@ -65,22 +64,22 @@ func TestRepoAPIURL(t *testing.T) {
|
|||||||
func TestWatchRepo(t *testing.T) {
|
func TestWatchRepo(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 3})
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, user, repo, true))
|
assert.NoError(t, WatchRepo(db.DefaultContext, user, repo, true))
|
||||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Watch{RepoID: repo.ID, UserID: user.ID})
|
unittest.AssertExistsAndLoadBean(t, &Watch{RepoID: repo.ID, UserID: user.ID})
|
||||||
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
|
unittest.CheckConsistencyFor(t, &Repository{ID: repo.ID})
|
||||||
|
|
||||||
assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, user, repo, false))
|
assert.NoError(t, WatchRepo(db.DefaultContext, user, repo, false))
|
||||||
unittest.AssertNotExistsBean(t, &repo_model.Watch{RepoID: repo.ID, UserID: user.ID})
|
unittest.AssertNotExistsBean(t, &Watch{RepoID: repo.ID, UserID: user.ID})
|
||||||
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
|
unittest.CheckConsistencyFor(t, &Repository{ID: repo.ID})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMetas(t *testing.T) {
|
func TestMetas(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
repo := &repo_model.Repository{Name: "testRepo"}
|
repo := &Repository{Name: "testRepo"}
|
||||||
repo.Owner = &user_model.User{Name: "testOwner"}
|
repo.Owner = &user_model.User{Name: "testOwner"}
|
||||||
repo.OwnerName = repo.Owner.Name
|
repo.OwnerName = repo.Owner.Name
|
||||||
|
|
||||||
@ -90,16 +89,16 @@ func TestMetas(t *testing.T) {
|
|||||||
assert.Equal(t, "testRepo", metas["repo"])
|
assert.Equal(t, "testRepo", metas["repo"])
|
||||||
assert.Equal(t, "testOwner", metas["user"])
|
assert.Equal(t, "testOwner", metas["user"])
|
||||||
|
|
||||||
externalTracker := repo_model.RepoUnit{
|
externalTracker := RepoUnit{
|
||||||
Type: unit.TypeExternalTracker,
|
Type: unit.TypeExternalTracker,
|
||||||
Config: &repo_model.ExternalTrackerConfig{
|
Config: &ExternalTrackerConfig{
|
||||||
ExternalTrackerFormat: "https://someurl.com/{user}/{repo}/{issue}",
|
ExternalTrackerFormat: "https://someurl.com/{user}/{repo}/{issue}",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testSuccess := func(expectedStyle string) {
|
testSuccess := func(expectedStyle string) {
|
||||||
repo.Units = []*repo_model.RepoUnit{&externalTracker}
|
repo.Units = []*RepoUnit{&externalTracker}
|
||||||
repo.RenderingMetas = nil
|
repo.commonRenderingMetas = nil
|
||||||
metas := repo.ComposeMetas(db.DefaultContext)
|
metas := repo.ComposeMetas(db.DefaultContext)
|
||||||
assert.Equal(t, expectedStyle, metas["style"])
|
assert.Equal(t, expectedStyle, metas["style"])
|
||||||
assert.Equal(t, "testRepo", metas["repo"])
|
assert.Equal(t, "testRepo", metas["repo"])
|
||||||
@ -118,7 +117,7 @@ func TestMetas(t *testing.T) {
|
|||||||
externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleRegexp
|
externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleRegexp
|
||||||
testSuccess(markup.IssueNameStyleRegexp)
|
testSuccess(markup.IssueNameStyleRegexp)
|
||||||
|
|
||||||
repo, err := repo_model.GetRepositoryByID(db.DefaultContext, 3)
|
repo, err := GetRepositoryByID(db.DefaultContext, 3)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
metas = repo.ComposeMetas(db.DefaultContext)
|
metas = repo.ComposeMetas(db.DefaultContext)
|
||||||
@ -132,7 +131,7 @@ func TestGetRepositoryByURL(t *testing.T) {
|
|||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
t.Run("InvalidPath", func(t *testing.T) {
|
t.Run("InvalidPath", func(t *testing.T) {
|
||||||
repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, "something")
|
repo, err := GetRepositoryByURL(db.DefaultContext, "something")
|
||||||
|
|
||||||
assert.Nil(t, repo)
|
assert.Nil(t, repo)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
@ -140,7 +139,7 @@ func TestGetRepositoryByURL(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("ValidHttpURL", func(t *testing.T) {
|
t.Run("ValidHttpURL", func(t *testing.T) {
|
||||||
test := func(t *testing.T, url string) {
|
test := func(t *testing.T, url string) {
|
||||||
repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, url)
|
repo, err := GetRepositoryByURL(db.DefaultContext, url)
|
||||||
|
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -155,7 +154,7 @@ func TestGetRepositoryByURL(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("ValidGitSshURL", func(t *testing.T) {
|
t.Run("ValidGitSshURL", func(t *testing.T) {
|
||||||
test := func(t *testing.T, url string) {
|
test := func(t *testing.T, url string) {
|
||||||
repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, url)
|
repo, err := GetRepositoryByURL(db.DefaultContext, url)
|
||||||
|
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -173,7 +172,7 @@ func TestGetRepositoryByURL(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("ValidImplicitSshURL", func(t *testing.T) {
|
t.Run("ValidImplicitSshURL", func(t *testing.T) {
|
||||||
test := func(t *testing.T, url string) {
|
test := func(t *testing.T, url string) {
|
||||||
repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, url)
|
repo, err := GetRepositoryByURL(db.DefaultContext, url)
|
||||||
|
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -200,21 +199,21 @@ func TestComposeSSHCloneURL(t *testing.T) {
|
|||||||
setting.SSH.Domain = "domain"
|
setting.SSH.Domain = "domain"
|
||||||
setting.SSH.Port = 22
|
setting.SSH.Port = 22
|
||||||
setting.Repository.UseCompatSSHURI = false
|
setting.Repository.UseCompatSSHURI = false
|
||||||
assert.Equal(t, "git@domain:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
assert.Equal(t, "git@domain:user/repo.git", ComposeSSHCloneURL("user", "repo"))
|
||||||
setting.Repository.UseCompatSSHURI = true
|
setting.Repository.UseCompatSSHURI = true
|
||||||
assert.Equal(t, "ssh://git@domain/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
assert.Equal(t, "ssh://git@domain/user/repo.git", ComposeSSHCloneURL("user", "repo"))
|
||||||
// test SSH_DOMAIN while use non-standard SSH port
|
// test SSH_DOMAIN while use non-standard SSH port
|
||||||
setting.SSH.Port = 123
|
setting.SSH.Port = 123
|
||||||
setting.Repository.UseCompatSSHURI = false
|
setting.Repository.UseCompatSSHURI = false
|
||||||
assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
assert.Equal(t, "ssh://git@domain:123/user/repo.git", ComposeSSHCloneURL("user", "repo"))
|
||||||
setting.Repository.UseCompatSSHURI = true
|
setting.Repository.UseCompatSSHURI = true
|
||||||
assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
assert.Equal(t, "ssh://git@domain:123/user/repo.git", ComposeSSHCloneURL("user", "repo"))
|
||||||
|
|
||||||
// test IPv6 SSH_DOMAIN
|
// test IPv6 SSH_DOMAIN
|
||||||
setting.Repository.UseCompatSSHURI = false
|
setting.Repository.UseCompatSSHURI = false
|
||||||
setting.SSH.Domain = "::1"
|
setting.SSH.Domain = "::1"
|
||||||
setting.SSH.Port = 22
|
setting.SSH.Port = 22
|
||||||
assert.Equal(t, "git@[::1]:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
assert.Equal(t, "git@[::1]:user/repo.git", ComposeSSHCloneURL("user", "repo"))
|
||||||
setting.SSH.Port = 123
|
setting.SSH.Port = 123
|
||||||
assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", ComposeSSHCloneURL("user", "repo"))
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ var OrderByMap = map[string]map[string]db.SearchOrderBy{
|
|||||||
var OrderByFlatMap = map[string]db.SearchOrderBy{
|
var OrderByFlatMap = map[string]db.SearchOrderBy{
|
||||||
"newest": OrderByMap["desc"]["created"],
|
"newest": OrderByMap["desc"]["created"],
|
||||||
"oldest": OrderByMap["asc"]["created"],
|
"oldest": OrderByMap["asc"]["created"],
|
||||||
|
"recentupdate": OrderByMap["desc"]["updated"],
|
||||||
"leastupdate": OrderByMap["asc"]["updated"],
|
"leastupdate": OrderByMap["asc"]["updated"],
|
||||||
"reversealphabetically": OrderByMap["desc"]["alpha"],
|
"reversealphabetically": OrderByMap["desc"]["alpha"],
|
||||||
"alphabetically": OrderByMap["asc"]["alpha"],
|
"alphabetically": OrderByMap["asc"]["alpha"],
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
"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/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
@ -146,57 +145,6 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
|
|||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetReviewers get all users can be requested to review:
|
|
||||||
// * for private repositories this returns all users that have read access or higher to the repository.
|
|
||||||
// * for public repositories this returns all users that have read access or higher to the repository,
|
|
||||||
// all repo watchers and all organization members.
|
|
||||||
// TODO: may be we should have a busy choice for users to block review request to them.
|
|
||||||
func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64) ([]*user_model.User, error) {
|
|
||||||
// Get the owner of the repository - this often already pre-cached and if so saves complexity for the following queries
|
|
||||||
if err := repo.LoadOwner(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cond := builder.And(builder.Neq{"`user`.id": posterID}).
|
|
||||||
And(builder.Eq{"`user`.is_active": true})
|
|
||||||
|
|
||||||
if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate {
|
|
||||||
// This a private repository:
|
|
||||||
// Anyone who can read the repository is a requestable reviewer
|
|
||||||
|
|
||||||
cond = cond.And(builder.In("`user`.id",
|
|
||||||
builder.Select("user_id").From("access").Where(
|
|
||||||
builder.Eq{"repo_id": repo.ID}.
|
|
||||||
And(builder.Gte{"mode": perm.AccessModeRead}),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
|
|
||||||
if repo.Owner.Type == user_model.UserTypeIndividual && repo.Owner.ID != posterID {
|
|
||||||
// as private *user* repos don't generate an entry in the `access` table,
|
|
||||||
// the owner of a private repo needs to be explicitly added.
|
|
||||||
cond = cond.Or(builder.Eq{"`user`.id": repo.Owner.ID})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// This is a "public" repository:
|
|
||||||
// Any user that has read access, is a watcher or organization member can be requested to review
|
|
||||||
cond = cond.And(builder.And(builder.In("`user`.id",
|
|
||||||
builder.Select("user_id").From("access").
|
|
||||||
Where(builder.Eq{"repo_id": repo.ID}.
|
|
||||||
And(builder.Gte{"mode": perm.AccessModeRead})),
|
|
||||||
).Or(builder.In("`user`.id",
|
|
||||||
builder.Select("user_id").From("watch").
|
|
||||||
Where(builder.Eq{"repo_id": repo.ID}.
|
|
||||||
And(builder.In("mode", WatchModeNormal, WatchModeAuto))),
|
|
||||||
).Or(builder.In("`user`.id",
|
|
||||||
builder.Select("uid").From("org_user").
|
|
||||||
Where(builder.Eq{"org_id": repo.OwnerID}),
|
|
||||||
)))))
|
|
||||||
}
|
|
||||||
|
|
||||||
users := make([]*user_model.User, 0, 8)
|
|
||||||
return users, db.GetEngine(ctx).Where(cond).OrderBy(user_model.GetOrderByName()).Find(&users)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIssuePostersWithSearch returns users with limit of 30 whose username started with prefix that have authored an issue/pull request for the given repository
|
// GetIssuePostersWithSearch returns users with limit of 30 whose username started with prefix that have authored an issue/pull request for the given repository
|
||||||
// If isShowFullName is set to true, also include full name prefix search
|
// If isShowFullName is set to true, also include full name prefix search
|
||||||
func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) {
|
func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) {
|
||||||
|
@ -38,46 +38,3 @@ func TestRepoAssignees(t *testing.T) {
|
|||||||
assert.NotContains(t, []int64{users[0].ID, users[1].ID, users[2].ID}, 15)
|
assert.NotContains(t, []int64{users[0].ID, users[1].ID, users[2].ID}, 15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepoGetReviewers(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
|
|
||||||
// test public repo
|
|
||||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
||||||
|
|
||||||
ctx := db.DefaultContext
|
|
||||||
reviewers, err := repo_model.GetReviewers(ctx, repo1, 2, 2)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
if assert.Len(t, reviewers, 3) {
|
|
||||||
assert.ElementsMatch(t, []int64{1, 4, 11}, []int64{reviewers[0].ID, reviewers[1].ID, reviewers[2].ID})
|
|
||||||
}
|
|
||||||
|
|
||||||
// should include doer if doer is not PR poster.
|
|
||||||
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 2)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, reviewers, 3)
|
|
||||||
|
|
||||||
// should not include PR poster, if PR poster would be otherwise eligible
|
|
||||||
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 4)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, reviewers, 2)
|
|
||||||
|
|
||||||
// test private user repo
|
|
||||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
|
||||||
|
|
||||||
reviewers, err = repo_model.GetReviewers(ctx, repo2, 2, 4)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, reviewers, 1)
|
|
||||||
assert.EqualValues(t, reviewers[0].ID, 2)
|
|
||||||
|
|
||||||
// test private org repo
|
|
||||||
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
|
||||||
|
|
||||||
reviewers, err = repo_model.GetReviewers(ctx, repo3, 2, 1)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, reviewers, 2)
|
|
||||||
|
|
||||||
reviewers, err = repo_model.GetReviewers(ctx, repo3, 2, 2)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, reviewers, 1)
|
|
||||||
}
|
|
||||||
|
@ -80,6 +80,27 @@ var (
|
|||||||
TypePullRequests,
|
TypePullRequests,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultMirrorRepoUnits contains the default unit types for mirrors
|
||||||
|
DefaultMirrorRepoUnits = []Type{
|
||||||
|
TypeCode,
|
||||||
|
TypeIssues,
|
||||||
|
TypeReleases,
|
||||||
|
TypeWiki,
|
||||||
|
TypeProjects,
|
||||||
|
TypePackages,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultTemplateRepoUnits contains the default unit types for templates
|
||||||
|
DefaultTemplateRepoUnits = []Type{
|
||||||
|
TypeCode,
|
||||||
|
TypeIssues,
|
||||||
|
TypePullRequests,
|
||||||
|
TypeReleases,
|
||||||
|
TypeWiki,
|
||||||
|
TypeProjects,
|
||||||
|
TypePackages,
|
||||||
|
}
|
||||||
|
|
||||||
// NotAllowedDefaultRepoUnits contains units that can't be default
|
// NotAllowedDefaultRepoUnits contains units that can't be default
|
||||||
NotAllowedDefaultRepoUnits = []Type{
|
NotAllowedDefaultRepoUnits = []Type{
|
||||||
TypeExternalWiki,
|
TypeExternalWiki,
|
||||||
@ -147,6 +168,7 @@ func LoadUnitConfig() error {
|
|||||||
if len(DefaultRepoUnits) == 0 {
|
if len(DefaultRepoUnits) == 0 {
|
||||||
return errors.New("no default repository units found")
|
return errors.New("no default repository units found")
|
||||||
}
|
}
|
||||||
|
// default fork repo units
|
||||||
setDefaultForkRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultForkRepoUnits...)
|
setDefaultForkRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultForkRepoUnits...)
|
||||||
if len(invalidKeys) > 0 {
|
if len(invalidKeys) > 0 {
|
||||||
log.Warn("Invalid keys in default fork repo units: %s", strings.Join(invalidKeys, ", "))
|
log.Warn("Invalid keys in default fork repo units: %s", strings.Join(invalidKeys, ", "))
|
||||||
@ -155,6 +177,24 @@ func LoadUnitConfig() error {
|
|||||||
if len(DefaultForkRepoUnits) == 0 {
|
if len(DefaultForkRepoUnits) == 0 {
|
||||||
return errors.New("no default fork repository units found")
|
return errors.New("no default fork repository units found")
|
||||||
}
|
}
|
||||||
|
// default mirror repo units
|
||||||
|
setDefaultMirrorRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultMirrorRepoUnits...)
|
||||||
|
if len(invalidKeys) > 0 {
|
||||||
|
log.Warn("Invalid keys in default mirror repo units: %s", strings.Join(invalidKeys, ", "))
|
||||||
|
}
|
||||||
|
DefaultMirrorRepoUnits = validateDefaultRepoUnits(DefaultMirrorRepoUnits, setDefaultMirrorRepoUnits)
|
||||||
|
if len(DefaultMirrorRepoUnits) == 0 {
|
||||||
|
return errors.New("no default mirror repository units found")
|
||||||
|
}
|
||||||
|
// default template repo units
|
||||||
|
setDefaultTemplateRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultTemplateRepoUnits...)
|
||||||
|
if len(invalidKeys) > 0 {
|
||||||
|
log.Warn("Invalid keys in default template repo units: %s", strings.Join(invalidKeys, ", "))
|
||||||
|
}
|
||||||
|
DefaultTemplateRepoUnits = validateDefaultRepoUnits(DefaultTemplateRepoUnits, setDefaultTemplateRepoUnits)
|
||||||
|
if len(DefaultTemplateRepoUnits) == 0 {
|
||||||
|
return errors.New("no default template repository units found")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,10 +4,8 @@
|
|||||||
package unittest
|
package unittest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@ -32,67 +30,73 @@ func Copy(src, dest string) error {
|
|||||||
return os.Symlink(target, dest)
|
return os.Symlink(target, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
sr, err := os.Open(src)
|
return util.CopyFile(src, dest)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer sr.Close()
|
|
||||||
|
|
||||||
dw, err := os.Create(dest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer dw.Close()
|
|
||||||
|
|
||||||
if _, err = io.Copy(dw, sr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set back file information.
|
|
||||||
if err = os.Chtimes(dest, si.ModTime(), si.ModTime()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.Chmod(dest, si.Mode())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyDir copy files recursively from source to target directory.
|
// Sync synchronizes the two files. This is skipped if both files
|
||||||
//
|
// exist and the size, modtime, and mode match.
|
||||||
// The filter accepts a function that process the path info.
|
func Sync(srcPath, destPath string) error {
|
||||||
// and should return true for need to filter.
|
dest, err := os.Stat(destPath)
|
||||||
//
|
if err != nil {
|
||||||
// It returns error when error occurs in underlying functions.
|
if os.IsNotExist(err) {
|
||||||
func CopyDir(srcPath, destPath string, filters ...func(filePath string) bool) error {
|
return Copy(srcPath, destPath)
|
||||||
// Check if target directory exists.
|
}
|
||||||
if _, err := os.Stat(destPath); !errors.Is(err, os.ErrNotExist) {
|
return err
|
||||||
return util.NewAlreadyExistErrorf("file or directory already exists: %s", destPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
src, err := os.Stat(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.Size() == dest.Size() &&
|
||||||
|
src.ModTime() == dest.ModTime() &&
|
||||||
|
src.Mode() == dest.Mode() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return Copy(srcPath, destPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncDirs synchronizes files recursively from source to target directory.
|
||||||
|
// It returns error when error occurs in underlying functions.
|
||||||
|
func SyncDirs(srcPath, destPath string) error {
|
||||||
err := os.MkdirAll(destPath, os.ModePerm)
|
err := os.MkdirAll(destPath, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather directory info.
|
// find and delete all untracked files
|
||||||
infos, err := util.StatDir(srcPath, true)
|
destFiles, err := util.StatDir(destPath, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
for _, destFile := range destFiles {
|
||||||
var filter func(filePath string) bool
|
destFilePath := filepath.Join(destPath, destFile)
|
||||||
if len(filters) > 0 {
|
if _, err = os.Stat(filepath.Join(srcPath, destFile)); err != nil {
|
||||||
filter = filters[0]
|
if os.IsNotExist(err) {
|
||||||
|
// if src file does not exist, remove dest file
|
||||||
|
if err = os.RemoveAll(destFilePath); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, info := range infos {
|
|
||||||
if filter != nil && filter(info) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
curPath := path.Join(destPath, info)
|
|
||||||
if strings.HasSuffix(info, "/") {
|
|
||||||
err = os.MkdirAll(curPath, os.ModePerm)
|
|
||||||
} else {
|
} else {
|
||||||
err = Copy(path.Join(srcPath, info), curPath)
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync src files to dest
|
||||||
|
srcFiles, err := util.StatDir(srcPath, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, srcFile := range srcFiles {
|
||||||
|
destFilePath := filepath.Join(destPath, srcFile)
|
||||||
|
// util.StatDir appends a slash to the directory name
|
||||||
|
if strings.HasSuffix(srcFile, "/") {
|
||||||
|
err = os.MkdirAll(destFilePath, os.ModePerm)
|
||||||
|
} else {
|
||||||
|
err = Sync(filepath.Join(srcPath, srcFile), destFilePath)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -164,35 +164,13 @@ 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 = util.RemoveAll(repoRootPath); err != nil {
|
if err = SyncDirs(filepath.Join(giteaRoot, "tests", "gitea-repositories-meta"), setting.RepoRootPath); err != nil {
|
||||||
fatalTestError("util.RemoveAll: %v\n", err)
|
fatalTestError("util.SyncDirs: %v\n", err)
|
||||||
}
|
|
||||||
if err = CopyDir(filepath.Join(giteaRoot, "tests", "gitea-repositories-meta"), setting.RepoRootPath); err != nil {
|
|
||||||
fatalTestError("util.CopyDir: %v\n", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = git.InitFull(context.Background()); err != nil {
|
if err = git.InitFull(context.Background()); err != nil {
|
||||||
fatalTestError("git.Init: %v\n", err)
|
fatalTestError("git.Init: %v\n", err)
|
||||||
}
|
}
|
||||||
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
|
||||||
if err != nil {
|
|
||||||
fatalTestError("unable to read the new repo root: %v\n", err)
|
|
||||||
}
|
|
||||||
for _, ownerDir := range ownerDirs {
|
|
||||||
if !ownerDir.Type().IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
|
|
||||||
if err != nil {
|
|
||||||
fatalTestError("unable to read the new repo root: %v\n", err)
|
|
||||||
}
|
|
||||||
for _, repoDir := range repoDirs {
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(testOpts) > 0 && testOpts[0].SetUp != nil {
|
if len(testOpts) > 0 && testOpts[0].SetUp != nil {
|
||||||
if err := testOpts[0].SetUp(); err != nil {
|
if err := testOpts[0].SetUp(); err != nil {
|
||||||
@ -255,24 +233,7 @@ func PrepareTestDatabase() error {
|
|||||||
// by tests that use the above MainTest(..) function.
|
// by tests that use the above MainTest(..) function.
|
||||||
func PrepareTestEnv(t testing.TB) {
|
func PrepareTestEnv(t testing.TB) {
|
||||||
assert.NoError(t, PrepareTestDatabase())
|
assert.NoError(t, PrepareTestDatabase())
|
||||||
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
|
|
||||||
metaPath := filepath.Join(giteaRoot, "tests", "gitea-repositories-meta")
|
metaPath := filepath.Join(giteaRoot, "tests", "gitea-repositories-meta")
|
||||||
assert.NoError(t, CopyDir(metaPath, setting.RepoRootPath))
|
assert.NoError(t, SyncDirs(metaPath, setting.RepoRootPath))
|
||||||
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, ownerDir := range ownerDirs {
|
|
||||||
if !ownerDir.Type().IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, repoDir := range repoDirs {
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
base.SetupGiteaRoot() // Makes sure GITEA_ROOT is set
|
base.SetupGiteaRoot() // Makes sure GITEA_ROOT is set
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -64,10 +65,10 @@ func BeanExists(t assert.TestingT, bean any, conditions ...any) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AssertExistsAndLoadBean assert that a bean exists and load it from the test database
|
// AssertExistsAndLoadBean assert that a bean exists and load it from the test database
|
||||||
func AssertExistsAndLoadBean[T any](t assert.TestingT, bean T, conditions ...any) T {
|
func AssertExistsAndLoadBean[T any](t require.TestingT, bean T, conditions ...any) T {
|
||||||
exists, err := LoadBeanIfExists(bean, conditions...)
|
exists, err := LoadBeanIfExists(bean, conditions...)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, exists,
|
require.True(t, exists,
|
||||||
"Expected to find %+v (of type %T, with conditions %+v), but did not",
|
"Expected to find %+v (of type %T, with conditions %+v), but did not",
|
||||||
bean, bean, conditions)
|
bean, bean, conditions)
|
||||||
return bean
|
return bean
|
||||||
|
@ -152,7 +152,7 @@ func SearchUsers(ctx context.Context, opts *SearchUserOptions) (users []*User, _
|
|||||||
|
|
||||||
sessQuery := opts.toSearchQueryBase(ctx).OrderBy(opts.OrderBy.String())
|
sessQuery := opts.toSearchQueryBase(ctx).OrderBy(opts.OrderBy.String())
|
||||||
defer sessQuery.Close()
|
defer sessQuery.Close()
|
||||||
if opts.Page != 0 {
|
if opts.Page > 0 {
|
||||||
sessQuery = db.SetSessionPagination(sessQuery, opts)
|
sessQuery = db.SetSessionPagination(sessQuery, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,19 +48,19 @@ const (
|
|||||||
UserTypeIndividual UserType = iota // Historic reason to make it starts at 0.
|
UserTypeIndividual UserType = iota // Historic reason to make it starts at 0.
|
||||||
|
|
||||||
// UserTypeOrganization defines an organization
|
// UserTypeOrganization defines an organization
|
||||||
UserTypeOrganization
|
UserTypeOrganization // 1
|
||||||
|
|
||||||
// UserTypeUserReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on
|
// UserTypeUserReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on
|
||||||
UserTypeUserReserved
|
UserTypeUserReserved // 2
|
||||||
|
|
||||||
// UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved
|
// UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved
|
||||||
UserTypeOrganizationReserved
|
UserTypeOrganizationReserved // 3
|
||||||
|
|
||||||
// UserTypeBot defines a bot user
|
// UserTypeBot defines a bot user
|
||||||
UserTypeBot
|
UserTypeBot // 4
|
||||||
|
|
||||||
// UserTypeRemoteUser defines a remote user for federated users
|
// UserTypeRemoteUser defines a remote user for federated users
|
||||||
UserTypeRemoteUser
|
UserTypeRemoteUser // 5
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -330,7 +330,7 @@ func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListO
|
|||||||
And("`user`.type=?", UserTypeIndividual).
|
And("`user`.type=?", UserTypeIndividual).
|
||||||
And(isUserVisibleToViewerCond(viewer))
|
And(isUserVisibleToViewerCond(viewer))
|
||||||
|
|
||||||
if listOptions.Page != 0 {
|
if listOptions.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
sess = db.SetSessionPagination(sess, &listOptions)
|
||||||
|
|
||||||
users := make([]*User, 0, listOptions.PageSize)
|
users := make([]*User, 0, listOptions.PageSize)
|
||||||
@ -352,7 +352,7 @@ func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListO
|
|||||||
And("`user`.type IN (?, ?)", UserTypeIndividual, UserTypeOrganization).
|
And("`user`.type IN (?, ?)", UserTypeIndividual, UserTypeOrganization).
|
||||||
And(isUserVisibleToViewerCond(viewer))
|
And(isUserVisibleToViewerCond(viewer))
|
||||||
|
|
||||||
if listOptions.Page != 0 {
|
if listOptions.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
sess = db.SetSessionPagination(sess, &listOptions)
|
||||||
|
|
||||||
users := make([]*User, 0, listOptions.PageSize)
|
users := make([]*User, 0, listOptions.PageSize)
|
||||||
@ -884,7 +884,13 @@ func UpdateUserCols(ctx context.Context, u *User, cols ...string) error {
|
|||||||
|
|
||||||
// GetInactiveUsers gets all inactive users
|
// GetInactiveUsers gets all inactive users
|
||||||
func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) {
|
func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) {
|
||||||
var cond builder.Cond = builder.Eq{"is_active": false}
|
cond := builder.And(
|
||||||
|
builder.Eq{"is_active": false},
|
||||||
|
builder.Or( // only plain user
|
||||||
|
builder.Eq{"`type`": UserTypeIndividual},
|
||||||
|
builder.Eq{"`type`": UserTypeUserReserved},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if olderThan > 0 {
|
if olderThan > 0 {
|
||||||
cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-olderThan).Unix()})
|
cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-olderThan).Unix()})
|
||||||
|
@ -588,3 +588,17 @@ func TestDisabledUserFeatures(t *testing.T) {
|
|||||||
assert.True(t, user_model.IsFeatureDisabledWithLoginType(user, f))
|
assert.True(t, user_model.IsFeatureDisabledWithLoginType(user, f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetInactiveUsers(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
// all inactive users
|
||||||
|
// user1's createdunix is 1730468968
|
||||||
|
users, err := user_model.GetInactiveUsers(db.DefaultContext, 0)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, users, 1)
|
||||||
|
interval := time.Now().Unix() - 1730468968 + 3600*24
|
||||||
|
users, err = user_model.GetInactiveUsers(db.DefaultContext, time.Duration(interval*int64(time.Second)))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, users, 0)
|
||||||
|
}
|
||||||
|
@ -4,13 +4,14 @@
|
|||||||
package webauthn
|
package webauthn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
"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/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
@ -38,40 +39,42 @@ func Init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// User represents an implementation of webauthn.User based on User model
|
// user represents an implementation of webauthn.User based on User model
|
||||||
type User user_model.User
|
type user struct {
|
||||||
|
ctx context.Context
|
||||||
|
User *user_model.User
|
||||||
|
|
||||||
|
defaultAuthFlags protocol.AuthenticatorFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ webauthn.User = (*user)(nil)
|
||||||
|
|
||||||
|
func NewWebAuthnUser(ctx context.Context, u *user_model.User, defaultAuthFlags ...protocol.AuthenticatorFlags) webauthn.User {
|
||||||
|
return &user{ctx: ctx, User: u, defaultAuthFlags: util.OptionalArg(defaultAuthFlags)}
|
||||||
|
}
|
||||||
|
|
||||||
// WebAuthnID implements the webauthn.User interface
|
// WebAuthnID implements the webauthn.User interface
|
||||||
func (u *User) WebAuthnID() []byte {
|
func (u *user) WebAuthnID() []byte {
|
||||||
id := make([]byte, 8)
|
id := make([]byte, 8)
|
||||||
binary.PutVarint(id, u.ID)
|
binary.PutVarint(id, u.User.ID)
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebAuthnName implements the webauthn.User interface
|
// WebAuthnName implements the webauthn.User interface
|
||||||
func (u *User) WebAuthnName() string {
|
func (u *user) WebAuthnName() string {
|
||||||
if u.LoginName == "" {
|
return util.IfZero(u.User.LoginName, u.User.Name)
|
||||||
return u.Name
|
|
||||||
}
|
|
||||||
return u.LoginName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebAuthnDisplayName implements the webauthn.User interface
|
// WebAuthnDisplayName implements the webauthn.User interface
|
||||||
func (u *User) WebAuthnDisplayName() string {
|
func (u *user) WebAuthnDisplayName() string {
|
||||||
return (*user_model.User)(u).DisplayName()
|
return u.User.DisplayName()
|
||||||
}
|
|
||||||
|
|
||||||
// WebAuthnIcon implements the webauthn.User interface
|
|
||||||
func (u *User) WebAuthnIcon() string {
|
|
||||||
return (*user_model.User)(u).AvatarLink(db.DefaultContext)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebAuthnCredentials implements the webauthn.User interface
|
// WebAuthnCredentials implements the webauthn.User interface
|
||||||
func (u *User) WebAuthnCredentials() []webauthn.Credential {
|
func (u *user) WebAuthnCredentials() []webauthn.Credential {
|
||||||
dbCreds, err := auth.GetWebAuthnCredentialsByUID(db.DefaultContext, u.ID)
|
dbCreds, err := auth.GetWebAuthnCredentialsByUID(u.ctx, u.User.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return dbCreds.ToCredentials(u.defaultAuthFlags)
|
||||||
return dbCreds.ToCredentials()
|
|
||||||
}
|
}
|
||||||
|
@ -147,6 +147,9 @@ func StringsToInt64s(strs []string) ([]int64, error) {
|
|||||||
}
|
}
|
||||||
ints := make([]int64, 0, len(strs))
|
ints := make([]int64, 0, len(strs))
|
||||||
for _, s := range strs {
|
for _, s := range strs {
|
||||||
|
if s == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
n, err := strconv.ParseInt(s, 10, 64)
|
n, err := strconv.ParseInt(s, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -152,6 +152,7 @@ func TestStringsToInt64s(t *testing.T) {
|
|||||||
}
|
}
|
||||||
testSuccess(nil, nil)
|
testSuccess(nil, nil)
|
||||||
testSuccess([]string{}, []int64{})
|
testSuccess([]string{}, []int64{})
|
||||||
|
testSuccess([]string{""}, []int64{})
|
||||||
testSuccess([]string{"-1234"}, []int64{-1234})
|
testSuccess([]string{"-1234"}, []int64{-1234})
|
||||||
testSuccess([]string{"1", "4", "16", "64", "256"}, []int64{1, 4, 16, 64, 256})
|
testSuccess([]string{"1", "4", "16", "64", "256"}, []int64{1, 4, 16, 64, 256})
|
||||||
|
|
||||||
|
@ -31,8 +31,8 @@ func (s Set[T]) AddMultiple(values ...T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains determines whether a set contains the specified elements.
|
// Contains determines whether a set contains all these elements.
|
||||||
// Returns true if the set contains the specified element; otherwise, false.
|
// Returns true if the set contains all these elements; otherwise, false.
|
||||||
func (s Set[T]) Contains(values ...T) bool {
|
func (s Set[T]) Contains(values ...T) bool {
|
||||||
ret := true
|
ret := true
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
|
@ -18,7 +18,9 @@ func TestSet(t *testing.T) {
|
|||||||
|
|
||||||
assert.True(t, s.Contains("key1"))
|
assert.True(t, s.Contains("key1"))
|
||||||
assert.True(t, s.Contains("key2"))
|
assert.True(t, s.Contains("key2"))
|
||||||
|
assert.True(t, s.Contains("key1", "key2"))
|
||||||
assert.False(t, s.Contains("key3"))
|
assert.False(t, s.Contains("key3"))
|
||||||
|
assert.False(t, s.Contains("key1", "key3"))
|
||||||
|
|
||||||
assert.True(t, s.Remove("key2"))
|
assert.True(t, s.Remove("key2"))
|
||||||
assert.False(t, s.Contains("key2"))
|
assert.False(t, s.Contains("key2"))
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
stdcsv "encoding/csv"
|
stdcsv "encoding/csv"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ func CreateReaderAndDetermineDelimiter(ctx *markup.RenderContext, rd io.Reader)
|
|||||||
func determineDelimiter(ctx *markup.RenderContext, data []byte) rune {
|
func determineDelimiter(ctx *markup.RenderContext, data []byte) rune {
|
||||||
extension := ".csv"
|
extension := ".csv"
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
extension = strings.ToLower(filepath.Ext(ctx.RelativePath))
|
extension = strings.ToLower(path.Ext(ctx.RenderOptions.RelativePath))
|
||||||
}
|
}
|
||||||
|
|
||||||
var delimiter rune
|
var delimiter rune
|
||||||
|
@ -5,13 +5,13 @@ package csv
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
|
||||||
@ -231,10 +231,7 @@ 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{
|
delimiter := determineDelimiter(markup.NewRenderContext(context.Background()).WithRelativePath(c.filename), []byte(decodeSlashes(t, c.csv)))
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,9 +146,8 @@ func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadBatchLine reads the header line from cat-file --batch
|
// ReadBatchLine reads the header line from cat-file --batch
|
||||||
// We expect:
|
// We expect: <oid> SP <type> SP <size> LF
|
||||||
// <sha> SP <type> SP <size> LF
|
// then leaving the rest of the stream "<contents> LF" to be read
|
||||||
// sha is a hex encoded here
|
|
||||||
func ReadBatchLine(rd *bufio.Reader) (sha []byte, typ string, size int64, err error) {
|
func ReadBatchLine(rd *bufio.Reader) (sha []byte, typ string, size int64, err error) {
|
||||||
typ, err = rd.ReadString('\n')
|
typ, err = rd.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -29,7 +28,7 @@ type Commit struct {
|
|||||||
Signature *CommitSignature
|
Signature *CommitSignature
|
||||||
|
|
||||||
Parents []ObjectID // ID strings
|
Parents []ObjectID // ID strings
|
||||||
submoduleCache *ObjectCache
|
submoduleCache *ObjectCache[*SubModule]
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitSignature represents a git commit signature part.
|
// CommitSignature represents a git commit signature part.
|
||||||
@ -357,69 +356,6 @@ func (c *Commit) GetFileContent(filename string, limit int) (string, error) {
|
|||||||
return string(bytes), nil
|
return string(bytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSubModules get all the sub modules of current revision git tree
|
|
||||||
func (c *Commit) GetSubModules() (*ObjectCache, error) {
|
|
||||||
if c.submoduleCache != nil {
|
|
||||||
return c.submoduleCache, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
entry, err := c.GetTreeEntryByPath(".gitmodules")
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(ErrNotExist); ok {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rd, err := entry.Blob().DataAsync()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer rd.Close()
|
|
||||||
scanner := bufio.NewScanner(rd)
|
|
||||||
c.submoduleCache = newObjectCache()
|
|
||||||
var ismodule bool
|
|
||||||
var path string
|
|
||||||
for scanner.Scan() {
|
|
||||||
if strings.HasPrefix(scanner.Text(), "[submodule") {
|
|
||||||
ismodule = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ismodule {
|
|
||||||
fields := strings.Split(scanner.Text(), "=")
|
|
||||||
k := strings.TrimSpace(fields[0])
|
|
||||||
if k == "path" {
|
|
||||||
path = strings.TrimSpace(fields[1])
|
|
||||||
} else if k == "url" {
|
|
||||||
c.submoduleCache.Set(path, &SubModule{path, strings.TrimSpace(fields[1])})
|
|
||||||
ismodule = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = scanner.Err(); err != nil {
|
|
||||||
return nil, fmt.Errorf("GetSubModules scan: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.submoduleCache, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSubModule get the sub module according entryname
|
|
||||||
func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
|
|
||||||
modules, err := c.GetSubModules()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if modules != nil {
|
|
||||||
module, has := modules.Get(entryname)
|
|
||||||
if has {
|
|
||||||
return module.(*SubModule), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
|
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
|
||||||
func (c *Commit) GetBranchName() (string, error) {
|
func (c *Commit) GetBranchName() (string, error) {
|
||||||
cmd := NewCommand(c.repo.Ctx, "name-rev")
|
cmd := NewCommand(c.repo.Ctx, "name-rev")
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user