mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-31 01:13:39 +02:00
Merge remote-tracking branch 'upstream/main' into limit-repo-size, fixes related to createrepository move from module to service layer (#26927)
This commit is contained in:
+9
-9
@@ -154,7 +154,7 @@ steps:
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
- pull_request
|
||||
|
||||
- name: publish-rootless
|
||||
image: plugins/docker:latest
|
||||
@@ -176,7 +176,7 @@ steps:
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
- pull_request
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
@@ -220,7 +220,7 @@ steps:
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
- pull_request
|
||||
|
||||
- name: publish-rootless
|
||||
image: plugins/docker:latest
|
||||
@@ -241,7 +241,7 @@ steps:
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
- pull_request
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
@@ -289,7 +289,7 @@ steps:
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
- pull_request
|
||||
|
||||
- name: publish-rootless
|
||||
image: plugins/docker:latest
|
||||
@@ -311,7 +311,7 @@ steps:
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
- pull_request
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
@@ -355,7 +355,7 @@ steps:
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
- pull_request
|
||||
|
||||
- name: publish-rootless
|
||||
image: plugins/docker:latest
|
||||
@@ -376,7 +376,7 @@ steps:
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
- pull_request
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
@@ -413,7 +413,7 @@ steps:
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
- "refs/tags/**"
|
||||
paths:
|
||||
exclude:
|
||||
- "docs/**"
|
||||
|
||||
+1
-1
@@ -156,7 +156,7 @@ rules:
|
||||
import/no-restricted-paths: [0]
|
||||
import/no-self-import: [2]
|
||||
import/no-unassigned-import: [0]
|
||||
import/no-unresolved: [2, {commonjs: true, ignore: [\?.+$, ^vitest/]}]
|
||||
import/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$", ^vitest/]}]
|
||||
import/no-unused-modules: [2, {unusedExports: true}]
|
||||
import/no-useless-path-segments: [2, {commonjs: true}]
|
||||
import/no-webpack-loader-syntax: [2]
|
||||
|
||||
@@ -2,90 +2,90 @@ name: Bug Report
|
||||
description: Found something you weren't expecting? Report it here!
|
||||
labels: ["kind/bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
1. Please speak English, this is the language all maintainers can speak and write.
|
||||
2. Please ask questions or configuration/deploy problems on our Discord
|
||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
||||
3. Make sure you are using the latest release and
|
||||
take a moment to check that your issue hasn't been reported before.
|
||||
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.com/help/faq)
|
||||
5. It's really important to provide pertinent details and logs (https://docs.gitea.com/help/support),
|
||||
incomplete details will be handled as an invalid report.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)
|
||||
If you are using a proxy or a CDN (e.g. Cloudflare) in front of Gitea, please disable the proxy/CDN fully and access Gitea directly to confirm the issue still persists without those services.
|
||||
- type: input
|
||||
id: gitea-ver
|
||||
attributes:
|
||||
label: Gitea Version
|
||||
description: Gitea version (or commit reference) of your instance
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: can-reproduce
|
||||
attributes:
|
||||
label: Can you reproduce the bug on the Gitea demo site?
|
||||
description: |
|
||||
If so, please provide a URL in the Description field
|
||||
URL of Gitea demo: https://try.gitea.io
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
It's really important to provide pertinent logs
|
||||
Please read https://docs.gitea.com/administration/logging-config#collecting-logs-for-help
|
||||
In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini
|
||||
- type: input
|
||||
id: logs
|
||||
attributes:
|
||||
label: Log Gist
|
||||
description: Please provide a gist URL of your logs, with any sensitive information (e.g. API keys) removed/hidden
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If this issue involves the Web Interface, please provide one or more screenshots
|
||||
- type: input
|
||||
id: git-ver
|
||||
attributes:
|
||||
label: Git Version
|
||||
description: The version of git running on the server
|
||||
- type: input
|
||||
id: os-ver
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: The operating system you are using to run Gitea
|
||||
- type: textarea
|
||||
id: run-info
|
||||
attributes:
|
||||
label: How are you running Gitea?
|
||||
description: |
|
||||
Please include information on whether you built Gitea yourself, used one of our downloads, are using https://try.gitea.io or are using some other package
|
||||
Please also tell us how you are running Gitea, e.g. if it is being run from docker, a command-line, systemd etc.
|
||||
If you are using a package or systemd tell us what distribution you are using
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: database
|
||||
attributes:
|
||||
label: Database
|
||||
description: What database system are you running?
|
||||
options:
|
||||
- PostgreSQL
|
||||
- MySQL/MariaDB
|
||||
- MSSQL
|
||||
- SQLite
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
1. Please speak English, this is the language all maintainers can speak and write.
|
||||
2. Please ask questions or configuration/deploy problems on our Discord
|
||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
||||
3. Make sure you are using the latest release and
|
||||
take a moment to check that your issue hasn't been reported before.
|
||||
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.com/help/faq)
|
||||
5. It's really important to provide pertinent details and logs (https://docs.gitea.com/help/support),
|
||||
incomplete details will be handled as an invalid report.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)
|
||||
If you are using a proxy or a CDN (e.g. Cloudflare) in front of Gitea, please disable the proxy/CDN fully and access Gitea directly to confirm the issue still persists without those services.
|
||||
- type: input
|
||||
id: gitea-ver
|
||||
attributes:
|
||||
label: Gitea Version
|
||||
description: Gitea version (or commit reference) of your instance
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: can-reproduce
|
||||
attributes:
|
||||
label: Can you reproduce the bug on the Gitea demo site?
|
||||
description: |
|
||||
If so, please provide a URL in the Description field
|
||||
URL of Gitea demo: https://try.gitea.io
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
It's really important to provide pertinent logs
|
||||
Please read https://docs.gitea.com/administration/logging-config#collecting-logs-for-help
|
||||
In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini
|
||||
- type: input
|
||||
id: logs
|
||||
attributes:
|
||||
label: Log Gist
|
||||
description: Please provide a gist URL of your logs, with any sensitive information (e.g. API keys) removed/hidden
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If this issue involves the Web Interface, please provide one or more screenshots
|
||||
- type: input
|
||||
id: git-ver
|
||||
attributes:
|
||||
label: Git Version
|
||||
description: The version of git running on the server
|
||||
- type: input
|
||||
id: os-ver
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: The operating system you are using to run Gitea
|
||||
- type: textarea
|
||||
id: run-info
|
||||
attributes:
|
||||
label: How are you running Gitea?
|
||||
description: |
|
||||
Please include information on whether you built Gitea yourself, used one of our downloads, are using https://try.gitea.io or are using some other package
|
||||
Please also tell us how you are running Gitea, e.g. if it is being run from docker, a command-line, systemd etc.
|
||||
If you are using a package or systemd tell us what distribution you are using
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: database
|
||||
attributes:
|
||||
label: Database
|
||||
description: What database system are you running?
|
||||
options:
|
||||
- PostgreSQL
|
||||
- MySQL/MariaDB
|
||||
- MSSQL
|
||||
- SQLite
|
||||
|
||||
@@ -2,23 +2,23 @@ name: Feature Request
|
||||
description: Got an idea for a feature that Gitea doesn't have currently? Submit your idea here!
|
||||
labels: ["kind/proposal"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
1. Please speak English, this is the language all maintainers can speak and write.
|
||||
2. Please ask questions or configuration/deploy problems on our Discord
|
||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
||||
3. Please take a moment to check that your feature hasn't already been suggested.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Feature Description
|
||||
placeholder: |
|
||||
I think it would be great if Gitea had...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If you can, provide screenshots of an implementation on another site e.g. GitHub
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
1. Please speak English, this is the language all maintainers can speak and write.
|
||||
2. Please ask questions or configuration/deploy problems on our Discord
|
||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
||||
3. Please take a moment to check that your feature hasn't already been suggested.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Feature Description
|
||||
placeholder: |
|
||||
I think it would be great if Gitea had...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If you can, provide screenshots of an implementation on another site e.g. GitHub
|
||||
|
||||
@@ -2,65 +2,65 @@ name: Web Interface Bug Report
|
||||
description: Something doesn't look quite as it should? Report it here!
|
||||
labels: ["kind/bug", "kind/ui"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
1. Please speak English, this is the language all maintainers can speak and write.
|
||||
2. Please ask questions or configuration/deploy problems on our Discord
|
||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
||||
3. Please take a moment to check that your issue doesn't already exist.
|
||||
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.com/help/faq)
|
||||
5. Please give all relevant information below for bug reports, because
|
||||
incomplete details will be handled as an invalid report.
|
||||
6. In particular it's really important to provide pertinent logs. If you are certain that this is a javascript
|
||||
error, show us the javascript console. If the error appears to relate to Gitea the server you must also give us
|
||||
DEBUG level logs. (See https://docs.gitea.com/administration/logging-config#collecting-logs-for-help)
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)
|
||||
If using a proxy or a CDN (e.g. CloudFlare) in front of gitea, please disable the proxy/CDN fully and connect to gitea directly to confirm the issue still persists without those services.
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: Please provide at least 1 screenshot showing the issue.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: gitea-ver
|
||||
attributes:
|
||||
label: Gitea Version
|
||||
description: Gitea version (or commit reference) your instance is running
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: can-reproduce
|
||||
attributes:
|
||||
label: Can you reproduce the bug on the Gitea demo site?
|
||||
description: |
|
||||
If so, please provide a URL in the Description field
|
||||
URL of Gitea demo: https://try.gitea.io
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os-ver
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: The operating system you are using to access Gitea
|
||||
- type: input
|
||||
id: browser-ver
|
||||
attributes:
|
||||
label: Browser Version
|
||||
description: The browser and version that you are using to access Gitea
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
1. Please speak English, this is the language all maintainers can speak and write.
|
||||
2. Please ask questions or configuration/deploy problems on our Discord
|
||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
||||
3. Please take a moment to check that your issue doesn't already exist.
|
||||
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.com/help/faq)
|
||||
5. Please give all relevant information below for bug reports, because
|
||||
incomplete details will be handled as an invalid report.
|
||||
6. In particular it's really important to provide pertinent logs. If you are certain that this is a javascript
|
||||
error, show us the javascript console. If the error appears to relate to Gitea the server you must also give us
|
||||
DEBUG level logs. (See https://docs.gitea.com/administration/logging-config#collecting-logs-for-help)
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)
|
||||
If using a proxy or a CDN (e.g. CloudFlare) in front of gitea, please disable the proxy/CDN fully and connect to gitea directly to confirm the issue still persists without those services.
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: Please provide at least 1 screenshot showing the issue.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: gitea-ver
|
||||
attributes:
|
||||
label: Gitea Version
|
||||
description: Gitea version (or commit reference) your instance is running
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: can-reproduce
|
||||
attributes:
|
||||
label: Can you reproduce the bug on the Gitea demo site?
|
||||
description: |
|
||||
If so, please provide a URL in the Description field
|
||||
URL of Gitea demo: https://try.gitea.io
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os-ver
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: The operating system you are using to access Gitea
|
||||
- type: input
|
||||
id: browser-ver
|
||||
attributes:
|
||||
label: Browser Version
|
||||
description: The browser and version that you are using to access Gitea
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
self-hosted-runner:
|
||||
labels:
|
||||
- actuated-4cpu-8gb
|
||||
- actuated-4cpu-16gb
|
||||
@@ -0,0 +1,35 @@
|
||||
kind/docs:
|
||||
- "**/*.md"
|
||||
- "docs/**"
|
||||
|
||||
kind/ui:
|
||||
- "web_src/**/*"
|
||||
- all: ["templates/**", "!templates/swagger/v1_json.tmpl"]
|
||||
|
||||
kind/api:
|
||||
- "templates/swagger/v1_json.tmpl"
|
||||
- "routers/api/**"
|
||||
|
||||
kind/build:
|
||||
- "Makefile"
|
||||
- "Dockerfile"
|
||||
- "Dockerfile.rootless"
|
||||
- "docker/**"
|
||||
- "webpack.config.js"
|
||||
|
||||
theme/package-registry:
|
||||
- "modules/packages/**"
|
||||
- "services/packages/**"
|
||||
- "routers/api/packages/**"
|
||||
- "routers/web/shared/packages/**"
|
||||
|
||||
kind/cli:
|
||||
- "cmd/**"
|
||||
|
||||
kind/lint:
|
||||
- ".eslintrc.yaml"
|
||||
- ".golangci.yml"
|
||||
- ".markdownlint.yaml"
|
||||
- ".spectral.yaml"
|
||||
- ".stylelintrc.yaml"
|
||||
- ".yamllint.yaml"
|
||||
+3
-3
@@ -9,8 +9,8 @@ daysUntilClose: 14
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- status/blocked
|
||||
- kind/security
|
||||
- status/blocked
|
||||
- kind/security
|
||||
- lgtm/done
|
||||
- reviewed/confirmed
|
||||
- priority/critical
|
||||
@@ -27,7 +27,7 @@ staleLabel: stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had recent activity.
|
||||
This issue has been automatically marked as stale because it has not had recent activity.
|
||||
I am here to help clear issues left open even if solved or waiting for more insight.
|
||||
This issue will be closed if no further activity occurs during the next 2 weeks.
|
||||
If the issue is still valid just add a comment to keep it alive.
|
||||
|
||||
@@ -17,6 +17,8 @@ on:
|
||||
value: ${{ jobs.detect.outputs.docker }}
|
||||
swagger:
|
||||
value: ${{ jobs.detect.outputs.swagger }}
|
||||
yaml:
|
||||
value: ${{ jobs.detect.outputs.yaml }}
|
||||
|
||||
jobs:
|
||||
detect:
|
||||
@@ -30,6 +32,7 @@ jobs:
|
||||
templates: ${{ steps.changes.outputs.templates }}
|
||||
docker: ${{ steps.changes.outputs.docker }}
|
||||
swagger: ${{ steps.changes.outputs.swagger }}
|
||||
yaml: ${{ steps.changes.outputs.yaml }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dorny/paths-filter@v2
|
||||
@@ -82,3 +85,8 @@ jobs:
|
||||
- "package.json"
|
||||
- "package-lock.json"
|
||||
- ".spectral.yaml"
|
||||
|
||||
yaml:
|
||||
- "**/*.yml"
|
||||
- "**/*.yaml"
|
||||
- ".yamllint.yaml"
|
||||
|
||||
@@ -39,6 +39,19 @@ jobs:
|
||||
- run: make deps-py
|
||||
- run: make lint-templates
|
||||
|
||||
lint-yaml:
|
||||
if: needs.files-changed.outputs.yaml == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- run: pip install poetry
|
||||
- run: make deps-py
|
||||
- run: make lint-yaml
|
||||
|
||||
lint-swagger:
|
||||
if: needs.files-changed.outputs.swagger == 'true'
|
||||
needs: files-changed
|
||||
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: true
|
||||
MYSQL_DATABASE: test
|
||||
ports:
|
||||
- "3306:3306"
|
||||
@@ -160,7 +160,7 @@ jobs:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: true
|
||||
MYSQL_DATABASE: test
|
||||
ports:
|
||||
- "3306:3306"
|
||||
@@ -205,7 +205,7 @@ jobs:
|
||||
mysql8:
|
||||
image: mysql:8
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: true
|
||||
MYSQL_DATABASE: testgitea
|
||||
ports:
|
||||
- "3306:3306"
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
name: labeler
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/labeler@v4
|
||||
with:
|
||||
dot: true
|
||||
sync-labels: true
|
||||
@@ -4,9 +4,13 @@ on:
|
||||
push:
|
||||
branches: [ main, release/v* ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
nightly-binary:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: actuated-4cpu-16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
@@ -51,13 +55,17 @@ jobs:
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
SOURCE_DIR: dist/release
|
||||
DEST_DIR: gitea/${{ steps.clean_name.outputs.branch }}
|
||||
nightly-docker:
|
||||
runs-on: ubuntu-latest
|
||||
nightly-docker-rootful:
|
||||
runs-on: actuated-4cpu-16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "~1.21"
|
||||
check-latest: true
|
||||
- uses: docker/setup-qemu-action@v2
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
- name: Get cleaned branch name
|
||||
@@ -75,6 +83,8 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: fetch go modules
|
||||
run: make vendor
|
||||
- name: build rootful docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
@@ -82,6 +92,36 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||
nightly-docker-rootless:
|
||||
runs-on: actuated-4cpu-16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "~1.21"
|
||||
check-latest: true
|
||||
- uses: docker/setup-qemu-action@v2
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
- name: Get cleaned branch name
|
||||
id: clean_name
|
||||
run: |
|
||||
# if main then say nightly otherwise cleanup name
|
||||
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||
echo "branch=nightly" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
||||
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: fetch go modules
|
||||
run: make vendor
|
||||
- name: build rootless docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
|
||||
@@ -95,6 +95,7 @@ cpu.out
|
||||
/.go-licenses
|
||||
|
||||
# Snapcraft
|
||||
/gitea_a*.txt
|
||||
snap/.snapcraft/
|
||||
parts/
|
||||
stage/
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
braces:
|
||||
min-spaces-inside: 0
|
||||
max-spaces-inside: 1
|
||||
min-spaces-inside-empty: 0
|
||||
max-spaces-inside-empty: 0
|
||||
|
||||
brackets:
|
||||
min-spaces-inside: 0
|
||||
max-spaces-inside: 1
|
||||
min-spaces-inside-empty: 0
|
||||
max-spaces-inside-empty: 0
|
||||
|
||||
comments:
|
||||
require-starting-space: true
|
||||
ignore-shebangs: true
|
||||
min-spaces-from-content: 1
|
||||
|
||||
comments-indentation:
|
||||
level: error
|
||||
|
||||
document-start:
|
||||
level: error
|
||||
present: false
|
||||
ignore: |
|
||||
/.drone.yml
|
||||
|
||||
document-end:
|
||||
present: false
|
||||
|
||||
empty-lines:
|
||||
max: 1
|
||||
|
||||
indentation:
|
||||
spaces: 2
|
||||
|
||||
line-length: disable
|
||||
|
||||
truthy:
|
||||
allowed-values: ["true", "false", "on", "off"]
|
||||
|
||||
ignore: |
|
||||
.venv
|
||||
node_modules
|
||||
/models/fixtures
|
||||
/models/migrations/fixtures
|
||||
@@ -4,6 +4,34 @@ This changelog goes through all the changes that have been made in each release
|
||||
without substantial changes to our git log; to see the highlights of what has
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
|
||||
## [1.20.4](https://github.com/go-gitea/gitea/releases/tag/v1.20.4) - 2023-09-08
|
||||
|
||||
* SECURITY
|
||||
* Check blocklist for emails when adding them to account (#26812) (#26831)
|
||||
* ENHANCEMENTS
|
||||
* Add `branch_filter` to hooks API endpoints (#26599) (#26632)
|
||||
* Fix incorrect "tabindex" attributes (#26733) (#26734)
|
||||
* Use line-height: normal by default (#26635) (#26708)
|
||||
* Fix unable to display individual-level project (#26198) (#26636)
|
||||
* BUGFIXES
|
||||
* Fix wrong review requested number (#26784) (#26880)
|
||||
* Avoid double-unescaping of form value (#26853) (#26863)
|
||||
* Redirect from `{repo}/issues/new` to `{repo}/issues/new/choose` when blank issues are disabled (#26813) (#26847)
|
||||
* Sync tags when adopting repos (#26816) (#26834)
|
||||
* Fix verifyCommits error when push a new branch (#26664) (#26810)
|
||||
* Include the GITHUB_TOKEN/GITEA_TOKEN secret for fork pull requests (#26759) (#26806)
|
||||
* Fix some slice append usages (#26778) (#26798)
|
||||
* Add fix incorrect can_create_org_repo for org owner team (#26683) (#26791)
|
||||
* Fix bug for ctx usage (#26763)
|
||||
* Make issue template field template access correct template data (#26698) (#26709)
|
||||
* Use correct minio error (#26634) (#26639)
|
||||
* Ignore the trailing slashes when comparing oauth2 redirect_uri (#26597) (#26618)
|
||||
* Set errwriter for urfave/cli v1 (#26616)
|
||||
* Fix reopen logic for agit flow pull request (#26399) (#26613)
|
||||
* Fix context filter has no effect in dashboard (#26695) (#26811)
|
||||
* Fix being unable to use a repo that prohibits accepting PRs as a PR source. (#26785) (#26790)
|
||||
* Fix Page Not Found error (#26768)
|
||||
|
||||
## [1.20.3](https://github.com/go-gitea/gitea/releases/tag/v1.20.3) - 2023-08-20
|
||||
|
||||
* BREAKING
|
||||
|
||||
@@ -55,3 +55,5 @@ Philip Peterson <philip.c.peterson@gmail.com> (@philip-peterson)
|
||||
Denys Konovalov <kontakt@denyskon.de> (@denyskon)
|
||||
Punit Inani <punitinani1@gmail.com> (@puni9869)
|
||||
CaiCandong <1290147055@qq.com> (@caicandong)
|
||||
Rui Chen <rui@chenrui.dev> (@chenrui333)
|
||||
Nanguan Lin <nanguanlin6@gmail.com> (@lng2020)
|
||||
|
||||
@@ -218,6 +218,7 @@ help:
|
||||
@echo " - lint-md lint markdown files"
|
||||
@echo " - lint-swagger lint swagger files"
|
||||
@echo " - lint-templates lint template files"
|
||||
@echo " - lint-yaml lint yaml files"
|
||||
@echo " - checks run various consistency checks"
|
||||
@echo " - checks-frontend check frontend files"
|
||||
@echo " - checks-backend check backend files"
|
||||
@@ -427,6 +428,10 @@ lint-actions:
|
||||
lint-templates: .venv
|
||||
@poetry run djlint $(shell find templates -type f -iname '*.tmpl')
|
||||
|
||||
.PHONY: lint-yaml
|
||||
lint-yaml: .venv
|
||||
@poetry run yamllint .
|
||||
|
||||
.PHONY: watch
|
||||
watch:
|
||||
@bash build/watch.sh
|
||||
|
||||
@@ -185,7 +185,7 @@ func runMigrateStorage(ctx *cli.Context) error {
|
||||
case string(setting.LocalStorageType):
|
||||
p := ctx.String("path")
|
||||
if p == "" {
|
||||
log.Fatal("Path must be given when storage is loal")
|
||||
log.Fatal("Path must be given when storage is local")
|
||||
return nil
|
||||
}
|
||||
dstStorage, err = storage.NewLocalStorage(
|
||||
|
||||
@@ -759,6 +759,8 @@ LEVEL = Info
|
||||
;;
|
||||
;; More detail: https://github.com/gogits/gogs/issues/165
|
||||
;ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
||||
; Enable this to allow reverse proxy authentication for API requests, the reverse proxy is responsible for ensuring that no CSRF is possible.
|
||||
;ENABLE_REVERSE_PROXY_AUTHENTICATION_API = false
|
||||
;ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
|
||||
;ENABLE_REVERSE_PROXY_EMAIL = false
|
||||
;ENABLE_REVERSE_PROXY_FULL_NAME = false
|
||||
@@ -1753,8 +1755,8 @@ LEVEL = Info
|
||||
;; Session cookie name
|
||||
;COOKIE_NAME = i_like_gitea
|
||||
;;
|
||||
;; If you use session in https only, default is false
|
||||
;COOKIE_SECURE = false
|
||||
;; If you use session in https only: true or false. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
|
||||
;COOKIE_SECURE =
|
||||
;;
|
||||
;; Session GC time interval in seconds, default is 86400 (1 day)
|
||||
;GC_INTERVAL_TIME = 86400
|
||||
@@ -2573,6 +2575,8 @@ LEVEL = Info
|
||||
;;
|
||||
;; Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance.
|
||||
;DEFAULT_ACTIONS_URL = github
|
||||
;; Default artifact retention time in days, default is 90 days
|
||||
;ARTIFACT_RETENTION_DAYS = 90
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -449,7 +449,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
||||
- `SQLITE_JOURNAL_MODE`: **""**: Change journal mode for SQlite3. Can be used to enable [WAL mode](https://www.sqlite.org/wal.html) when high load causes write congestion. See [SQlite3 docs](https://www.sqlite.org/pragma.html#pragma_journal_mode) for possible values. Defaults to the default for the database file, often DELETE.
|
||||
- `ITERATE_BUFFER_SIZE`: **50**: Internal buffer size for iterating.
|
||||
- `PATH`: **data/gitea.db**: For SQLite3 only, the database file path.
|
||||
- `LOG_SQL`: **true**: Log the executed SQL.
|
||||
- `LOG_SQL`: **false**: Log the executed SQL.
|
||||
- `DB_RETRIES`: **10**: How many ORM init / DB connect attempts allowed.
|
||||
- `DB_RETRY_BACKOFF`: **3s**: time.Duration to wait before trying another ORM init / DB connect attempt, if failure occurred.
|
||||
- `MAX_OPEN_CONNS` **0**: Database maximum open connections - default is 0, meaning there is no limit.
|
||||
@@ -624,7 +624,8 @@ And the following unique queues:
|
||||
BASIC and the user's password. Please note if you disable this you will not be able to access the
|
||||
tokens API endpoints using a password. Further, this only disables BASIC authentication using the
|
||||
password - not tokens or OAuth Basic.
|
||||
- `ENABLE_REVERSE_PROXY_AUTHENTICATION`: **false**: Enable this to allow reverse proxy authentication.
|
||||
- `ENABLE_REVERSE_PROXY_AUTHENTICATION`: **false**: Enable this to allow reverse proxy authentication for web requests
|
||||
- `ENABLE_REVERSE_PROXY_AUTHENTICATION_API`: **false**: Enable this to allow reverse proxy authentication for API requests, the reverse proxy is responsible for ensuring that no CSRF is possible.
|
||||
- `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration
|
||||
for reverse authentication.
|
||||
- `ENABLE_REVERSE_PROXY_EMAIL`: **false**: Enable this to allow to auto-registration with a
|
||||
@@ -779,7 +780,7 @@ and
|
||||
|
||||
- `PROVIDER`: **memory**: Session engine provider \[memory, file, redis, redis-cluster, db, mysql, couchbase, memcache, postgres\]. Setting `db` will reuse the configuration in `[database]`
|
||||
- `PROVIDER_CONFIG`: **data/sessions**: For file, the root path; for db, empty (database config will be used); for others, the connection string. Relative paths will be made absolute against _`AppWorkPath`_.
|
||||
- `COOKIE_SECURE`: **false**: Enable this to force using HTTPS for all session access.
|
||||
- `COOKIE_SECURE`:**_empty_**: `true` or `false`. Enable this to force using HTTPS for all session access. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
|
||||
- `COOKIE_NAME`: **i\_like\_gitea**: The name of the cookie used for the session ID.
|
||||
- `GC_INTERVAL_TIME`: **86400**: GC interval in seconds.
|
||||
- `SESSION_LIFE_TIME`: **86400**: Session life time in seconds, default is 86400 (1 day)
|
||||
@@ -958,6 +959,12 @@ Default templates for project boards:
|
||||
- `SCHEDULE`: **@midnight** : Interval as a duration between each synchronization, it will always attempt synchronization when the instance starts.
|
||||
- `UPDATE_EXISTING`: **true**: Create new users, update existing user data and disable users that are not in external source anymore (default) or only create new users if UPDATE_EXISTING is set to false.
|
||||
|
||||
## Cron - Cleanup Expired Actions Assets (`cron.cleanup_actions`)
|
||||
|
||||
- `ENABLED`: **true**: Enable cleanup expired actions assets job.
|
||||
- `RUN_AT_START`: **true**: Run job at start time (if ENABLED).
|
||||
- `SCHEDULE`: **@midnight** : Cron syntax for the job.
|
||||
|
||||
### Extended cron tasks (not enabled by default)
|
||||
|
||||
#### Cron - Garbage collect all repositories (`cron.git_gc_repos`)
|
||||
@@ -1384,6 +1391,7 @@ PROXY_HOSTS = *.github.com
|
||||
- `DEFAULT_ACTIONS_URL`: **github**: Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance.
|
||||
- `STORAGE_TYPE`: **local**: Storage type for actions logs, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]`
|
||||
- `MINIO_BASE_PATH`: **actions_log/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio`
|
||||
- `ARTIFACT_RETENTION_DAYS`: **90**: Number of days to keep artifacts. Set to 0 to disable artifact retention. Default is 90 days if not set.
|
||||
|
||||
`DEFAULT_ACTIONS_URL` indicates where the Gitea Actions runners should find the actions with relative path.
|
||||
For example, `uses: actions/checkout@v3` means `https://github.com/actions/checkout@v3` since the value of `DEFAULT_ACTIONS_URL` is `github`.
|
||||
|
||||
@@ -436,7 +436,7 @@ menu:
|
||||
- `SQLITE_JOURNAL_MODE`:**""**:更改 SQlite3 的日志模式。可以用于在高负载导致写入拥塞时启用 [WAL 模式](https://www.sqlite.org/wal.html)。有关可能的值,请参阅 [SQlite3 文档](https://www.sqlite.org/pragma.html#pragma_journal_mode)。默认为数据库文件的默认值,通常为 DELETE。
|
||||
- `ITERATE_BUFFER_SIZE`:**50**:用于迭代的内部缓冲区大小。
|
||||
- `PATH`:**data/gitea.db**:仅适用于 SQLite3 的数据库文件路径。
|
||||
- `LOG_SQL`:**true**:记录已执行的 SQL。
|
||||
- `LOG_SQL`:**false**:记录已执行的 SQL。
|
||||
- `DB_RETRIES`:**10**:允许多少次 ORM 初始化 / DB 连接尝试。
|
||||
- `DB_RETRY_BACKOFF`:**3s**:如果发生故障,等待另一个 ORM 初始化 / DB 连接尝试的 time.Duration。
|
||||
- `MAX_OPEN_CONNS`:**0**:数据库最大打开连接数 - 默认为 0,表示没有限制。
|
||||
@@ -742,7 +742,7 @@ Gitea 创建以下非唯一队列:
|
||||
|
||||
- `PROVIDER`: **memory**:会话存储引擎 \[memory, file, redis, redis-cluster, db, mysql, couchbase, memcache, postgres\]。设置为 `db` 将会重用 `[database]` 的配置信息。
|
||||
- `PROVIDER_CONFIG`: **data/sessions**:对于文件,为根路径;对于 db,为空(将使用数据库配置);对于其他引擎,为连接字符串。相对路径将根据 _`AppWorkPath`_ 绝对化。
|
||||
- `COOKIE_SECURE`: **false**:启用此选项以强制在所有会话访问中使用 HTTPS。
|
||||
- `COOKIE_SECURE`: **_empty_**:`true` 或 `false`。启用此选项以强制在所有会话访问中使用 HTTPS。如果没有设置,当 ROOT_URL 是 https 链接的时候默认设置为 true。
|
||||
- `COOKIE_NAME`: **i\_like\_gitea**:用于会话 ID 的 cookie 名称。
|
||||
- `GC_INTERVAL_TIME`: **86400**:GC 间隔时间,以秒为单位。
|
||||
- `SESSION_LIFE_TIME`: **86400**:会话生命周期,以秒为单位,默认为 86400(1 天)。
|
||||
|
||||
@@ -29,6 +29,8 @@ server {
|
||||
location / {
|
||||
client_max_body_size 512M;
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_set_header Connection $http_connection;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
@@ -92,6 +92,12 @@ it's recommended to use `const _promise = asyncFoo()` to tell readers
|
||||
that this is done by purpose, we want to call the async function and ignore the Promise.
|
||||
Some lint rules and IDEs also have warnings if the returned Promise is not handled.
|
||||
|
||||
### Fetching data
|
||||
|
||||
To fetch data, use the wrapper functions `GET`, `POST` etc. from `modules/fetch.js`. They
|
||||
accept a `data` option for the content, will automatically set CSFR token and return a
|
||||
Promise for a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response).
|
||||
|
||||
### HTML Attributes and `dataset`
|
||||
|
||||
The usage of `dataset` is forbidden, its camel-casing behaviour makes it hard to grep for attributes.
|
||||
|
||||
@@ -51,6 +51,15 @@ Open "Windows Services", search for the service named "gitea", right-click it an
|
||||
"Run". If everything is OK, Gitea will be reachable on `http://localhost:3000` (or the port
|
||||
that was configured).
|
||||
|
||||
## Service startup type
|
||||
|
||||
It was observed that on loaded systems during boot Gitea service may fail to start with timeout records in Windows Event Log.
|
||||
In that case change startup type to `Automatic-Delayed`. This can be done during service creation, or by running config command
|
||||
|
||||
```
|
||||
sc.exe config gitea start= delayed-auto
|
||||
```
|
||||
|
||||
## Adding startup dependencies
|
||||
|
||||
To add a startup dependency to the Gitea Windows service (eg Mysql, Mariadb), as an Administrator, then run the following command:
|
||||
|
||||
@@ -157,12 +157,13 @@ Uses the following fields:
|
||||
|
||||
- User Attribute in Group (optional)
|
||||
|
||||
- Which user LDAP attribute is listed in the group.
|
||||
- Example: `uid`
|
||||
- The user attribute that is used to reference a user in the group object.
|
||||
- Example: `uid` if the group objects contains a `member: bender` and the user object contains a `uid: bender`.
|
||||
- Example: `dn` if the group object contains a `member: uid=bender,ou=users,dc=planetexpress,dc=com`.
|
||||
|
||||
- Group Attribute for User (optional)
|
||||
- Which group LDAP attribute contains an array above user attribute names.
|
||||
- Example: `memberUid`
|
||||
- The attribute of the group object that lists/contains the group members.
|
||||
- Example: `memberUid` or `member`
|
||||
|
||||
## PAM (Pluggable Authentication Module)
|
||||
|
||||
|
||||
@@ -50,10 +50,11 @@ Add the following text to the configuration file located in the current users ho
|
||||
default = "gitea"
|
||||
|
||||
[registries.gitea]
|
||||
index = "https://gitea.example.com/{owner}/_cargo-index.git"
|
||||
index = "sparse+https://gitea.example.com/api/packages/{owner}/cargo/" # Sparse index
|
||||
# index = "https://gitea.example.com/{owner}/_cargo-index.git" # Git
|
||||
|
||||
[net]
|
||||
git-fetch-with-cli = true
|
||||
# [net]
|
||||
# git-fetch-with-cli = true
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
@@ -72,6 +73,12 @@ token = "Bearer {token}"
|
||||
| --------- | ----------- |
|
||||
| `token` | Your [personal access token](development/api-usage.md#authentication) |
|
||||
|
||||
## Git vs Sparse
|
||||
|
||||
Currently, cargo supports two ways for fetching crates in a registry: Git index & sparse index.
|
||||
Sparse index is the newest method and offers better performance when updating crates compared to git.
|
||||
Since Rust 1.68, sparse is the default method for crates.io.
|
||||
|
||||
## Publish a package
|
||||
|
||||
Publish a package by running the following command in your project:
|
||||
|
||||
@@ -50,7 +50,8 @@ Cargo 将可用软件包的信息存储在一个存储在 git 仓库中的软件
|
||||
default = "gitea"
|
||||
|
||||
[registries.gitea]
|
||||
index = "https://gitea.example.com/{owner}/_cargo-index.git"
|
||||
index = "sparse+https://gitea.example.com/api/packages/{owner}/cargo/" # Sparse index
|
||||
# index = "https://gitea.example.com/{owner}/_cargo-index.git" # Git
|
||||
|
||||
[net]
|
||||
git-fetch-with-cli = true
|
||||
|
||||
@@ -15,6 +15,6 @@ menu:
|
||||
|
||||
# Profile READMEs
|
||||
|
||||
To display a markdown file in your Gitea profile page, simply make a repository named ".profile" and edit the README.md file inside. Gitea will automatically pull this file in and display it above your repositories.
|
||||
To display a Markdown file in your Gitea profile page, simply create a repository named `.profile` and add a new file called `README.md`. Gitea will automatically display the contents of the file on your profile, above your repositories.
|
||||
|
||||
Note. You are welcome to make this repository private. Doing so will hide your source files from public viewing and allow you to privitize certain files. However, the README.md file will be the only file present on your profile. If you wish to have an entirely private .profile repository, remove or rename the README.md file.
|
||||
Making the `.profile` repository private will hide the Profile README.
|
||||
|
||||
@@ -17,7 +17,7 @@ require (
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/PuerkitoBio/goquery v1.8.1
|
||||
github.com/alecthomas/chroma/v2 v2.8.0
|
||||
github.com/alecthomas/chroma/v2 v2.9.1
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
github.com/blevesearch/bleve/v2 v2.3.9
|
||||
github.com/bufbuild/connect-go v1.10.0
|
||||
|
||||
@@ -119,8 +119,8 @@ github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ
|
||||
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
|
||||
github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/chroma/v2 v2.8.0 h1:w9WJUjFFmHHB2e8mRpL9jjy3alYDlU0QLDezj1xE264=
|
||||
github.com/alecthomas/chroma/v2 v2.8.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
|
||||
github.com/alecthomas/chroma/v2 v2.9.1 h1:0O3lTQh9FxazJ4BYE/MOi/vDGuHn7B+6Bu902N2UZvU=
|
||||
github.com/alecthomas/chroma/v2 v2.9.1/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
|
||||
+28
-10
@@ -9,19 +9,21 @@ package actions
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// ArtifactStatus is the status of an artifact, uploading, expired or need-delete
|
||||
type ArtifactStatus int64
|
||||
|
||||
const (
|
||||
// ArtifactStatusUploadPending is the status of an artifact upload that is pending
|
||||
ArtifactStatusUploadPending = 1
|
||||
// ArtifactStatusUploadConfirmed is the status of an artifact upload that is confirmed
|
||||
ArtifactStatusUploadConfirmed = 2
|
||||
// ArtifactStatusUploadError is the status of an artifact upload that is errored
|
||||
ArtifactStatusUploadError = 3
|
||||
ArtifactStatusUploadPending ArtifactStatus = iota + 1 // 1, ArtifactStatusUploadPending is the status of an artifact upload that is pending
|
||||
ArtifactStatusUploadConfirmed // 2, ArtifactStatusUploadConfirmed is the status of an artifact upload that is confirmed
|
||||
ArtifactStatusUploadError // 3, ArtifactStatusUploadError is the status of an artifact upload that is errored
|
||||
ArtifactStatusExpired // 4, ArtifactStatusExpired is the status of an artifact that is expired
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -45,9 +47,10 @@ type ActionArtifact struct {
|
||||
Status int64 `xorm:"index"` // The status of the artifact, uploading, expired or need-delete
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated index"`
|
||||
ExpiredUnix timeutil.TimeStamp `xorm:"index"` // The time when the artifact will be expired
|
||||
}
|
||||
|
||||
func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPath string) (*ActionArtifact, error) {
|
||||
func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPath string, expiredDays int64) (*ActionArtifact, error) {
|
||||
if err := t.LoadJob(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -61,7 +64,8 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa
|
||||
RepoID: t.RepoID,
|
||||
OwnerID: t.OwnerID,
|
||||
CommitSHA: t.CommitSHA,
|
||||
Status: ArtifactStatusUploadPending,
|
||||
Status: int64(ArtifactStatusUploadPending),
|
||||
ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + 3600*24*expiredDays),
|
||||
}
|
||||
if _, err := db.GetEngine(ctx).Insert(artifact); err != nil {
|
||||
return nil, err
|
||||
@@ -126,15 +130,16 @@ func ListUploadedArtifactsByRunID(ctx context.Context, runID int64) ([]*ActionAr
|
||||
type ActionArtifactMeta struct {
|
||||
ArtifactName string
|
||||
FileSize int64
|
||||
Status int64
|
||||
}
|
||||
|
||||
// ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run
|
||||
func ListUploadedArtifactsMeta(ctx context.Context, runID int64) ([]*ActionArtifactMeta, error) {
|
||||
arts := make([]*ActionArtifactMeta, 0, 10)
|
||||
return arts, db.GetEngine(ctx).Table("action_artifact").
|
||||
Where("run_id=? AND status=?", runID, ArtifactStatusUploadConfirmed).
|
||||
Where("run_id=? AND (status=? OR status=?)", runID, ArtifactStatusUploadConfirmed, ArtifactStatusExpired).
|
||||
GroupBy("artifact_name").
|
||||
Select("artifact_name, sum(file_size) as file_size").
|
||||
Select("artifact_name, sum(file_size) as file_size, max(status) as status").
|
||||
Find(&arts)
|
||||
}
|
||||
|
||||
@@ -149,3 +154,16 @@ func ListArtifactsByRunIDAndName(ctx context.Context, runID int64, name string)
|
||||
arts := make([]*ActionArtifact, 0, 10)
|
||||
return arts, db.GetEngine(ctx).Where("run_id=? AND artifact_name=?", runID, name).Find(&arts)
|
||||
}
|
||||
|
||||
// ListNeedExpiredArtifacts returns all need expired artifacts but not deleted
|
||||
func ListNeedExpiredArtifacts(ctx context.Context) ([]*ActionArtifact, error) {
|
||||
arts := make([]*ActionArtifact, 0, 10)
|
||||
return arts, db.GetEngine(ctx).
|
||||
Where("expired_unix < ? AND status = ?", timeutil.TimeStamp(time.Now().Unix()), ArtifactStatusUploadConfirmed).Find(&arts)
|
||||
}
|
||||
|
||||
// SetArtifactExpired sets an artifact to expired
|
||||
func SetArtifactExpired(ctx context.Context, artifactID int64) error {
|
||||
_, err := db.GetEngine(ctx).Where("id=? AND status = ?", artifactID, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusExpired)})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package actions
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -34,7 +35,8 @@ type ActionRun struct {
|
||||
Index int64 `xorm:"index unique(repo_index)"` // a unique number for each run of a repository
|
||||
TriggerUserID int64 `xorm:"index"`
|
||||
TriggerUser *user_model.User `xorm:"-"`
|
||||
Ref string `xorm:"index"` // the commit/tag/… that caused the run
|
||||
ScheduleID int64
|
||||
Ref string `xorm:"index"` // the commit/tag/… that caused the run
|
||||
CommitSHA string
|
||||
IsForkPullRequest bool // If this is triggered by a PR from a forked repository or an untrusted user, we need to check if it is approved and limit permissions when running the workflow.
|
||||
NeedApproval bool // may need approval if it's a fork pull request
|
||||
@@ -350,7 +352,7 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
|
||||
// It's impossible that the run is not found, since Gitea never deletes runs.
|
||||
}
|
||||
|
||||
if run.Status != 0 || util.SliceContains(cols, "status") {
|
||||
if run.Status != 0 || slices.Contains(cols, "status") {
|
||||
if run.RepoID == 0 {
|
||||
run, err = GetRunByID(ctx, run.ID)
|
||||
if err != nil {
|
||||
|
||||
@@ -6,6 +6,7 @@ package actions
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@@ -107,11 +108,11 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if affected == 0 || (!util.SliceContains(cols, "status") && job.Status == 0) {
|
||||
if affected == 0 || (!slices.Contains(cols, "status") && job.Status == 0) {
|
||||
return affected, nil
|
||||
}
|
||||
|
||||
if affected != 0 && util.SliceContains(cols, "status") && job.Status.IsWaiting() {
|
||||
if affected != 0 && slices.Contains(cols, "status") && job.Status.IsWaiting() {
|
||||
// if the status of job changes to waiting again, increase tasks version.
|
||||
if err := IncreaseTaskVersion(ctx, job.OwnerID, job.RepoID); err != nil {
|
||||
return 0, err
|
||||
|
||||
@@ -189,6 +189,12 @@ func (opts FindRunnerOptions) toOrder() string {
|
||||
return "last_online ASC"
|
||||
case "alphabetically":
|
||||
return "name ASC"
|
||||
case "reversealphabetically":
|
||||
return "name DESC"
|
||||
case "newest":
|
||||
return "id DESC"
|
||||
case "oldest":
|
||||
return "id ASC"
|
||||
}
|
||||
return "last_online DESC"
|
||||
}
|
||||
|
||||
@@ -126,6 +126,15 @@ func (at ActionType) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (at ActionType) InActions(actions ...string) bool {
|
||||
for _, action := range actions {
|
||||
if action == at.String() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Action represents user operation type and other information to
|
||||
// repository. It implemented interface base.Actioner so that can be
|
||||
// used in template render.
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
_ "code.gitea.io/gitea/models"
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
_ "code.gitea.io/gitea/models"
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
_ "code.gitea.io/gitea/models/activities"
|
||||
_ "code.gitea.io/gitea/models/auth"
|
||||
_ "code.gitea.io/gitea/models/perm/access"
|
||||
|
||||
@@ -153,7 +153,12 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final
|
||||
return DefaultAvatarLink()
|
||||
}
|
||||
|
||||
enableFederatedAvatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureEnableFederatedAvatar)
|
||||
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar,
|
||||
setting.GetDefaultDisableGravatar(),
|
||||
)
|
||||
|
||||
enableFederatedAvatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureEnableFederatedAvatar,
|
||||
setting.GetDefaultEnableFederatedAvatar(disableGravatar))
|
||||
|
||||
var err error
|
||||
if enableFederatedAvatar && system_model.LibravatarService != nil {
|
||||
@@ -174,7 +179,6 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final
|
||||
return urlStr
|
||||
}
|
||||
|
||||
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
|
||||
if !disableGravatar {
|
||||
// copy GravatarSourceURL, because we will modify its Path.
|
||||
avatarURLCopy := *system_model.GravatarSourceURL
|
||||
|
||||
@@ -73,8 +73,8 @@
|
||||
-
|
||||
id: 10
|
||||
uid: 3
|
||||
email: user3@example.com
|
||||
lower_email: user3@example.com
|
||||
email: org3@example.com
|
||||
lower_email: org3@example.com
|
||||
is_activated: true
|
||||
is_primary: true
|
||||
|
||||
@@ -97,16 +97,16 @@
|
||||
-
|
||||
id: 13
|
||||
uid: 6
|
||||
email: user6@example.com
|
||||
lower_email: user6@example.com
|
||||
email: org6@example.com
|
||||
lower_email: org6@example.com
|
||||
is_activated: true
|
||||
is_primary: true
|
||||
|
||||
-
|
||||
id: 14
|
||||
uid: 7
|
||||
email: user7@example.com
|
||||
lower_email: user7@example.com
|
||||
email: org7@example.com
|
||||
lower_email: org7@example.com
|
||||
is_activated: true
|
||||
is_primary: true
|
||||
|
||||
@@ -153,8 +153,8 @@
|
||||
-
|
||||
id: 20
|
||||
uid: 17
|
||||
email: user17@example.com
|
||||
lower_email: user17@example.com
|
||||
email: org17@example.com
|
||||
lower_email: org17@example.com
|
||||
is_activated: true
|
||||
is_primary: true
|
||||
|
||||
@@ -169,8 +169,8 @@
|
||||
-
|
||||
id: 22
|
||||
uid: 19
|
||||
email: user19@example.com
|
||||
lower_email: user19@example.com
|
||||
email: org19@example.com
|
||||
lower_email: org19@example.com
|
||||
is_activated: true
|
||||
is_primary: true
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
-
|
||||
id: 3
|
||||
owner_id: 3
|
||||
owner_name: user3
|
||||
owner_name: org3
|
||||
lower_name: repo3
|
||||
name: repo3
|
||||
default_branch: master
|
||||
@@ -126,7 +126,7 @@
|
||||
-
|
||||
id: 5
|
||||
owner_id: 3
|
||||
owner_name: user3
|
||||
owner_name: org3
|
||||
lower_name: repo5
|
||||
name: repo5
|
||||
num_watches: 0
|
||||
@@ -671,7 +671,7 @@
|
||||
-
|
||||
id: 23
|
||||
owner_id: 17
|
||||
owner_name: user17
|
||||
owner_name: org17
|
||||
lower_name: big_test_public_4
|
||||
name: big_test_public_4
|
||||
num_watches: 0
|
||||
@@ -701,7 +701,7 @@
|
||||
-
|
||||
id: 24
|
||||
owner_id: 17
|
||||
owner_name: user17
|
||||
owner_name: org17
|
||||
lower_name: big_test_private_4
|
||||
name: big_test_private_4
|
||||
num_watches: 0
|
||||
@@ -791,7 +791,7 @@
|
||||
-
|
||||
id: 27
|
||||
owner_id: 19
|
||||
owner_name: user19
|
||||
owner_name: org19
|
||||
lower_name: big_test_public_mirror_6
|
||||
name: big_test_public_mirror_6
|
||||
num_watches: 0
|
||||
@@ -821,7 +821,7 @@
|
||||
-
|
||||
id: 28
|
||||
owner_id: 19
|
||||
owner_name: user19
|
||||
owner_name: org19
|
||||
lower_name: big_test_private_mirror_6
|
||||
name: big_test_private_mirror_6
|
||||
num_watches: 0
|
||||
@@ -942,7 +942,7 @@
|
||||
-
|
||||
id: 32 # org public repo
|
||||
owner_id: 3
|
||||
owner_name: user3
|
||||
owner_name: org3
|
||||
lower_name: repo21
|
||||
name: repo21
|
||||
num_watches: 0
|
||||
|
||||
@@ -129,6 +129,6 @@
|
||||
type: 1
|
||||
reviewer_id: 6
|
||||
issue_id: 11
|
||||
content: "singular review from user6 and final review for this pr"
|
||||
content: "singular review from org6 and final review for this pr"
|
||||
updated_unix: 946684831
|
||||
created_unix: 946684831
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-
|
||||
id: 1
|
||||
setting_key: 'disable_gravatar'
|
||||
setting_key: 'picture.disable_gravatar'
|
||||
setting_value: 'false'
|
||||
version: 1
|
||||
created: 1653533198
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
-
|
||||
id: 2
|
||||
setting_key: 'enable_federated_avatar'
|
||||
setting_key: 'picture.enable_federated_avatar'
|
||||
setting_value: 'false'
|
||||
version: 1
|
||||
created: 1653533198
|
||||
|
||||
+29
-29
@@ -76,17 +76,17 @@
|
||||
|
||||
-
|
||||
id: 3
|
||||
lower_name: user3
|
||||
name: user3
|
||||
lower_name: org3
|
||||
name: org3
|
||||
full_name: ' <<<< >> >> > >> > >>> >> '
|
||||
email: user3@example.com
|
||||
email: org3@example.com
|
||||
keep_email_private: false
|
||||
email_notifications_preference: onmention
|
||||
passwd: ZogKvWdyEx:password
|
||||
passwd_hash_algo: dummy
|
||||
must_change_password: false
|
||||
login_source: 0
|
||||
login_name: user3
|
||||
login_name: org3
|
||||
type: 1
|
||||
salt: ZogKvWdyEx
|
||||
max_repo_creation: -1
|
||||
@@ -98,7 +98,7 @@
|
||||
allow_create_organization: true
|
||||
prohibit_login: false
|
||||
avatar: avatar3
|
||||
avatar_email: user3@example.com
|
||||
avatar_email: org3@example.com
|
||||
use_custom_avatar: false
|
||||
num_followers: 0
|
||||
num_following: 0
|
||||
@@ -187,17 +187,17 @@
|
||||
|
||||
-
|
||||
id: 6
|
||||
lower_name: user6
|
||||
name: user6
|
||||
full_name: User Six
|
||||
email: user6@example.com
|
||||
lower_name: org6
|
||||
name: org6
|
||||
full_name: Org Six
|
||||
email: org6@example.com
|
||||
keep_email_private: false
|
||||
email_notifications_preference: enabled
|
||||
passwd: ZogKvWdyEx:password
|
||||
passwd_hash_algo: dummy
|
||||
must_change_password: false
|
||||
login_source: 0
|
||||
login_name: user6
|
||||
login_name: org6
|
||||
type: 1
|
||||
salt: ZogKvWdyEx
|
||||
max_repo_creation: -1
|
||||
@@ -209,7 +209,7 @@
|
||||
allow_create_organization: true
|
||||
prohibit_login: false
|
||||
avatar: avatar6
|
||||
avatar_email: user6@example.com
|
||||
avatar_email: org6@example.com
|
||||
use_custom_avatar: false
|
||||
num_followers: 0
|
||||
num_following: 0
|
||||
@@ -224,17 +224,17 @@
|
||||
|
||||
-
|
||||
id: 7
|
||||
lower_name: user7
|
||||
name: user7
|
||||
full_name: User Seven
|
||||
email: user7@example.com
|
||||
lower_name: org7
|
||||
name: org7
|
||||
full_name: Org Seven
|
||||
email: org7@example.com
|
||||
keep_email_private: false
|
||||
email_notifications_preference: disabled
|
||||
passwd: ZogKvWdyEx:password
|
||||
passwd_hash_algo: dummy
|
||||
must_change_password: false
|
||||
login_source: 0
|
||||
login_name: user7
|
||||
login_name: org7
|
||||
type: 1
|
||||
salt: ZogKvWdyEx
|
||||
max_repo_creation: -1
|
||||
@@ -246,7 +246,7 @@
|
||||
allow_create_organization: true
|
||||
prohibit_login: false
|
||||
avatar: avatar7
|
||||
avatar_email: user7@example.com
|
||||
avatar_email: org7@example.com
|
||||
use_custom_avatar: false
|
||||
num_followers: 0
|
||||
num_following: 0
|
||||
@@ -594,17 +594,17 @@
|
||||
|
||||
-
|
||||
id: 17
|
||||
lower_name: user17
|
||||
name: user17
|
||||
full_name: User 17
|
||||
email: user17@example.com
|
||||
lower_name: org17
|
||||
name: org17
|
||||
full_name: org 17
|
||||
email: org17@example.com
|
||||
keep_email_private: false
|
||||
email_notifications_preference: enabled
|
||||
passwd: ZogKvWdyEx:password
|
||||
passwd_hash_algo: dummy
|
||||
must_change_password: false
|
||||
login_source: 0
|
||||
login_name: user17
|
||||
login_name: org17
|
||||
type: 1
|
||||
salt: ZogKvWdyEx
|
||||
max_repo_creation: -1
|
||||
@@ -616,7 +616,7 @@
|
||||
allow_create_organization: true
|
||||
prohibit_login: false
|
||||
avatar: avatar17
|
||||
avatar_email: user17@example.com
|
||||
avatar_email: org17@example.com
|
||||
use_custom_avatar: false
|
||||
num_followers: 0
|
||||
num_following: 0
|
||||
@@ -668,17 +668,17 @@
|
||||
|
||||
-
|
||||
id: 19
|
||||
lower_name: user19
|
||||
name: user19
|
||||
full_name: User 19
|
||||
email: user19@example.com
|
||||
lower_name: org19
|
||||
name: org19
|
||||
full_name: Org 19
|
||||
email: org19@example.com
|
||||
keep_email_private: false
|
||||
email_notifications_preference: enabled
|
||||
passwd: ZogKvWdyEx:password
|
||||
passwd_hash_algo: dummy
|
||||
must_change_password: false
|
||||
login_source: 0
|
||||
login_name: user19
|
||||
login_name: org19
|
||||
type: 1
|
||||
salt: ZogKvWdyEx
|
||||
max_repo_creation: -1
|
||||
@@ -690,7 +690,7 @@
|
||||
allow_create_organization: true
|
||||
prohibit_login: false
|
||||
avatar: avatar19
|
||||
avatar_email: user19@example.com
|
||||
avatar_email: org19@example.com
|
||||
use_custom_avatar: false
|
||||
num_followers: 0
|
||||
num_following: 0
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
_ "code.gitea.io/gitea/models"
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
_ "code.gitea.io/gitea/models/activities"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@@ -435,7 +436,7 @@ func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, curre
|
||||
|
||||
whitelist = make([]int64, 0, len(teams))
|
||||
for i := range teams {
|
||||
if util.SliceContains(newWhitelist, teams[i].ID) {
|
||||
if slices.Contains(newWhitelist, teams[i].ID) {
|
||||
whitelist = append(whitelist, teams[i].ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,9 @@ func TestUpdateAssignee(t *testing.T) {
|
||||
_, _, err = issues_model.ToggleIssueAssignee(db.DefaultContext, issue, &user_model.User{ID: 1}, user2.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
user3, err := user_model.GetUserByID(db.DefaultContext, 3)
|
||||
org3, err := user_model.GetUserByID(db.DefaultContext, 3)
|
||||
assert.NoError(t, err)
|
||||
_, _, err = issues_model.ToggleIssueAssignee(db.DefaultContext, issue, &user_model.User{ID: 1}, user3.ID)
|
||||
_, _, err = issues_model.ToggleIssueAssignee(db.DefaultContext, issue, &user_model.User{ID: 1}, org3.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
user1, err := user_model.GetUserByID(db.DefaultContext, 1) // This user is already assigned (see the definition in fixtures), so running UpdateAssignee should unassign him
|
||||
@@ -47,7 +47,7 @@ func TestUpdateAssignee(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
var expectedAssignees []*user_model.User
|
||||
expectedAssignees = append(expectedAssignees, user2, user3)
|
||||
expectedAssignees = append(expectedAssignees, user2, org3)
|
||||
|
||||
for in, assignee := range issue.Assignees {
|
||||
assert.Equal(t, assignee.ID, expectedAssignees[in].ID)
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@@ -1247,3 +1248,44 @@ func FixCommentTypeLabelWithOutsideLabels(ctx context.Context) (int64, error) {
|
||||
func (c *Comment) HasOriginalAuthor() bool {
|
||||
return c.OriginalAuthor != "" && c.OriginalAuthorID != 0
|
||||
}
|
||||
|
||||
// InsertIssueComments inserts many comments of issues.
|
||||
func InsertIssueComments(comments []*Comment) error {
|
||||
if len(comments) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
issueIDs := make(container.Set[int64])
|
||||
for _, comment := range comments {
|
||||
issueIDs.Add(comment.IssueID)
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
for _, comment := range comments {
|
||||
if _, err := db.GetEngine(ctx).NoAutoTime().Insert(comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, reaction := range comment.Reactions {
|
||||
reaction.IssueID = comment.IssueID
|
||||
reaction.CommentID = comment.ID
|
||||
}
|
||||
if len(comment.Reactions) > 0 {
|
||||
if err := db.Insert(ctx, comment.Reactions); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for issueID := range issueIDs {
|
||||
if _, err := db.Exec(ctx, "UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ? AND `type`=?) WHERE id = ?",
|
||||
issueID, CommentTypeComment, issueID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
@@ -70,3 +70,30 @@ func TestAsCommentType(t *testing.T) {
|
||||
assert.Equal(t, issues_model.CommentTypeComment, issues_model.AsCommentType("comment"))
|
||||
assert.Equal(t, issues_model.CommentTypePRUnScheduledToAutoMerge, issues_model.AsCommentType("pull_cancel_scheduled_merge"))
|
||||
}
|
||||
|
||||
func TestMigrate_InsertIssueComments(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
||||
_ = issue.LoadRepo(db.DefaultContext)
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID})
|
||||
reaction := &issues_model.Reaction{
|
||||
Type: "heart",
|
||||
UserID: owner.ID,
|
||||
}
|
||||
|
||||
comment := &issues_model.Comment{
|
||||
PosterID: owner.ID,
|
||||
Poster: owner,
|
||||
IssueID: issue.ID,
|
||||
Issue: issue,
|
||||
Reactions: []*issues_model.Reaction{reaction},
|
||||
}
|
||||
|
||||
err := issues_model.InsertIssueComments([]*issues_model.Comment{comment})
|
||||
assert.NoError(t, err)
|
||||
|
||||
issueModified := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
||||
assert.EqualValues(t, issue.NumComments+1, issueModified.NumComments)
|
||||
|
||||
unittest.CheckConsistencyFor(t, &issues_model.Issue{})
|
||||
}
|
||||
|
||||
+50
-2
@@ -8,6 +8,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
@@ -605,7 +606,7 @@ func IsUserParticipantsOfIssue(user *user_model.User, issue *Issue) bool {
|
||||
log.Error(err.Error())
|
||||
return false
|
||||
}
|
||||
return util.SliceContains(userIDs, user.ID)
|
||||
return slices.Contains(userIDs, user.ID)
|
||||
}
|
||||
|
||||
// DependencyInfo represents high level information about an issue which is a dependency of another issue.
|
||||
@@ -630,7 +631,7 @@ func (issue *Issue) GetParticipantIDsByIssue(ctx context.Context) ([]int64, erro
|
||||
Find(&userIDs); err != nil {
|
||||
return nil, fmt.Errorf("get poster IDs: %w", err)
|
||||
}
|
||||
if !util.SliceContains(userIDs, issue.PosterID) {
|
||||
if !slices.Contains(userIDs, issue.PosterID) {
|
||||
return append(userIDs, issue.PosterID), nil
|
||||
}
|
||||
return userIDs, nil
|
||||
@@ -891,3 +892,50 @@ func IsNewPinAllowed(ctx context.Context, repoID int64, isPull bool) (bool, erro
|
||||
func IsErrIssueMaxPinReached(err error) bool {
|
||||
return err == ErrIssueMaxPinReached
|
||||
}
|
||||
|
||||
// InsertIssues insert issues to database
|
||||
func InsertIssues(issues ...*Issue) error {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
for _, issue := range issues {
|
||||
if err := insertIssue(ctx, issue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func insertIssue(ctx context.Context, issue *Issue) error {
|
||||
sess := db.GetEngine(ctx)
|
||||
if _, err := sess.NoAutoTime().Insert(issue); err != nil {
|
||||
return err
|
||||
}
|
||||
issueLabels := make([]IssueLabel, 0, len(issue.Labels))
|
||||
for _, label := range issue.Labels {
|
||||
issueLabels = append(issueLabels, IssueLabel{
|
||||
IssueID: issue.ID,
|
||||
LabelID: label.ID,
|
||||
})
|
||||
}
|
||||
if len(issueLabels) > 0 {
|
||||
if _, err := sess.Insert(issueLabels); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, reaction := range issue.Reactions {
|
||||
reaction.IssueID = issue.ID
|
||||
}
|
||||
|
||||
if len(issue.Reactions) > 0 {
|
||||
if _, err := sess.Insert(issue.Reactions); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -430,11 +430,11 @@ func TestIssue_ResolveMentions(t *testing.T) {
|
||||
// Public repo, doer
|
||||
testSuccess("user2", "repo1", "user1", []string{"user1"}, []int64{})
|
||||
// Private repo, team member
|
||||
testSuccess("user17", "big_test_private_4", "user20", []string{"user2"}, []int64{2})
|
||||
testSuccess("org17", "big_test_private_4", "user20", []string{"user2"}, []int64{2})
|
||||
// Private repo, not a team member
|
||||
testSuccess("user17", "big_test_private_4", "user20", []string{"user5"}, []int64{})
|
||||
testSuccess("org17", "big_test_private_4", "user20", []string{"user5"}, []int64{})
|
||||
// Private repo, whole team
|
||||
testSuccess("user17", "big_test_private_4", "user15", []string{"user17/owners"}, []int64{18})
|
||||
testSuccess("org17", "big_test_private_4", "user15", []string{"org17/owners"}, []int64{18})
|
||||
}
|
||||
|
||||
func TestResourceIndex(t *testing.T) {
|
||||
@@ -573,3 +573,45 @@ func TestIssueLoadAttributes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertCreateIssues(t *testing.T, isPull bool) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reponame := "repo1"
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
|
||||
milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1})
|
||||
assert.EqualValues(t, milestone.ID, 1)
|
||||
reaction := &issues_model.Reaction{
|
||||
Type: "heart",
|
||||
UserID: owner.ID,
|
||||
}
|
||||
|
||||
title := "issuetitle1"
|
||||
is := &issues_model.Issue{
|
||||
RepoID: repo.ID,
|
||||
MilestoneID: milestone.ID,
|
||||
Repo: repo,
|
||||
Title: title,
|
||||
Content: "issuecontent1",
|
||||
IsPull: isPull,
|
||||
PosterID: owner.ID,
|
||||
Poster: owner,
|
||||
IsClosed: true,
|
||||
Labels: []*issues_model.Label{label},
|
||||
Reactions: []*issues_model.Reaction{reaction},
|
||||
}
|
||||
err := issues_model.InsertIssues(is)
|
||||
assert.NoError(t, err)
|
||||
|
||||
i := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: title})
|
||||
unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: owner.ID, IssueID: i.ID})
|
||||
}
|
||||
|
||||
func TestMigrate_CreateIssuesIsPullFalse(t *testing.T) {
|
||||
assertCreateIssues(t, false)
|
||||
}
|
||||
|
||||
func TestMigrate_CreateIssuesIsPullTrue(t *testing.T) {
|
||||
assertCreateIssues(t, true)
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestXRef_AddCrossReferences(t *testing.T) {
|
||||
itarget = testCreateIssue(t, 3, 3, "title4", "content4", false)
|
||||
|
||||
// Cross-reference to issue #4 by admin
|
||||
content = fmt.Sprintf("content5, mentions user3/repo3#%d", itarget.Index)
|
||||
content = fmt.Sprintf("content5, mentions org3/repo3#%d", itarget.Index)
|
||||
i = testCreateIssue(t, 2, 1, "title5", content, false)
|
||||
ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0})
|
||||
assert.Equal(t, issues_model.CommentTypeIssueRef, ref.Type)
|
||||
@@ -63,7 +63,7 @@ func TestXRef_AddCrossReferences(t *testing.T) {
|
||||
assert.Equal(t, references.XRefActionNone, ref.RefAction)
|
||||
|
||||
// Cross-reference to issue #4 with no permission
|
||||
content = fmt.Sprintf("content6, mentions user3/repo3#%d", itarget.Index)
|
||||
content = fmt.Sprintf("content6, mentions org3/repo3#%d", itarget.Index)
|
||||
i = testCreateIssue(t, 4, 5, "title6", content, false)
|
||||
unittest.AssertNotExistsBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0})
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
_ "code.gitea.io/gitea/models"
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
_ "code.gitea.io/gitea/models/activities"
|
||||
_ "code.gitea.io/gitea/models/repo"
|
||||
_ "code.gitea.io/gitea/models/user"
|
||||
|
||||
|
||||
+26
-308
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@@ -323,261 +322,6 @@ func DeleteMilestoneByRepoID(repoID, id int64) error {
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// MilestoneList is a list of milestones offering additional functionality
|
||||
type MilestoneList []*Milestone
|
||||
|
||||
func (milestones MilestoneList) getMilestoneIDs() []int64 {
|
||||
ids := make([]int64, 0, len(milestones))
|
||||
for _, ms := range milestones {
|
||||
ids = append(ids, ms.ID)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// GetMilestonesOption contain options to get milestones
|
||||
type GetMilestonesOption struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
State api.StateType
|
||||
Name string
|
||||
SortType string
|
||||
}
|
||||
|
||||
func (opts GetMilestonesOption) toCond() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID != 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
|
||||
switch opts.State {
|
||||
case api.StateClosed:
|
||||
cond = cond.And(builder.Eq{"is_closed": true})
|
||||
case api.StateAll:
|
||||
break
|
||||
// api.StateOpen:
|
||||
default:
|
||||
cond = cond.And(builder.Eq{"is_closed": false})
|
||||
}
|
||||
|
||||
if len(opts.Name) != 0 {
|
||||
cond = cond.And(db.BuildCaseInsensitiveLike("name", opts.Name))
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
// GetMilestones returns milestones filtered by GetMilestonesOption's
|
||||
func GetMilestones(opts GetMilestonesOption) (MilestoneList, int64, error) {
|
||||
sess := db.GetEngine(db.DefaultContext).Where(opts.toCond())
|
||||
|
||||
if opts.Page != 0 {
|
||||
sess = db.SetSessionPagination(sess, &opts)
|
||||
}
|
||||
|
||||
switch opts.SortType {
|
||||
case "furthestduedate":
|
||||
sess.Desc("deadline_unix")
|
||||
case "leastcomplete":
|
||||
sess.Asc("completeness")
|
||||
case "mostcomplete":
|
||||
sess.Desc("completeness")
|
||||
case "leastissues":
|
||||
sess.Asc("num_issues")
|
||||
case "mostissues":
|
||||
sess.Desc("num_issues")
|
||||
case "id":
|
||||
sess.Asc("id")
|
||||
default:
|
||||
sess.Asc("deadline_unix").Asc("id")
|
||||
}
|
||||
|
||||
miles := make([]*Milestone, 0, opts.PageSize)
|
||||
total, err := sess.FindAndCount(&miles)
|
||||
return miles, total, err
|
||||
}
|
||||
|
||||
// GetMilestoneIDsByNames returns a list of milestone ids by given names.
|
||||
// It doesn't filter them by repo, so it could return milestones belonging to different repos.
|
||||
// It's used for filtering issues via indexer, otherwise it would be useless.
|
||||
// Since it could return milestones with the same name, so the length of returned ids could be more than the length of names.
|
||||
func GetMilestoneIDsByNames(ctx context.Context, names []string) ([]int64, error) {
|
||||
var ids []int64
|
||||
return ids, db.GetEngine(ctx).Table("milestone").
|
||||
Where(db.BuildCaseInsensitiveIn("name", names)).
|
||||
Cols("id").
|
||||
Find(&ids)
|
||||
}
|
||||
|
||||
// SearchMilestones search milestones
|
||||
func SearchMilestones(repoCond builder.Cond, page int, isClosed bool, sortType, keyword string) (MilestoneList, error) {
|
||||
miles := make([]*Milestone, 0, setting.UI.IssuePagingNum)
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed)
|
||||
if len(keyword) > 0 {
|
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
|
||||
}
|
||||
if repoCond.IsValid() {
|
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
|
||||
}
|
||||
if page > 0 {
|
||||
sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum)
|
||||
}
|
||||
|
||||
switch sortType {
|
||||
case "furthestduedate":
|
||||
sess.Desc("deadline_unix")
|
||||
case "leastcomplete":
|
||||
sess.Asc("completeness")
|
||||
case "mostcomplete":
|
||||
sess.Desc("completeness")
|
||||
case "leastissues":
|
||||
sess.Asc("num_issues")
|
||||
case "mostissues":
|
||||
sess.Desc("num_issues")
|
||||
default:
|
||||
sess.Asc("deadline_unix")
|
||||
}
|
||||
return miles, sess.Find(&miles)
|
||||
}
|
||||
|
||||
// GetMilestonesByRepoIDs returns a list of milestones of given repositories and status.
|
||||
func GetMilestonesByRepoIDs(repoIDs []int64, page int, isClosed bool, sortType string) (MilestoneList, error) {
|
||||
return SearchMilestones(
|
||||
builder.In("repo_id", repoIDs),
|
||||
page,
|
||||
isClosed,
|
||||
sortType,
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
// MilestonesStats represents milestone statistic information.
|
||||
type MilestonesStats struct {
|
||||
OpenCount, ClosedCount int64
|
||||
}
|
||||
|
||||
// Total returns the total counts of milestones
|
||||
func (m MilestonesStats) Total() int64 {
|
||||
return m.OpenCount + m.ClosedCount
|
||||
}
|
||||
|
||||
// GetMilestonesStatsByRepoCond returns milestone statistic information for dashboard by given conditions.
|
||||
func GetMilestonesStatsByRepoCond(repoCond builder.Cond) (*MilestonesStats, error) {
|
||||
var err error
|
||||
stats := &MilestonesStats{}
|
||||
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", false)
|
||||
if repoCond.IsValid() {
|
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
|
||||
}
|
||||
stats.OpenCount, err = sess.Count(new(Milestone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sess = db.GetEngine(db.DefaultContext).Where("is_closed = ?", true)
|
||||
if repoCond.IsValid() {
|
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
|
||||
}
|
||||
stats.ClosedCount, err = sess.Count(new(Milestone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// GetMilestonesStatsByRepoCondAndKw returns milestone statistic information for dashboard by given repo conditions and name keyword.
|
||||
func GetMilestonesStatsByRepoCondAndKw(repoCond builder.Cond, keyword string) (*MilestonesStats, error) {
|
||||
var err error
|
||||
stats := &MilestonesStats{}
|
||||
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", false)
|
||||
if len(keyword) > 0 {
|
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
|
||||
}
|
||||
if repoCond.IsValid() {
|
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
|
||||
}
|
||||
stats.OpenCount, err = sess.Count(new(Milestone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sess = db.GetEngine(db.DefaultContext).Where("is_closed = ?", true)
|
||||
if len(keyword) > 0 {
|
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
|
||||
}
|
||||
if repoCond.IsValid() {
|
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
|
||||
}
|
||||
stats.ClosedCount, err = sess.Count(new(Milestone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// CountMilestones returns number of milestones in given repository with other options
|
||||
func CountMilestones(ctx context.Context, opts GetMilestonesOption) (int64, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Where(opts.toCond()).
|
||||
Count(new(Milestone))
|
||||
}
|
||||
|
||||
// CountMilestonesByRepoCond map from repo conditions to number of milestones matching the options`
|
||||
func CountMilestonesByRepoCond(repoCond builder.Cond, isClosed bool) (map[int64]int64, error) {
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed)
|
||||
if repoCond.IsValid() {
|
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
|
||||
}
|
||||
|
||||
countsSlice := make([]*struct {
|
||||
RepoID int64
|
||||
Count int64
|
||||
}, 0, 10)
|
||||
if err := sess.GroupBy("repo_id").
|
||||
Select("repo_id AS repo_id, COUNT(*) AS count").
|
||||
Table("milestone").
|
||||
Find(&countsSlice); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
countMap := make(map[int64]int64, len(countsSlice))
|
||||
for _, c := range countsSlice {
|
||||
countMap[c.RepoID] = c.Count
|
||||
}
|
||||
return countMap, nil
|
||||
}
|
||||
|
||||
// CountMilestonesByRepoCondAndKw map from repo conditions and the keyword of milestones' name to number of milestones matching the options`
|
||||
func CountMilestonesByRepoCondAndKw(repoCond builder.Cond, keyword string, isClosed bool) (map[int64]int64, error) {
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed)
|
||||
if len(keyword) > 0 {
|
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
|
||||
}
|
||||
if repoCond.IsValid() {
|
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
|
||||
}
|
||||
|
||||
countsSlice := make([]*struct {
|
||||
RepoID int64
|
||||
Count int64
|
||||
}, 0, 10)
|
||||
if err := sess.GroupBy("repo_id").
|
||||
Select("repo_id AS repo_id, COUNT(*) AS count").
|
||||
Table("milestone").
|
||||
Find(&countsSlice); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
countMap := make(map[int64]int64, len(countsSlice))
|
||||
for _, c := range countsSlice {
|
||||
countMap[c.RepoID] = c.Count
|
||||
}
|
||||
return countMap, nil
|
||||
}
|
||||
|
||||
func updateRepoMilestoneNum(ctx context.Context, repoID int64) error {
|
||||
_, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_milestones=(SELECT count(*) FROM milestone WHERE repo_id=?),num_closed_milestones=(SELECT count(*) FROM milestone WHERE repo_id=? AND is_closed=?) WHERE id=?",
|
||||
repoID,
|
||||
@@ -588,53 +332,6 @@ func updateRepoMilestoneNum(ctx context.Context, repoID int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// _____ _ _ _____ _
|
||||
// |_ _| __ __ _ ___| | _____ __| |_ _(_)_ __ ___ ___ ___
|
||||
// | || '__/ _` |/ __| |/ / _ \/ _` | | | | | '_ ` _ \ / _ \/ __|
|
||||
// | || | | (_| | (__| < __/ (_| | | | | | | | | | | __/\__ \
|
||||
// |_||_| \__,_|\___|_|\_\___|\__,_| |_| |_|_| |_| |_|\___||___/
|
||||
//
|
||||
|
||||
func (milestones MilestoneList) loadTotalTrackedTimes(ctx context.Context) error {
|
||||
type totalTimesByMilestone struct {
|
||||
MilestoneID int64
|
||||
Time int64
|
||||
}
|
||||
if len(milestones) == 0 {
|
||||
return nil
|
||||
}
|
||||
trackedTimes := make(map[int64]int64, len(milestones))
|
||||
|
||||
// Get total tracked time by milestone_id
|
||||
rows, err := db.GetEngine(ctx).Table("issue").
|
||||
Join("INNER", "milestone", "issue.milestone_id = milestone.id").
|
||||
Join("LEFT", "tracked_time", "tracked_time.issue_id = issue.id").
|
||||
Where("tracked_time.deleted = ?", false).
|
||||
Select("milestone_id, sum(time) as time").
|
||||
In("milestone_id", milestones.getMilestoneIDs()).
|
||||
GroupBy("milestone_id").
|
||||
Rows(new(totalTimesByMilestone))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var totalTime totalTimesByMilestone
|
||||
err = rows.Scan(&totalTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
trackedTimes[totalTime.MilestoneID] = totalTime.Time
|
||||
}
|
||||
|
||||
for _, milestone := range milestones {
|
||||
milestone.TotalTrackedTime = trackedTimes[milestone.ID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Milestone) loadTotalTrackedTime(ctx context.Context) error {
|
||||
type totalTimesByMilestone struct {
|
||||
MilestoneID int64
|
||||
@@ -658,12 +355,33 @@ func (m *Milestone) loadTotalTrackedTime(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime by a batch request
|
||||
func (milestones MilestoneList) LoadTotalTrackedTimes() error {
|
||||
return milestones.loadTotalTrackedTimes(db.DefaultContext)
|
||||
}
|
||||
|
||||
// LoadTotalTrackedTime loads the tracked time for the milestone
|
||||
func (m *Milestone) LoadTotalTrackedTime() error {
|
||||
return m.loadTotalTrackedTime(db.DefaultContext)
|
||||
}
|
||||
|
||||
// InsertMilestones creates milestones of repository.
|
||||
func InsertMilestones(ms ...*Milestone) (err error) {
|
||||
if len(ms) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
// to return the id, so we should not use batch insert
|
||||
for _, m := range ms {
|
||||
if _, err = sess.NoAutoTime().Insert(m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = db.Exec(ctx, "UPDATE `repository` SET num_milestones = num_milestones + ? WHERE id = ?", len(ms), ms[0].RepoID); err != nil {
|
||||
return err
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package issues
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// MilestoneList is a list of milestones offering additional functionality
|
||||
type MilestoneList []*Milestone
|
||||
|
||||
func (milestones MilestoneList) getMilestoneIDs() []int64 {
|
||||
ids := make([]int64, 0, len(milestones))
|
||||
for _, ms := range milestones {
|
||||
ids = append(ids, ms.ID)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// GetMilestonesOption contain options to get milestones
|
||||
type GetMilestonesOption struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
State api.StateType
|
||||
Name string
|
||||
SortType string
|
||||
}
|
||||
|
||||
func (opts GetMilestonesOption) toCond() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID != 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
|
||||
switch opts.State {
|
||||
case api.StateClosed:
|
||||
cond = cond.And(builder.Eq{"is_closed": true})
|
||||
case api.StateAll:
|
||||
break
|
||||
// api.StateOpen:
|
||||
default:
|
||||
cond = cond.And(builder.Eq{"is_closed": false})
|
||||
}
|
||||
|
||||
if len(opts.Name) != 0 {
|
||||
cond = cond.And(db.BuildCaseInsensitiveLike("name", opts.Name))
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
// GetMilestones returns milestones filtered by GetMilestonesOption's
|
||||
func GetMilestones(opts GetMilestonesOption) (MilestoneList, int64, error) {
|
||||
sess := db.GetEngine(db.DefaultContext).Where(opts.toCond())
|
||||
|
||||
if opts.Page != 0 {
|
||||
sess = db.SetSessionPagination(sess, &opts)
|
||||
}
|
||||
|
||||
switch opts.SortType {
|
||||
case "furthestduedate":
|
||||
sess.Desc("deadline_unix")
|
||||
case "leastcomplete":
|
||||
sess.Asc("completeness")
|
||||
case "mostcomplete":
|
||||
sess.Desc("completeness")
|
||||
case "leastissues":
|
||||
sess.Asc("num_issues")
|
||||
case "mostissues":
|
||||
sess.Desc("num_issues")
|
||||
case "id":
|
||||
sess.Asc("id")
|
||||
default:
|
||||
sess.Asc("deadline_unix").Asc("id")
|
||||
}
|
||||
|
||||
miles := make([]*Milestone, 0, opts.PageSize)
|
||||
total, err := sess.FindAndCount(&miles)
|
||||
return miles, total, err
|
||||
}
|
||||
|
||||
// GetMilestoneIDsByNames returns a list of milestone ids by given names.
|
||||
// It doesn't filter them by repo, so it could return milestones belonging to different repos.
|
||||
// It's used for filtering issues via indexer, otherwise it would be useless.
|
||||
// Since it could return milestones with the same name, so the length of returned ids could be more than the length of names.
|
||||
func GetMilestoneIDsByNames(ctx context.Context, names []string) ([]int64, error) {
|
||||
var ids []int64
|
||||
return ids, db.GetEngine(ctx).Table("milestone").
|
||||
Where(db.BuildCaseInsensitiveIn("name", names)).
|
||||
Cols("id").
|
||||
Find(&ids)
|
||||
}
|
||||
|
||||
// SearchMilestones search milestones
|
||||
func SearchMilestones(repoCond builder.Cond, page int, isClosed bool, sortType, keyword string) (MilestoneList, error) {
|
||||
miles := make([]*Milestone, 0, setting.UI.IssuePagingNum)
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed)
|
||||
if len(keyword) > 0 {
|
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
|
||||
}
|
||||
if repoCond.IsValid() {
|
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
|
||||
}
|
||||
if page > 0 {
|
||||
sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum)
|
||||
}
|
||||
|
||||
switch sortType {
|
||||
case "furthestduedate":
|
||||
sess.Desc("deadline_unix")
|
||||
case "leastcomplete":
|
||||
sess.Asc("completeness")
|
||||
case "mostcomplete":
|
||||
sess.Desc("completeness")
|
||||
case "leastissues":
|
||||
sess.Asc("num_issues")
|
||||
case "mostissues":
|
||||
sess.Desc("num_issues")
|
||||
default:
|
||||
sess.Asc("deadline_unix")
|
||||
}
|
||||
return miles, sess.Find(&miles)
|
||||
}
|
||||
|
||||
// GetMilestonesByRepoIDs returns a list of milestones of given repositories and status.
|
||||
func GetMilestonesByRepoIDs(repoIDs []int64, page int, isClosed bool, sortType string) (MilestoneList, error) {
|
||||
return SearchMilestones(
|
||||
builder.In("repo_id", repoIDs),
|
||||
page,
|
||||
isClosed,
|
||||
sortType,
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
func (milestones MilestoneList) loadTotalTrackedTimes(ctx context.Context) error {
|
||||
type totalTimesByMilestone struct {
|
||||
MilestoneID int64
|
||||
Time int64
|
||||
}
|
||||
if len(milestones) == 0 {
|
||||
return nil
|
||||
}
|
||||
trackedTimes := make(map[int64]int64, len(milestones))
|
||||
|
||||
// Get total tracked time by milestone_id
|
||||
rows, err := db.GetEngine(ctx).Table("issue").
|
||||
Join("INNER", "milestone", "issue.milestone_id = milestone.id").
|
||||
Join("LEFT", "tracked_time", "tracked_time.issue_id = issue.id").
|
||||
Where("tracked_time.deleted = ?", false).
|
||||
Select("milestone_id, sum(time) as time").
|
||||
In("milestone_id", milestones.getMilestoneIDs()).
|
||||
GroupBy("milestone_id").
|
||||
Rows(new(totalTimesByMilestone))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var totalTime totalTimesByMilestone
|
||||
err = rows.Scan(&totalTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
trackedTimes[totalTime.MilestoneID] = totalTime.Time
|
||||
}
|
||||
|
||||
for _, milestone := range milestones {
|
||||
milestone.TotalTrackedTime = trackedTimes[milestone.ID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime by a batch request
|
||||
func (milestones MilestoneList) LoadTotalTrackedTimes() error {
|
||||
return milestones.loadTotalTrackedTimes(db.DefaultContext)
|
||||
}
|
||||
|
||||
// CountMilestones returns number of milestones in given repository with other options
|
||||
func CountMilestones(ctx context.Context, opts GetMilestonesOption) (int64, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Where(opts.toCond()).
|
||||
Count(new(Milestone))
|
||||
}
|
||||
|
||||
// CountMilestonesByRepoCond map from repo conditions to number of milestones matching the options`
|
||||
func CountMilestonesByRepoCond(repoCond builder.Cond, isClosed bool) (map[int64]int64, error) {
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed)
|
||||
if repoCond.IsValid() {
|
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
|
||||
}
|
||||
|
||||
countsSlice := make([]*struct {
|
||||
RepoID int64
|
||||
Count int64
|
||||
}, 0, 10)
|
||||
if err := sess.GroupBy("repo_id").
|
||||
Select("repo_id AS repo_id, COUNT(*) AS count").
|
||||
Table("milestone").
|
||||
Find(&countsSlice); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
countMap := make(map[int64]int64, len(countsSlice))
|
||||
for _, c := range countsSlice {
|
||||
countMap[c.RepoID] = c.Count
|
||||
}
|
||||
return countMap, nil
|
||||
}
|
||||
|
||||
// CountMilestonesByRepoCondAndKw map from repo conditions and the keyword of milestones' name to number of milestones matching the options`
|
||||
func CountMilestonesByRepoCondAndKw(repoCond builder.Cond, keyword string, isClosed bool) (map[int64]int64, error) {
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed)
|
||||
if len(keyword) > 0 {
|
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
|
||||
}
|
||||
if repoCond.IsValid() {
|
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
|
||||
}
|
||||
|
||||
countsSlice := make([]*struct {
|
||||
RepoID int64
|
||||
Count int64
|
||||
}, 0, 10)
|
||||
if err := sess.GroupBy("repo_id").
|
||||
Select("repo_id AS repo_id, COUNT(*) AS count").
|
||||
Table("milestone").
|
||||
Find(&countsSlice); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
countMap := make(map[int64]int64, len(countsSlice))
|
||||
for _, c := range countsSlice {
|
||||
countMap[c.RepoID] = c.Count
|
||||
}
|
||||
return countMap, nil
|
||||
}
|
||||
|
||||
// MilestonesStats represents milestone statistic information.
|
||||
type MilestonesStats struct {
|
||||
OpenCount, ClosedCount int64
|
||||
}
|
||||
|
||||
// Total returns the total counts of milestones
|
||||
func (m MilestonesStats) Total() int64 {
|
||||
return m.OpenCount + m.ClosedCount
|
||||
}
|
||||
|
||||
// GetMilestonesStatsByRepoCond returns milestone statistic information for dashboard by given conditions.
|
||||
func GetMilestonesStatsByRepoCond(repoCond builder.Cond) (*MilestonesStats, error) {
|
||||
var err error
|
||||
stats := &MilestonesStats{}
|
||||
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", false)
|
||||
if repoCond.IsValid() {
|
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
|
||||
}
|
||||
stats.OpenCount, err = sess.Count(new(Milestone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sess = db.GetEngine(db.DefaultContext).Where("is_closed = ?", true)
|
||||
if repoCond.IsValid() {
|
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
|
||||
}
|
||||
stats.ClosedCount, err = sess.Count(new(Milestone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// GetMilestonesStatsByRepoCondAndKw returns milestone statistic information for dashboard by given repo conditions and name keyword.
|
||||
func GetMilestonesStatsByRepoCondAndKw(repoCond builder.Cond, keyword string) (*MilestonesStats, error) {
|
||||
var err error
|
||||
stats := &MilestonesStats{}
|
||||
|
||||
sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", false)
|
||||
if len(keyword) > 0 {
|
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
|
||||
}
|
||||
if repoCond.IsValid() {
|
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
|
||||
}
|
||||
stats.OpenCount, err = sess.Count(new(Milestone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sess = db.GetEngine(db.DefaultContext).Where("is_closed = ?", true)
|
||||
if len(keyword) > 0 {
|
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
|
||||
}
|
||||
if repoCond.IsValid() {
|
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
|
||||
}
|
||||
stats.ClosedCount, err = sess.Count(new(Milestone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
@@ -351,3 +351,21 @@ func TestUpdateMilestoneCounters(t *testing.T) {
|
||||
assert.NoError(t, issues_model.UpdateMilestoneCounters(db.DefaultContext, issue.MilestoneID))
|
||||
unittest.CheckConsistencyFor(t, &issues_model.Milestone{})
|
||||
}
|
||||
|
||||
func TestMigrate_InsertMilestones(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reponame := "repo1"
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame})
|
||||
name := "milestonetest1"
|
||||
ms := &issues_model.Milestone{
|
||||
RepoID: repo.ID,
|
||||
Name: name,
|
||||
}
|
||||
err := issues_model.InsertMilestones(ms)
|
||||
assert.NoError(t, err)
|
||||
unittest.AssertExistsAndLoadBean(t, ms)
|
||||
repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
|
||||
assert.EqualValues(t, repo.NumMilestones+1, repoModified.NumMilestones)
|
||||
|
||||
unittest.CheckConsistencyFor(t, &issues_model.Milestone{})
|
||||
}
|
||||
|
||||
@@ -1105,3 +1105,23 @@ func TokenizeCodeOwnersLine(line string) []string {
|
||||
|
||||
return tokens
|
||||
}
|
||||
|
||||
// InsertPullRequests inserted pull requests
|
||||
func InsertPullRequests(ctx context.Context, prs ...*PullRequest) error {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
sess := db.GetEngine(ctx)
|
||||
for _, pr := range prs {
|
||||
if err := insertIssue(ctx, pr.Issue); err != nil {
|
||||
return err
|
||||
}
|
||||
pr.IssueID = pr.Issue.ID
|
||||
if _, err := sess.NoAutoTime().Insert(pr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -316,9 +317,9 @@ func TestParseCodeOwnersLine(t *testing.T) {
|
||||
{Line: "# comment", Tokens: []string{}},
|
||||
{Line: "!.* @user1 @org1/team1", Tokens: []string{"!.*", "@user1", "@org1/team1"}},
|
||||
{Line: `.*\\.js @user2 #comment`, Tokens: []string{`.*\.js`, "@user2"}},
|
||||
{Line: `docs/(aws|google|azure)/[^/]*\\.(md|txt) @user3 @org2/team2`, Tokens: []string{`docs/(aws|google|azure)/[^/]*\.(md|txt)`, "@user3", "@org2/team2"}},
|
||||
{Line: `\#path @user3`, Tokens: []string{`#path`, "@user3"}},
|
||||
{Line: `path\ with\ spaces/ @user3`, Tokens: []string{`path with spaces/`, "@user3"}},
|
||||
{Line: `docs/(aws|google|azure)/[^/]*\\.(md|txt) @org3 @org2/team2`, Tokens: []string{`docs/(aws|google|azure)/[^/]*\.(md|txt)`, "@org3", "@org2/team2"}},
|
||||
{Line: `\#path @org3`, Tokens: []string{`#path`, "@org3"}},
|
||||
{Line: `path\ with\ spaces/ @org3`, Tokens: []string{`path with spaces/`, "@org3"}},
|
||||
}
|
||||
|
||||
for _, g := range given {
|
||||
@@ -334,6 +335,34 @@ func TestGetApprovers(t *testing.T) {
|
||||
// to assert that there are no duplicated approvers.
|
||||
setting.Repository.PullRequest.DefaultMergeMessageOfficialApproversOnly = false
|
||||
approvers := pr.GetApprovers()
|
||||
expected := "Reviewed-by: User Five <user5@example.com>\nReviewed-by: User Six <user6@example.com>\n"
|
||||
expected := "Reviewed-by: User Five <user5@example.com>\nReviewed-by: Org Six <org6@example.com>\n"
|
||||
assert.EqualValues(t, expected, approvers)
|
||||
}
|
||||
|
||||
func TestMigrate_InsertPullRequests(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reponame := "repo1"
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
i := &issues_model.Issue{
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
Title: "title1",
|
||||
Content: "issuecontent1",
|
||||
IsPull: true,
|
||||
PosterID: owner.ID,
|
||||
Poster: owner,
|
||||
}
|
||||
|
||||
p := &issues_model.PullRequest{
|
||||
Issue: i,
|
||||
}
|
||||
|
||||
err := issues_model.InsertPullRequests(db.DefaultContext, p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_ = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{IssueID: i.ID})
|
||||
|
||||
unittest.CheckConsistencyFor(t, &issues_model.Issue{}, &issues_model.PullRequest{})
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func TestIssueReactionCount(t *testing.T) {
|
||||
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
ghost := user_model.NewGhostUser()
|
||||
|
||||
@@ -92,8 +92,8 @@ func TestIssueReactionCount(t *testing.T) {
|
||||
|
||||
addReaction(t, user1.ID, issueID, 0, "heart")
|
||||
addReaction(t, user2.ID, issueID, 0, "heart")
|
||||
addReaction(t, user3.ID, issueID, 0, "heart")
|
||||
addReaction(t, user3.ID, issueID, 0, "+1")
|
||||
addReaction(t, org3.ID, issueID, 0, "heart")
|
||||
addReaction(t, org3.ID, issueID, 0, "+1")
|
||||
addReaction(t, user4.ID, issueID, 0, "+1")
|
||||
addReaction(t, user4.ID, issueID, 0, "heart")
|
||||
addReaction(t, ghost.ID, issueID, 0, "-1")
|
||||
@@ -136,7 +136,7 @@ func TestIssueCommentDeleteReaction(t *testing.T) {
|
||||
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
var issue1ID int64 = 1
|
||||
@@ -144,7 +144,7 @@ func TestIssueCommentDeleteReaction(t *testing.T) {
|
||||
|
||||
addReaction(t, user1.ID, issue1ID, comment1ID, "heart")
|
||||
addReaction(t, user2.ID, issue1ID, comment1ID, "heart")
|
||||
addReaction(t, user3.ID, issue1ID, comment1ID, "heart")
|
||||
addReaction(t, org3.ID, issue1ID, comment1ID, "heart")
|
||||
addReaction(t, user4.ID, issue1ID, comment1ID, "+1")
|
||||
|
||||
reactionsList, _, err := issues_model.FindReactions(db.DefaultContext, issues_model.FindReactionsOptions{
|
||||
|
||||
@@ -80,7 +80,7 @@ func TestFindLatestReviews(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, reviews, 2)
|
||||
assert.Equal(t, "duplicate review from user5 (latest)", reviews[0].Content)
|
||||
assert.Equal(t, "singular review from user6 and final review for this pr", reviews[1].Content)
|
||||
assert.Equal(t, "singular review from org6 and final review for this pr", reviews[1].Content)
|
||||
}
|
||||
|
||||
func TestGetCurrentReview(t *testing.T) {
|
||||
@@ -123,13 +123,13 @@ func TestGetReviewersByIssueID(t *testing.T) {
|
||||
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
expectedReviews := []*issues_model.Review{}
|
||||
expectedReviews = append(expectedReviews,
|
||||
&issues_model.Review{
|
||||
Reviewer: user3,
|
||||
Reviewer: org3,
|
||||
Type: issues_model.ReviewTypeReject,
|
||||
UpdatedUnix: 946684812,
|
||||
},
|
||||
|
||||
@@ -60,7 +60,7 @@ func TestCreateOrStopIssueStopwatch(t *testing.T) {
|
||||
|
||||
user2, err := user_model.GetUserByID(db.DefaultContext, 2)
|
||||
assert.NoError(t, err)
|
||||
user3, err := user_model.GetUserByID(db.DefaultContext, 3)
|
||||
org3, err := user_model.GetUserByID(db.DefaultContext, 3)
|
||||
assert.NoError(t, err)
|
||||
|
||||
issue1, err := issues_model.GetIssueByID(db.DefaultContext, 1)
|
||||
@@ -68,7 +68,7 @@ func TestCreateOrStopIssueStopwatch(t *testing.T) {
|
||||
issue2, err := issues_model.GetIssueByID(db.DefaultContext, 2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, issues_model.CreateOrStopIssueStopwatch(user3, issue1))
|
||||
assert.NoError(t, issues_model.CreateOrStopIssueStopwatch(org3, issue1))
|
||||
sw := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: 3, IssueID: 1})
|
||||
assert.LessOrEqual(t, sw.CreatedUnix, timeutil.TimeStampNow())
|
||||
|
||||
|
||||
@@ -18,14 +18,14 @@ import (
|
||||
func TestAddTime(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user3, err := user_model.GetUserByID(db.DefaultContext, 3)
|
||||
org3, err := user_model.GetUserByID(db.DefaultContext, 3)
|
||||
assert.NoError(t, err)
|
||||
|
||||
issue1, err := issues_model.GetIssueByID(db.DefaultContext, 1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 3661 = 1h 1min 1s
|
||||
trackedTime, err := issues_model.AddTime(db.DefaultContext, user3, issue1, 3661, time.Now())
|
||||
trackedTime, err := issues_model.AddTime(db.DefaultContext, org3, issue1, 3661, time.Now())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(3), trackedTime.UserID)
|
||||
assert.Equal(t, int64(1), trackedTime.IssueID)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
_ "code.gitea.io/gitea/models/system"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
||||
// InsertMilestones creates milestones of repository.
|
||||
func InsertMilestones(ms ...*issues_model.Milestone) (err error) {
|
||||
if len(ms) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
// to return the id, so we should not use batch insert
|
||||
for _, m := range ms {
|
||||
if _, err = sess.NoAutoTime().Insert(m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = db.Exec(ctx, "UPDATE `repository` SET num_milestones = num_milestones + ? WHERE id = ?", len(ms), ms[0].RepoID); err != nil {
|
||||
return err
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// InsertIssues insert issues to database
|
||||
func InsertIssues(issues ...*issues_model.Issue) error {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
for _, issue := range issues {
|
||||
if err := insertIssue(ctx, issue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func insertIssue(ctx context.Context, issue *issues_model.Issue) error {
|
||||
sess := db.GetEngine(ctx)
|
||||
if _, err := sess.NoAutoTime().Insert(issue); err != nil {
|
||||
return err
|
||||
}
|
||||
issueLabels := make([]issues_model.IssueLabel, 0, len(issue.Labels))
|
||||
for _, label := range issue.Labels {
|
||||
issueLabels = append(issueLabels, issues_model.IssueLabel{
|
||||
IssueID: issue.ID,
|
||||
LabelID: label.ID,
|
||||
})
|
||||
}
|
||||
if len(issueLabels) > 0 {
|
||||
if _, err := sess.Insert(issueLabels); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, reaction := range issue.Reactions {
|
||||
reaction.IssueID = issue.ID
|
||||
}
|
||||
|
||||
if len(issue.Reactions) > 0 {
|
||||
if _, err := sess.Insert(issue.Reactions); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertIssueComments inserts many comments of issues.
|
||||
func InsertIssueComments(comments []*issues_model.Comment) error {
|
||||
if len(comments) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
issueIDs := make(container.Set[int64])
|
||||
for _, comment := range comments {
|
||||
issueIDs.Add(comment.IssueID)
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
for _, comment := range comments {
|
||||
if _, err := db.GetEngine(ctx).NoAutoTime().Insert(comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, reaction := range comment.Reactions {
|
||||
reaction.IssueID = comment.IssueID
|
||||
reaction.CommentID = comment.ID
|
||||
}
|
||||
if len(comment.Reactions) > 0 {
|
||||
if err := db.Insert(ctx, comment.Reactions); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for issueID := range issueIDs {
|
||||
if _, err := db.Exec(ctx, "UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ? AND `type`=?) WHERE id = ?",
|
||||
issueID, issues_model.CommentTypeComment, issueID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// InsertPullRequests inserted pull requests
|
||||
func InsertPullRequests(ctx context.Context, prs ...*issues_model.PullRequest) error {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
sess := db.GetEngine(ctx)
|
||||
for _, pr := range prs {
|
||||
if err := insertIssue(ctx, pr.Issue); err != nil {
|
||||
return err
|
||||
}
|
||||
pr.IssueID = pr.Issue.ID
|
||||
if _, err := sess.NoAutoTime().Insert(pr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// InsertReleases migrates release
|
||||
func InsertReleases(rels ...*repo_model.Release) error {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
for _, rel := range rels {
|
||||
if _, err := sess.NoAutoTime().Insert(rel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(rel.Attachments) > 0 {
|
||||
for i := range rel.Attachments {
|
||||
rel.Attachments[i].ReleaseID = rel.ID
|
||||
}
|
||||
|
||||
if _, err := sess.NoAutoTime().Insert(rel.Attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// UpdateMigrationsByType updates all migrated repositories' posterid from gitServiceType to replace originalAuthorID to posterID
|
||||
func UpdateMigrationsByType(tp structs.GitServiceType, externalUserID string, userID int64) error {
|
||||
if err := issues_model.UpdateIssuesMigrationsByType(tp, externalUserID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := issues_model.UpdateCommentsMigrationsByType(tp, externalUserID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := repo_model.UpdateReleasesMigrationsByType(tp, externalUserID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := issues_model.UpdateReactionsMigrationsByType(tp, externalUserID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
return issues_model.UpdateReviewsMigrationsByType(tp, externalUserID, userID)
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMigrate_InsertMilestones(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reponame := "repo1"
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame})
|
||||
name := "milestonetest1"
|
||||
ms := &issues_model.Milestone{
|
||||
RepoID: repo.ID,
|
||||
Name: name,
|
||||
}
|
||||
err := InsertMilestones(ms)
|
||||
assert.NoError(t, err)
|
||||
unittest.AssertExistsAndLoadBean(t, ms)
|
||||
repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
|
||||
assert.EqualValues(t, repo.NumMilestones+1, repoModified.NumMilestones)
|
||||
|
||||
unittest.CheckConsistencyFor(t, &issues_model.Milestone{})
|
||||
}
|
||||
|
||||
func assertCreateIssues(t *testing.T, isPull bool) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reponame := "repo1"
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
|
||||
milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1})
|
||||
assert.EqualValues(t, milestone.ID, 1)
|
||||
reaction := &issues_model.Reaction{
|
||||
Type: "heart",
|
||||
UserID: owner.ID,
|
||||
}
|
||||
|
||||
title := "issuetitle1"
|
||||
is := &issues_model.Issue{
|
||||
RepoID: repo.ID,
|
||||
MilestoneID: milestone.ID,
|
||||
Repo: repo,
|
||||
Title: title,
|
||||
Content: "issuecontent1",
|
||||
IsPull: isPull,
|
||||
PosterID: owner.ID,
|
||||
Poster: owner,
|
||||
IsClosed: true,
|
||||
Labels: []*issues_model.Label{label},
|
||||
Reactions: []*issues_model.Reaction{reaction},
|
||||
}
|
||||
err := InsertIssues(is)
|
||||
assert.NoError(t, err)
|
||||
|
||||
i := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: title})
|
||||
unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: owner.ID, IssueID: i.ID})
|
||||
}
|
||||
|
||||
func TestMigrate_CreateIssuesIsPullFalse(t *testing.T) {
|
||||
assertCreateIssues(t, false)
|
||||
}
|
||||
|
||||
func TestMigrate_CreateIssuesIsPullTrue(t *testing.T) {
|
||||
assertCreateIssues(t, true)
|
||||
}
|
||||
|
||||
func TestMigrate_InsertIssueComments(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
||||
_ = issue.LoadRepo(db.DefaultContext)
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID})
|
||||
reaction := &issues_model.Reaction{
|
||||
Type: "heart",
|
||||
UserID: owner.ID,
|
||||
}
|
||||
|
||||
comment := &issues_model.Comment{
|
||||
PosterID: owner.ID,
|
||||
Poster: owner,
|
||||
IssueID: issue.ID,
|
||||
Issue: issue,
|
||||
Reactions: []*issues_model.Reaction{reaction},
|
||||
}
|
||||
|
||||
err := InsertIssueComments([]*issues_model.Comment{comment})
|
||||
assert.NoError(t, err)
|
||||
|
||||
issueModified := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
||||
assert.EqualValues(t, issue.NumComments+1, issueModified.NumComments)
|
||||
|
||||
unittest.CheckConsistencyFor(t, &issues_model.Issue{})
|
||||
}
|
||||
|
||||
func TestMigrate_InsertPullRequests(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reponame := "repo1"
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
i := &issues_model.Issue{
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
Title: "title1",
|
||||
Content: "issuecontent1",
|
||||
IsPull: true,
|
||||
PosterID: owner.ID,
|
||||
Poster: owner,
|
||||
}
|
||||
|
||||
p := &issues_model.PullRequest{
|
||||
Issue: i,
|
||||
}
|
||||
|
||||
err := InsertPullRequests(db.DefaultContext, p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_ = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{IssueID: i.ID})
|
||||
|
||||
unittest.CheckConsistencyFor(t, &issues_model.Issue{}, &issues_model.PullRequest{})
|
||||
}
|
||||
|
||||
func TestMigrate_InsertReleases(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
a := &repo_model.Attachment{
|
||||
UUID: "a0eebc91-9c0c-4ef7-bb6e-6bb9bd380a12",
|
||||
}
|
||||
r := &repo_model.Release{
|
||||
Attachments: []*repo_model.Attachment{a},
|
||||
}
|
||||
|
||||
err := InsertReleases(r)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -528,6 +528,10 @@ var migrations = []Migration{
|
||||
NewMigration("Add Version to ActionRun table", v1_21.AddVersionToActionRunTable),
|
||||
// v273 -> v274
|
||||
NewMigration("Add Action Schedule Table", v1_21.AddActionScheduleTable),
|
||||
// v274 -> v275
|
||||
NewMigration("Add Actions artifacts expiration date", v1_21.AddExpiredUnixColumnInActionArtifactTable),
|
||||
// v275 -> v276
|
||||
NewMigration("Add ScheduleID for ActionRun", v1_21.AddScheduleIDForActionRun),
|
||||
// to modify later
|
||||
NewMigration("Add size limit on repository", v1_21.AddSizeLimitOnRepo),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_21 //nolint
|
||||
import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddExpiredUnixColumnInActionArtifactTable(x *xorm.Engine) error {
|
||||
type ActionArtifact struct {
|
||||
ExpiredUnix timeutil.TimeStamp `xorm:"index"` // time when the artifact will be expired
|
||||
}
|
||||
if err := x.Sync(new(ActionArtifact)); err != nil {
|
||||
return err
|
||||
}
|
||||
return updateArtifactsExpiredUnixTo90Days(x)
|
||||
}
|
||||
|
||||
func updateArtifactsExpiredUnixTo90Days(x *xorm.Engine) error {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
expiredTime := time.Now().AddDate(0, 0, 90).Unix()
|
||||
if _, err := sess.Exec(`UPDATE action_artifact SET expired_unix=? WHERE status='2' AND expired_unix is NULL`, expiredTime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_21 //nolint
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddScheduleIDForActionRun(x *xorm.Engine) error {
|
||||
type ActionRun struct {
|
||||
ScheduleID int64
|
||||
}
|
||||
return x.Sync(new(ActionRun))
|
||||
}
|
||||
+32
-81
@@ -151,85 +151,6 @@ func removeAllRepositories(ctx context.Context, t *organization.Team) (err error
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasRepository returns true if given repository belong to team.
|
||||
func HasRepository(t *organization.Team, repoID int64) bool {
|
||||
return organization.HasTeamRepo(db.DefaultContext, t.OrgID, t.ID, repoID)
|
||||
}
|
||||
|
||||
// removeRepository removes a repository from a team and recalculates access
|
||||
// Note: Repository shall not be removed from team if it includes all repositories (unless the repository is deleted)
|
||||
func removeRepository(ctx context.Context, t *organization.Team, repo *repo_model.Repository, recalculate bool) (err error) {
|
||||
e := db.GetEngine(ctx)
|
||||
if err = organization.RemoveTeamRepo(ctx, t.ID, repo.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.NumRepos--
|
||||
if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Don't need to recalculate when delete a repository from organization.
|
||||
if recalculate {
|
||||
if err = access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
teamUsers, err := organization.GetTeamUsersByTeamID(ctx, t.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getTeamUsersByTeamID: %w", err)
|
||||
}
|
||||
for _, teamUser := range teamUsers {
|
||||
has, err := access_model.HasAccess(ctx, teamUser.UID, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = repo_model.WatchRepo(ctx, teamUser.UID, repo.ID, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove all IssueWatches a user has subscribed to in the repositories
|
||||
if err := issues_model.RemoveIssueWatchersByRepoID(ctx, teamUser.UID, repo.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveRepository removes repository from team of organization.
|
||||
// If the team shall include all repositories the request is ignored.
|
||||
func RemoveRepository(t *organization.Team, repoID int64) error {
|
||||
if !HasRepository(t, repoID) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if t.IncludesAllRepositories {
|
||||
return nil
|
||||
}
|
||||
|
||||
repo, err := repo_model.GetRepositoryByID(db.DefaultContext, repoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if err = removeRepository(ctx, t, repo, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// NewTeam creates a record of new team.
|
||||
// It's caller's responsibility to assign organization ID.
|
||||
func NewTeam(t *organization.Team) (err error) {
|
||||
@@ -564,12 +485,12 @@ func removeTeamMember(ctx context.Context, team *organization.Team, userID int64
|
||||
}
|
||||
|
||||
// Remove watches from now unaccessible
|
||||
if err := reconsiderWatches(ctx, repo, userID); err != nil {
|
||||
if err := ReconsiderWatches(ctx, repo, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove issue assignments from now unaccessible
|
||||
if err := reconsiderRepoIssuesAssignee(ctx, repo, userID); err != nil {
|
||||
if err := ReconsiderRepoIssuesAssignee(ctx, repo, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -602,3 +523,33 @@ func RemoveTeamMember(team *organization.Team, userID int64) error {
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func ReconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Repository, uid int64) error {
|
||||
user, err := user_model.GetUserByID(ctx, uid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if canAssigned, err := access_model.CanBeAssigned(ctx, user, repo, true); err != nil || canAssigned {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).Where(builder.Eq{"assignee_id": uid}).
|
||||
In("issue_id", builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repo.ID})).
|
||||
Delete(&issues_model.IssueAssignees{}); err != nil {
|
||||
return fmt.Errorf("Could not delete assignee[%d] %w", uid, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReconsiderWatches(ctx context.Context, repo *repo_model.Repository, uid int64) error {
|
||||
if has, err := access_model.HasAccess(ctx, uid, repo); err != nil || has {
|
||||
return err
|
||||
}
|
||||
if err := repo_model.WatchRepo(ctx, uid, repo.ID, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove all IssueWatches a user has subscribed to in the repository
|
||||
return issues_model.RemoveIssueWatchersByRepoID(ctx, uid, repo.ID)
|
||||
}
|
||||
|
||||
@@ -51,36 +51,6 @@ func TestTeam_RemoveMember(t *testing.T) {
|
||||
assert.True(t, organization.IsErrLastOrgOwner(err))
|
||||
}
|
||||
|
||||
func TestTeam_HasRepository(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
test := func(teamID, repoID int64, expected bool) {
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
||||
assert.Equal(t, expected, HasRepository(team, repoID))
|
||||
}
|
||||
test(1, 1, false)
|
||||
test(1, 3, true)
|
||||
test(1, 5, true)
|
||||
test(1, unittest.NonexistentID, false)
|
||||
|
||||
test(2, 3, true)
|
||||
test(2, 5, false)
|
||||
}
|
||||
|
||||
func TestTeam_RemoveRepository(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
testSuccess := func(teamID, repoID int64) {
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
||||
assert.NoError(t, RemoveRepository(team, repoID))
|
||||
unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: teamID, RepoID: repoID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &repo_model.Repository{ID: repoID})
|
||||
}
|
||||
testSuccess(2, 3)
|
||||
testSuccess(2, 5)
|
||||
testSuccess(1, unittest.NonexistentID)
|
||||
}
|
||||
|
||||
func TestIsUsableTeamName(t *testing.T) {
|
||||
assert.NoError(t, organization.IsUsableTeamName("usable"))
|
||||
assert.True(t, db.IsErrNameReserved(organization.IsUsableTeamName("new")))
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
_ "code.gitea.io/gitea/models"
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
_ "code.gitea.io/gitea/models/activities"
|
||||
_ "code.gitea.io/gitea/models/organization"
|
||||
_ "code.gitea.io/gitea/models/repo"
|
||||
_ "code.gitea.io/gitea/models/user"
|
||||
|
||||
@@ -115,10 +115,10 @@ func TestUser_GetMembers(t *testing.T) {
|
||||
func TestGetOrgByName(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
org, err := organization.GetOrgByName(db.DefaultContext, "user3")
|
||||
org, err := organization.GetOrgByName(db.DefaultContext, "org3")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3, org.ID)
|
||||
assert.Equal(t, "user3", org.Name)
|
||||
assert.Equal(t, "org3", org.Name)
|
||||
|
||||
_, err = organization.GetOrgByName(db.DefaultContext, "user2") // user2 is an individual
|
||||
assert.True(t, organization.IsErrOrgNotExist(err))
|
||||
@@ -343,7 +343,7 @@ func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
|
||||
func TestHasOrgVisibleTypePublic(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
|
||||
const newOrgName = "test-org-public"
|
||||
org := &organization.Organization{
|
||||
@@ -356,7 +356,7 @@ func TestHasOrgVisibleTypePublic(t *testing.T) {
|
||||
org = unittest.AssertExistsAndLoadBean(t,
|
||||
&organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization})
|
||||
test1 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), owner)
|
||||
test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user3)
|
||||
test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), org3)
|
||||
test3 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), nil)
|
||||
assert.True(t, test1) // owner of org
|
||||
assert.True(t, test2) // user not a part of org
|
||||
@@ -366,7 +366,7 @@ func TestHasOrgVisibleTypePublic(t *testing.T) {
|
||||
func TestHasOrgVisibleTypeLimited(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
|
||||
const newOrgName = "test-org-limited"
|
||||
org := &organization.Organization{
|
||||
@@ -379,7 +379,7 @@ func TestHasOrgVisibleTypeLimited(t *testing.T) {
|
||||
org = unittest.AssertExistsAndLoadBean(t,
|
||||
&organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization})
|
||||
test1 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), owner)
|
||||
test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user3)
|
||||
test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), org3)
|
||||
test3 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), nil)
|
||||
assert.True(t, test1) // owner of org
|
||||
assert.True(t, test2) // user not a part of org
|
||||
@@ -389,7 +389,7 @@ func TestHasOrgVisibleTypeLimited(t *testing.T) {
|
||||
func TestHasOrgVisibleTypePrivate(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
|
||||
const newOrgName = "test-org-private"
|
||||
org := &organization.Organization{
|
||||
@@ -402,7 +402,7 @@ func TestHasOrgVisibleTypePrivate(t *testing.T) {
|
||||
org = unittest.AssertExistsAndLoadBean(t,
|
||||
&organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization})
|
||||
test1 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), owner)
|
||||
test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user3)
|
||||
test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), org3)
|
||||
test3 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), nil)
|
||||
assert.True(t, test1) // owner of org
|
||||
assert.False(t, test2) // user not a part of org
|
||||
@@ -493,7 +493,7 @@ func TestCreateOrganization3(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
org := &organization.Organization{Name: "user3"} // should already exist
|
||||
org := &organization.Organization{Name: "org3"} // should already exist
|
||||
unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: org.Name}) // sanity check
|
||||
err := organization.CreateOrganization(org, owner)
|
||||
assert.Error(t, err)
|
||||
|
||||
@@ -30,12 +30,12 @@ func TestTeamInvite(t *testing.T) {
|
||||
t.Run("CreateAndRemove", func(t *testing.T) {
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
invite, err := organization.CreateTeamInvite(db.DefaultContext, user1, team, "user3@example.com")
|
||||
invite, err := organization.CreateTeamInvite(db.DefaultContext, user1, team, "org3@example.com")
|
||||
assert.NotNil(t, invite)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Shouldn't allow duplicate invite
|
||||
_, err = organization.CreateTeamInvite(db.DefaultContext, user1, team, "user3@example.com")
|
||||
_, err = organization.CreateTeamInvite(db.DefaultContext, user1, team, "org3@example.com")
|
||||
assert.Error(t, err)
|
||||
|
||||
// should remove invite
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
_ "code.gitea.io/gitea/models"
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
_ "code.gitea.io/gitea/models/activities"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
_ "code.gitea.io/gitea/models"
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
_ "code.gitea.io/gitea/models/activities"
|
||||
_ "code.gitea.io/gitea/models/repo"
|
||||
_ "code.gitea.io/gitea/models/user"
|
||||
)
|
||||
|
||||
-326
@@ -11,28 +11,15 @@ import (
|
||||
|
||||
_ "image/jpeg" // Needed for jpeg support
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
admin_model "code.gitea.io/gitea/models/admin"
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
secret_model "code.gitea.io/gitea/models/secret"
|
||||
system_model "code.gitea.io/gitea/models/system"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/models/webhook"
|
||||
actions_module "code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// Init initialize model
|
||||
@@ -43,319 +30,6 @@ func Init(ctx context.Context) error {
|
||||
return system_model.Init(ctx)
|
||||
}
|
||||
|
||||
// DeleteRepository deletes a repository for a user or organization.
|
||||
// make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock)
|
||||
func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
// Query the action tasks of this repo, they will be needed after they have been deleted to remove the logs
|
||||
tasks, err := actions_model.FindTasks(ctx, actions_model.FindTaskOptions{RepoID: repoID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("find actions tasks of repo %v: %w", repoID, err)
|
||||
}
|
||||
|
||||
// Query the artifacts of this repo, they will be needed after they have been deleted to remove artifacts files in ObjectStorage
|
||||
artifacts, err := actions_model.ListArtifactsByRepoID(ctx, repoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("list actions artifacts of repo %v: %w", repoID, err)
|
||||
}
|
||||
|
||||
// In case is a organization.
|
||||
org, err := user_model.GetUserByID(ctx, uid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo := &repo_model.Repository{OwnerID: uid}
|
||||
has, err := sess.ID(repoID).Get(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return repo_model.ErrRepoNotExist{
|
||||
ID: repoID,
|
||||
UID: uid,
|
||||
OwnerName: "",
|
||||
Name: "",
|
||||
}
|
||||
}
|
||||
|
||||
// Delete Deploy Keys
|
||||
deployKeys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: repoID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("listDeployKeys: %w", err)
|
||||
}
|
||||
needRewriteKeysFile := len(deployKeys) > 0
|
||||
for _, dKey := range deployKeys {
|
||||
if err := DeleteDeployKey(ctx, doer, dKey.ID); err != nil {
|
||||
return fmt.Errorf("deleteDeployKeys: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if cnt, err := sess.ID(repoID).Delete(&repo_model.Repository{}); err != nil {
|
||||
return err
|
||||
} else if cnt != 1 {
|
||||
return repo_model.ErrRepoNotExist{
|
||||
ID: repoID,
|
||||
UID: uid,
|
||||
OwnerName: "",
|
||||
Name: "",
|
||||
}
|
||||
}
|
||||
|
||||
if org.IsOrganization() {
|
||||
teams, err := organization.FindOrgTeams(ctx, org.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, t := range teams {
|
||||
if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repoID) {
|
||||
continue
|
||||
} else if err = removeRepository(ctx, t, repo, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attachments := make([]*repo_model.Attachment, 0, 20)
|
||||
if err = sess.Join("INNER", "`release`", "`release`.id = `attachment`.release_id").
|
||||
Where("`release`.repo_id = ?", repoID).
|
||||
Find(&attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
releaseAttachments := make([]string, 0, len(attachments))
|
||||
for i := 0; i < len(attachments); i++ {
|
||||
releaseAttachments = append(releaseAttachments, attachments[i].RelativePath())
|
||||
}
|
||||
|
||||
if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars=num_stars-1 WHERE id IN (SELECT `uid` FROM `star` WHERE repo_id = ?)", repo.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).In("hook_id", builder.Select("id").From("webhook").Where(builder.Eq{"webhook.repo_id": repo.ID})).
|
||||
Delete(&webhook.HookTask{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := db.DeleteBeans(ctx,
|
||||
&access_model.Access{RepoID: repo.ID},
|
||||
&activities_model.Action{RepoID: repo.ID},
|
||||
&repo_model.Collaboration{RepoID: repoID},
|
||||
&issues_model.Comment{RefRepoID: repoID},
|
||||
&git_model.CommitStatus{RepoID: repoID},
|
||||
&git_model.Branch{RepoID: repoID},
|
||||
&git_model.LFSLock{RepoID: repoID},
|
||||
&repo_model.LanguageStat{RepoID: repoID},
|
||||
&issues_model.Milestone{RepoID: repoID},
|
||||
&repo_model.Mirror{RepoID: repoID},
|
||||
&activities_model.Notification{RepoID: repoID},
|
||||
&git_model.ProtectedBranch{RepoID: repoID},
|
||||
&git_model.ProtectedTag{RepoID: repoID},
|
||||
&repo_model.PushMirror{RepoID: repoID},
|
||||
&repo_model.Release{RepoID: repoID},
|
||||
&repo_model.RepoIndexerStatus{RepoID: repoID},
|
||||
&repo_model.Redirect{RedirectRepoID: repoID},
|
||||
&repo_model.RepoUnit{RepoID: repoID},
|
||||
&repo_model.Star{RepoID: repoID},
|
||||
&admin_model.Task{RepoID: repoID},
|
||||
&repo_model.Watch{RepoID: repoID},
|
||||
&webhook.Webhook{RepoID: repoID},
|
||||
&secret_model.Secret{RepoID: repoID},
|
||||
&actions_model.ActionTaskStep{RepoID: repoID},
|
||||
&actions_model.ActionTask{RepoID: repoID},
|
||||
&actions_model.ActionRunJob{RepoID: repoID},
|
||||
&actions_model.ActionRun{RepoID: repoID},
|
||||
&actions_model.ActionRunner{RepoID: repoID},
|
||||
&actions_model.ActionScheduleSpec{RepoID: repoID},
|
||||
&actions_model.ActionSchedule{RepoID: repoID},
|
||||
&actions_model.ActionArtifact{RepoID: repoID},
|
||||
); err != nil {
|
||||
return fmt.Errorf("deleteBeans: %w", err)
|
||||
}
|
||||
|
||||
// Delete Labels and related objects
|
||||
if err := issues_model.DeleteLabelsByRepoID(ctx, repoID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete Pulls and related objects
|
||||
if err := issues_model.DeletePullsByBaseRepoID(ctx, repoID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete Issues and related objects
|
||||
var attachmentPaths []string
|
||||
if attachmentPaths, err = issues_model.DeleteIssuesByRepoID(ctx, repoID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete issue index
|
||||
if err := db.DeleteResourceIndex(ctx, "issue_index", repoID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if repo.IsFork {
|
||||
if _, err := db.Exec(ctx, "UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil {
|
||||
return fmt.Errorf("decrease fork count: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := db.Exec(ctx, "UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", uid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(repo.Topics) > 0 {
|
||||
if err := repo_model.RemoveTopicsFromRepo(ctx, repo.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := project_model.DeleteProjectByRepoID(ctx, repoID); err != nil {
|
||||
return fmt.Errorf("unable to delete projects for repo[%d]: %w", repoID, err)
|
||||
}
|
||||
|
||||
// Remove LFS objects
|
||||
var lfsObjects []*git_model.LFSMetaObject
|
||||
if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lfsPaths := make([]string, 0, len(lfsObjects))
|
||||
for _, v := range lfsObjects {
|
||||
count, err := db.CountByBean(ctx, &git_model.LFSMetaObject{Pointer: lfs.Pointer{Oid: v.Oid}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
lfsPaths = append(lfsPaths, v.RelativePath())
|
||||
}
|
||||
|
||||
if _, err := db.DeleteByBean(ctx, &git_model.LFSMetaObject{RepositoryID: repoID}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove archives
|
||||
var archives []*repo_model.RepoArchiver
|
||||
if err = sess.Where("repo_id=?", repoID).Find(&archives); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
archivePaths := make([]string, 0, len(archives))
|
||||
for _, v := range archives {
|
||||
archivePaths = append(archivePaths, v.RelativePath())
|
||||
}
|
||||
|
||||
if _, err := db.DeleteByBean(ctx, &repo_model.RepoArchiver{RepoID: repoID}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if repo.NumForks > 0 {
|
||||
if _, err = sess.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil {
|
||||
log.Error("reset 'fork_id' and 'is_fork': %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get all attachments with both issue_id and release_id are zero
|
||||
var newAttachments []*repo_model.Attachment
|
||||
if err := sess.Where(builder.Eq{
|
||||
"repo_id": repo.ID,
|
||||
"issue_id": 0,
|
||||
"release_id": 0,
|
||||
}).Find(&newAttachments); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newAttachmentPaths := make([]string, 0, len(newAttachments))
|
||||
for _, attach := range newAttachments {
|
||||
newAttachmentPaths = append(newAttachmentPaths, attach.RelativePath())
|
||||
}
|
||||
|
||||
if _, err := sess.Where("repo_id=?", repo.ID).Delete(new(repo_model.Attachment)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = committer.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
committer.Close()
|
||||
|
||||
if needRewriteKeysFile {
|
||||
if err := asymkey_model.RewriteAllPublicKeys(); err != nil {
|
||||
log.Error("RewriteAllPublicKeys failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// We should always delete the files after the database transaction succeed. If
|
||||
// we delete the file but the database rollback, the repository will be broken.
|
||||
|
||||
// Remove repository files.
|
||||
repoPath := repo.RepoPath()
|
||||
system_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath)
|
||||
|
||||
// Remove wiki files
|
||||
if repo.HasWiki() {
|
||||
system_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository wiki", repo.WikiPath())
|
||||
}
|
||||
|
||||
// Remove archives
|
||||
for _, archive := range archivePaths {
|
||||
system_model.RemoveStorageWithNotice(db.DefaultContext, storage.RepoArchives, "Delete repo archive file", archive)
|
||||
}
|
||||
|
||||
// Remove lfs objects
|
||||
for _, lfsObj := range lfsPaths {
|
||||
system_model.RemoveStorageWithNotice(db.DefaultContext, storage.LFS, "Delete orphaned LFS file", lfsObj)
|
||||
}
|
||||
|
||||
// Remove issue attachment files.
|
||||
for _, attachment := range attachmentPaths {
|
||||
system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachment)
|
||||
}
|
||||
|
||||
// Remove release attachment files.
|
||||
for _, releaseAttachment := range releaseAttachments {
|
||||
system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete release attachment", releaseAttachment)
|
||||
}
|
||||
|
||||
// Remove attachment with no issue_id and release_id.
|
||||
for _, newAttachment := range newAttachmentPaths {
|
||||
system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachment)
|
||||
}
|
||||
|
||||
if len(repo.Avatar) > 0 {
|
||||
if err := storage.RepoAvatars.Delete(repo.CustomAvatarRelativePath()); err != nil {
|
||||
return fmt.Errorf("Failed to remove %s: %w", repo.Avatar, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, delete action logs after the actions have already been deleted to avoid new log files
|
||||
for _, task := range tasks {
|
||||
err := actions_module.RemoveLogs(ctx, task.LogInStorage, task.LogFilename)
|
||||
if err != nil {
|
||||
log.Error("remove log file %q: %v", task.LogFilename, err)
|
||||
// go on
|
||||
}
|
||||
}
|
||||
|
||||
// delete actions artifacts in ObjectStorage after the repo have already been deleted
|
||||
for _, art := range artifacts {
|
||||
if err := storage.ActionsArtifacts.Delete(art.StoragePath); err != nil {
|
||||
log.Error("remove artifact file %q: %v", art.StoragePath, err)
|
||||
// go on
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type repoChecker struct {
|
||||
querySQL func(ctx context.Context) ([]map[string][]byte, error)
|
||||
correctSQL func(ctx context.Context, id int64) error
|
||||
|
||||
+3
-1
@@ -3,7 +3,9 @@
|
||||
|
||||
package repo
|
||||
|
||||
import "code.gitea.io/gitea/models/db"
|
||||
import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
)
|
||||
|
||||
// MergeStyle represents the approach to merge commits into base branch.
|
||||
type MergeStyle string
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
_ "code.gitea.io/gitea/models" // register table model
|
||||
_ "code.gitea.io/gitea/models" // register table model
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
_ "code.gitea.io/gitea/models/activities"
|
||||
_ "code.gitea.io/gitea/models/perm/access" // register table model
|
||||
_ "code.gitea.io/gitea/models/repo" // register table model
|
||||
_ "code.gitea.io/gitea/models/user" // register table model
|
||||
|
||||
@@ -552,3 +552,31 @@ func (r *Release) GetExternalName() string { return r.OriginalAuthor }
|
||||
|
||||
// ExternalID ExternalUserRemappable interface
|
||||
func (r *Release) GetExternalID() int64 { return r.OriginalAuthorID }
|
||||
|
||||
// InsertReleases migrates release
|
||||
func InsertReleases(rels ...*Release) error {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
for _, rel := range rels {
|
||||
if _, err := sess.NoAutoTime().Insert(rel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(rel.Attachments) > 0 {
|
||||
for i := range rel.Attachments {
|
||||
rel.Attachments[i].ReleaseID = rel.ID
|
||||
}
|
||||
|
||||
if _, err := sess.NoAutoTime().Insert(rel.Attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMigrate_InsertReleases(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
a := &Attachment{
|
||||
UUID: "a0eebc91-9c0c-4ef7-bb6e-6bb9bd380a12",
|
||||
}
|
||||
r := &Release{
|
||||
Attachments: []*Attachment{a},
|
||||
}
|
||||
|
||||
err := InsertReleases(r)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func getTestCases() []struct {
|
||||
count: 0,
|
||||
},
|
||||
{
|
||||
name: "PublicRepositoriesOfUser3",
|
||||
name: "PublicRepositoriesOfOrg3",
|
||||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Collaborate: util.OptionalBoolFalse},
|
||||
count: 2,
|
||||
},
|
||||
@@ -81,7 +81,7 @@ func getTestCases() []struct {
|
||||
count: 0,
|
||||
},
|
||||
{
|
||||
name: "PublicAndPrivateRepositoriesOfUser3",
|
||||
name: "PublicAndPrivateRepositoriesOfOrg3",
|
||||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true, Collaborate: util.OptionalBoolFalse},
|
||||
count: 4,
|
||||
},
|
||||
@@ -96,7 +96,7 @@ func getTestCases() []struct {
|
||||
count: 1,
|
||||
},
|
||||
{
|
||||
name: "PublicRepositoriesOfUser3IncludingCollaborative",
|
||||
name: "PublicRepositoriesOfOrg3IncludingCollaborative",
|
||||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20},
|
||||
count: 3,
|
||||
},
|
||||
@@ -111,7 +111,7 @@ func getTestCases() []struct {
|
||||
count: 4,
|
||||
},
|
||||
{
|
||||
name: "PublicAndPrivateRepositoriesOfUser3IncludingCollaborative",
|
||||
name: "PublicAndPrivateRepositoriesOfOrg3IncludingCollaborative",
|
||||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true},
|
||||
count: 7,
|
||||
},
|
||||
@@ -168,7 +168,7 @@ func getTestCases() []struct {
|
||||
{
|
||||
name: "OwnerSlashRepoSearch",
|
||||
opts: &repo_model.SearchRepoOptions{Keyword: "user/repo2", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0},
|
||||
count: 3,
|
||||
count: 2,
|
||||
},
|
||||
{
|
||||
name: "OwnerSlashSearch",
|
||||
|
||||
@@ -121,7 +121,7 @@ func TestMetas(t *testing.T) {
|
||||
metas = repo.ComposeMetas()
|
||||
assert.Contains(t, metas, "org")
|
||||
assert.Contains(t, metas, "teams")
|
||||
assert.Equal(t, "user3", metas["org"])
|
||||
assert.Equal(t, "org3", metas["org"])
|
||||
assert.Equal(t, ",owners,team1,", metas["teams"])
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ package repo
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@@ -176,7 +177,7 @@ func (cfg *ActionsConfig) ToString() string {
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) IsWorkflowDisabled(file string) bool {
|
||||
return util.SliceContains(cfg.DisabledWorkflows, file)
|
||||
return slices.Contains(cfg.DisabledWorkflows, file)
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) DisableWorkflow(file string) {
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2020 The Gitea Authors.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// DeleteCollaboration removes collaboration relation between the user and repository.
|
||||
func DeleteCollaboration(repo *repo_model.Repository, uid int64) (err error) {
|
||||
collaboration := &repo_model.Collaboration{
|
||||
RepoID: repo.ID,
|
||||
UserID: uid,
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if has, err := db.GetEngine(ctx).Delete(collaboration); err != nil || has == 0 {
|
||||
return err
|
||||
} else if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = repo_model.WatchRepo(ctx, uid, repo.ID, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = reconsiderWatches(ctx, repo, uid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unassign a user from any issue (s)he has been assigned to in the repository
|
||||
if err := reconsiderRepoIssuesAssignee(ctx, repo, uid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func reconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Repository, uid int64) error {
|
||||
user, err := user_model.GetUserByID(ctx, uid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if canAssigned, err := access_model.CanBeAssigned(ctx, user, repo, true); err != nil || canAssigned {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).Where(builder.Eq{"assignee_id": uid}).
|
||||
In("issue_id", builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repo.ID})).
|
||||
Delete(&issues_model.IssueAssignees{}); err != nil {
|
||||
return fmt.Errorf("Could not delete assignee[%d] %w", uid, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func reconsiderWatches(ctx context.Context, repo *repo_model.Repository, uid int64) error {
|
||||
if has, err := access_model.HasAccess(ctx, uid, repo); err != nil || has {
|
||||
return err
|
||||
}
|
||||
if err := repo_model.WatchRepo(ctx, uid, repo.ID, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove all IssueWatches a user has subscribed to in the repository
|
||||
return issues_model.RemoveIssueWatchersByRepoID(ctx, uid, repo.ID)
|
||||
}
|
||||
@@ -41,10 +41,10 @@ func TestRepositoryTransfer(t *testing.T) {
|
||||
assert.NoError(t, transfer.LoadAttributes(db.DefaultContext))
|
||||
assert.Equal(t, "user2", transfer.Recipient.Name)
|
||||
|
||||
user6 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
org6 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
|
||||
// Only transfer can be started at any given time
|
||||
err = CreatePendingRepositoryTransfer(db.DefaultContext, doer, user6, repo.ID, nil)
|
||||
err = CreatePendingRepositoryTransfer(db.DefaultContext, doer, org6, repo.ID, nil)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrRepoTransferInProgress(err))
|
||||
|
||||
|
||||
+24
-84
@@ -33,12 +33,6 @@ type ErrSecretNotFound struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// IsErrSecretNotFound checks if an error is a ErrSecretNotFound.
|
||||
func IsErrSecretNotFound(err error) bool {
|
||||
_, ok := err.(ErrSecretNotFound)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrSecretNotFound) Error() string {
|
||||
return fmt.Sprintf("secret was not found [name: %s]", err.Name)
|
||||
}
|
||||
@@ -47,23 +41,18 @@ func (err ErrSecretNotFound) Unwrap() error {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
// newSecret Creates a new already encrypted secret
|
||||
func newSecret(ownerID, repoID int64, name, data string) *Secret {
|
||||
return &Secret{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
Name: strings.ToUpper(name),
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// InsertEncryptedSecret Creates, encrypts, and validates a new secret with yet unencrypted data and insert into database
|
||||
func InsertEncryptedSecret(ctx context.Context, ownerID, repoID int64, name, data string) (*Secret, error) {
|
||||
encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secret := newSecret(ownerID, repoID, name, encrypted)
|
||||
secret := &Secret{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
Name: strings.ToUpper(name),
|
||||
Data: encrypted,
|
||||
}
|
||||
if err := secret.Validate(); err != nil {
|
||||
return secret, err
|
||||
}
|
||||
@@ -83,8 +72,10 @@ func (s *Secret) Validate() error {
|
||||
|
||||
type FindSecretsOptions struct {
|
||||
db.ListOptions
|
||||
OwnerID int64
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
RepoID int64
|
||||
SecretID int64
|
||||
Name string
|
||||
}
|
||||
|
||||
func (opts *FindSecretsOptions) toConds() builder.Cond {
|
||||
@@ -95,6 +86,12 @@ func (opts *FindSecretsOptions) toConds() builder.Cond {
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.SecretID != 0 {
|
||||
cond = cond.And(builder.Eq{"id": opts.SecretID})
|
||||
}
|
||||
if opts.Name != "" {
|
||||
cond = cond.And(builder.Eq{"name": strings.ToUpper(opts.Name)})
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
@@ -116,75 +113,18 @@ func CountSecrets(ctx context.Context, opts *FindSecretsOptions) (int64, error)
|
||||
}
|
||||
|
||||
// UpdateSecret changes org or user reop secret.
|
||||
func UpdateSecret(ctx context.Context, orgID, repoID int64, name, data string) error {
|
||||
sc := new(Secret)
|
||||
name = strings.ToUpper(name)
|
||||
has, err := db.GetEngine(ctx).
|
||||
Where("owner_id=?", orgID).
|
||||
And("repo_id=?", repoID).
|
||||
And("name=?", name).
|
||||
Get(sc)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return ErrSecretNotFound{Name: name}
|
||||
}
|
||||
|
||||
func UpdateSecret(ctx context.Context, secretID int64, data string) error {
|
||||
encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sc.Data = encrypted
|
||||
_, err = db.GetEngine(ctx).ID(sc.ID).Cols("data").Update(sc)
|
||||
s := &Secret{
|
||||
Data: encrypted,
|
||||
}
|
||||
affected, err := db.GetEngine(ctx).ID(secretID).Cols("data").Update(s)
|
||||
if affected != 1 {
|
||||
return ErrSecretNotFound{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteSecret deletes secret from an organization.
|
||||
func DeleteSecret(ctx context.Context, orgID, repoID int64, name string) error {
|
||||
sc := new(Secret)
|
||||
has, err := db.GetEngine(ctx).
|
||||
Where("owner_id=?", orgID).
|
||||
And("repo_id=?", repoID).
|
||||
And("name=?", strings.ToUpper(name)).
|
||||
Get(sc)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return ErrSecretNotFound{Name: name}
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).ID(sc.ID).Delete(new(Secret)); err != nil {
|
||||
return fmt.Errorf("Delete: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateSecret creates or updates a secret and returns true if it was created
|
||||
func CreateOrUpdateSecret(ctx context.Context, orgID, repoID int64, name, data string) (bool, error) {
|
||||
sc := new(Secret)
|
||||
name = strings.ToUpper(name)
|
||||
has, err := db.GetEngine(ctx).
|
||||
Where("owner_id=?", orgID).
|
||||
And("repo_id=?", repoID).
|
||||
And("name=?", name).
|
||||
Get(sc)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !has {
|
||||
_, err = InsertEncryptedSecret(ctx, orgID, repoID, name, data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if err := UpdateSecret(ctx, orgID, repoID, name, data); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
_ "code.gitea.io/gitea/models" // register models
|
||||
_ "code.gitea.io/gitea/models" // register models
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
_ "code.gitea.io/gitea/models/activities"
|
||||
_ "code.gitea.io/gitea/models/system" // register models of system
|
||||
)
|
||||
|
||||
|
||||
+29
-33
@@ -94,11 +94,14 @@ func GetSetting(ctx context.Context, key string) (*Setting, error) {
|
||||
const contextCacheKey = "system_setting"
|
||||
|
||||
// GetSettingWithCache returns the setting value via the key
|
||||
func GetSettingWithCache(ctx context.Context, key string) (string, error) {
|
||||
func GetSettingWithCache(ctx context.Context, key, defaultVal string) (string, error) {
|
||||
return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) {
|
||||
return cache.GetString(genSettingCacheKey(key), func() (string, error) {
|
||||
res, err := GetSetting(ctx, key)
|
||||
if err != nil {
|
||||
if IsErrSettingIsNotExist(err) {
|
||||
return defaultVal, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return res.SettingValue, nil
|
||||
@@ -108,17 +111,21 @@ func GetSettingWithCache(ctx context.Context, key string) (string, error) {
|
||||
|
||||
// GetSettingBool return bool value of setting,
|
||||
// none existing keys and errors are ignored and result in false
|
||||
func GetSettingBool(ctx context.Context, key string) bool {
|
||||
s, _ := GetSetting(ctx, key)
|
||||
if s == nil {
|
||||
return false
|
||||
func GetSettingBool(ctx context.Context, key string, defaultVal bool) (bool, error) {
|
||||
s, err := GetSetting(ctx, key)
|
||||
switch {
|
||||
case err == nil:
|
||||
v, _ := strconv.ParseBool(s.SettingValue)
|
||||
return v, nil
|
||||
case IsErrSettingIsNotExist(err):
|
||||
return defaultVal, nil
|
||||
default:
|
||||
return false, err
|
||||
}
|
||||
v, _ := strconv.ParseBool(s.SettingValue)
|
||||
return v
|
||||
}
|
||||
|
||||
func GetSettingWithCacheBool(ctx context.Context, key string) bool {
|
||||
s, _ := GetSettingWithCache(ctx, key)
|
||||
func GetSettingWithCacheBool(ctx context.Context, key string, defaultVal bool) bool {
|
||||
s, _ := GetSettingWithCache(ctx, key, strconv.FormatBool(defaultVal))
|
||||
v, _ := strconv.ParseBool(s)
|
||||
return v
|
||||
}
|
||||
@@ -259,52 +266,41 @@ var (
|
||||
)
|
||||
|
||||
func Init(ctx context.Context) error {
|
||||
var disableGravatar bool
|
||||
disableGravatarSetting, err := GetSetting(ctx, KeyPictureDisableGravatar)
|
||||
if IsErrSettingIsNotExist(err) {
|
||||
disableGravatar = setting_module.GetDefaultDisableGravatar()
|
||||
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
|
||||
} else if err != nil {
|
||||
disableGravatar, err := GetSettingBool(ctx, KeyPictureDisableGravatar, setting_module.GetDefaultDisableGravatar())
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
disableGravatar = disableGravatarSetting.GetValueBool()
|
||||
}
|
||||
|
||||
var enableFederatedAvatar bool
|
||||
enableFederatedAvatarSetting, err := GetSetting(ctx, KeyPictureEnableFederatedAvatar)
|
||||
if IsErrSettingIsNotExist(err) {
|
||||
enableFederatedAvatar = setting_module.GetDefaultEnableFederatedAvatar(disableGravatar)
|
||||
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
|
||||
} else if err != nil {
|
||||
enableFederatedAvatar, err := GetSettingBool(ctx, KeyPictureEnableFederatedAvatar, setting_module.GetDefaultEnableFederatedAvatar(disableGravatar))
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
enableFederatedAvatar = disableGravatarSetting.GetValueBool()
|
||||
}
|
||||
|
||||
if setting_module.OfflineMode {
|
||||
disableGravatar = true
|
||||
enableFederatedAvatar = false
|
||||
if !GetSettingBool(ctx, KeyPictureDisableGravatar) {
|
||||
if !disableGravatar {
|
||||
if err := SetSettingNoVersion(ctx, KeyPictureDisableGravatar, "true"); err != nil {
|
||||
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureDisableGravatar, err)
|
||||
return fmt.Errorf("failed to set setting %q: %w", KeyPictureDisableGravatar, err)
|
||||
}
|
||||
}
|
||||
if GetSettingBool(ctx, KeyPictureEnableFederatedAvatar) {
|
||||
disableGravatar = true
|
||||
|
||||
if enableFederatedAvatar {
|
||||
if err := SetSettingNoVersion(ctx, KeyPictureEnableFederatedAvatar, "false"); err != nil {
|
||||
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err)
|
||||
return fmt.Errorf("failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err)
|
||||
}
|
||||
}
|
||||
enableFederatedAvatar = false
|
||||
}
|
||||
|
||||
if enableFederatedAvatar || !disableGravatar {
|
||||
var err error
|
||||
GravatarSourceURL, err = url.Parse(setting_module.GravatarSource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse Gravatar URL(%s): %w", setting_module.GravatarSource, err)
|
||||
return fmt.Errorf("failed to parse Gravatar URL(%s): %w", setting_module.GravatarSource, err)
|
||||
}
|
||||
}
|
||||
|
||||
if GravatarSourceURL != nil && enableFederatedAvatarSetting.GetValueBool() {
|
||||
if GravatarSourceURL != nil && enableFederatedAvatar {
|
||||
LibravatarService = libravatar.New()
|
||||
if GravatarSourceURL.Scheme == "https" {
|
||||
LibravatarService.SetUseHTTPS(true)
|
||||
|
||||
@@ -67,7 +67,9 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
||||
useLocalAvatar := false
|
||||
autoGenerateAvatar := false
|
||||
|
||||
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
|
||||
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar,
|
||||
setting.GetDefaultDisableGravatar(),
|
||||
)
|
||||
|
||||
switch {
|
||||
case u.UseCustomAvatar:
|
||||
|
||||
@@ -209,7 +209,7 @@ func TestListEmails(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 18 }))
|
||||
// 'user3' is an organization
|
||||
// 'org3' is an organization
|
||||
assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 3 }))
|
||||
|
||||
// Must find no records
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
_ "code.gitea.io/gitea/models"
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
_ "code.gitea.io/gitea/models/activities"
|
||||
_ "code.gitea.io/gitea/models/user"
|
||||
)
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestGetUserEmailsByNames(t *testing.T) {
|
||||
assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user9"}))
|
||||
assert.ElementsMatch(t, []string{"user8@example.com", "user5@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user5"}))
|
||||
|
||||
assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user7"}))
|
||||
assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "org7"}))
|
||||
}
|
||||
|
||||
func TestCanCreateOrganization(t *testing.T) {
|
||||
@@ -372,9 +372,9 @@ func TestUpdateUser(t *testing.T) {
|
||||
func TestUpdateUserEmailAlreadyUsed(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
|
||||
user2.Email = user3.Email
|
||||
user2.Email = org3.Email
|
||||
err := user_model.UpdateUser(db.DefaultContext, user2, true)
|
||||
assert.True(t, user_model.IsErrEmailAlreadyUsed(err))
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
_ "code.gitea.io/gitea/models" // https://discourse.gitea.io/t/testfixtures-could-not-clean-table-access-no-such-table-access/4137/4
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
_ "code.gitea.io/gitea/models"
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
_ "code.gitea.io/gitea/models/activities"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
||||
@@ -250,6 +250,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Data["ContextUser"] = ctx.ContextUser
|
||||
|
||||
ctx.Data["CanReadProjects"] = ctx.Org.CanReadUnit(ctx, unit.TypeProjects)
|
||||
ctx.Data["CanReadPackages"] = ctx.Org.CanReadUnit(ctx, unit.TypePackages)
|
||||
|
||||
@@ -471,6 +471,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
}
|
||||
ctx.Repo.Owner = owner
|
||||
ctx.ContextUser = owner
|
||||
ctx.Data["ContextUser"] = ctx.ContextUser
|
||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||
|
||||
// redirect link to wiki
|
||||
|
||||
+12
-2
@@ -221,8 +221,18 @@ type RunOpts struct {
|
||||
Dir string
|
||||
|
||||
Stdout, Stderr io.Writer
|
||||
Stdin io.Reader
|
||||
PipelineFunc func(context.Context, context.CancelFunc) error
|
||||
|
||||
// Stdin is used for passing input to the command
|
||||
// The caller must make sure the Stdin writer is closed properly to finish the Run function.
|
||||
// Otherwise, the Run function may hang for long time or forever, especially when the Git's context deadline is not the same as the caller's.
|
||||
// Some common mistakes:
|
||||
// * `defer stdinWriter.Close()` then call `cmd.Run()`: the Run() would never return if the command is killed by timeout
|
||||
// * `go { case <- parentContext.Done(): stdinWriter.Close() }` with `cmd.Run(DefaultTimeout)`: the command would have been killed by timeout but the Run doesn't return until stdinWriter.Close()
|
||||
// * `go { if stdoutReader.Read() err != nil: stdinWriter.Close() }` with `cmd.Run()`: the stdoutReader may never return error if the command is killed by timeout
|
||||
// In the future, ideally the git module itself should have full control of the stdin, to avoid such problems and make it easier to refactor to a better architecture.
|
||||
Stdin io.Reader
|
||||
|
||||
PipelineFunc func(context.Context, context.CancelFunc) error
|
||||
}
|
||||
|
||||
func commonBaseEnvs() []string {
|
||||
|
||||
@@ -30,7 +30,14 @@ func getRepoChanges(ctx context.Context, repo *repo_model.Repository, revision s
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(status.CommitSha) == 0 {
|
||||
needGenesis := len(status.CommitSha) == 0
|
||||
if !needGenesis {
|
||||
hasAncestorCmd := git.NewCommand(ctx, "merge-base").AddDynamicArguments(repo.CodeIndexerStatus.CommitSha, revision)
|
||||
stdout, _, _ := hasAncestorCmd.RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
|
||||
needGenesis = len(stdout) == 0
|
||||
}
|
||||
|
||||
if needGenesis {
|
||||
return genesisChanges(ctx, repo, revision)
|
||||
}
|
||||
return nonGenesisChanges(ctx, repo, revision)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
"slices"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -20,7 +21,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/queue"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -54,22 +54,22 @@ func index(ctx context.Context, indexer internal.Indexer, repoID int64) error {
|
||||
}
|
||||
|
||||
// skip forks from being indexed if unit is not present
|
||||
if !util.SliceContains(repoTypes, "forks") && repo.IsFork {
|
||||
if !slices.Contains(repoTypes, "forks") && repo.IsFork {
|
||||
return nil
|
||||
}
|
||||
|
||||
// skip mirrors from being indexed if unit is not present
|
||||
if !util.SliceContains(repoTypes, "mirrors") && repo.IsMirror {
|
||||
if !slices.Contains(repoTypes, "mirrors") && repo.IsMirror {
|
||||
return nil
|
||||
}
|
||||
|
||||
// skip templates from being indexed if unit is not present
|
||||
if !util.SliceContains(repoTypes, "templates") && repo.IsTemplate {
|
||||
if !slices.Contains(repoTypes, "templates") && repo.IsTemplate {
|
||||
return nil
|
||||
}
|
||||
|
||||
// skip regular repos from being indexed if unit is not present
|
||||
if !util.SliceContains(repoTypes, "sources") && !repo.IsFork && !repo.IsMirror && !repo.IsTemplate {
|
||||
if !slices.Contains(repoTypes, "sources") && !repo.IsFork && !repo.IsMirror && !repo.IsTemplate {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -122,21 +122,6 @@ func Init() {
|
||||
indexer := *globalIndexer.Load()
|
||||
for _, indexerData := range items {
|
||||
log.Trace("IndexerData Process Repo: %d", indexerData.RepoID)
|
||||
|
||||
// FIXME: it seems there is a bug in `CatFileBatch` or `nio.Pipe`, which will cause the process to hang forever in rare cases
|
||||
/*
|
||||
sync.(*Cond).Wait(cond.go:70)
|
||||
github.com/djherbis/nio/v3.(*PipeReader).Read(sync.go:106)
|
||||
bufio.(*Reader).fill(bufio.go:106)
|
||||
bufio.(*Reader).ReadSlice(bufio.go:372)
|
||||
bufio.(*Reader).collectFragments(bufio.go:447)
|
||||
bufio.(*Reader).ReadString(bufio.go:494)
|
||||
code.gitea.io/gitea/modules/git.ReadBatchLine(batch_reader.go:149)
|
||||
code.gitea.io/gitea/modules/indexer/code.(*BleveIndexer).addUpdate(bleve.go:214)
|
||||
code.gitea.io/gitea/modules/indexer/code.(*BleveIndexer).Index(bleve.go:296)
|
||||
code.gitea.io/gitea/modules/indexer/code.(*wrappedIndexer).Index(wrapped.go:74)
|
||||
code.gitea.io/gitea/modules/indexer/code.index(indexer.go:105)
|
||||
*/
|
||||
if err := index(ctx, indexer, indexerData.RepoID); err != nil {
|
||||
unhandled = append(unhandled, indexerData)
|
||||
if !setting.IsInTesting {
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"code.gitea.io/gitea/modules/indexer/code/internal"
|
||||
|
||||
_ "code.gitea.io/gitea/models"
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
_ "code.gitea.io/gitea/models/activities"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user