Merge branch 'forgejo' into forgejo

This commit is contained in:
Alan Pope 2024-03-19 09:29:19 +00:00
commit 0095dff5e8
868 changed files with 15269 additions and 8574 deletions

View file

@ -162,9 +162,6 @@ package "code.gitea.io/gitea/modules/cache"
package "code.gitea.io/gitea/modules/charset" package "code.gitea.io/gitea/modules/charset"
func (*BreakWriter).Write func (*BreakWriter).Write
package "code.gitea.io/gitea/modules/context"
func GetPrivateContext
package "code.gitea.io/gitea/modules/emoji" package "code.gitea.io/gitea/modules/emoji"
func ReplaceCodes func ReplaceCodes
@ -192,6 +189,7 @@ package "code.gitea.io/gitea/modules/gitgraph"
package "code.gitea.io/gitea/modules/gitrepo" package "code.gitea.io/gitea/modules/gitrepo"
func GetBranchCommitID func GetBranchCommitID
func GetWikiDefaultBranch
package "code.gitea.io/gitea/modules/graceful" package "code.gitea.io/gitea/modules/graceful"
func (*Manager).TerminateContext func (*Manager).TerminateContext
@ -296,7 +294,6 @@ package "code.gitea.io/gitea/modules/translation"
package "code.gitea.io/gitea/modules/util" package "code.gitea.io/gitea/modules/util"
func UnsafeStringToBytes func UnsafeStringToBytes
func OptionalBoolFromGeneric
package "code.gitea.io/gitea/modules/util/filebuffer" package "code.gitea.io/gitea/modules/util/filebuffer"
func CreateFromReader func CreateFromReader
@ -316,6 +313,9 @@ package "code.gitea.io/gitea/routers/web/org"
func getActionIssues func getActionIssues
func UpdateIssueProject func UpdateIssueProject
package "code.gitea.io/gitea/services/context"
func GetPrivateContext
package "code.gitea.io/gitea/services/convert" package "code.gitea.io/gitea/services/convert"
func ToSecret func ToSecret

View file

@ -8,7 +8,9 @@
}, },
"ghcr.io/devcontainers/features/git-lfs:1.1.0": {}, "ghcr.io/devcontainers/features/git-lfs:1.1.0": {},
"ghcr.io/devcontainers-contrib/features/poetry:2": {}, "ghcr.io/devcontainers-contrib/features/poetry:2": {},
"ghcr.io/devcontainers/features/python:1": {} "ghcr.io/devcontainers/features/python:1": {
"version": "3.12"
}
}, },
"customizations": { "customizations": {
"vscode": { "vscode": {

View file

@ -5,17 +5,26 @@ set -ex
end_to_end=$1 end_to_end=$1
end_to_end_pr=$2 end_to_end_pr=$2
forgejo=$3 forgejo=$3
forgejo_pr=$4 forgejo_pr_or_ref=$4
cd $forgejo
full_version=$(make show-version-full)
major_version=$(make show-version-major)
head_url=$(jq --raw-output .head.repo.html_url < $forgejo_pr)
test "$head_url" != null
branch=$(jq --raw-output .head.ref < $forgejo_pr)
test "$branch" != null
cd $end_to_end cd $end_to_end
echo $head_url $branch 7.0.0+0-gitea-1.22.0 > forgejo/sources/1.22
date > last-upgrade date > last-upgrade
base_url=$(jq --raw-output .base.repo.html_url < $forgejo_pr) if test -f "$forgejo_pr_or_ref" ; then
test "$base_url" != null forgejo_pr=$forgejo_pr_or_ref
head_url=$(jq --raw-output .head.repo.html_url < $forgejo_pr)
test "$head_url" != null
branch=$(jq --raw-output .head.ref < $forgejo_pr)
test "$branch" != null
echo $head_url $branch $full_version > forgejo/sources/$major_version
else
forgejo_ref=$forgejo_pr_or_ref
echo $GITHUB_SERVER_URL/$GITHUB_REPOSITORY ${forgejo_ref#refs/heads/} $full_version > forgejo/sources/$major_version
fi
test "$GITHUB_RUN_NUMBER" test "$GITHUB_RUN_NUMBER"
echo $base_url/actions/runs/$GITHUB_RUN_NUMBER/artifacts/forgejo > forgejo/binary-url echo $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER/artifacts/forgejo > forgejo/binary-url

View file

@ -1,5 +1,23 @@
# Copyright 2024 The Forgejo Authors
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
#
# To modify this workflow:
#
# - push it to the wip-ci-end-to-end branch on the forgejo repository
# otherwise it will not have access to the secrets required to push
# the cascading PR
#
# - once it works, open a pull request for the sake of keeping track
# of the change even if the PR won't run it because it will use
# whatever is in the default branch instead
#
# - after it is merged, double check it works by setting the
# run-end-to-end-test on a pull request (any pull request will doe
#
on: on:
push:
branches:
- 'wip-ci-end-to-end'
pull_request_target: pull_request_target:
types: types:
- labeled - labeled
@ -20,9 +38,18 @@ jobs:
cat <<'EOF' cat <<'EOF'
${{ toJSON(github.event.pull_request.labels.*.name) }} ${{ toJSON(github.event.pull_request.labels.*.name) }}
EOF EOF
cat <<'EOF'
${{ toJSON(github.event) }}
EOF
build: build:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') && github.event.action == 'label_updated' && contains(github.event.pull_request.labels.*.name, 'run-end-to-end-tests') }} if: >
!startsWith(vars.ROLE, 'forgejo-') && (
github.event_name == 'push' ||
(
github.event.action == 'label_updated' && contains(github.event.pull_request.labels.*.name, 'run-end-to-end-tests')
)
)
runs-on: docker runs-on: docker
container: container:
image: 'docker.io/node:20-bookworm' image: 'docker.io/node:20-bookworm'
@ -55,19 +82,29 @@ jobs:
path: forgejo path: forgejo
cascade: cascade:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') && github.event.action == 'label_updated' && contains(github.event.pull_request.labels.*.name, 'run-end-to-end-tests') }} if: >
!startsWith(vars.ROLE, 'forgejo-') && (
github.event_name == 'push' ||
(
github.event.action == 'label_updated' && contains(github.event.pull_request.labels.*.name, 'run-end-to-end-tests')
)
)
needs: [build] needs: [build]
runs-on: docker runs-on: docker
container: container:
image: node:20-bookworm image: node:20-bookworm
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/cascading-pr@v1 with:
fetch-depth: '0'
show-progress: 'false'
- uses: actions/cascading-pr@v2
with: with:
origin-url: ${{ env.GITHUB_SERVER_URL }} origin-url: ${{ env.GITHUB_SERVER_URL }}
origin-repo: ${{ github.repository }} origin-repo: ${{ github.repository }}
origin-token: ${{ secrets.END_TO_END_CASCADING_PR_ORIGIN }} origin-token: ${{ secrets.END_TO_END_CASCADING_PR_ORIGIN }}
origin-pr: ${{ github.event.pull_request.number }} origin-pr: ${{ github.event.pull_request.number }}
origin-ref: ${{ github.event_name == 'push' && github.event.ref || '' }}
destination-url: https://code.forgejo.org destination-url: https://code.forgejo.org
destination-fork-repo: cascading-pr/end-to-end destination-fork-repo: cascading-pr/end-to-end
destination-repo: forgejo/end-to-end destination-repo: forgejo/end-to-end

View file

@ -21,8 +21,6 @@ jobs:
check-latest: true check-latest: true
- run: make deps-backend deps-tools - run: make deps-backend deps-tools
- run: make --always-make -j$(nproc) lint-backend checks-backend # ensure the "go-licenses" make target runs - run: make --always-make -j$(nproc) lint-backend checks-backend # ensure the "go-licenses" make target runs
env:
TAGS: bindata sqlite sqlite_unlock_notify
frontend-checks: frontend-checks:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
@ -43,8 +41,11 @@ jobs:
image: 'docker.io/node:20-bookworm' image: 'docker.io/node:20-bookworm'
services: services:
minio: minio:
image: 'docker.io/bitnami/minio:2023.8.31' image: bitnami/minio:2024.2.26
options: >-
--hostname gitea.minio
env: env:
MINIO_DOMAIN: minio
MINIO_ROOT_USER: 123456 MINIO_ROOT_USER: 123456
MINIO_ROOT_PASSWORD: 12345678 MINIO_ROOT_PASSWORD: 12345678
steps: steps:
@ -130,10 +131,10 @@ jobs:
image: 'docker.io/node:20-bookworm' image: 'docker.io/node:20-bookworm'
services: services:
minio: minio:
image: bitnami/minio:2021.3.17 image: bitnami/minio:2024.2.26
env: env:
MINIO_ACCESS_KEY: 123456 MINIO_ROOT_USER: 123456
MINIO_SECRET_KEY: 12345678 MINIO_ROOT_PASSWORD: 12345678
pgsql: pgsql:
image: 'docker.io/postgres:15' image: 'docker.io/postgres:15'
env: env:

2
.gitignore vendored
View file

@ -18,7 +18,7 @@ _test
# MS VSCode # MS VSCode
.vscode .vscode
__debug_bin __debug_bin*
*.cgo1.go *.cgo1.go
*.cgo2.c *.cgo2.c

View file

@ -64,6 +64,7 @@ rules:
"@stylistic/media-query-list-comma-newline-before": null "@stylistic/media-query-list-comma-newline-before": null
"@stylistic/media-query-list-comma-space-after": null "@stylistic/media-query-list-comma-space-after": null
"@stylistic/media-query-list-comma-space-before": null "@stylistic/media-query-list-comma-space-before": null
"@stylistic/named-grid-areas-alignment": null
"@stylistic/no-empty-first-line": null "@stylistic/no-empty-first-line": null
"@stylistic/no-eol-whitespace": true "@stylistic/no-eol-whitespace": true
"@stylistic/no-extra-semicolons": true "@stylistic/no-extra-semicolons": true

View file

@ -93,6 +93,14 @@ ifneq ($(STORED_VERSION),)
else else
FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//')+${GITEA_COMPATIBILITY} FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//')+${GITEA_COMPATIBILITY}
endif endif
FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//')
show-version-full:
@echo ${FORGEJO_VERSION}
show-version-major:
@echo ${FORGEJO_VERSION_MAJOR}
RELEASE_VERSION ?= ${FORGEJO_VERSION} RELEASE_VERSION ?= ${FORGEJO_VERSION}
VERSION ?= ${RELEASE_VERSION} VERSION ?= ${RELEASE_VERSION}
@ -100,8 +108,10 @@ LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeV
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64 LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/)) ifeq ($(HAS_GO), yes)
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) $(shell $(GO) list code.gitea.io/gitea/models/forgejo_migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/)) GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) $(shell $(GO) list code.gitea.io/gitea/models/forgejo_migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
endif
FOMANTIC_WORK_DIR := web_src/fomantic FOMANTIC_WORK_DIR := web_src/fomantic
@ -140,7 +150,9 @@ GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/optio
GO_SOURCES += $(GENERATED_GO_DEST) GO_SOURCES += $(GENERATED_GO_DEST)
GO_SOURCES_NO_BINDATA := $(GO_SOURCES) GO_SOURCES_NO_BINDATA := $(GO_SOURCES)
MIGRATION_PACKAGES := $(shell $(GO) list code.gitea.io/gitea/models/migrations/... code.gitea.io/gitea/models/forgejo_migrations/...) ifeq ($(HAS_GO), yes)
MIGRATION_PACKAGES := $(shell $(GO) list code.gitea.io/gitea/models/migrations/... code.gitea.io/gitea/models/forgejo_migrations/...)
endif
ifeq ($(filter $(TAGS_SPLIT),bindata),bindata) ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
GO_SOURCES += $(BINDATA_DEST) GO_SOURCES += $(BINDATA_DEST)
@ -219,6 +231,8 @@ help:
@echo " - checks-frontend check frontend files" @echo " - checks-frontend check frontend files"
@echo " - checks-backend check backend files" @echo " - checks-backend check backend files"
@echo " - test test everything" @echo " - test test everything"
@echo " - show-version-full show the same version as the API endpoint"
@echo " - show-version-major show major release number only"
@echo " - test-frontend test frontend files" @echo " - test-frontend test frontend files"
@echo " - test-backend test backend files" @echo " - test-backend test backend files"
@echo " - test-e2e[\#TestSpecificName] test end to end using playwright" @echo " - test-e2e[\#TestSpecificName] test end to end using playwright"
@ -299,12 +313,8 @@ fmt:
.PHONY: fmt-check .PHONY: fmt-check
fmt-check: fmt fmt-check: fmt
@diff=$$(git diff --color=always $(GO_SOURCES) templates $(WEB_DIRS)); \ @git diff --exit-code --color=always $(GO_SOURCES) templates $(WEB_DIRS) \
if [ -n "$$diff" ]; then \ || (code=$$?; echo "Please run 'make fmt' and commit the result"; exit $${code})
echo "Please run 'make fmt' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi
.PHONY: $(TAGS_EVIDENCE) .PHONY: $(TAGS_EVIDENCE)
$(TAGS_EVIDENCE): $(TAGS_EVIDENCE):
@ -325,12 +335,8 @@ generate-forgejo-api: $(FORGEJO_API_SPEC)
.PHONY: forgejo-api-check .PHONY: forgejo-api-check
forgejo-api-check: generate-forgejo-api forgejo-api-check: generate-forgejo-api
@diff=$$(git diff $(FORGEJO_API_SERVER) ; \ @git diff --exit-code --color=always $(FORGEJO_API_SERVER) \
if [ -n "$$diff" ]; then \ || (code=$$?; echo "Please run 'make generate-forgejo-api' and commit the result"; exit $${code})
echo "Please run 'make generate-forgejo-api' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi
.PHONY: forgejo-api-validate .PHONY: forgejo-api-validate
forgejo-api-validate: forgejo-api-validate:
@ -347,12 +353,8 @@ $(SWAGGER_SPEC): $(GO_SOURCES_NO_BINDATA)
.PHONY: swagger-check .PHONY: swagger-check
swagger-check: generate-swagger swagger-check: generate-swagger
@diff=$$(git diff --color=always '$(SWAGGER_SPEC)'); \ @git diff --exit-code --color=always '$(SWAGGER_SPEC)' \
if [ -n "$$diff" ]; then \ || (code=$$?; echo "Please run 'make generate-swagger' and commit the result"; exit $${code})
echo "Please run 'make generate-swagger' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi
.PHONY: swagger-validate .PHONY: swagger-validate
swagger-validate: swagger-validate:
@ -423,11 +425,8 @@ lint-spell-fix:
lint-go: lint-go:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) $(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS)
$(GO) run $(DEADCODE_PACKAGE) -generated=false -test code.gitea.io/gitea > .cur-deadcode-out $(GO) run $(DEADCODE_PACKAGE) -generated=false -test code.gitea.io/gitea > .cur-deadcode-out
@$(DIFF) .deadcode-out .cur-deadcode-out; \ @$(DIFF) .deadcode-out .cur-deadcode-out \
if [ $$? -eq 1 ]; then \ || (code=$$?; echo "Please run 'make lint-go-fix' and commit the result"; exit $${code})
echo "Please run 'make lint-go-fix' and commit the result"; \
exit 1; \
fi
.PHONY: lint-go-fix .PHONY: lint-go-fix
lint-go-fix: lint-go-fix:
@ -527,12 +526,8 @@ vendor: go.mod go.sum
.PHONY: tidy-check .PHONY: tidy-check
tidy-check: tidy tidy-check: tidy
@diff=$$(git diff --color=always go.mod go.sum $(GO_LICENSE_FILE)); \ @git diff --exit-code --color=always go.mod go.sum $(GO_LICENSE_FILE) \
if [ -n "$$diff" ]; then \ || (code=$$?; echo "Please run 'make tidy' and commit the result"; exit $${code})
echo "Please run 'make tidy' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi
.PHONY: go-licenses .PHONY: go-licenses
go-licenses: $(GO_LICENSE_FILE) go-licenses: $(GO_LICENSE_FILE)
@ -947,6 +942,7 @@ fomantic:
cd $(FOMANTIC_WORK_DIR) && npm install --no-save cd $(FOMANTIC_WORK_DIR) && npm install --no-save
cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/ cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
$(SED_INPLACE) -e 's/ overrideBrowserslist\r/ overrideBrowserslist: ["defaults"]\r/g' $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/tasks/config/tasks.js
cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build
# fomantic uses "touchstart" as click event for some browsers, it's not ideal, so we force fomantic to always use "click" as click event # fomantic uses "touchstart" as click event for some browsers, it's not ideal, so we force fomantic to always use "click" as click event
$(SED_INPLACE) -e 's/clickEvent[ \t]*=/clickEvent = "click", unstableClickEvent =/g' $(FOMANTIC_WORK_DIR)/build/semantic.js $(SED_INPLACE) -e 's/clickEvent[ \t]*=/clickEvent = "click", unstableClickEvent =/g' $(FOMANTIC_WORK_DIR)/build/semantic.js
@ -970,23 +966,14 @@ svg: node-check | node_modules
.PHONY: svg-check .PHONY: svg-check
svg-check: svg svg-check: svg
@git add $(SVG_DEST_DIR) @git add $(SVG_DEST_DIR)
@diff=$$(git diff --color=always --cached $(SVG_DEST_DIR)); \ @git diff --exit-code --color=always --cached $(SVG_DEST_DIR) \
if [ -n "$$diff" ]; then \ || (code=$$?; echo "Please run 'make svg' and commit the result"; exit $${code})
echo "Please run 'make svg' and 'git add $(SVG_DEST_DIR)' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi
.PHONY: lockfile-check .PHONY: lockfile-check
lockfile-check: lockfile-check:
npm install --package-lock-only npm install --package-lock-only
@diff=$$(git diff --color=always package-lock.json); \ @git diff --exit-code --color=always package-lock.json \
if [ -n "$$diff" ]; then \ || (code=$$?; echo "Please run 'npm install --package-lock-only' and commit the result"; exit $${code})
echo "package-lock.json is inconsistent with package.json"; \
echo "Please run 'npm install --package-lock-only' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi
.PHONY: update-translations .PHONY: update-translations
update-translations: update-translations:

View file

@ -4,6 +4,43 @@ A Forgejo release is published shortly after a Gitea release is published and th
The Forgejo admin should carefully read the required manual actions before upgrading. A point release (e.g. v1.21.1-0 or v1.21.2-0) does not require manual actions but others might (e.g. v1.20, v1.21). The Forgejo admin should carefully read the required manual actions before upgrading. A point release (e.g. v1.21.1-0 or v1.21.2-0) does not require manual actions but others might (e.g. v1.20, v1.21).
## 1.21.7-0
The [complete list of commits](https://codeberg.org/forgejo/forgejo/commits/branch/v1.21/forgejo) included in the `Forgejo v1.21.7-0` release can be reviewed from the command line with:
```shell
$ git clone https://codeberg.org/forgejo/forgejo/
$ git -C forgejo log --oneline --no-merges v1.21.6-0..v1.21.7-0
```
This stable release contains bug fixes and a **security fix**.
* Recommended Action
We recommend that all Forgejo installations are [upgraded](https://forgejo.org/docs/v1.21/admin/upgrade/) to the latest version as soon as possible.
* [Forgejo Semantic Version](https://forgejo.org/docs/v1.21/user/semver/)
The semantic version was updated to `6.0.7+0-gitea-1.21.7`
* Built with Go 1.21.8
It [includes vulnerability fixes](https://groups.google.com/g/golang-announce/c/5pwGVUPoMbg).
* [CVE-2023-45290](https://go.dev/issue/65383) which could lead to memory exhaustion when parsing a multipart form.
* [CVE-2023-45289](https://go.dev/issue/65065) which could allow incorrect forwarding of sensitive headers and cookies on HTTP redirect.
* Security fix
* The google.golang.org/protobuf module was bumped to version v1.33.0 to fix a bug in the google.golang.org/protobuf/encoding/protojson package which could cause the Unmarshal function to enter an infinite loop when handling some invalid inputs. [Read more in the announcement](https://groups.google.com/g/golang-announce/c/ArQ6CDgtEjY).
* Bug fixes
The most prominent ones are described here, others can be found in the list of commits included in the release as described above.
* [Fix tarball/zipball download bug](https://codeberg.org/forgejo/forgejo/commit/8e2c991b35de8c94899ad053e89339cea4538589).
* [Ensure `HasIssueContentHistory` takes into account `comment_id`](https://codeberg.org/forgejo/forgejo/commit/8fb027fea5e9525293802d977fd3ee0c374ba9ba).
## 1.21.6-0 ## 1.21.6-0
The [complete list of commits](https://codeberg.org/forgejo/forgejo/commits/branch/v1.21/forgejo) included in the `Forgejo v1.21.6-0` release can be reviewed from the command line with: The [complete list of commits](https://codeberg.org/forgejo/forgejo/commits/branch/v1.21/forgejo) included in the `Forgejo v1.21.6-0` release can be reviewed from the command line with:

View file

@ -34,6 +34,11 @@
"path": "dario.cat/mergo/LICENSE", "path": "dario.cat/mergo/LICENSE",
"licenseText": "Copyright (c) 2013 Dario Castañé. All rights reserved.\nCopyright (c) 2012 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" "licenseText": "Copyright (c) 2013 Dario Castañé. All rights reserved.\nCopyright (c) 2012 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
}, },
{
"name": "filippo.io/edwards25519",
"path": "filippo.io/edwards25519/LICENSE",
"licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
},
{ {
"name": "git.sr.ht/~mariusor/go-xsd-duration", "name": "git.sr.ht/~mariusor/go-xsd-duration",
"path": "git.sr.ht/~mariusor/go-xsd-duration/LICENSE", "path": "git.sr.ht/~mariusor/go-xsd-duration/LICENSE",

View file

@ -47,7 +47,8 @@ var microcmdUserCreate = &cli.Command{
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "must-change-password", Name: "must-change-password",
Usage: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: true)", Usage: "Set this option to false to prevent forcing the user to change their password after initial login",
Value: true,
}, },
&cli.IntFlag{ &cli.IntFlag{
Name: "random-password-length", Name: "random-password-length",
@ -110,8 +111,7 @@ func runCreateUser(c *cli.Context) error {
return errors.New("must set either password or random-password flag") return errors.New("must set either password or random-password flag")
} }
// always default to true changePassword := c.Bool("must-change-password")
changePassword := true
// If this is the first user being created. // If this is the first user being created.
// Take it as the admin and don't force a password update. // Take it as the admin and don't force a password update.
@ -119,10 +119,6 @@ func runCreateUser(c *cli.Context) error {
changePassword = false changePassword = false
} }
if c.IsSet("must-change-password") {
changePassword = c.Bool("must-change-password")
}
restricted := optional.None[bool]() restricted := optional.None[bool]()
if c.IsSet("restricted") { if c.IsSet("restricted") {

View file

@ -969,6 +969,12 @@ LEVEL = Info
;GO_GET_CLONE_URL_PROTOCOL = https ;GO_GET_CLONE_URL_PROTOCOL = https
;; ;;
;; Close issues as long as a commit on any branch marks it as fixed ;; Close issues as long as a commit on any branch marks it as fixed
;DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
;;
;; Allow users to push local repositories to Gitea and have them automatically created for a user or an org
;ENABLE_PUSH_CREATE_USER = false
;ENABLE_PUSH_CREATE_ORG = false
;;
;; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki, repo.projects, repo.packages, repo.actions. ;; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki, repo.projects, repo.packages, repo.actions.
;DISABLED_REPO_UNITS = ;DISABLED_REPO_UNITS =
;; ;;
@ -1492,8 +1498,10 @@ LEVEL = Info
;DEFAULT_EMAIL_NOTIFICATIONS = enabled ;DEFAULT_EMAIL_NOTIFICATIONS = enabled
;; Send an email to all admins when a new user signs up to inform the admins about this act. Options: true, false ;; Send an email to all admins when a new user signs up to inform the admins about this act. Options: true, false
;SEND_NOTIFICATION_EMAIL_ON_NEW_USER = false ;SEND_NOTIFICATION_EMAIL_ON_NEW_USER = false
;; Disabled features for users, could be "deletion", more features can be disabled in future ;; Disabled features for users, could be "deletion", "manage_ssh_keys","manage_gpg_keys" more features can be disabled in future
;; - deletion: a user cannot delete their own account ;; - deletion: a user cannot delete their own account
;; - manage_ssh_keys: a user cannot configure ssh keys
;; - manage_gpg_keys: a user cannot configure gpg keys
;USER_DISABLED_FEATURES = ;USER_DISABLED_FEATURES =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -518,7 +518,10 @@ And the following unique queues:
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled - `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
- `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations. - `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations.
- `SEND_NOTIFICATION_EMAIL_ON_NEW_USER`: **false**: Send an email to all admins when a new user signs up to inform the admins about this act. - `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys` and more features can be added in future.
- `deletion`: User cannot delete their own account.
- `manage_ssh_keys`: User cannot configure ssh keys.
- `manage_gpg_keys`: User cannot configure gpg keys.
## Security (`security`) ## Security (`security`)
@ -829,7 +832,7 @@ Default templates for project boards:
## Issue and pull request attachments (`attachment`) ## Issue and pull request attachments (`attachment`)
- `ENABLED`: **true**: Whether issue and pull request attachments are enabled. - `ENABLED`: **true**: Whether issue and pull request attachments are enabled.
- `ALLOWED_TYPES`: **.csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. - `ALLOWED_TYPES`: **.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
- `MAX_SIZE`: **2048**: Maximum size (MB). - `MAX_SIZE`: **2048**: Maximum size (MB).
- `MAX_FILES`: **5**: Maximum number of attachments that can be uploaded at once. - `MAX_FILES`: **5**: Maximum number of attachments that can be uploaded at once.
- `STORAGE_TYPE`: **local**: Storage type for attachments, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]` - `STORAGE_TYPE`: **local**: Storage type for attachments, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]`
@ -839,6 +842,10 @@ Default templates for project boards:
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` - `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` - `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` - `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments only available when STORAGE_TYPE is `minio`
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio bucket lookup type only available when `STORAGE_TYPE` is `minio`
- `auto` Auto detect
- `dns` Virtual Host Style bucket lookup
- `path` Path style bucket lookup
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when STORAGE_TYPE is `minio` - `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when STORAGE_TYPE is `minio`
- `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio` - `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio`
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when STORAGE_TYPE is `minio` - `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when STORAGE_TYPE is `minio`
@ -1269,6 +1276,10 @@ is `data/lfs` and the default of `MINIO_BASE_PATH` is `lfs/`.
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio` - `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio`
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio` - `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio`
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the lfs only available when `STORAGE_TYPE` is `minio` - `MINIO_BUCKET`: **gitea**: Minio bucket to store the lfs only available when `STORAGE_TYPE` is `minio`
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio bucket lookup type only available when `STORAGE_TYPE` is `minio`
- `auto` Auto detect
- `dns` Virtual Host Style bucket lookup
- `path` Path style bucket lookup
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio` - `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
- `MINIO_BASE_PATH`: **lfs/**: Minio base path on the bucket only available when `STORAGE_TYPE` is `minio` - `MINIO_BASE_PATH`: **lfs/**: Minio base path on the bucket only available when `STORAGE_TYPE` is `minio`
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio` - `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
@ -1284,6 +1295,10 @@ Default storage configuration for attachments, lfs, avatars, repo-avatars, repo-
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio` - `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio`
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio` - `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio`
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the data only available when `STORAGE_TYPE` is `minio` - `MINIO_BUCKET`: **gitea**: Minio bucket to store the data only available when `STORAGE_TYPE` is `minio`
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio bucket lookup type only available when `STORAGE_TYPE` is `minio`
- `auto` Auto detect
- `dns` Virtual Host Style bucket lookup
- `path` Path style bucket lookup
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio` - `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio` - `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio` - `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
@ -1369,6 +1384,10 @@ is `data/repo-archive` and the default of `MINIO_BASE_PATH` is `repo-archive/`.
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio` - `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio`
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio` - `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio`
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the lfs only available when `STORAGE_TYPE` is `minio` - `MINIO_BUCKET`: **gitea**: Minio bucket to store the lfs only available when `STORAGE_TYPE` is `minio`
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio bucket lookup type only available when `STORAGE_TYPE` is `minio`
- `auto` Auto detect
- `dns` Virtual Host Style bucket lookup
- `path` Path style bucket lookup
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio` - `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
- `MINIO_BASE_PATH`: **repo-archive/**: Minio base path on the bucket only available when `STORAGE_TYPE` is `minio` - `MINIO_BASE_PATH`: **repo-archive/**: Minio base path on the bucket only available when `STORAGE_TYPE` is `minio`
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio` - `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`

View file

@ -497,6 +497,10 @@ Gitea 创建以下非唯一队列:
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**用户电子邮件通知的默认配置用户可配置。选项enabled、onmention、disabled - `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**用户电子邮件通知的默认配置用户可配置。选项enabled、onmention、disabled
- `DISABLE_REGULAR_ORG_CREATION`: **false**:禁止普通(非管理员)用户创建组织。 - `DISABLE_REGULAR_ORG_CREATION`: **false**:禁止普通(非管理员)用户创建组织。
- `USER_DISABLED_FEATURES`:**_empty_** 禁用的用户特性,当前允许为空或者 `deletion``manage_ssh_keys` `manage_gpg_keys` 未来可以增加更多设置。
- `deletion`: 用户不能通过界面或者API删除他自己。
- `manage_ssh_keys`: 用户不能通过界面或者API配置SSH Keys。
- `manage_gpg_keys`: 用户不能配置 GPG 密钥。
## 安全性 (`security`) ## 安全性 (`security`)
@ -778,7 +782,7 @@ Gitea 创建以下非唯一队列:
## 工单和合并请求的附件 (`attachment`) ## 工单和合并请求的附件 (`attachment`)
- `ENABLED`: **true**: 是否允许用户上传附件。 - `ENABLED`: **true**: 是否允许用户上传附件。
- `ALLOWED_TYPES`: **.csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: 允许的文件扩展名(`.zip`、mime 类型(`text/plain`)或通配符类型(`image/*`、`audio/*`、`video/*`)的逗号分隔列表。空值或 `*/*` 允许所有类型。 - `ALLOWED_TYPES`: **.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: 允许的文件扩展名(`.zip`、mime 类型(`text/plain`)或通配符类型(`image/*`、`audio/*`、`video/*`)的逗号分隔列表。空值或 `*/*` 允许所有类型。
- `MAX_SIZE`: **2048**: 附件的最大限制MB - `MAX_SIZE`: **2048**: 附件的最大限制MB
- `MAX_FILES`: **5**: 一次最多上传的附件数量。 - `MAX_FILES`: **5**: 一次最多上传的附件数量。
- `STORAGE_TYPE`: **local**: 附件的存储类型,`local` 表示本地磁盘,`minio` 表示兼容 S3 的对象存储服务,如果未设置将使用默认值 `local` 或其他在 `[storage.xxx]` 中定义的名称。 - `STORAGE_TYPE`: **local**: 附件的存储类型,`local` 表示本地磁盘,`minio` 表示兼容 S3 的对象存储服务,如果未设置将使用默认值 `local` 或其他在 `[storage.xxx]` 中定义的名称。
@ -788,6 +792,10 @@ Gitea 创建以下非唯一队列:
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID 以连接,仅当 STORAGE_TYPE 为 `minio` 时可用。 - `MINIO_ACCESS_KEY_ID`: Minio accessKeyID 以连接,仅当 STORAGE_TYPE 为 `minio` 时可用。
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey 以连接,仅当 STORAGE_TYPE 为 `minio` 时可用。 - `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey 以连接,仅当 STORAGE_TYPE 为 `minio` 时可用。
- `MINIO_BUCKET`: **gitea**: Minio 存储附件的存储桶,仅当 STORAGE_TYPE 为 `minio` 时可用。 - `MINIO_BUCKET`: **gitea**: Minio 存储附件的存储桶,仅当 STORAGE_TYPE 为 `minio` 时可用。
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio 存储桶寻址方式, 仅当 `STORAGE_TYPE``minio` 时可用。
- `auto` 自动检测
- `dns` 子域名寻址
- `path` 路径寻址
- `MINIO_LOCATION`: **us-east-1**: Minio 存储桶的位置以创建,仅当 STORAGE_TYPE 为 `minio` 时可用。 - `MINIO_LOCATION`: **us-east-1**: Minio 存储桶的位置以创建,仅当 STORAGE_TYPE 为 `minio` 时可用。
- `MINIO_BASE_PATH`: **attachments/**: Minio 存储桶上的基本路径,仅当 STORAGE_TYPE 为 `minio` 时可用。 - `MINIO_BASE_PATH`: **attachments/**: Minio 存储桶上的基本路径,仅当 STORAGE_TYPE 为 `minio` 时可用。
- `MINIO_USE_SSL`: **false**: Minio 启用 SSL仅当 STORAGE_TYPE 为 `minio` 时可用。 - `MINIO_USE_SSL`: **false**: Minio 启用 SSL仅当 STORAGE_TYPE 为 `minio` 时可用。
@ -1203,6 +1211,10 @@ ALLOW_DATA_URI_IMAGES = true
- `MINIO_ACCESS_KEY_ID`Minio 的 accessKeyID仅在 `STORAGE_TYPE``minio` 时可用。 - `MINIO_ACCESS_KEY_ID`Minio 的 accessKeyID仅在 `STORAGE_TYPE``minio` 时可用。
- `MINIO_SECRET_ACCESS_KEY`Minio 的 secretAccessKey仅在 `STORAGE_TYPE``minio` 时可用。 - `MINIO_SECRET_ACCESS_KEY`Minio 的 secretAccessKey仅在 `STORAGE_TYPE``minio` 时可用。
- `MINIO_BUCKET`**gitea**:用于存储 lfs 的 Minio 桶,仅在 `STORAGE_TYPE``minio` 时可用。 - `MINIO_BUCKET`**gitea**:用于存储 lfs 的 Minio 桶,仅在 `STORAGE_TYPE``minio` 时可用。
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio 存储桶寻址方式,可选值为 `auto`, `dns`, `path` 仅当 `STORAGE_TYPE``minio` 时可用。
- `auto` 自动检测
- `dns` 子域名寻址
- `path` 路径寻址
- `MINIO_LOCATION`**us-east-1**:创建桶的 Minio 位置,仅在 `STORAGE_TYPE``minio` 时可用。 - `MINIO_LOCATION`**us-east-1**:创建桶的 Minio 位置,仅在 `STORAGE_TYPE``minio` 时可用。
- `MINIO_BASE_PATH`**lfs/**:桶上的 Minio 基本路径,仅在 `STORAGE_TYPE``minio` 时可用。 - `MINIO_BASE_PATH`**lfs/**:桶上的 Minio 基本路径,仅在 `STORAGE_TYPE``minio` 时可用。
- `MINIO_USE_SSL`**false**Minio 启用 ssl仅在 `STORAGE_TYPE``minio` 时可用。 - `MINIO_USE_SSL`**false**Minio 启用 ssl仅在 `STORAGE_TYPE``minio` 时可用。
@ -1218,6 +1230,10 @@ ALLOW_DATA_URI_IMAGES = true
- `MINIO_ACCESS_KEY_ID`Minio 的 accessKeyID仅在 `STORAGE_TYPE``minio` 时可用。 - `MINIO_ACCESS_KEY_ID`Minio 的 accessKeyID仅在 `STORAGE_TYPE``minio` 时可用。
- `MINIO_SECRET_ACCESS_KEY`Minio 的 secretAccessKey仅在 `STORAGE_TYPE``minio` 时可用。 - `MINIO_SECRET_ACCESS_KEY`Minio 的 secretAccessKey仅在 `STORAGE_TYPE``minio` 时可用。
- `MINIO_BUCKET`**gitea**:用于存储数据的 Minio 桶,仅在 `STORAGE_TYPE``minio` 时可用。 - `MINIO_BUCKET`**gitea**:用于存储数据的 Minio 桶,仅在 `STORAGE_TYPE``minio` 时可用。
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio 存储桶寻址方式,可选值为 `auto`, `dns`, `path` 仅当 `STORAGE_TYPE``minio` 时可用。
- `auto` 自动检测
- `dns` 子域名寻址
- `path` 路径寻址
- `MINIO_LOCATION`**us-east-1**:创建桶的 Minio 位置,仅在 `STORAGE_TYPE``minio` 时可用。 - `MINIO_LOCATION`**us-east-1**:创建桶的 Minio 位置,仅在 `STORAGE_TYPE``minio` 时可用。
- `MINIO_USE_SSL`**false**Minio 启用 ssl仅在 `STORAGE_TYPE``minio` 时可用。 - `MINIO_USE_SSL`**false**Minio 启用 ssl仅在 `STORAGE_TYPE``minio` 时可用。
- `MINIO_INSECURE_SKIP_VERIFY`**false**Minio 跳过 SSL 验证,仅在 `STORAGE_TYPE``minio` 时可用。 - `MINIO_INSECURE_SKIP_VERIFY`**false**Minio 跳过 SSL 验证,仅在 `STORAGE_TYPE``minio` 时可用。
@ -1301,6 +1317,10 @@ MINIO_INSECURE_SKIP_VERIFY = false
- `MINIO_ACCESS_KEY_ID`: Minio的accessKeyID仅在`STORAGE_TYPE`为`minio`时可用。 - `MINIO_ACCESS_KEY_ID`: Minio的accessKeyID仅在`STORAGE_TYPE`为`minio`时可用。
- `MINIO_SECRET_ACCESS_KEY`: Minio的secretAccessKey仅在`STORAGE_TYPE`为`minio`时可用。 - `MINIO_SECRET_ACCESS_KEY`: Minio的secretAccessKey仅在`STORAGE_TYPE`为`minio`时可用。
- `MINIO_BUCKET`: **gitea**用于存储归档的Minio存储桶仅在`STORAGE_TYPE`为`minio`时可用。 - `MINIO_BUCKET`: **gitea**用于存储归档的Minio存储桶仅在`STORAGE_TYPE`为`minio`时可用。
- `MINIO_BUCKET_LOOKUP`: **auto**: Minio 存储桶寻址方式,可选值为 `auto`, `dns`, `path` 仅当 `STORAGE_TYPE``minio` 时可用。
- `auto` 自动检测
- `dns` 子域名寻址
- `path` 路径寻址
- `MINIO_LOCATION`: **us-east-1**用于创建存储桶的Minio位置仅在`STORAGE_TYPE`为`minio`时可用。 - `MINIO_LOCATION`: **us-east-1**用于创建存储桶的Minio位置仅在`STORAGE_TYPE`为`minio`时可用。
- `MINIO_BASE_PATH`: **repo-archive/**存储桶上的Minio基本路径仅在`STORAGE_TYPE`为`minio`时可用。 - `MINIO_BASE_PATH`: **repo-archive/**存储桶上的Minio基本路径仅在`STORAGE_TYPE`为`minio`时可用。
- `MINIO_USE_SSL`: **false**启用Minio的SSL仅在`STORAGE_TYPE`为`minio`时可用。 - `MINIO_USE_SSL`: **false**启用Minio的SSL仅在`STORAGE_TYPE`为`minio`时可用。

View file

@ -222,9 +222,9 @@ Please check [Gitea's logs](administration/logging-config.md) for error messages
<a href="{{.Link}}">{{.Repo}}#{{.Issue.Index}}</a>. <a href="{{.Link}}">{{.Repo}}#{{.Issue.Index}}</a>.
</p> </p>
{{if not (eq .Body "")}} {{if not (eq .Body "")}}
<h3>Message content:</h3> <h3>Message content</h3>
<hr> <hr>
{{.Body | Str2html}} {{.Body}}
{{end}} {{end}}
</p> </p>
<hr> <hr>
@ -245,7 +245,7 @@ This template produces something along these lines:
> [@rhonda](#) (Rhonda Myers) updated [mike/stuff#38](#). > [@rhonda](#) (Rhonda Myers) updated [mike/stuff#38](#).
> >
> #### Message content: > #### Message content
> >
> \_********************************\_******************************** > \_********************************\_********************************
> >
@ -260,19 +260,19 @@ The template system contains several functions that can be used to further proce
the messages. Here's a list of some of them: the messages. Here's a list of some of them:
| Name | Parameters | Available | Usage | | Name | Parameters | Available | Usage |
| ---------------- | ----------- | --------- | --------------------------------------------------------------------------- | | ---------------- | ----------- | --------- | ------------------------------------------------------------------- |
| `AppUrl` | - | Any | Gitea's URL | | `AppUrl` | - | Any | Gitea's URL |
| `AppName` | - | Any | Set from `app.ini`, usually "Gitea" | | `AppName` | - | Any | Set from `app.ini`, usually "Gitea" |
| `AppDomain` | - | Any | Gitea's host name | | `AppDomain` | - | Any | Gitea's host name |
| `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed | | `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed |
| `Str2html` | string | Body only | Sanitizes text by removing any HTML tags from it. | | `SanitizeHTML` | string | Body only | Sanitizes text by removing any dangerous HTML tags from it |
| `Safe` | string | Body only | Takes the input as HTML; can be used for `.ReviewComments.RenderedContent`. | | `SafeHTML` | string | Body only | Takes the input as HTML, can be used for outputing raw HTML content |
These are _functions_, not metadata, so they have to be used: These are _functions_, not metadata, so they have to be used:
```html ```html
Like this: {{Str2html "Escape<my>text"}} Like this: {{SanitizeHTML "Escape<my>text"}}
Or this: {{"Escape<my>text" | Str2html}} Or this: {{"Escape<my>text" | SanitizeHTML}}
Or this: {{AppUrl}} Or this: {{AppUrl}}
But not like this: {{.AppUrl}} But not like this: {{.AppUrl}}
``` ```

View file

@ -207,7 +207,7 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
{{if not (eq .Body "")}} {{if not (eq .Body "")}}
<h3>消息内容:</h3> <h3>消息内容:</h3>
<hr> <hr>
{{.Body | Str2html}} {{.Body}}
{{end}} {{end}}
</p> </p>
<hr> <hr>
@ -228,7 +228,7 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
> [@rhonda](#)Rhonda Myers更新了 [mike/stuff#38](#)。 > [@rhonda](#)Rhonda Myers更新了 [mike/stuff#38](#)。
> >
> #### 消息内容 > #### 消息内容
> >
> \_********************************\_******************************** > \_********************************\_********************************
> >
@ -243,19 +243,19 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
模板系统包含一些函数,可用于进一步处理和格式化消息。以下是其中一些函数的列表: 模板系统包含一些函数,可用于进一步处理和格式化消息。以下是其中一些函数的列表:
| 函数名 | 参数 | 可用于 | 用法 | | 函数名 | 参数 | 可用于 | 用法 |
| ----------------- | ----------- | ------------ | --------------------------------------------------------------------------------- | |------------------| ----------- | ------------ | ------------------------------ |
| `AppUrl` | - | 任何地方 | Gitea 的 URL | | `AppUrl` | - | 任何地方 | Gitea 的 URL |
| `AppName` | - | 任何地方 | 从 `app.ini` 中设置,通常为 "Gitea" | | `AppName` | - | 任何地方 | 从 `app.ini` 中设置,通常为 "Gitea" |
| `AppDomain` | - | 任何地方 | Gitea 的主机名 | | `AppDomain` | - | 任何地方 | Gitea 的主机名 |
| `EllipsisString` | string, int | 任何地方 | 将字符串截断为指定长度;根据需要添加省略号 | | `EllipsisString` | string, int | 任何地方 | 将字符串截断为指定长度;根据需要添加省略号 |
| `Str2html` | string | 仅正文部分 | 通过删除其中的 HTML 标签对文本进行清理 | | `SanitizeHTML` | string | 仅正文部分 | 通过删除其中的危险 HTML 标签对文本进行清理 |
| `Safe` | string | 仅正文部分 | 将输入作为 HTML 处理;可用于 `.ReviewComments.RenderedContent` 等字段 | | `SafeHTML` | string | 仅正文部分 | 将输入作为 HTML 处理;可用于输出原始的 HTML 内容 |
这些都是 _函数_,而不是元数据,因此必须按以下方式使用: 这些都是 _函数_,而不是元数据,因此必须按以下方式使用:
```html ```html
像这样使用: {{Str2html "Escape<my>text"}} 像这样使用: {{SanitizeHTML "Escape<my>text"}}
或者这样使用: {{"Escape<my>text" | Str2html}} 或者这样使用: {{"Escape<my>text" | SanitizeHTML}}
或者这样使用: {{AppUrl}} 或者这样使用: {{AppUrl}}
但不要像这样使用: {{.AppUrl}} 但不要像这样使用: {{.AppUrl}}
``` ```

View file

@ -47,7 +47,7 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h
9. Avoid unnecessary `!important` in CSS, add comments to explain why it's necessary if it can't be avoided. 9. Avoid unnecessary `!important` in CSS, add comments to explain why it's necessary if it can't be avoided.
10. Avoid mixing different events in one event listener, prefer to use individual event listeners for every event. 10. Avoid mixing different events in one event listener, prefer to use individual event listeners for every event.
11. Custom event names are recommended to use `ce-` prefix. 11. Custom event names are recommended to use `ce-` prefix.
12. Gitea's tailwind-style CSS classes use `gt-` prefix (`gt-relative`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`). 12. Prefer using Tailwind CSS which is available via `tw-` prefix, e.g. `tw-relative`. Gitea's helper CSS classes use `gt-` prefix (`gt-df`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`).
13. Avoid inline scripts & styles as much as possible, it's recommended to put JS code into JS files and use CSS classes. If inline scripts & styles are unavoidable, explain the reason why it can't be avoided. 13. Avoid inline scripts & styles as much as possible, it's recommended to put JS code into JS files and use CSS classes. If inline scripts & styles are unavoidable, explain the reason why it can't be avoided.
### Accessibility / ARIA ### Accessibility / ARIA

View file

@ -34,7 +34,7 @@ HTML 页面由[Go HTML Template](https://pkg.go.dev/html/template)渲染。
我们推荐使用[Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html)和[Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html)。 我们推荐使用[Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html)和[Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html)。
## Gitea 特定准则 ## Gitea 特定准则
1. 每个功能Fomantic-UI/jQuery 模块)应放在单独的文件/目录中。 1. 每个功能Fomantic-UI/jQuery 模块)应放在单独的文件/目录中。
2. HTML 的 id 和 class 应使用 kebab-case最好包含2-3个与功能相关的关键词。 2. HTML 的 id 和 class 应使用 kebab-case最好包含2-3个与功能相关的关键词。
@ -47,7 +47,8 @@ HTML 页面由[Go HTML Template](https://pkg.go.dev/html/template)渲染。
9. 避免在 CSS 中使用不必要的`!important`,如果无法避免,添加注释解释为什么需要它。 9. 避免在 CSS 中使用不必要的`!important`,如果无法避免,添加注释解释为什么需要它。
10. 避免在一个事件监听器中混合不同的事件,优先为每个事件使用独立的事件监听器。 10. 避免在一个事件监听器中混合不同的事件,优先为每个事件使用独立的事件监听器。
11. 推荐使用自定义事件名称前缀`ce-`。 11. 推荐使用自定义事件名称前缀`ce-`。
12. Gitea 的 tailwind-style CSS 类使用`gt-`前缀(`gt-relative`),而 Gitea 自身的私有框架级 CSS 类使用`g-`前缀(`g-modal-confirm`)。 12. 建议使用 Tailwind CSS它可以通过 `tw-` 前缀获得,例如 `tw-relative`. Gitea 自身的助手类 CSS 使用 `gt-` 前缀(`gt-df`Gitea 自身的私有框架级 CSS 类使用 `g-` 前缀(`g-modal-confirm`)。
13. 尽量避免内联脚本和样式建议将JS代码放入JS文件中并使用CSS类。如果内联脚本和样式不可避免请解释无法避免的原因。
### 可访问性 / ARIA ### 可访问性 / ARIA
@ -64,18 +65,21 @@ Gitea使用一些补丁使Fomantic UI更具可访问性参见`aria.js`和`ari
* Vue + Vanilla JS * Vue + Vanilla JS
* Fomantic-UIjQuery * Fomantic-UIjQuery
* htmx (部分页面重新加载其他静态组件)
* Vanilla JS * Vanilla JS
不推荐的实现方式: 不推荐的实现方式:
* Vue + Fomantic-UIjQuery * Vue + Fomantic-UIjQuery
* jQuery + Vanilla JS * jQuery + Vanilla JS
* htmx + 任何其他需要大量 JavaScript 代码或不必要的功能,如 htmx 脚本 (`hx-on`)
为了保持界面一致Vue 组件可以使用 Fomantic-UI 的 CSS 类。 为了保持界面一致Vue 组件可以使用 Fomantic-UI 的 CSS 类。
尽管不建议混合使用不同的框架, 尽管不建议混合使用不同的框架,
我们使用 htmx 进行简单的交互。您可以在此 [PR](https://github.com/go-gitea/gitea/pull/28908) 中查看一个简单交互的示例,其中应使用 htmx。如果您需要更高级的反应性请不要使用 htmx请使用其他框架Vue/Vanilla JS
但如果混合使用是必要的,并且代码设计良好且易于维护,也可以工作。 但如果混合使用是必要的,并且代码设计良好且易于维护,也可以工作。
### async 函数 ### `async` 函数
只有当函数内部存在`await`调用或返回`Promise`时,才将函数标记为`async`。 只有当函数内部存在`await`调用或返回`Promise`时,才将函数标记为`async`。
@ -91,6 +95,12 @@ Gitea使用一些补丁使Fomantic UI更具可访问性参见`aria.js`和`ari
这是有意为之的我们想调用异步函数并忽略Promise。 这是有意为之的我们想调用异步函数并忽略Promise。
一些 lint 规则和 IDE 也会在未处理返回的 Promise 时发出警告。 一些 lint 规则和 IDE 也会在未处理返回的 Promise 时发出警告。
### 获取数据
要获取数据,请使用`modules/fetch.js`中的包装函数`GET`、`POST`等。他们
接受内容的`data`选项,将自动设置 CSRF 令牌并返回
[Response](https://developer.mozilla.org/en-US/docs/Web/API/Response)。
### HTML 属性和 dataset ### HTML 属性和 dataset
禁止使用`dataset`,它的驼峰命名行为使得搜索属性变得困难。 禁止使用`dataset`,它的驼峰命名行为使得搜索属性变得困难。
@ -132,3 +142,7 @@ Gitea使用一些补丁使Fomantic UI更具可访问性参见`aria.js`和`ari
### Vue3 和 JSX ### Vue3 和 JSX
Gitea 现在正在使用 Vue3。我们决定不引入 JSX以保持 HTML 代码和 JavaScript 代码分离。 Gitea 现在正在使用 Vue3。我们决定不引入 JSX以保持 HTML 代码和 JavaScript 代码分离。
### UI示例
Gitea 使用一些自制的 UI 元素并自定义其他元素,以将它们更好地集成到通用 UI 方法中。当在开发模式(`RUN_MODE=dev`)下运行 Gitea 时,在 `http(s)://your-gitea-url:port/devtest` 下会提供一个包含一些标准化 UI 示例的页面。

View file

@ -135,6 +135,12 @@ body:
attributes: attributes:
value: | value: |
Thanks for taking the time to fill out this bug report! Thanks for taking the time to fill out this bug report!
# some markdown that will only be visible once the issue has been created
- type: markdown
attributes:
value: |
This issue was created by an issue **template** :)
visible: [content]
- type: input - type: input
id: contact id: contact
attributes: attributes:
@ -186,11 +192,16 @@ body:
options: options:
- label: I agree to follow this project's Code of Conduct - label: I agree to follow this project's Code of Conduct
required: true required: true
- label: I have also read the CONTRIBUTION.MD
required: true
visible: [form]
- label: This is a TODO only visible after issue creation
visible: [content]
``` ```
### Markdown ### Markdown
You can use a `markdown` element to display Markdown in your form that provides extra context to the user, but is not submitted. You can use a `markdown` element to display Markdown in your form that provides extra context to the user, but is not submitted by default.
Attributes: Attributes:
@ -198,6 +209,8 @@ Attributes:
|-------|--------------------------------------------------------------|----------|--------|---------|--------------| |-------|--------------------------------------------------------------|----------|--------|---------|--------------|
| value | The text that is rendered. Markdown formatting is supported. | Required | String | - | - | | value | The text that is rendered. Markdown formatting is supported. | Required | String | - | - |
visible: Default is **[form]**
### Textarea ### Textarea
You can use a `textarea` element to add a multi-line text field to your form. Contributors can also attach files in `textarea` fields. You can use a `textarea` element to add a multi-line text field to your form. Contributors can also attach files in `textarea` fields.
@ -218,6 +231,8 @@ Validations:
|----------|------------------------------------------------------|----------|---------|---------|--------------| |----------|------------------------------------------------------|----------|---------|---------|--------------|
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - | | required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
visible: Default is **[form, content]**
### Input ### Input
You can use an `input` element to add a single-line text field to your form. You can use an `input` element to add a single-line text field to your form.
@ -239,6 +254,8 @@ Validations:
| is_number | Prevents form submission until element is filled with a number. | Optional | Boolean | false | - | | is_number | Prevents form submission until element is filled with a number. | Optional | Boolean | false | - |
| regex | Prevents form submission until element is filled with a value that match the regular expression. | Optional | String | - | a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) | | regex | Prevents form submission until element is filled with a value that match the regular expression. | Optional | String | - | a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) |
visible: Default is **[form, content]**
### Dropdown ### Dropdown
You can use a `dropdown` element to add a dropdown menu in your form. You can use a `dropdown` element to add a dropdown menu in your form.
@ -258,6 +275,8 @@ Validations:
|----------|------------------------------------------------------|----------|---------|---------|--------------| |----------|------------------------------------------------------|----------|---------|---------|--------------|
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - | | required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
visible: Default is **[form, content]**
### Checkboxes ### Checkboxes
You can use the `checkboxes` element to add a set of checkboxes to your form. You can use the `checkboxes` element to add a set of checkboxes to your form.
@ -265,7 +284,7 @@ You can use the `checkboxes` element to add a set of checkboxes to your form.
Attributes: Attributes:
| Key | Description | Required | Type | Default | Valid values | | Key | Description | Required | Type | Default | Valid values |
|-------------|-------------------------------------------------------------------------------------------------------|----------|--------|--------------|--------------| | ----------- | ----------------------------------------------------------------------------------------------------- | -------- | ------ | ------------ | ------------ |
| label | A brief description of the expected user input, which is displayed in the form. | Required | String | - | - | | label | A brief description of the expected user input, which is displayed in the form. | Required | String | - | - |
| description | A description of the set of checkboxes, which is displayed in the form. Supports Markdown formatting. | Optional | String | Empty String | - | | description | A description of the set of checkboxes, which is displayed in the form. Supports Markdown formatting. | Optional | String | Empty String | - |
| options | An array of checkboxes that the user can select. For syntax, see below. | Required | Array | - | - | | options | An array of checkboxes that the user can select. For syntax, see below. | Required | Array | - | - |
@ -273,9 +292,12 @@ Attributes:
For each value in the options array, you can set the following keys. For each value in the options array, you can set the following keys.
| Key | Description | Required | Type | Default | Options | | Key | Description | Required | Type | Default | Options |
|----------|------------------------------------------------------------------------------------------------------------------------------------------|----------|---------|---------|---------| |--------------|------------------------------------------------------------------------------------------------------------------------------------------|----------|--------------|---------|---------|
| label | The identifier for the option, which is displayed in the form. Markdown is supported for bold or italic text formatting, and hyperlinks. | Required | String | - | - | | label | The identifier for the option, which is displayed in the form. Markdown is supported for bold or italic text formatting, and hyperlinks. | Required | String | - | - |
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - | | required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
| visible | Whether a specific checkbox appears in the form only, in the created issue only, or both. Valid options are "form" and "content". | Optional | String array | false | - |
visible: Default is **[form, content]**
## Syntax for issue config ## Syntax for issue config
@ -292,14 +314,14 @@ contact_links:
### Possible Options ### Possible Options
| Key | Description | Type | Default | | Key | Description | Type | Default |
|----------------------|-------------------------------------------------------------------------------------------------------|--------------------|----------------| |----------------------|-------------------------------------------------------|--------------------|-------------|
| blank_issues_enabled | If set to false, the User is forced to use a Template | Boolean | true | | blank_issues_enabled | If set to false, the User is forced to use a Template | Boolean | true |
| contact_links | Custom Links to show in the Choose Box | Contact Link Array | Empty Array | | contact_links | Custom Links to show in the Choose Box | Contact Link Array | Empty Array |
### Contact Link ### Contact Link
| Key | Description | Type | Required | | Key | Description | Type | Required |
|----------------------|-------------------------------------------------------------------------------------------------------|---------|----------| |-------|----------------------------------|--------|----------|
| name | the name of your link | String | true | | name | the name of your link | String | true |
| url | The URL of your Link | String | true | | url | The URL of your Link | String | true |
| about | A short description of your Link | String | true | | about | A short description of your Link | String | true |

17
go.mod
View file

@ -45,9 +45,9 @@ require (
github.com/go-git/go-billy/v5 v5.5.0 github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.11.0 github.com/go-git/go-git/v5 v5.11.0
github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-ldap/ldap/v3 v3.4.6
github.com/go-sql-driver/mysql v1.7.1 github.com/go-sql-driver/mysql v1.8.0
github.com/go-swagger/go-swagger v0.30.5 github.com/go-swagger/go-swagger v0.30.5
github.com/go-testfixtures/testfixtures/v3 v3.9.0 github.com/go-testfixtures/testfixtures/v3 v3.10.0
github.com/go-webauthn/webauthn v0.10.0 github.com/go-webauthn/webauthn v0.10.0
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
@ -55,7 +55,7 @@ require (
github.com/golang-jwt/jwt/v5 v5.2.0 github.com/golang-jwt/jwt/v5 v5.2.0
github.com/google/go-github/v57 v57.0.0 github.com/google/go-github/v57 v57.0.0
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 github.com/google/pprof v0.0.0-20240117000934-35fc243c5815
github.com/google/uuid v1.5.0 github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.1.2 github.com/gorilla/feeds v1.1.2
github.com/gorilla/sessions v1.2.2 github.com/gorilla/sessions v1.2.2
github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/go-version v1.6.0
@ -71,7 +71,7 @@ require (
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/markbates/goth v1.78.0 github.com/markbates/goth v1.78.0
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-sqlite3 v1.14.19 github.com/mattn/go-sqlite3 v1.14.22
github.com/meilisearch/meilisearch-go v0.26.1 github.com/meilisearch/meilisearch-go v0.26.1
github.com/mholt/archiver/v3 v3.5.1 github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.26 github.com/microcosm-cc/bluemonday v1.0.26
@ -109,7 +109,7 @@ require (
golang.org/x/text v0.14.0 golang.org/x/text v0.14.0
golang.org/x/tools v0.17.0 golang.org/x/tools v0.17.0
google.golang.org/grpc v1.60.1 google.golang.org/grpc v1.60.1
google.golang.org/protobuf v1.32.0 google.golang.org/protobuf v1.33.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
@ -123,9 +123,10 @@ require (
cloud.google.com/go/compute v1.23.3 // indirect cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect
dario.cat/mergo v1.0.0 // indirect dario.cat/mergo v1.0.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
github.com/ClickHouse/ch-go v0.61.1 // indirect github.com/ClickHouse/ch-go v0.61.1 // indirect
github.com/ClickHouse/clickhouse-go/v2 v2.17.1 // indirect github.com/ClickHouse/clickhouse-go/v2 v2.18.0 // indirect
github.com/DataDog/zstd v1.5.5 // indirect github.com/DataDog/zstd v1.5.5 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect
@ -238,7 +239,7 @@ require (
github.com/oklog/ulid v1.3.1 // indirect github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect
github.com/paulmach/orb v0.11.0 // indirect github.com/paulmach/orb v0.11.1 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect
@ -298,7 +299,7 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
replace github.com/nektos/act => gitea.com/gitea/act v0.2.51 replace github.com/nektos/act => gitea.com/gitea/act v0.259.1
replace github.com/gorilla/feeds => github.com/yardenshoham/feeds v0.0.0-20240110072658-f3d0c21c0bd5 replace github.com/gorilla/feeds => github.com/yardenshoham/feeds v0.0.0-20240110072658-f3d0c21c0bd5

34
go.sum
View file

@ -48,10 +48,12 @@ connectrpc.com/connect v1.15.0/go.mod h1:bQmjpDY8xItMnttnurVgOkHUBMRT9cpsNi2O4Aj
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
gitea.com/gitea/act v0.2.51 h1:gXc/B4OlTciTTzAx9cmNyw04n2SDO7exPjAsR5Idu+c= gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw=
gitea.com/gitea/act v0.2.51/go.mod h1:CoaX2053jqBlD6JMgu4d4UgFL/rp2I14Kt5mMqcs0Z0= gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8=
gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669 h1:RUBX+MK/TsDxpHmymaOaydfigEbbzqUnG1OTZU/HAeo= gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669 h1:RUBX+MK/TsDxpHmymaOaydfigEbbzqUnG1OTZU/HAeo=
gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc= gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc=
gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0= gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0=
@ -80,8 +82,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/ch-go v0.61.1 h1:j5rx3qnvcnYjhnP1IdXE/vdIRQiqgwAzyqOaasA6QCw= github.com/ClickHouse/ch-go v0.61.1 h1:j5rx3qnvcnYjhnP1IdXE/vdIRQiqgwAzyqOaasA6QCw=
github.com/ClickHouse/ch-go v0.61.1/go.mod h1:myxt/JZgy2BYHFGQqzmaIpbfr5CMbs3YHVULaWQj5YU= github.com/ClickHouse/ch-go v0.61.1/go.mod h1:myxt/JZgy2BYHFGQqzmaIpbfr5CMbs3YHVULaWQj5YU=
github.com/ClickHouse/clickhouse-go/v2 v2.17.1 h1:ZCmAYWpu75IyEi7+Yrs/uaAjiCGY5wfW5kXo64exkX4= github.com/ClickHouse/clickhouse-go/v2 v2.18.0 h1:O1LicIeg2JS2V29fKRH4+yT3f6jvvcJBm506dpVQ4mQ=
github.com/ClickHouse/clickhouse-go/v2 v2.17.1/go.mod h1:rkGTvFDTLqLIm0ma+13xmcCfr/08Gvs7KmFt1tgiWHQ= github.com/ClickHouse/clickhouse-go/v2 v2.18.0/go.mod h1:ztQvX6wm7kAbhJslS87EXEhOVNY/TObXwyURnGju5FQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
@ -342,8 +344,8 @@ github.com/go-openapi/validate v0.22.6/go.mod h1:eaddXSqKeTg5XpSmj1dYyFTK/95n/XH
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v8 v8.4.0/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M= github.com/go-redis/redis/v8 v8.4.0/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-swagger/go-swagger v0.30.5 h1:SQ2+xSonWjjoEMOV5tcOnZJVlfyUfCBhGQGArS1b9+U= github.com/go-swagger/go-swagger v0.30.5 h1:SQ2+xSonWjjoEMOV5tcOnZJVlfyUfCBhGQGArS1b9+U=
github.com/go-swagger/go-swagger v0.30.5/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewetCdvEXqgPvbc/Q= github.com/go-swagger/go-swagger v0.30.5/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewetCdvEXqgPvbc/Q=
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0= github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0=
@ -351,8 +353,8 @@ github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.m
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-testfixtures/testfixtures/v3 v3.9.0 h1:938g5V+GWLVejm3Hc+nWCuEXRlcglZDDlN/t1gWzcSY= github.com/go-testfixtures/testfixtures/v3 v3.10.0 h1:BrBwN7AuC+74g5qtk9D59TLGOaEa8Bw1WmIsf+SyzWc=
github.com/go-testfixtures/testfixtures/v3 v3.9.0/go.mod h1:cdsKD2ApFBjdog9jRsz6EJqF+LClq/hrwE9K/1Dzo4s= github.com/go-testfixtures/testfixtures/v3 v3.10.0/go.mod h1:z8RoleoNtibi6Ar8ziCW7e6PQ+jWiqbUWvuv8AMe4lo=
github.com/go-webauthn/webauthn v0.10.0 h1:yuW2e1tXnRAwAvKrR4q4LQmc6XtCMH639/ypZGhZCwk= github.com/go-webauthn/webauthn v0.10.0 h1:yuW2e1tXnRAwAvKrR4q4LQmc6XtCMH639/ypZGhZCwk=
github.com/go-webauthn/webauthn v0.10.0/go.mod h1:l0NiauXhL6usIKqNLCUM3Qir43GK7ORg8ggold0Uv/Y= github.com/go-webauthn/webauthn v0.10.0/go.mod h1:l0NiauXhL6usIKqNLCUM3Qir43GK7ORg8ggold0Uv/Y=
github.com/go-webauthn/x v0.1.6 h1:QNAX+AWeqRt9loE8mULeWJCqhVG5D/jvdmJ47fIWCkQ= github.com/go-webauthn/x v0.1.6 h1:QNAX+AWeqRt9loE8mULeWJCqhVG5D/jvdmJ47fIWCkQ=
@ -455,8 +457,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -607,8 +609,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/meilisearch/meilisearch-go v0.26.1 h1:3bmo2uLijX7kvBmiZ9LupVfC95TFcRJDgrRTzbOoE4A= github.com/meilisearch/meilisearch-go v0.26.1 h1:3bmo2uLijX7kvBmiZ9LupVfC95TFcRJDgrRTzbOoE4A=
github.com/meilisearch/meilisearch-go v0.26.1/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0= github.com/meilisearch/meilisearch-go v0.26.1/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0=
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
@ -679,8 +681,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU= github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU=
github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/paulmach/orb v0.11.0 h1:JfVXJUBeH9ifc/OrhBY0lL16QsmPgpCHMlqSSYhcgAA= github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
github.com/paulmach/orb v0.11.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
@ -1239,8 +1241,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View file

@ -13,6 +13,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/shared/types" "code.gitea.io/gitea/models/shared/types"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -159,7 +160,7 @@ type FindRunnerOptions struct {
OwnerID int64 OwnerID int64
Sort string Sort string
Filter string Filter string
IsOnline util.OptionalBool IsOnline optional.Option[bool]
WithAvailable bool // not only runners belong to, but also runners can be used WithAvailable bool // not only runners belong to, but also runners can be used
} }
@ -186,11 +187,13 @@ func (opts FindRunnerOptions) ToConds() builder.Cond {
cond = cond.And(builder.Like{"name", opts.Filter}) cond = cond.And(builder.Like{"name", opts.Filter})
} }
if opts.IsOnline.IsTrue() { if opts.IsOnline.Has() {
if opts.IsOnline.Value() {
cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()}) cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
} else if opts.IsOnline.IsFalse() { } else {
cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()}) cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
} }
}
return cond return cond
} }

View file

@ -10,6 +10,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -82,3 +83,35 @@ func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error)
}) })
return count != 0, err return count != 0, err
} }
func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) {
variables := map[string]string{}
// Global
globalVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{})
if err != nil {
log.Error("find global variables: %v", err)
return nil, err
}
// Org / User level
ownerVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{OwnerID: run.Repo.OwnerID})
if err != nil {
log.Error("find variables of org: %d, error: %v", run.Repo.OwnerID, err)
return nil, err
}
// Repo level
repoVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{RepoID: run.RepoID})
if err != nil {
log.Error("find variables of repo: %d, error: %v", run.RepoID, err)
return nil, err
}
// Level precedence: Repo > Org / User > Global
for _, v := range append(globalVariables, append(ownerVariables, repoVariables...)...) {
variables[v.Name] = v.Data
}
return variables, nil
}

View file

@ -227,8 +227,8 @@ func (a *Action) ShortActUserName(ctx context.Context) string {
return base.EllipsisString(a.GetActUserName(ctx), 20) return base.EllipsisString(a.GetActUserName(ctx), 20)
} }
// GetDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank. // GetActDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank.
func (a *Action) GetDisplayName(ctx context.Context) string { func (a *Action) GetActDisplayName(ctx context.Context) string {
if setting.UI.DefaultShowFullName { if setting.UI.DefaultShowFullName {
trimmedFullName := strings.TrimSpace(a.GetActFullName(ctx)) trimmedFullName := strings.TrimSpace(a.GetActFullName(ctx))
if len(trimmedFullName) > 0 { if len(trimmedFullName) > 0 {
@ -238,8 +238,8 @@ func (a *Action) GetDisplayName(ctx context.Context) string {
return a.ShortActUserName(ctx) return a.ShortActUserName(ctx)
} }
// GetDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME // GetActDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME
func (a *Action) GetDisplayNameTitle(ctx context.Context) string { func (a *Action) GetActDisplayNameTitle(ctx context.Context) string {
if setting.UI.DefaultShowFullName { if setting.UI.DefaultShowFullName {
return a.ShortActUserName(ctx) return a.ShortActUserName(ctx)
} }
@ -395,10 +395,14 @@ func (a *Action) GetCreate() time.Time {
return a.CreatedUnix.AsTime() return a.CreatedUnix.AsTime()
} }
// GetIssueInfos returns a list of issues associated with // GetIssueInfos returns a list of associated information with the action.
// the action.
func (a *Action) GetIssueInfos() []string { func (a *Action) GetIssueInfos() []string {
return strings.SplitN(a.Content, "|", 3) // make sure it always returns 3 elements, because there are some access to the a[1] and a[2] without checking the length
ret := strings.SplitN(a.Content, "|", 3)
for len(ret) < 3 {
ret = append(ret, "")
}
return ret
} }
// GetIssueTitle returns the title of first issue associated with the action. // GetIssueTitle returns the title of first issue associated with the action.

View file

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -243,14 +244,14 @@ func CreateSource(ctx context.Context, source *Source) error {
type FindSourcesOptions struct { type FindSourcesOptions struct {
db.ListOptions db.ListOptions
IsActive util.OptionalBool IsActive optional.Option[bool]
LoginType Type LoginType Type
} }
func (opts FindSourcesOptions) ToConds() builder.Cond { func (opts FindSourcesOptions) ToConds() builder.Cond {
conds := builder.NewCond() conds := builder.NewCond()
if !opts.IsActive.IsNone() { if opts.IsActive.Has() {
conds = conds.And(builder.Eq{"is_active": opts.IsActive.IsTrue()}) conds = conds.And(builder.Eq{"is_active": opts.IsActive.Value()})
} }
if opts.LoginType != NoType { if opts.LoginType != NoType {
conds = conds.And(builder.Eq{"`type`": opts.LoginType}) conds = conds.And(builder.Eq{"`type`": opts.LoginType})
@ -262,7 +263,7 @@ func (opts FindSourcesOptions) ToConds() builder.Cond {
// source of type LoginSSPI // source of type LoginSSPI
func IsSSPIEnabled(ctx context.Context) bool { func IsSSPIEnabled(ctx context.Context) bool {
exist, err := db.Exist[Source](ctx, FindSourcesOptions{ exist, err := db.Exist[Source](ctx, FindSourcesOptions{
IsActive: util.OptionalBoolTrue, IsActive: optional.Some(true),
LoginType: SSPI, LoginType: SSPI,
}.ToConds()) }.ToConds())
if err != nil { if err != nil {

View file

@ -166,8 +166,7 @@ func preprocessDatabaseCollation(x *xorm.Engine) {
// try to alter database collation to expected if the database is empty, it might fail in some cases (and it isn't necessary to succeed) // try to alter database collation to expected if the database is empty, it might fail in some cases (and it isn't necessary to succeed)
// at the moment, there is no "altering" solution for MSSQL, site admin should manually change the database collation // at the moment, there is no "altering" solution for MSSQL, site admin should manually change the database collation
// and there is a bug https://github.com/go-testfixtures/testfixtures/pull/182 mssql: Invalid object name 'information_schema.tables'. if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) && r.ExistingTableNumber == 0 {
if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) && r.ExistingTableNumber == 0 && x.Dialect().URI().DBType == schemas.MYSQL {
if err = alterDatabaseCollation(x, r.ExpectedCollation); err != nil { if err = alterDatabaseCollation(x, r.ExpectedCollation); err != nil {
log.Error("Failed to change database collation to %q: %v", r.ExpectedCollation, err) log.Error("Failed to change database collation to %q: %v", r.ExpectedCollation, err)
} else { } else {

View file

@ -17,3 +17,22 @@
updated: 1683636626 updated: 1683636626
need_approval: 0 need_approval: 0
approved_by: 0 approved_by: 0
-
id: 792
title: "update actions"
repo_id: 4
owner_id: 1
workflow_id: "artifact.yaml"
index: 188
trigger_user_id: 1
ref: "refs/heads/master"
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
event: "push"
is_fork_pull_request: 0
status: 1
started: 1683636528
stopped: 1683636626
created: 1683636108
updated: 1683636626
need_approval: 0
approved_by: 0

View file

@ -12,3 +12,17 @@
status: 1 status: 1
started: 1683636528 started: 1683636528
stopped: 1683636626 stopped: 1683636626
-
id: 193
run_id: 792
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
name: job_2
attempt: 1
job_id: job_2
task_id: 48
status: 1
started: 1683636528
stopped: 1683636626

View file

@ -18,3 +18,23 @@
log_length: 707 log_length: 707
log_size: 90179 log_size: 90179
log_expired: 0 log_expired: 0
-
id: 48
job_id: 193
attempt: 1
runner_id: 1
status: 6 # 6 is the status code for "running", running task can upload artifacts
started: 1683636528
stopped: 1683636626
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
token_hash: ffffcfffffffbffffffffffffffffefffffffafffffffffffffffffffffffffffffdffffffffffffffffffffffffffffffff
token_salt: ffffffffff
token_last_eight: ffffffff
log_filename: artifact-test2/2f/47.log
log_in_storage: 1
log_length: 707
log_size: 90179
log_expired: 0

View file

@ -3,3 +3,35 @@
hook_id: 1 hook_id: 1
uuid: uuid1 uuid: uuid1
is_delivered: true is_delivered: true
is_succeed: false
request_content: >
{
"url": "/matrix-delivered",
"http_method":"PUT",
"headers": {
"X-Head": "42"
},
"body": "{}"
}
-
id: 2
hook_id: 1
uuid: uuid2
is_delivered: false
-
id: 3
hook_id: 1
uuid: uuid3
is_delivered: true
is_succeed: true
payload_content: '{"key":"value"}' # legacy task, payload saved in payload_content (and not in request_content)
request_content: >
{
"url": "/matrix-success",
"http_method":"PUT",
"headers": {
"X-Head": "42"
}
}

View file

@ -162,6 +162,11 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
return &branch, nil return &branch, nil
} }
func GetBranches(ctx context.Context, repoID int64, branchNames []string) ([]*Branch, error) {
branches := make([]*Branch, 0, len(branchNames))
return branches, db.GetEngine(ctx).Where("repo_id=?", repoID).In("name", branchNames).Find(&branches)
}
func AddBranches(ctx context.Context, branches []*Branch) error { func AddBranches(ctx context.Context, branches []*Branch) error {
for _, branch := range branches { for _, branch := range branches {
if _, err := db.GetEngine(ctx).Insert(branch); err != nil { if _, err := db.GetEngine(ctx).Insert(branch); err != nil {

View file

@ -8,6 +8,7 @@ package issues
import ( import (
"context" "context"
"fmt" "fmt"
"html/template"
"strconv" "strconv"
"unicode/utf8" "unicode/utf8"
@ -21,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/references" "code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -260,7 +262,7 @@ type Comment struct {
Line int64 // - previous line / + proposed line Line int64 // - previous line / + proposed line
TreePath string TreePath string
Content string `xorm:"LONGTEXT"` Content string `xorm:"LONGTEXT"`
RenderedContent string `xorm:"-"` RenderedContent template.HTML `xorm:"-"`
// Path represents the 4 lines of code cemented by this comment // Path represents the 4 lines of code cemented by this comment
Patch string `xorm:"-"` Patch string `xorm:"-"`
@ -1043,8 +1045,8 @@ type FindCommentsOptions struct {
TreePath string TreePath string
Type CommentType Type CommentType
IssueIDs []int64 IssueIDs []int64
Invalidated util.OptionalBool Invalidated optional.Option[bool]
IsPull util.OptionalBool IsPull optional.Option[bool]
} }
// ToConds implements FindOptions interface // ToConds implements FindOptions interface
@ -1076,11 +1078,11 @@ func (opts FindCommentsOptions) ToConds() builder.Cond {
if len(opts.TreePath) > 0 { if len(opts.TreePath) > 0 {
cond = cond.And(builder.Eq{"comment.tree_path": opts.TreePath}) cond = cond.And(builder.Eq{"comment.tree_path": opts.TreePath})
} }
if !opts.Invalidated.IsNone() { if opts.Invalidated.Has() {
cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.IsTrue()}) cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.Value()})
} }
if opts.IsPull != util.OptionalBoolNone { if opts.IsPull.Has() {
cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.IsTrue()}) cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.Value()})
} }
return cond return cond
} }
@ -1089,7 +1091,7 @@ func (opts FindCommentsOptions) ToConds() builder.Cond {
func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList, error) { func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList, error) {
comments := make([]*Comment, 0, 10) comments := make([]*Comment, 0, 10)
sess := db.GetEngine(ctx).Where(opts.ToConds()) sess := db.GetEngine(ctx).Where(opts.ToConds())
if opts.RepoID > 0 || opts.IsPull != util.OptionalBoolNone { if opts.RepoID > 0 || opts.IsPull.Has() {
sess.Join("INNER", "issue", "issue.id = comment.issue_id") sess.Join("INNER", "issue", "issue.id = comment.issue_id")
} }

View file

@ -7,6 +7,7 @@ package issues
import ( import (
"context" "context"
"fmt" "fmt"
"html/template"
"regexp" "regexp"
"slices" "slices"
@ -105,7 +106,7 @@ type Issue struct {
OriginalAuthorID int64 `xorm:"index"` OriginalAuthorID int64 `xorm:"index"`
Title string `xorm:"name"` Title string `xorm:"name"`
Content string `xorm:"LONGTEXT"` Content string `xorm:"LONGTEXT"`
RenderedContent string `xorm:"-"` RenderedContent template.HTML `xorm:"-"`
Labels []*Label `xorm:"-"` Labels []*Label `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"` MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"` Milestone *Milestone `xorm:"-"`

View file

@ -13,7 +13,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"xorm.io/builder" "xorm.io/builder"
"xorm.io/xorm" "xorm.io/xorm"
@ -34,8 +34,8 @@ type IssuesOptions struct { //nolint
MilestoneIDs []int64 MilestoneIDs []int64
ProjectID int64 ProjectID int64
ProjectBoardID int64 ProjectBoardID int64
IsClosed util.OptionalBool IsClosed optional.Option[bool]
IsPull util.OptionalBool IsPull optional.Option[bool]
LabelIDs []int64 LabelIDs []int64
IncludedLabelNames []string IncludedLabelNames []string
ExcludedLabelNames []string ExcludedLabelNames []string
@ -46,7 +46,7 @@ type IssuesOptions struct { //nolint
UpdatedBeforeUnix int64 UpdatedBeforeUnix int64
// prioritize issues from this repo // prioritize issues from this repo
PriorityRepoID int64 PriorityRepoID int64
IsArchived util.OptionalBool IsArchived optional.Option[bool]
Org *organization.Organization // issues permission scope Org *organization.Organization // issues permission scope
Team *organization.Team // issues permission scope Team *organization.Team // issues permission scope
User *user_model.User // issues permission scope User *user_model.User // issues permission scope
@ -217,8 +217,8 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
applyRepoConditions(sess, opts) applyRepoConditions(sess, opts)
if !opts.IsClosed.IsNone() { if opts.IsClosed.Has() {
sess.And("issue.is_closed=?", opts.IsClosed.IsTrue()) sess.And("issue.is_closed=?", opts.IsClosed.Value())
} }
if opts.AssigneeID > 0 { if opts.AssigneeID > 0 {
@ -260,21 +260,18 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
applyProjectBoardCondition(sess, opts) applyProjectBoardCondition(sess, opts)
switch opts.IsPull { if opts.IsPull.Has() {
case util.OptionalBoolTrue: sess.And("issue.is_pull=?", opts.IsPull.Value())
sess.And("issue.is_pull=?", true)
case util.OptionalBoolFalse:
sess.And("issue.is_pull=?", false)
} }
if opts.IsArchived != util.OptionalBoolNone { if opts.IsArchived.Has() {
sess.And(builder.Eq{"repository.is_archived": opts.IsArchived.IsTrue()}) sess.And(builder.Eq{"repository.is_archived": opts.IsArchived.Value()})
} }
applyLabelsCondition(sess, opts) applyLabelsCondition(sess, opts)
if opts.User != nil { if opts.User != nil {
sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.IsTrue())) sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.Value()))
} }
return sess return sess

View file

@ -8,7 +8,6 @@ import (
"fmt" "fmt"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
"xorm.io/xorm" "xorm.io/xorm"
@ -170,11 +169,8 @@ func applyIssuesOptions(sess *xorm.Session, opts *IssuesOptions, issueIDs []int6
applyReviewedCondition(sess, opts.ReviewedID) applyReviewedCondition(sess, opts.ReviewedID)
} }
switch opts.IsPull { if opts.IsPull.Has() {
case util.OptionalBoolTrue: sess.And("issue.is_pull=?", opts.IsPull.Value())
sess.And("issue.is_pull=?", true)
case util.OptionalBoolFalse:
sess.And("issue.is_pull=?", false)
} }
return sess return sess

View file

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/label" "code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -126,7 +127,7 @@ func (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{ counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{
RepoIDs: []int64{repoID}, RepoIDs: []int64{repoID},
LabelIDs: []int64{labelID}, LabelIDs: []int64{labelID},
IsClosed: util.OptionalBoolFalse, IsClosed: optional.Some(false),
}) })
for _, count := range counts { for _, count := range counts {

View file

@ -6,10 +6,12 @@ package issues
import ( import (
"context" "context"
"fmt" "fmt"
"html/template"
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -48,7 +50,7 @@ type Milestone struct {
Repo *repo_model.Repository `xorm:"-"` Repo *repo_model.Repository `xorm:"-"`
Name string Name string
Content string `xorm:"TEXT"` Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"` RenderedContent template.HTML `xorm:"-"`
IsClosed bool IsClosed bool
NumIssues int NumIssues int
NumClosedIssues int NumClosedIssues int
@ -313,7 +315,7 @@ func DeleteMilestoneByRepoID(ctx context.Context, repoID, id int64) error {
} }
numClosedMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{ numClosedMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{
RepoID: repo.ID, RepoID: repo.ID,
IsClosed: util.OptionalBoolTrue, IsClosed: optional.Some(true),
}) })
if err != nil { if err != nil {
return err return err

View file

@ -8,7 +8,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"xorm.io/builder" "xorm.io/builder"
) )
@ -28,7 +28,7 @@ func (milestones MilestoneList) getMilestoneIDs() []int64 {
type FindMilestoneOptions struct { type FindMilestoneOptions struct {
db.ListOptions db.ListOptions
RepoID int64 RepoID int64
IsClosed util.OptionalBool IsClosed optional.Option[bool]
Name string Name string
SortType string SortType string
RepoCond builder.Cond RepoCond builder.Cond
@ -40,8 +40,8 @@ func (opts FindMilestoneOptions) ToConds() builder.Cond {
if opts.RepoID != 0 { if opts.RepoID != 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
} }
if opts.IsClosed != util.OptionalBoolNone { if opts.IsClosed.Has() {
cond = cond.And(builder.Eq{"is_closed": opts.IsClosed.IsTrue()}) cond = cond.And(builder.Eq{"is_closed": opts.IsClosed.Value()})
} }
if opts.RepoCond != nil && opts.RepoCond.IsValid() { if opts.RepoCond != nil && opts.RepoCond.IsValid() {
cond = cond.And(builder.In("repo_id", builder.Select("id").From("repository").Where(opts.RepoCond))) cond = cond.And(builder.In("repo_id", builder.Select("id").From("repository").Where(opts.RepoCond)))

View file

@ -11,10 +11,10 @@ import (
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -39,10 +39,10 @@ func TestGetMilestoneByRepoID(t *testing.T) {
func TestGetMilestonesByRepoID(t *testing.T) { func TestGetMilestonesByRepoID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
test := func(repoID int64, state api.StateType) { test := func(repoID int64, state api.StateType) {
var isClosed util.OptionalBool var isClosed optional.Option[bool]
switch state { switch state {
case api.StateClosed, api.StateOpen: case api.StateClosed, api.StateOpen:
isClosed = util.OptionalBoolOf(state == api.StateClosed) isClosed = optional.Some(state == api.StateClosed)
} }
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{ milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
@ -84,7 +84,7 @@ func TestGetMilestonesByRepoID(t *testing.T) {
milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{ milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
RepoID: unittest.NonexistentID, RepoID: unittest.NonexistentID,
IsClosed: util.OptionalBoolFalse, IsClosed: optional.Some(false),
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, milestones, 0) assert.Len(t, milestones, 0)
@ -101,7 +101,7 @@ func TestGetMilestones(t *testing.T) {
PageSize: setting.UI.IssuePagingNum, PageSize: setting.UI.IssuePagingNum,
}, },
RepoID: repo.ID, RepoID: repo.ID,
IsClosed: util.OptionalBoolFalse, IsClosed: optional.Some(false),
SortType: sortType, SortType: sortType,
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -118,7 +118,7 @@ func TestGetMilestones(t *testing.T) {
PageSize: setting.UI.IssuePagingNum, PageSize: setting.UI.IssuePagingNum,
}, },
RepoID: repo.ID, RepoID: repo.ID,
IsClosed: util.OptionalBoolTrue, IsClosed: optional.Some(true),
Name: "", Name: "",
SortType: sortType, SortType: sortType,
}) })
@ -178,7 +178,7 @@ func TestCountRepoClosedMilestones(t *testing.T) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
count, err := db.Count[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{ count, err := db.Count[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
RepoID: repoID, RepoID: repoID,
IsClosed: util.OptionalBoolTrue, IsClosed: optional.Some(true),
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, repo.NumClosedMilestones, count) assert.EqualValues(t, repo.NumClosedMilestones, count)
@ -189,7 +189,7 @@ func TestCountRepoClosedMilestones(t *testing.T) {
count, err := db.Count[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{ count, err := db.Count[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
RepoID: unittest.NonexistentID, RepoID: unittest.NonexistentID,
IsClosed: util.OptionalBoolTrue, IsClosed: optional.Some(true),
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 0, count) assert.EqualValues(t, 0, count)
@ -206,7 +206,7 @@ func TestCountMilestonesByRepoIDs(t *testing.T) {
openCounts, err := issues_model.CountMilestonesMap(db.DefaultContext, issues_model.FindMilestoneOptions{ openCounts, err := issues_model.CountMilestonesMap(db.DefaultContext, issues_model.FindMilestoneOptions{
RepoIDs: []int64{1, 2}, RepoIDs: []int64{1, 2},
IsClosed: util.OptionalBoolFalse, IsClosed: optional.Some(false),
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, repo1OpenCount, openCounts[1]) assert.EqualValues(t, repo1OpenCount, openCounts[1])
@ -215,7 +215,7 @@ func TestCountMilestonesByRepoIDs(t *testing.T) {
closedCounts, err := issues_model.CountMilestonesMap(db.DefaultContext, closedCounts, err := issues_model.CountMilestonesMap(db.DefaultContext,
issues_model.FindMilestoneOptions{ issues_model.FindMilestoneOptions{
RepoIDs: []int64{1, 2}, RepoIDs: []int64{1, 2},
IsClosed: util.OptionalBoolTrue, IsClosed: optional.Some(true),
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, repo1ClosedCount, closedCounts[1]) assert.EqualValues(t, repo1ClosedCount, closedCounts[1])
@ -234,7 +234,7 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
PageSize: setting.UI.IssuePagingNum, PageSize: setting.UI.IssuePagingNum,
}, },
RepoIDs: []int64{repo1.ID, repo2.ID}, RepoIDs: []int64{repo1.ID, repo2.ID},
IsClosed: util.OptionalBoolFalse, IsClosed: optional.Some(false),
SortType: sortType, SortType: sortType,
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -252,7 +252,7 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
PageSize: setting.UI.IssuePagingNum, PageSize: setting.UI.IssuePagingNum,
}, },
RepoIDs: []int64{repo1.ID, repo2.ID}, RepoIDs: []int64{repo1.ID, repo2.ID},
IsClosed: util.OptionalBoolTrue, IsClosed: optional.Some(true),
SortType: sortType, SortType: sortType,
}) })
assert.NoError(t, err) assert.NoError(t, err)

View file

@ -891,6 +891,14 @@ func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullReque
return nil return nil
} }
if err := pull.LoadRepo(ctx); err != nil {
return err
}
if pull.Repo.IsFork {
return nil
}
if err := pr.LoadBaseRepo(ctx); err != nil { if err := pr.LoadBaseRepo(ctx); err != nil {
return err return err
} }
@ -901,12 +909,7 @@ func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullReque
} }
defer repo.Close() defer repo.Close()
branch, err := repo.GetDefaultBranch() commit, err := repo.GetBranchCommit(pr.BaseRepo.DefaultBranch)
if err != nil {
return err
}
commit, err := repo.GetBranchCommit(branch)
if err != nil { if err != nil {
return err return err
} }
@ -929,7 +932,7 @@ func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullReque
} }
// Use the merge base as the base instead of the main branch to avoid problems // Use the merge base as the base instead of the main branch to avoid problems
// if the pull request is out of date with the base branch. // if the pull request is out of date with the base branch.
changedFiles, err := repo.GetFilesChangedBetween(prInfo.MergeBase, pr.HeadCommitID) changedFiles, err := repo.GetFilesChangedBetween(prInfo.MergeBase, prInfo.HeadCommitID)
if err != nil { if err != nil {
return err return err
} }

View file

@ -9,7 +9,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"xorm.io/builder" "xorm.io/builder"
) )
@ -68,7 +68,7 @@ type FindReviewOptions struct {
IssueID int64 IssueID int64
ReviewerID int64 ReviewerID int64
OfficialOnly bool OfficialOnly bool
Dismissed util.OptionalBool Dismissed optional.Option[bool]
} }
func (opts *FindReviewOptions) toCond() builder.Cond { func (opts *FindReviewOptions) toCond() builder.Cond {
@ -85,8 +85,8 @@ func (opts *FindReviewOptions) toCond() builder.Cond {
if opts.OfficialOnly { if opts.OfficialOnly {
cond = cond.And(builder.Eq{"official": true}) cond = cond.And(builder.Eq{"official": true})
} }
if !opts.Dismissed.IsNone() { if opts.Dismissed.Has() {
cond = cond.And(builder.Eq{"dismissed": opts.Dismissed.IsTrue()}) cond = cond.And(builder.Eq{"dismissed": opts.Dismissed.Value()})
} }
return cond return cond
} }

View file

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -340,7 +341,7 @@ func GetTrackedTimeByID(ctx context.Context, id int64) (*TrackedTime, error) {
} }
// GetIssueTotalTrackedTime returns the total tracked time for issues by given conditions. // GetIssueTotalTrackedTime returns the total tracked time for issues by given conditions.
func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed util.OptionalBool) (int64, error) { func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed optional.Option[bool]) (int64, error) {
if len(opts.IssueIDs) <= MaxQueryParameters { if len(opts.IssueIDs) <= MaxQueryParameters {
return getIssueTotalTrackedTimeChunk(ctx, opts, isClosed, opts.IssueIDs) return getIssueTotalTrackedTimeChunk(ctx, opts, isClosed, opts.IssueIDs)
} }
@ -363,7 +364,7 @@ func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed
return accum, nil return accum, nil
} }
func getIssueTotalTrackedTimeChunk(ctx context.Context, opts *IssuesOptions, isClosed util.OptionalBool, issueIDs []int64) (int64, error) { func getIssueTotalTrackedTimeChunk(ctx context.Context, opts *IssuesOptions, isClosed optional.Option[bool], issueIDs []int64) (int64, error) {
sumSession := func(opts *IssuesOptions, issueIDs []int64) *xorm.Session { sumSession := func(opts *IssuesOptions, issueIDs []int64) *xorm.Session {
sess := db.GetEngine(ctx). sess := db.GetEngine(ctx).
Table("tracked_time"). Table("tracked_time").
@ -378,8 +379,8 @@ func getIssueTotalTrackedTimeChunk(ctx context.Context, opts *IssuesOptions, isC
} }
session := sumSession(opts, issueIDs) session := sumSession(opts, issueIDs)
if !isClosed.IsNone() { if isClosed.Has() {
session = session.And("issue.is_closed = ?", isClosed.IsTrue()) session = session.And("issue.is_closed = ?", isClosed.Value())
} }
return session.SumInt(new(trackedTime), "tracked_time.time") return session.SumInt(new(trackedTime), "tracked_time.time")
} }

View file

@ -11,7 +11,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -120,15 +120,15 @@ func TestTotalTimesForEachUser(t *testing.T) {
func TestGetIssueTotalTrackedTime(t *testing.T) { func TestGetIssueTotalTrackedTime(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
ttt, err := issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, util.OptionalBoolFalse) ttt, err := issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, optional.Some(false))
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 3682, ttt) assert.EqualValues(t, 3682, ttt)
ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, util.OptionalBoolTrue) ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, optional.Some(true))
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 0, ttt) assert.EqualValues(t, 0, ttt)
ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, util.OptionalBoolNone) ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, optional.None[bool]())
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 3682, ttt) assert.EqualValues(t, 3682, ttt)
} }

View file

@ -36,12 +36,14 @@ func Test_DropTableColumns(t *testing.T) {
"updated_unix", "updated_unix",
} }
for i := range columns {
x.SetMapper(names.GonicMapper{}) x.SetMapper(names.GonicMapper{})
for i := range columns {
if err := x.Sync(new(DropTest)); err != nil { if err := x.Sync(new(DropTest)); err != nil {
t.Errorf("unable to create DropTest table: %v", err) t.Errorf("unable to create DropTest table: %v", err)
return return
} }
sess := x.NewSession() sess := x.NewSession()
if err := sess.Begin(); err != nil { if err := sess.Begin(); err != nil {
sess.Close() sess.Close()
@ -64,7 +66,6 @@ func Test_DropTableColumns(t *testing.T) {
return return
} }
for j := range columns[i+1:] { for j := range columns[i+1:] {
x.SetMapper(names.GonicMapper{})
if err := x.Sync(new(DropTest)); err != nil { if err := x.Sync(new(DropTest)); err != nil {
t.Errorf("unable to create DropTest table: %v", err) t.Errorf("unable to create DropTest table: %v", err)
return return

View file

@ -0,0 +1,4 @@
-
id: 1
repo_id: 1
index: 1

View file

@ -0,0 +1,16 @@
- id: 11
uuid: uuid11
hook_id: 1
payload_content: >
{"data":"payload"}
event_type: create
delivered: 1706106005
- id: 101
uuid: uuid101
hook_id: 1
payload_content: >
{"data":"payload"}
event_type: create
delivered: 1706106006
is_delivered: true

View file

@ -0,0 +1,18 @@
- id: 11
uuid: uuid11
hook_id: 1
payload_content: >
{"data":"payload"}
event_type: create
delivered: 1706106005
payload_version: 1
- id: 101
uuid: uuid101
hook_id: 1
payload_content: >
{"data":"payload"}
event_type: create
delivered: 1706106006
is_delivered: true
payload_version: 1

View file

@ -0,0 +1,11 @@
-
id: 1
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
issue_id: 1
release_id: 0
-
id: 2
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12
issue_id: 0
release_id: 1

View file

@ -0,0 +1,3 @@
-
id: 1
repo_id: 1

View file

@ -0,0 +1,3 @@
-
id: 1
repo_id: 1

View file

@ -0,0 +1,3 @@
-
id: 1
commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d

View file

@ -0,0 +1,3 @@
-
id: 1
context_hash: 19fe5caf872476db265596eaac1dc35ad1c6422d

View file

@ -0,0 +1,5 @@
-
id: 1
commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
merge_base: 19fe5caf872476db265596eaac1dc35ad1c6422d
merged_commit_id: 19fe5caf872476db265596eaac1dc35ad1c6422d

View file

@ -0,0 +1,3 @@
-
id: 1
sha1: 19fe5caf872476db265596eaac1dc35ad1c6422d

View file

@ -0,0 +1,3 @@
-
id: 1
commit_id: 19fe5caf872476db265596eaac1dc35ad1c6422d

View file

@ -0,0 +1,3 @@
-
id: 1
commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d

View file

@ -0,0 +1,3 @@
-
id: 1
commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d

View file

@ -0,0 +1,4 @@
-
id: 1
description: the badge
image_url: https://gitea.com/myimage.png

View file

@ -560,6 +560,14 @@ var migrations = []Migration{
NewMigration("Add PreviousDuration to ActionRun", v1_22.AddPreviousDurationToActionRun), NewMigration("Add PreviousDuration to ActionRun", v1_22.AddPreviousDurationToActionRun),
// v286 -> v287 // v286 -> v287
NewMigration("Add support for SHA256 git repositories", v1_22.AdjustDBForSha256), NewMigration("Add support for SHA256 git repositories", v1_22.AdjustDBForSha256),
// v287 -> v288
NewMigration("Use Slug instead of ID for Badges", v1_22.UseSlugInsteadOfIDForBadges),
// v288 -> v289
NewMigration("Add user_blocking table", v1_22.AddUserBlockingTable),
// v289 -> v290
NewMigration("Add default_wiki_branch to repository table", v1_22.AddDefaultWikiBranch),
// v290 -> v291
NewMigration("Add PayloadVersion to HookTask", v1_22.AddPayloadVersionToHookTaskTable),
} }
// GetCurrentDBVersion returns the current db version // GetCurrentDBVersion returns the current db version

View file

@ -15,7 +15,6 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
type Attachment struct { type Attachment struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
UUID string `xorm:"uuid UNIQUE"` UUID string `xorm:"uuid UNIQUE"`
RepoID int64 `xorm:"INDEX"` // this should not be zero
IssueID int64 `xorm:"INDEX"` // maybe zero when creating IssueID int64 `xorm:"INDEX"` // maybe zero when creating
ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating
UploaderID int64 `xorm:"INDEX DEFAULT 0"` UploaderID int64 `xorm:"INDEX DEFAULT 0"`
@ -44,12 +43,21 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
return return
} }
var issueAttachments []*Attachment type NewAttachment struct {
err := x.Where("issue_id > 0").Find(&issueAttachments) ID int64 `xorm:"pk autoincr"`
UUID string `xorm:"uuid UNIQUE"`
RepoID int64 `xorm:"INDEX"` // this should not be zero
IssueID int64 `xorm:"INDEX"` // maybe zero when creating
ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating
UploaderID int64 `xorm:"INDEX DEFAULT 0"`
}
var issueAttachments []*NewAttachment
err := x.Table("attachment").Where("issue_id > 0").Find(&issueAttachments)
assert.NoError(t, err) assert.NoError(t, err)
for _, attach := range issueAttachments { for _, attach := range issueAttachments {
assert.Greater(t, attach.RepoID, 0) assert.Greater(t, attach.RepoID, int64(0))
assert.Greater(t, attach.IssueID, 0) assert.Greater(t, attach.IssueID, int64(0))
var issue Issue var issue Issue
has, err := x.ID(attach.IssueID).Get(&issue) has, err := x.ID(attach.IssueID).Get(&issue)
assert.NoError(t, err) assert.NoError(t, err)
@ -57,12 +65,12 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
assert.EqualValues(t, attach.RepoID, issue.RepoID) assert.EqualValues(t, attach.RepoID, issue.RepoID)
} }
var releaseAttachments []*Attachment var releaseAttachments []*NewAttachment
err = x.Where("release_id > 0").Find(&releaseAttachments) err = x.Table("attachment").Where("release_id > 0").Find(&releaseAttachments)
assert.NoError(t, err) assert.NoError(t, err)
for _, attach := range releaseAttachments { for _, attach := range releaseAttachments {
assert.Greater(t, attach.RepoID, 0) assert.Greater(t, attach.RepoID, int64(0))
assert.Greater(t, attach.IssueID, 0) assert.Greater(t, attach.ReleaseID, int64(0))
var release Release var release Release
has, err := x.ID(attach.ReleaseID).Get(&release) has, err := x.ID(attach.ReleaseID).Get(&release)
assert.NoError(t, err) assert.NoError(t, err)

View file

@ -4,10 +4,40 @@
package v1_22 //nolint package v1_22 //nolint
import ( import (
"fmt"
"xorm.io/xorm" "xorm.io/xorm"
"xorm.io/xorm/schemas"
) )
func AddCombinedIndexToIssueUser(x *xorm.Engine) error { func AddCombinedIndexToIssueUser(x *xorm.Engine) error {
type OldIssueUser struct {
IssueID int64
UID int64
Cnt int64
}
var duplicatedIssueUsers []OldIssueUser
if err := x.SQL("select * from (select issue_id, uid, count(1) as cnt from issue_user group by issue_id, uid) a where a.cnt > 1").
Find(&duplicatedIssueUsers); err != nil {
return err
}
for _, issueUser := range duplicatedIssueUsers {
if x.Dialect().URI().DBType == schemas.MSSQL {
if _, err := x.Exec(fmt.Sprintf("delete from issue_user where id in (SELECT top %d id FROM issue_user WHERE issue_id = ? and uid = ?)", issueUser.Cnt-1), issueUser.IssueID, issueUser.UID); err != nil {
return err
}
} else {
var ids []int64
if err := x.SQL("SELECT id FROM issue_user WHERE issue_id = ? and uid = ? limit ?", issueUser.IssueID, issueUser.UID, issueUser.Cnt-1).Find(&ids); err != nil {
return err
}
if _, err := x.Table("issue_user").In("id", ids).Delete(); err != nil {
return err
}
}
}
type IssueUser struct { type IssueUser struct {
UID int64 `xorm:"INDEX unique(uid_to_issue)"` // User ID. UID int64 `xorm:"INDEX unique(uid_to_issue)"` // User ID.
IssueID int64 `xorm:"INDEX unique(uid_to_issue)"` IssueID int64 `xorm:"INDEX unique(uid_to_issue)"`

View file

@ -36,9 +36,9 @@ func expandHashReferencesToSha256(x *xorm.Engine) error {
if setting.Database.Type.IsMSSQL() { if setting.Database.Type.IsMSSQL() {
// drop indexes that need to be re-created afterwards // drop indexes that need to be re-created afterwards
droppedIndexes := []string{ droppedIndexes := []string{
"DROP INDEX commit_status.IDX_commit_status_context_hash", "DROP INDEX IF EXISTS [IDX_commit_status_context_hash] ON [commit_status]",
"DROP INDEX review_state.UQE_review_state_pull_commit_user", "DROP INDEX IF EXISTS [UQE_review_state_pull_commit_user] ON [review_state]",
"DROP INDEX repo_archiver.UQE_repo_archiver_s", "DROP INDEX IF EXISTS [UQE_repo_archiver_s] ON [repo_archiver]",
} }
for _, s := range droppedIndexes { for _, s := range droppedIndexes {
_, err := db.Exec(s) _, err := db.Exec(s)
@ -53,7 +53,7 @@ func expandHashReferencesToSha256(x *xorm.Engine) error {
if setting.Database.Type.IsMySQL() { if setting.Database.Type.IsMySQL() {
_, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(64)", alts[0], alts[1])) _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(64)", alts[0], alts[1]))
} else if setting.Database.Type.IsMSSQL() { } else if setting.Database.Type.IsMSSQL() {
_, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` VARCHAR(64)", alts[0], alts[1])) _, err = db.Exec(fmt.Sprintf("ALTER TABLE [%s] ALTER COLUMN [%s] VARCHAR(64)", alts[0], alts[1]))
} else { } else {
_, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` TYPE VARCHAR(64)", alts[0], alts[1])) _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` TYPE VARCHAR(64)", alts[0], alts[1]))
} }

View file

@ -15,58 +15,74 @@ import (
func PrepareOldRepository(t *testing.T) (*xorm.Engine, func()) { func PrepareOldRepository(t *testing.T) (*xorm.Engine, func()) {
type Repository struct { // old struct type Repository struct { // old struct
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
ObjectFormatName string `xorm:"VARCHAR(6) NOT NULL DEFAULT 'sha1'"`
} }
type CommitStatus struct { // old struct type CommitStatus struct {
ID int64 `xorm:"pk autoincr"` ID int64
ContextHash string `xorm:"char(40)"` ContextHash string
} }
type Comment struct { // old struct type RepoArchiver struct {
ID int64 `xorm:"pk autoincr"` ID int64
CommitSHA string `xorm:"VARCHAR(40)"` RepoID int64
Type int
CommitID string
} }
type PullRequest struct { // old struct type ReviewState struct {
ID int64 `xorm:"pk autoincr"` ID int64
MergeBase string `xorm:"VARCHAR(40)"` CommitSHA string
MergedCommitID string `xorm:"VARCHAR(40)"` UserID int64
PullID int64
} }
type Review struct { // old struct type Comment struct {
ID int64 `xorm:"pk autoincr"` ID int64
CommitID string `xorm:"VARCHAR(40)"` CommitSHA string
} }
type ReviewState struct { // old struct type PullRequest struct {
ID int64 `xorm:"pk autoincr"` ID int64
CommitSHA string `xorm:"VARCHAR(40)"` CommitSHA string
MergeBase string
MergedCommitID string
} }
type RepoArchiver struct { // old struct type Release struct {
ID int64 `xorm:"pk autoincr"` ID int64
CommitID string `xorm:"VARCHAR(40)"` Sha1 string
} }
type Release struct { // old struct type RepoIndexerStatus struct {
ID int64 `xorm:"pk autoincr"` ID int64
Sha1 string `xorm:"VARCHAR(40)"` CommitSHA string
} }
type RepoIndexerStatus struct { // old struct type Review struct {
ID int64 `xorm:"pk autoincr"` ID int64
CommitSha string `xorm:"VARCHAR(40)"` CommitID string
} }
// Prepare and load the testing database // Prepare and load the testing database
return base.PrepareTestEnv(t, 0, new(Repository), new(CommitStatus), new(Comment), new(PullRequest), new(Review), new(ReviewState), new(RepoArchiver), new(Release), new(RepoIndexerStatus)) return base.PrepareTestEnv(t, 0,
new(Repository),
new(CommitStatus),
new(RepoArchiver),
new(ReviewState),
new(Review),
new(Comment),
new(PullRequest),
new(Release),
new(RepoIndexerStatus),
)
} }
func Test_RepositoryFormat(t *testing.T) { func Test_RepositoryFormat(t *testing.T) {
x, deferable := PrepareOldRepository(t) x, deferable := PrepareOldRepository(t)
defer deferable() defer deferable()
assert.NoError(t, AdjustDBForSha256(x))
type Repository struct { type Repository struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
ObjectFormatName string `xorg:"not null default('sha1')"` ObjectFormatName string `xorg:"not null default('sha1')"`
@ -79,12 +95,10 @@ func Test_RepositoryFormat(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 4, count) assert.EqualValues(t, 4, count)
assert.NoError(t, AdjustDBForSha256(x))
repo.ID = 20
repo.ObjectFormatName = "sha256" repo.ObjectFormatName = "sha256"
_, err = x.Insert(repo) _, err = x.Insert(repo)
assert.NoError(t, err) assert.NoError(t, err)
id := repo.ID
count, err = x.Count(new(Repository)) count, err = x.Count(new(Repository))
assert.NoError(t, err) assert.NoError(t, err)
@ -97,7 +111,7 @@ func Test_RepositoryFormat(t *testing.T) {
assert.EqualValues(t, "sha1", repo.ObjectFormatName) assert.EqualValues(t, "sha1", repo.ObjectFormatName)
repo = new(Repository) repo = new(Repository)
ok, err = x.ID(20).Get(repo) ok, err = x.ID(id).Get(repo)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, true, ok) assert.EqualValues(t, true, ok)
assert.EqualValues(t, "sha256", repo.ObjectFormatName) assert.EqualValues(t, "sha256", repo.ObjectFormatName)

View file

@ -0,0 +1,46 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_22 //nolint
import (
"xorm.io/xorm"
)
type BadgeUnique struct {
ID int64 `xorm:"pk autoincr"`
Slug string `xorm:"UNIQUE"`
}
func (BadgeUnique) TableName() string {
return "badge"
}
func UseSlugInsteadOfIDForBadges(x *xorm.Engine) error {
type Badge struct {
Slug string
}
err := x.Sync(new(Badge))
if err != nil {
return err
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
_, err = sess.Exec("UPDATE `badge` SET `slug` = `id` Where `slug` IS NULL")
if err != nil {
return err
}
err = sess.Sync(new(BadgeUnique))
if err != nil {
return err
}
return sess.Commit()
}

View file

@ -0,0 +1,26 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_22 //nolint
import (
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
type Blocking struct {
ID int64 `xorm:"pk autoincr"`
BlockerID int64 `xorm:"UNIQUE(block)"`
BlockeeID int64 `xorm:"UNIQUE(block)"`
Note string
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}
func (*Blocking) TableName() string {
return "user_blocking"
}
func AddUserBlockingTable(x *xorm.Engine) error {
return x.Sync(&Blocking{})
}

View file

@ -0,0 +1,18 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_22 //nolint
import "xorm.io/xorm"
func AddDefaultWikiBranch(x *xorm.Engine) error {
type Repository struct {
ID int64
DefaultWikiBranch string
}
if err := x.Sync(&Repository{}); err != nil {
return err
}
_, err := x.Exec("UPDATE `repository` SET default_wiki_branch = 'master' WHERE (default_wiki_branch IS NULL) OR (default_wiki_branch = '')")
return err
}

View file

@ -0,0 +1,39 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_22 //nolint
import (
"code.gitea.io/gitea/modules/timeutil"
webhook_module "code.gitea.io/gitea/modules/webhook"
"xorm.io/xorm"
)
// HookTask represents a hook task.
// exact copy of models/webhook/hooktask.go when this migration was created
// - xorm:"-" fields deleted
type HookTask struct {
ID int64 `xorm:"pk autoincr"`
HookID int64 `xorm:"index"`
UUID string `xorm:"unique"`
PayloadContent string `xorm:"LONGTEXT"`
EventType webhook_module.HookEventType
IsDelivered bool
Delivered timeutil.TimeStampNano
// History info.
IsSucceed bool
RequestContent string `xorm:"LONGTEXT"`
ResponseContent string `xorm:"LONGTEXT"`
// Version number to allow for smooth version upgrades:
// - Version 1: PayloadContent contains the JSON as send to the URL
// - Version 2: PayloadContent contains the original event
PayloadVersion int `xorm:"DEFAULT 1"`
}
func AddPayloadVersionToHookTaskTable(x *xorm.Engine) error {
// create missing column
return x.Sync(new(HookTask))
}

View file

@ -0,0 +1,58 @@
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_22 //nolint
import (
"strconv"
"testing"
"code.gitea.io/gitea/models/migrations/base"
"code.gitea.io/gitea/modules/timeutil"
webhook_module "code.gitea.io/gitea/modules/webhook"
"github.com/stretchr/testify/assert"
)
func Test_AddPayloadVersionToHookTaskTable(t *testing.T) {
type HookTaskMigrated HookTask
// HookTask represents a hook task, as of before the migration
type HookTask struct {
ID int64 `xorm:"pk autoincr"`
HookID int64 `xorm:"index"`
UUID string `xorm:"unique"`
PayloadContent string `xorm:"LONGTEXT"`
EventType webhook_module.HookEventType
IsDelivered bool
Delivered timeutil.TimeStampNano
// History info.
IsSucceed bool
RequestContent string `xorm:"LONGTEXT"`
ResponseContent string `xorm:"LONGTEXT"`
}
// Prepare and load the testing database
x, deferable := base.PrepareTestEnv(t, 0, new(HookTask), new(HookTaskMigrated))
defer deferable()
if x == nil || t.Failed() {
return
}
assert.NoError(t, AddPayloadVersionToHookTaskTable(x))
expected := []HookTaskMigrated{}
assert.NoError(t, x.Table("hook_task_migrated").Asc("id").Find(&expected))
assert.Len(t, expected, 2)
got := []HookTaskMigrated{}
assert.NoError(t, x.Table("hook_task").Asc("id").Find(&got))
for i, expected := range expected {
expected, got := expected, got[i]
t.Run(strconv.FormatInt(expected.ID, 10), func(t *testing.T) {
assert.Equal(t, expected.PayloadVersion, got.PayloadVersion)
})
}
}

View file

@ -70,16 +70,26 @@ type PackageFileDescriptor struct {
Properties PackagePropertyList Properties PackagePropertyList
} }
// PackageWebLink returns the package web link // PackageWebLink returns the relative package web link
func (pd *PackageDescriptor) PackageWebLink() string { func (pd *PackageDescriptor) PackageWebLink() string {
return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HomeLink(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName)) return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HomeLink(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName))
} }
// FullWebLink returns the package version web link // VersionWebLink returns the relative package version web link
func (pd *PackageDescriptor) FullWebLink() string { func (pd *PackageDescriptor) VersionWebLink() string {
return fmt.Sprintf("%s/%s", pd.PackageWebLink(), url.PathEscape(pd.Version.LowerVersion)) return fmt.Sprintf("%s/%s", pd.PackageWebLink(), url.PathEscape(pd.Version.LowerVersion))
} }
// PackageHTMLURL returns the absolute package HTML URL
func (pd *PackageDescriptor) PackageHTMLURL() string {
return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HTMLURL(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName))
}
// VersionHTMLURL returns the absolute package version HTML URL
func (pd *PackageDescriptor) VersionHTMLURL() string {
return fmt.Sprintf("%s/%s", pd.PackageHTMLURL(), url.PathEscape(pd.Version.LowerVersion))
}
// CalculateBlobSize returns the total blobs size in bytes // CalculateBlobSize returns the total blobs size in bytes
func (pd *PackageDescriptor) CalculateBlobSize() int64 { func (pd *PackageDescriptor) CalculateBlobSize() int64 {
size := int64(0) size := int64(0)

View file

@ -55,7 +55,7 @@ func CountPackages(ctx context.Context, opts *packages_model.PackageSearchOption
func toConds(opts *packages_model.PackageSearchOptions) builder.Cond { func toConds(opts *packages_model.PackageSearchOptions) builder.Cond {
var cond builder.Cond = builder.Eq{ var cond builder.Cond = builder.Eq{
"package.is_internal": opts.IsInternal.IsTrue(), "package.is_internal": opts.IsInternal.Value(),
"package.owner_id": opts.OwnerID, "package.owner_id": opts.OwnerID,
"package.type": packages_model.TypeNuGet, "package.type": packages_model.TypeNuGet,
} }

View file

@ -9,6 +9,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -105,7 +106,7 @@ func getVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType
ExactMatch: true, ExactMatch: true,
Value: version, Value: version,
}, },
IsInternal: util.OptionalBoolOf(isInternal), IsInternal: optional.Some(isInternal),
Paginator: db.NewAbsoluteListOptions(0, 1), Paginator: db.NewAbsoluteListOptions(0, 1),
}) })
if err != nil { if err != nil {
@ -122,7 +123,7 @@ func GetVersionsByPackageType(ctx context.Context, ownerID int64, packageType Ty
pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{ pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{
OwnerID: ownerID, OwnerID: ownerID,
Type: packageType, Type: packageType,
IsInternal: util.OptionalBoolFalse, IsInternal: optional.Some(false),
}) })
return pvs, err return pvs, err
} }
@ -136,7 +137,7 @@ func GetVersionsByPackageName(ctx context.Context, ownerID int64, packageType Ty
ExactMatch: true, ExactMatch: true,
Value: name, Value: name,
}, },
IsInternal: util.OptionalBoolFalse, IsInternal: optional.Some(false),
}) })
return pvs, err return pvs, err
} }
@ -182,18 +183,18 @@ type PackageSearchOptions struct {
Name SearchValue // only results with the specific name are found Name SearchValue // only results with the specific name are found
Version SearchValue // only results with the specific version are found Version SearchValue // only results with the specific version are found
Properties map[string]string // only results are found which contain all listed version properties with the specific value Properties map[string]string // only results are found which contain all listed version properties with the specific value
IsInternal util.OptionalBool IsInternal optional.Option[bool]
HasFileWithName string // only results are found which are associated with a file with the specific name HasFileWithName string // only results are found which are associated with a file with the specific name
HasFiles util.OptionalBool // only results are found which have associated files HasFiles optional.Option[bool] // only results are found which have associated files
Sort VersionSort Sort VersionSort
db.Paginator db.Paginator
} }
func (opts *PackageSearchOptions) ToConds() builder.Cond { func (opts *PackageSearchOptions) ToConds() builder.Cond {
cond := builder.NewCond() cond := builder.NewCond()
if !opts.IsInternal.IsNone() { if opts.IsInternal.Has() {
cond = builder.Eq{ cond = builder.Eq{
"package_version.is_internal": opts.IsInternal.IsTrue(), "package_version.is_internal": opts.IsInternal.Value(),
} }
} }
@ -250,10 +251,10 @@ func (opts *PackageSearchOptions) ToConds() builder.Cond {
cond = cond.And(builder.Exists(builder.Select("package_file.id").From("package_file").Where(fileCond))) cond = cond.And(builder.Exists(builder.Select("package_file.id").From("package_file").Where(fileCond)))
} }
if !opts.HasFiles.IsNone() { if opts.HasFiles.Has() {
filesCond := builder.Exists(builder.Select("package_file.id").From("package_file").Where(builder.Expr("package_file.version_id = package_version.id"))) filesCond := builder.Exists(builder.Select("package_file.id").From("package_file").Where(builder.Expr("package_file.version_id = package_version.id")))
if opts.HasFiles.IsFalse() { if !opts.HasFiles.Value() {
filesCond = builder.Not{filesCond} filesCond = builder.Not{filesCond}
} }
@ -307,8 +308,8 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
And(builder.Expr("pv2.id IS NULL")) And(builder.Expr("pv2.id IS NULL"))
joinCond := builder.Expr("package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))") joinCond := builder.Expr("package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))")
if !opts.IsInternal.IsNone() { if opts.IsInternal.Has() {
joinCond = joinCond.And(builder.Eq{"pv2.is_internal": opts.IsInternal.IsTrue()}) joinCond = joinCond.And(builder.Eq{"pv2.is_internal": opts.IsInternal.Value()})
} }
sess := db.GetEngine(ctx). sess := db.GetEngine(ctx).

View file

@ -232,7 +232,7 @@ func UpdateBoard(ctx context.Context, board *Board) error {
func (p *Project) GetBoards(ctx context.Context) (BoardList, error) { func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
boards := make([]*Board, 0, 5) boards := make([]*Board, 0, 5)
if err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, false).OrderBy("Sorting").Find(&boards); err != nil { if err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, false).OrderBy("sorting").Find(&boards); err != nil {
return nil, err return nil, err
} }

View file

@ -6,11 +6,13 @@ package project
import ( import (
"context" "context"
"fmt" "fmt"
"html/template"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -100,7 +102,7 @@ type Project struct {
CardType CardType CardType CardType
Type Type Type Type
RenderedContent string `xorm:"-"` RenderedContent template.HTML `xorm:"-"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
@ -195,7 +197,7 @@ type SearchOptions struct {
db.ListOptions db.ListOptions
OwnerID int64 OwnerID int64
RepoID int64 RepoID int64
IsClosed util.OptionalBool IsClosed optional.Option[bool]
OrderBy db.SearchOrderBy OrderBy db.SearchOrderBy
Type Type Type Type
Title string Title string
@ -206,11 +208,8 @@ func (opts SearchOptions) ToConds() builder.Cond {
if opts.RepoID > 0 { if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
} }
switch opts.IsClosed { if opts.IsClosed.Has() {
case util.OptionalBoolTrue: cond = cond.And(builder.Eq{"is_closed": opts.IsClosed.Value()})
cond = cond.And(builder.Eq{"is_closed": true})
case util.OptionalBoolFalse:
cond = cond.And(builder.Eq{"is_closed": false})
} }
if opts.Type > 0 { if opts.Type > 0 {

View file

@ -7,6 +7,7 @@ package repo
import ( import (
"context" "context"
"fmt" "fmt"
"html/template"
"net/url" "net/url"
"sort" "sort"
"strconv" "strconv"
@ -15,6 +16,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -79,7 +81,7 @@ type Release struct {
NumCommits int64 NumCommits int64
NumCommitsBehind int64 `xorm:"-"` NumCommitsBehind int64 `xorm:"-"`
Note string `xorm:"TEXT"` Note string `xorm:"TEXT"`
RenderedNote string `xorm:"-"` RenderedNote template.HTML `xorm:"-"`
IsDraft bool `xorm:"NOT NULL DEFAULT false"` IsDraft bool `xorm:"NOT NULL DEFAULT false"`
IsPrerelease bool `xorm:"NOT NULL DEFAULT false"` IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
IsTag bool `xorm:"NOT NULL DEFAULT false"` // will be true only if the record is a tag and has no related releases IsTag bool `xorm:"NOT NULL DEFAULT false"` // will be true only if the record is a tag and has no related releases
@ -228,10 +230,10 @@ type FindReleasesOptions struct {
RepoID int64 RepoID int64
IncludeDrafts bool IncludeDrafts bool
IncludeTags bool IncludeTags bool
IsPreRelease util.OptionalBool IsPreRelease optional.Option[bool]
IsDraft util.OptionalBool IsDraft optional.Option[bool]
TagNames []string TagNames []string
HasSha1 util.OptionalBool // useful to find draft releases which are created with existing tags HasSha1 optional.Option[bool] // useful to find draft releases which are created with existing tags
} }
func (opts FindReleasesOptions) ToConds() builder.Cond { func (opts FindReleasesOptions) ToConds() builder.Cond {
@ -246,14 +248,14 @@ func (opts FindReleasesOptions) ToConds() builder.Cond {
if len(opts.TagNames) > 0 { if len(opts.TagNames) > 0 {
cond = cond.And(builder.In("tag_name", opts.TagNames)) cond = cond.And(builder.In("tag_name", opts.TagNames))
} }
if !opts.IsPreRelease.IsNone() { if opts.IsPreRelease.Has() {
cond = cond.And(builder.Eq{"is_prerelease": opts.IsPreRelease.IsTrue()}) cond = cond.And(builder.Eq{"is_prerelease": opts.IsPreRelease.Value()})
} }
if !opts.IsDraft.IsNone() { if opts.IsDraft.Has() {
cond = cond.And(builder.Eq{"is_draft": opts.IsDraft.IsTrue()}) cond = cond.And(builder.Eq{"is_draft": opts.IsDraft.Value()})
} }
if !opts.HasSha1.IsNone() { if opts.HasSha1.Has() {
if opts.HasSha1.IsTrue() { if opts.HasSha1.Value() {
cond = cond.And(builder.Neq{"sha1": ""}) cond = cond.And(builder.Neq{"sha1": ""})
} else { } else {
cond = cond.And(builder.Eq{"sha1": ""}) cond = cond.And(builder.Eq{"sha1": ""})
@ -275,7 +277,7 @@ func GetTagNamesByRepoID(ctx context.Context, repoID int64) ([]string, error) {
ListOptions: listOptions, ListOptions: listOptions,
IncludeDrafts: true, IncludeDrafts: true,
IncludeTags: true, IncludeTags: true,
HasSha1: util.OptionalBoolTrue, HasSha1: optional.Some(true),
RepoID: repoID, RepoID: repoID,
} }

View file

@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -873,7 +874,7 @@ func (repo *Repository) TemplateRepo(ctx context.Context) *Repository {
type CountRepositoryOptions struct { type CountRepositoryOptions struct {
OwnerID int64 OwnerID int64
Private util.OptionalBool Private optional.Option[bool]
} }
// CountRepositories returns number of repositories. // CountRepositories returns number of repositories.
@ -885,8 +886,8 @@ func CountRepositories(ctx context.Context, opts CountRepositoryOptions) (int64,
if opts.OwnerID > 0 { if opts.OwnerID > 0 {
sess.And("owner_id = ?", opts.OwnerID) sess.And("owner_id = ?", opts.OwnerID)
} }
if !opts.Private.IsNone() { if opts.Private.Has() {
sess.And("is_private=?", opts.Private.IsTrue()) sess.And("is_private=?", opts.Private.Value())
} }
count, err := sess.Count(new(Repository)) count, err := sess.Count(new(Repository))

View file

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -125,11 +126,11 @@ type SearchRepoOptions struct {
// None -> include public and private // None -> include public and private
// True -> include just private // True -> include just private
// False -> include just public // False -> include just public
IsPrivate util.OptionalBool IsPrivate optional.Option[bool]
// None -> include collaborative AND non-collaborative // None -> include collaborative AND non-collaborative
// True -> include just collaborative // True -> include just collaborative
// False -> include just non-collaborative // False -> include just non-collaborative
Collaborate util.OptionalBool Collaborate optional.Option[bool]
// What type of unit the user can be collaborative in, // What type of unit the user can be collaborative in,
// it is ignored if Collaborate is False. // it is ignored if Collaborate is False.
// TypeInvalid means any unit type. // TypeInvalid means any unit type.
@ -137,19 +138,19 @@ type SearchRepoOptions struct {
// None -> include forks AND non-forks // None -> include forks AND non-forks
// True -> include just forks // True -> include just forks
// False -> include just non-forks // False -> include just non-forks
Fork util.OptionalBool Fork optional.Option[bool]
// None -> include templates AND non-templates // None -> include templates AND non-templates
// True -> include just templates // True -> include just templates
// False -> include just non-templates // False -> include just non-templates
Template util.OptionalBool Template optional.Option[bool]
// None -> include mirrors AND non-mirrors // None -> include mirrors AND non-mirrors
// True -> include just mirrors // True -> include just mirrors
// False -> include just non-mirrors // False -> include just non-mirrors
Mirror util.OptionalBool Mirror optional.Option[bool]
// None -> include archived AND non-archived // None -> include archived AND non-archived
// True -> include just archived // True -> include just archived
// False -> include just non-archived // False -> include just non-archived
Archived util.OptionalBool Archived optional.Option[bool]
// only search topic name // only search topic name
TopicOnly bool TopicOnly bool
// only search repositories with specified primary language // only search repositories with specified primary language
@ -159,7 +160,7 @@ type SearchRepoOptions struct {
// None -> include has milestones AND has no milestone // None -> include has milestones AND has no milestone
// True -> include just has milestones // True -> include just has milestones
// False -> include just has no milestone // False -> include just has no milestone
HasMilestones util.OptionalBool HasMilestones optional.Option[bool]
// LowerNames represents valid lower names to restrict to // LowerNames represents valid lower names to restrict to
LowerNames []string LowerNames []string
// When specified true, apply some filters over the conditions: // When specified true, apply some filters over the conditions:
@ -359,12 +360,12 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
))) )))
} }
if opts.IsPrivate != util.OptionalBoolNone { if opts.IsPrivate.Has() {
cond = cond.And(builder.Eq{"is_private": opts.IsPrivate.IsTrue()}) cond = cond.And(builder.Eq{"is_private": opts.IsPrivate.Value()})
} }
if opts.Template != util.OptionalBoolNone { if opts.Template.Has() {
cond = cond.And(builder.Eq{"is_template": opts.Template == util.OptionalBoolTrue}) cond = cond.And(builder.Eq{"is_template": opts.Template.Value()})
} }
// Restrict to starred repositories // Restrict to starred repositories
@ -380,11 +381,11 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
// Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate // Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate
if opts.OwnerID > 0 { if opts.OwnerID > 0 {
accessCond := builder.NewCond() accessCond := builder.NewCond()
if opts.Collaborate != util.OptionalBoolTrue { if !opts.Collaborate.Value() {
accessCond = builder.Eq{"owner_id": opts.OwnerID} accessCond = builder.Eq{"owner_id": opts.OwnerID}
} }
if opts.Collaborate != util.OptionalBoolFalse { if opts.Collaborate.ValueOrDefault(true) {
// A Collaboration is: // A Collaboration is:
collaborateCond := builder.NewCond() collaborateCond := builder.NewCond()
@ -472,32 +473,33 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
Where(builder.Eq{"language": opts.Language}).And(builder.Eq{"is_primary": true}))) Where(builder.Eq{"language": opts.Language}).And(builder.Eq{"is_primary": true})))
} }
if opts.Fork != util.OptionalBoolNone || opts.OnlyShowRelevant { if opts.Fork.Has() || opts.OnlyShowRelevant {
if opts.OnlyShowRelevant && opts.Fork == util.OptionalBoolNone { if opts.OnlyShowRelevant && !opts.Fork.Has() {
cond = cond.And(builder.Eq{"is_fork": false}) cond = cond.And(builder.Eq{"is_fork": false})
} else { } else {
cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue}) cond = cond.And(builder.Eq{"is_fork": opts.Fork.Value()})
} }
} }
if opts.Mirror != util.OptionalBoolNone { if opts.Mirror.Has() {
cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue}) cond = cond.And(builder.Eq{"is_mirror": opts.Mirror.Value()})
} }
if opts.Actor != nil && opts.Actor.IsRestricted { if opts.Actor != nil && opts.Actor.IsRestricted {
cond = cond.And(AccessibleRepositoryCondition(opts.Actor, unit.TypeInvalid)) cond = cond.And(AccessibleRepositoryCondition(opts.Actor, unit.TypeInvalid))
} }
if opts.Archived != util.OptionalBoolNone { if opts.Archived.Has() {
cond = cond.And(builder.Eq{"is_archived": opts.Archived == util.OptionalBoolTrue}) cond = cond.And(builder.Eq{"is_archived": opts.Archived.Value()})
} }
switch opts.HasMilestones { if opts.HasMilestones.Has() {
case util.OptionalBoolTrue: if opts.HasMilestones.Value() {
cond = cond.And(builder.Gt{"num_milestones": 0}) cond = cond.And(builder.Gt{"num_milestones": 0})
case util.OptionalBoolFalse: } else {
cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"})) cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"}))
} }
}
if opts.OnlyShowRelevant { if opts.OnlyShowRelevant {
// Only show a repo that has at least a topic, an icon, or a description // Only show a repo that has at least a topic, an icon, or a description

View file

@ -10,7 +10,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -27,62 +27,62 @@ func getTestCases() []struct {
}{ }{
{ {
name: "PublicRepositoriesByName", name: "PublicRepositoriesByName",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, Collaborate: optional.Some(false)},
count: 7, count: 7,
}, },
{ {
name: "PublicAndPrivateRepositoriesByName", name: "PublicAndPrivateRepositoriesByName",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, Collaborate: optional.Some(false)},
count: 14, count: 14,
}, },
{ {
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFirstPage", name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFirstPage",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
count: 14, count: 14,
}, },
{ {
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitSecondPage", name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitSecondPage",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 2, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 2, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
count: 14, count: 14,
}, },
{ {
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitThirdPage", name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitThirdPage",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
count: 14, count: 14,
}, },
{ {
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFourthPage", name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFourthPage",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
count: 14, count: 14,
}, },
{ {
name: "PublicRepositoriesOfUser", name: "PublicRepositoriesOfUser",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Collaborate: optional.Some(false)},
count: 2, count: 2,
}, },
{ {
name: "PublicRepositoriesOfUser2", name: "PublicRepositoriesOfUser2",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Collaborate: optional.Some(false)},
count: 0, count: 0,
}, },
{ {
name: "PublicRepositoriesOfOrg3", name: "PublicRepositoriesOfOrg3",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Collaborate: optional.Some(false)},
count: 2, count: 2,
}, },
{ {
name: "PublicAndPrivateRepositoriesOfUser", name: "PublicAndPrivateRepositoriesOfUser",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, Collaborate: optional.Some(false)},
count: 4, count: 4,
}, },
{ {
name: "PublicAndPrivateRepositoriesOfUser2", name: "PublicAndPrivateRepositoriesOfUser2",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, Collaborate: optional.Some(false)},
count: 0, count: 0,
}, },
{ {
name: "PublicAndPrivateRepositoriesOfOrg3", name: "PublicAndPrivateRepositoriesOfOrg3",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true, Collaborate: optional.Some(false)},
count: 4, count: 4,
}, },
{ {
@ -117,32 +117,32 @@ func getTestCases() []struct {
}, },
{ {
name: "PublicRepositoriesOfOrganization", name: "PublicRepositoriesOfOrganization",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Collaborate: optional.Some(false)},
count: 1, count: 1,
}, },
{ {
name: "PublicAndPrivateRepositoriesOfOrganization", name: "PublicAndPrivateRepositoriesOfOrganization",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Private: true, Collaborate: optional.Some(false)},
count: 2, count: 2,
}, },
{ {
name: "AllPublic/PublicRepositoriesByName", name: "AllPublic/PublicRepositoriesByName",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, AllPublic: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, AllPublic: true, Collaborate: optional.Some(false)},
count: 7, count: 7,
}, },
{ {
name: "AllPublic/PublicAndPrivateRepositoriesByName", name: "AllPublic/PublicAndPrivateRepositoriesByName",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, AllPublic: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, AllPublic: true, Collaborate: optional.Some(false)},
count: 14, count: 14,
}, },
{ {
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: optional.Some(false)},
count: 34, count: 34,
}, },
{ {
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: optional.Some(false)},
count: 39, count: 39,
}, },
{ {
@ -157,12 +157,12 @@ func getTestCases() []struct {
}, },
{ {
name: "AllPublic/PublicRepositoriesOfOrganization", name: "AllPublic/PublicRepositoriesOfOrganization",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: optional.Some(false), Template: optional.Some(false)},
count: 34, count: 34,
}, },
{ {
name: "AllTemplates", name: "AllTemplates",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Template: util.OptionalBoolTrue}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Template: optional.Some(true)},
count: 2, count: 2,
}, },
{ {
@ -190,7 +190,7 @@ func TestSearchRepository(t *testing.T) {
PageSize: 10, PageSize: 10,
}, },
Keyword: "repo_12", Keyword: "repo_12",
Collaborate: util.OptionalBoolFalse, Collaborate: optional.Some(false),
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -205,7 +205,7 @@ func TestSearchRepository(t *testing.T) {
PageSize: 10, PageSize: 10,
}, },
Keyword: "test_repo", Keyword: "test_repo",
Collaborate: util.OptionalBoolFalse, Collaborate: optional.Some(false),
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -220,7 +220,7 @@ func TestSearchRepository(t *testing.T) {
}, },
Keyword: "repo_13", Keyword: "repo_13",
Private: true, Private: true,
Collaborate: util.OptionalBoolFalse, Collaborate: optional.Some(false),
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -236,7 +236,7 @@ func TestSearchRepository(t *testing.T) {
}, },
Keyword: "test_repo", Keyword: "test_repo",
Private: true, Private: true,
Collaborate: util.OptionalBoolFalse, Collaborate: optional.Some(false),
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -257,7 +257,7 @@ func TestSearchRepository(t *testing.T) {
PageSize: 10, PageSize: 10,
}, },
Keyword: "description_14", Keyword: "description_14",
Collaborate: util.OptionalBoolFalse, Collaborate: optional.Some(false),
IncludeDescription: true, IncludeDescription: true,
}) })
@ -274,7 +274,7 @@ func TestSearchRepository(t *testing.T) {
PageSize: 10, PageSize: 10,
}, },
Keyword: "description_14", Keyword: "description_14",
Collaborate: util.OptionalBoolFalse, Collaborate: optional.Some(false),
IncludeDescription: false, IncludeDescription: false,
}) })
@ -327,30 +327,25 @@ func TestSearchRepository(t *testing.T) {
assert.False(t, repo.IsPrivate) assert.False(t, repo.IsPrivate)
} }
if testCase.opts.Fork == util.OptionalBoolTrue && testCase.opts.Mirror == util.OptionalBoolTrue { if testCase.opts.Fork.Value() && testCase.opts.Mirror.Value() {
assert.True(t, repo.IsFork || repo.IsMirror) assert.True(t, repo.IsFork && repo.IsMirror)
} else { } else {
switch testCase.opts.Fork { if testCase.opts.Fork.Has() {
case util.OptionalBoolFalse: assert.Equal(t, testCase.opts.Fork.Value(), repo.IsFork)
assert.False(t, repo.IsFork)
case util.OptionalBoolTrue:
assert.True(t, repo.IsFork)
} }
switch testCase.opts.Mirror { if testCase.opts.Mirror.Has() {
case util.OptionalBoolFalse: assert.Equal(t, testCase.opts.Mirror.Value(), repo.IsMirror)
assert.False(t, repo.IsMirror)
case util.OptionalBoolTrue:
assert.True(t, repo.IsMirror)
} }
} }
if testCase.opts.OwnerID > 0 && !testCase.opts.AllPublic { if testCase.opts.OwnerID > 0 && !testCase.opts.AllPublic {
switch testCase.opts.Collaborate { if testCase.opts.Collaborate.Has() {
case util.OptionalBoolFalse: if testCase.opts.Collaborate.Value() {
assert.Equal(t, testCase.opts.OwnerID, repo.Owner.ID)
case util.OptionalBoolTrue:
assert.NotEqual(t, testCase.opts.OwnerID, repo.Owner.ID) assert.NotEqual(t, testCase.opts.OwnerID, repo.Owner.ID)
} else {
assert.Equal(t, testCase.opts.OwnerID, repo.Owner.ID)
}
} }
} }
} }

View file

@ -12,17 +12,17 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var ( var (
countRepospts = repo_model.CountRepositoryOptions{OwnerID: 10} countRepospts = repo_model.CountRepositoryOptions{OwnerID: 10}
countReposptsPublic = repo_model.CountRepositoryOptions{OwnerID: 10, Private: util.OptionalBoolFalse} countReposptsPublic = repo_model.CountRepositoryOptions{OwnerID: 10, Private: optional.Some(false)}
countReposptsPrivate = repo_model.CountRepositoryOptions{OwnerID: 10, Private: util.OptionalBoolTrue} countReposptsPrivate = repo_model.CountRepositoryOptions{OwnerID: 10, Private: optional.Some(true)}
) )
func TestGetRepositoryCount(t *testing.T) { func TestGetRepositoryCount(t *testing.T) {

View file

@ -9,7 +9,10 @@ import (
"fmt" "fmt"
"strings" "strings"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
actions_module "code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/log"
secret_module "code.gitea.io/gitea/modules/secret" secret_module "code.gitea.io/gitea/modules/secret"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -112,3 +115,39 @@ func UpdateSecret(ctx context.Context, secretID int64, data string) error {
} }
return err return err
} }
func GetSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) (map[string]string, error) {
secrets := map[string]string{}
secrets["GITHUB_TOKEN"] = task.Token
secrets["GITEA_TOKEN"] = task.Token
if task.Job.Run.IsForkPullRequest && task.Job.Run.TriggerEvent != actions_module.GithubEventPullRequestTarget {
// ignore secrets for fork pull request, except GITHUB_TOKEN and GITEA_TOKEN which are automatically generated.
// for the tasks triggered by pull_request_target event, they could access the secrets because they will run in the context of the base branch
// see the documentation: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target
return secrets, nil
}
ownerSecrets, err := db.Find[Secret](ctx, FindSecretsOptions{OwnerID: task.Job.Run.Repo.OwnerID})
if err != nil {
log.Error("find secrets of owner %v: %v", task.Job.Run.Repo.OwnerID, err)
return nil, err
}
repoSecrets, err := db.Find[Secret](ctx, FindSecretsOptions{RepoID: task.Job.Run.RepoID})
if err != nil {
log.Error("find secrets of repo %v: %v", task.Job.Run.RepoID, err)
return nil, err
}
for _, secret := range append(ownerSecrets, repoSecrets...) {
v, err := secret_module.DecryptSecret(setting.SecretKey, secret.Data)
if err != nil {
log.Error("decrypt secret %v %q: %v", secret.ID, secret.Name, err)
return nil, err
}
secrets[secret.Name] = v
}
return secrets, nil
}

View file

@ -34,7 +34,7 @@ func NewMockWebServer(t *testing.T, liveServerBaseURL, testDataDir string, liveM
path := NormalizedFullPath(r.URL) path := NormalizedFullPath(r.URL)
log.Info("Mock HTTP Server: got request for path %s", r.URL.Path) log.Info("Mock HTTP Server: got request for path %s", r.URL.Path)
// TODO check request method (support POST?) // TODO check request method (support POST?)
fixturePath := fmt.Sprintf("%s/%s", testDataDir, strings.NewReplacer("/", "_", "?", "!").Replace(path)) fixturePath := fmt.Sprintf("%s/%s_%s", testDataDir, r.Method, url.PathEscape(path))
if liveMode { if liveMode {
liveURL := fmt.Sprintf("%s%s", liveServerBaseURL, path) liveURL := fmt.Sprintf("%s%s", liveServerBaseURL, path)
@ -51,6 +51,7 @@ func NewMockWebServer(t *testing.T, liveServerBaseURL, testDataDir string, liveM
response, err := http.DefaultClient.Do(request) response, err := http.DefaultClient.Do(request)
assert.NoError(t, err, "HTTP request to %s failed: %s", liveURL) assert.NoError(t, err, "HTTP request to %s failed: %s", liveURL)
assert.Less(t, response.StatusCode, 400, "unexpected status code for %s", liveURL)
fixture, err := os.Create(fixturePath) fixture, err := os.Create(fixturePath)
assert.NoError(t, err, "failed to open the fixture file %s for writing", fixturePath) assert.NoError(t, err, "failed to open the fixture file %s for writing", fixturePath)

View file

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation" "code.gitea.io/gitea/modules/validation"
@ -156,37 +157,18 @@ func UpdateEmailAddress(ctx context.Context, email *EmailAddress) error {
var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
// ValidateEmail check if email is a allowed address // ValidateEmail check if email is a valid & allowed address
func ValidateEmail(email string) error { func ValidateEmail(email string) error {
if len(email) == 0 { if err := validateEmailBasic(email); err != nil {
return ErrEmailInvalid{email} return err
} }
return validateEmailDomain(email)
}
if !emailRegexp.MatchString(email) { // ValidateEmailForAdmin check if email is a valid address when admins manually add or edit users
return ErrEmailCharIsNotSupported{email} func ValidateEmailForAdmin(email string) error {
} return validateEmailBasic(email)
// In this case we do not need to check the email domain
if email[0] == '-' {
return ErrEmailInvalid{email}
}
if _, err := mail.ParseAddress(email); err != nil {
return ErrEmailInvalid{email}
}
// if there is no allow list, then check email against block list
if len(setting.Service.EmailDomainAllowList) == 0 &&
validation.IsEmailDomainListed(setting.Service.EmailDomainBlockList, email) {
return ErrEmailInvalid{email}
}
// if there is an allow list, then check email against allow list
if len(setting.Service.EmailDomainAllowList) > 0 &&
!validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, email) {
return ErrEmailInvalid{email}
}
return nil
} }
func GetEmailAddressByEmail(ctx context.Context, email string) (*EmailAddress, error) { func GetEmailAddressByEmail(ctx context.Context, email string) (*EmailAddress, error) {
@ -425,8 +407,8 @@ type SearchEmailOptions struct {
db.ListOptions db.ListOptions
Keyword string Keyword string
SortType SearchEmailOrderBy SortType SearchEmailOrderBy
IsPrimary util.OptionalBool IsPrimary optional.Option[bool]
IsActivated util.OptionalBool IsActivated optional.Option[bool]
} }
// SearchEmailResult is an e-mail address found in the user or email_address table // SearchEmailResult is an e-mail address found in the user or email_address table
@ -453,18 +435,12 @@ func SearchEmails(ctx context.Context, opts *SearchEmailOptions) ([]*SearchEmail
)) ))
} }
switch { if opts.IsPrimary.Has() {
case opts.IsPrimary.IsTrue(): cond = cond.And(builder.Eq{"email_address.is_primary": opts.IsPrimary.Value()})
cond = cond.And(builder.Eq{"email_address.is_primary": true})
case opts.IsPrimary.IsFalse():
cond = cond.And(builder.Eq{"email_address.is_primary": false})
} }
switch { if opts.IsActivated.Has() {
case opts.IsActivated.IsTrue(): cond = cond.And(builder.Eq{"email_address.is_activated": opts.IsActivated.Value()})
cond = cond.And(builder.Eq{"email_address.is_activated": true})
case opts.IsActivated.IsFalse():
cond = cond.And(builder.Eq{"email_address.is_activated": false})
} }
count, err := db.GetEngine(ctx).Join("INNER", "`user`", "`user`.ID = email_address.uid"). count, err := db.GetEngine(ctx).Join("INNER", "`user`", "`user`.ID = email_address.uid").
@ -548,3 +524,41 @@ func ActivateUserEmail(ctx context.Context, userID int64, email string, activate
return committer.Commit() return committer.Commit()
} }
// validateEmailBasic checks whether the email complies with the rules
func validateEmailBasic(email string) error {
if len(email) == 0 {
return ErrEmailInvalid{email}
}
if !emailRegexp.MatchString(email) {
return ErrEmailCharIsNotSupported{email}
}
if email[0] == '-' {
return ErrEmailInvalid{email}
}
if _, err := mail.ParseAddress(email); err != nil {
return ErrEmailInvalid{email}
}
return nil
}
// validateEmailDomain checks whether the email domain is allowed or blocked
func validateEmailDomain(email string) error {
// if there is no allow list, then check email against block list
if len(setting.Service.EmailDomainAllowList) == 0 &&
validation.IsEmailDomainListed(setting.Service.EmailDomainBlockList, email) {
return ErrEmailInvalid{email}
}
// if there is an allow list, then check email against allow list
if len(setting.Service.EmailDomainAllowList) > 0 &&
!validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, email) {
return ErrEmailInvalid{email}
}
return nil
}

View file

@ -10,7 +10,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -138,14 +138,14 @@ func TestListEmails(t *testing.T) {
assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 27 })) assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 27 }))
// Must find only primary addresses (i.e. from the `user` table) // Must find only primary addresses (i.e. from the `user` table)
opts = &user_model.SearchEmailOptions{IsPrimary: util.OptionalBoolTrue} opts = &user_model.SearchEmailOptions{IsPrimary: optional.Some(true)}
emails, _, err = user_model.SearchEmails(db.DefaultContext, opts) emails, _, err = user_model.SearchEmails(db.DefaultContext, opts)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.IsPrimary })) assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.IsPrimary }))
assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsPrimary })) assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsPrimary }))
// Must find only inactive addresses (i.e. not validated) // Must find only inactive addresses (i.e. not validated)
opts = &user_model.SearchEmailOptions{IsActivated: util.OptionalBoolFalse} opts = &user_model.SearchEmailOptions{IsActivated: optional.Some(false)}
emails, _, err = user_model.SearchEmails(db.DefaultContext, opts) emails, _, err = user_model.SearchEmails(db.DefaultContext, opts)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsActivated })) assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsActivated }))

View file

@ -9,8 +9,9 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
"xorm.io/xorm" "xorm.io/xorm"
@ -30,11 +31,13 @@ type SearchUserOptions struct {
Actor *User // The user doing the search Actor *User // The user doing the search
SearchByEmail bool // Search by email as well as username/full name SearchByEmail bool // Search by email as well as username/full name
IsActive util.OptionalBool SupportedSortOrders container.Set[string] // if not nil, only allow to use the sort orders in this set
IsAdmin util.OptionalBool
IsRestricted util.OptionalBool IsActive optional.Option[bool]
IsTwoFactorEnabled util.OptionalBool IsAdmin optional.Option[bool]
IsProhibitLogin util.OptionalBool IsRestricted optional.Option[bool]
IsTwoFactorEnabled optional.Option[bool]
IsProhibitLogin optional.Option[bool]
IncludeReserved bool IncludeReserved bool
ExtraParamStrings map[string]string ExtraParamStrings map[string]string
@ -86,24 +89,24 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Sess
cond = cond.And(builder.Eq{"login_name": opts.LoginName}) cond = cond.And(builder.Eq{"login_name": opts.LoginName})
} }
if !opts.IsActive.IsNone() { if opts.IsActive.Has() {
cond = cond.And(builder.Eq{"is_active": opts.IsActive.IsTrue()}) cond = cond.And(builder.Eq{"is_active": opts.IsActive.Value()})
} }
if !opts.IsAdmin.IsNone() { if opts.IsAdmin.Has() {
cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.IsTrue()}) cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.Value()})
} }
if !opts.IsRestricted.IsNone() { if opts.IsRestricted.Has() {
cond = cond.And(builder.Eq{"is_restricted": opts.IsRestricted.IsTrue()}) cond = cond.And(builder.Eq{"is_restricted": opts.IsRestricted.Value()})
} }
if !opts.IsProhibitLogin.IsNone() { if opts.IsProhibitLogin.Has() {
cond = cond.And(builder.Eq{"prohibit_login": opts.IsProhibitLogin.IsTrue()}) cond = cond.And(builder.Eq{"prohibit_login": opts.IsProhibitLogin.Value()})
} }
e := db.GetEngine(ctx) e := db.GetEngine(ctx)
if opts.IsTwoFactorEnabled.IsNone() { if !opts.IsTwoFactorEnabled.Has() {
return e.Where(cond) return e.Where(cond)
} }
@ -111,7 +114,7 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Sess
// While using LEFT JOIN, sometimes the performance might not be good, but it won't be a problem now, such SQL is seldom executed. // While using LEFT JOIN, sometimes the performance might not be good, but it won't be a problem now, such SQL is seldom executed.
// There are some possible methods to refactor this SQL in future when we really need to optimize the performance (but not now): // There are some possible methods to refactor this SQL in future when we really need to optimize the performance (but not now):
// (1) add a column in user table (2) add a setting value in user_setting table (3) use search engines (bleve/elasticsearch) // (1) add a column in user table (2) add a setting value in user_setting table (3) use search engines (bleve/elasticsearch)
if opts.IsTwoFactorEnabled.IsTrue() { if opts.IsTwoFactorEnabled.Value() {
cond = cond.And(builder.Expr("two_factor.uid IS NOT NULL")) cond = cond.And(builder.Expr("two_factor.uid IS NOT NULL"))
} else { } else {
cond = cond.And(builder.Expr("two_factor.uid IS NULL")) cond = cond.And(builder.Expr("two_factor.uid IS NULL"))
@ -128,7 +131,7 @@ func SearchUsers(ctx context.Context, opts *SearchUserOptions) (users []*User, _
defer sessCount.Close() defer sessCount.Close()
count, err := sessCount.Count(new(User)) count, err := sessCount.Count(new(User))
if err != nil { if err != nil {
return nil, 0, fmt.Errorf("Count: %w", err) return nil, 0, fmt.Errorf("count: %w", err)
} }
if len(opts.OrderBy) == 0 { if len(opts.OrderBy) == 0 {

View file

@ -598,6 +598,16 @@ type CreateUserOverwriteOptions struct {
// CreateUser creates record of a new user. // CreateUser creates record of a new user.
func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
return createUser(ctx, u, false, overwriteDefault...)
}
// AdminCreateUser is used by admins to manually create users
func AdminCreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
return createUser(ctx, u, true, overwriteDefault...)
}
// createUser creates record of a new user.
func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
if err = IsUsableUsername(u.Name); err != nil { if err = IsUsableUsername(u.Name); err != nil {
return err return err
} }
@ -651,9 +661,15 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
return err return err
} }
if createdByAdmin {
if err := ValidateEmailForAdmin(u.Email); err != nil {
return err
}
} else {
if err := ValidateEmail(u.Email); err != nil { if err := ValidateEmail(u.Email); err != nil {
return err return err
} }
}
ctx, committer, err := db.TxContext(ctx) ctx, committer, err := db.TxContext(ctx)
if err != nil { if err != nil {
@ -727,7 +743,7 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
// IsLastAdminUser check whether user is the last admin // IsLastAdminUser check whether user is the last admin
func IsLastAdminUser(ctx context.Context, user *User) bool { func IsLastAdminUser(ctx context.Context, user *User) bool {
if user.IsAdmin && CountUsers(ctx, &CountUserFilter{IsAdmin: util.OptionalBoolTrue}) <= 1 { if user.IsAdmin && CountUsers(ctx, &CountUserFilter{IsAdmin: optional.Some(true)}) <= 1 {
return true return true
} }
return false return false
@ -736,7 +752,7 @@ func IsLastAdminUser(ctx context.Context, user *User) bool {
// CountUserFilter represent optional filters for CountUsers // CountUserFilter represent optional filters for CountUsers
type CountUserFilter struct { type CountUserFilter struct {
LastLoginSince *int64 LastLoginSince *int64
IsAdmin util.OptionalBool IsAdmin optional.Option[bool]
} }
// CountUsers returns number of users. // CountUsers returns number of users.
@ -754,8 +770,8 @@ func countUsers(ctx context.Context, opts *CountUserFilter) int64 {
cond = cond.And(builder.Gte{"last_login_unix": *opts.LastLoginSince}) cond = cond.And(builder.Gte{"last_login_unix": *opts.LastLoginSince})
} }
if !opts.IsAdmin.IsNone() { if opts.IsAdmin.Has() {
cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.IsTrue()}) cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.Value()})
} }
} }

View file

@ -16,10 +16,10 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password/hash" "code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -103,29 +103,29 @@ func TestSearchUsers(t *testing.T) {
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}}, testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}},
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40}) []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolFalse}, testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(false)},
[]int64{9}) []int64{9})
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40}) []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40})
testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) []int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
// order by name asc default // order by name asc default
testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) []int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsAdmin: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsAdmin: optional.Some(true)},
[]int64{1}) []int64{1})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsRestricted: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsRestricted: optional.Some(true)},
[]int64{29}) []int64{29})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsProhibitLogin: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsProhibitLogin: optional.Some(true)},
[]int64{37}) []int64{37})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsTwoFactorEnabled: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsTwoFactorEnabled: optional.Some(true)},
[]int64{24}) []int64{24})
} }

View file

@ -5,13 +5,13 @@ package webhook
import ( import (
"context" "context"
"errors"
"time" "time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
webhook_module "code.gitea.io/gitea/modules/webhook" webhook_module "code.gitea.io/gitea/modules/webhook"
@ -31,6 +31,7 @@ type HookRequest struct {
URL string `json:"url"` URL string `json:"url"`
HTTPMethod string `json:"http_method"` HTTPMethod string `json:"http_method"`
Headers map[string]string `json:"headers"` Headers map[string]string `json:"headers"`
Body string `json:"body"`
} }
// HookResponse represents hook task response information. // HookResponse represents hook task response information.
@ -45,8 +46,12 @@ type HookTask struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
HookID int64 `xorm:"index"` HookID int64 `xorm:"index"`
UUID string `xorm:"unique"` UUID string `xorm:"unique"`
api.Payloader `xorm:"-"`
PayloadContent string `xorm:"LONGTEXT"` PayloadContent string `xorm:"LONGTEXT"`
// PayloadVersion number to allow for smooth version upgrades:
// - PayloadVersion 1: PayloadContent contains the JSON as sent to the URL
// - PayloadVersion 2: PayloadContent contains the original event
PayloadVersion int `xorm:"DEFAULT 1"`
EventType webhook_module.HookEventType EventType webhook_module.HookEventType
IsDelivered bool IsDelivered bool
Delivered timeutil.TimeStampNano Delivered timeutil.TimeStampNano
@ -115,16 +120,12 @@ func HookTasks(ctx context.Context, hookID int64, page int) ([]*HookTask, error)
// it handles conversion from Payload to PayloadContent. // it handles conversion from Payload to PayloadContent.
func CreateHookTask(ctx context.Context, t *HookTask) (*HookTask, error) { func CreateHookTask(ctx context.Context, t *HookTask) (*HookTask, error) {
t.UUID = gouuid.New().String() t.UUID = gouuid.New().String()
if t.Payloader != nil {
data, err := t.Payloader.JSONPayload()
if err != nil {
return nil, err
}
t.PayloadContent = string(data)
}
if t.Delivered == 0 { if t.Delivered == 0 {
t.Delivered = timeutil.TimeStampNanoNow() t.Delivered = timeutil.TimeStampNanoNow()
} }
if t.PayloadVersion == 0 {
return nil, errors.New("missing HookTask.PayloadVersion")
}
return t, db.Insert(ctx, t) return t, db.Insert(ctx, t)
} }
@ -165,6 +166,7 @@ func ReplayHookTask(ctx context.Context, hookID int64, uuid string) (*HookTask,
HookID: task.HookID, HookID: task.HookID,
PayloadContent: task.PayloadContent, PayloadContent: task.PayloadContent,
EventType: task.EventType, EventType: task.EventType,
PayloadVersion: task.PayloadVersion,
}) })
} }

View file

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/secret"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -433,7 +434,7 @@ type ListWebhookOptions struct {
db.ListOptions db.ListOptions
RepoID int64 RepoID int64
OwnerID int64 OwnerID int64
IsActive util.OptionalBool IsActive optional.Option[bool]
} }
func (opts ListWebhookOptions) ToConds() builder.Cond { func (opts ListWebhookOptions) ToConds() builder.Cond {
@ -444,8 +445,8 @@ func (opts ListWebhookOptions) ToConds() builder.Cond {
if opts.OwnerID != 0 { if opts.OwnerID != 0 {
cond = cond.And(builder.Eq{"webhook.owner_id": opts.OwnerID}) cond = cond.And(builder.Eq{"webhook.owner_id": opts.OwnerID})
} }
if !opts.IsActive.IsNone() { if opts.IsActive.Has() {
cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.IsTrue()}) cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.Value()})
} }
return cond return cond
} }

View file

@ -8,7 +8,7 @@ import (
"fmt" "fmt"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
) )
// GetDefaultWebhooks returns all admin-default webhooks. // GetDefaultWebhooks returns all admin-default webhooks.
@ -34,15 +34,15 @@ func GetSystemOrDefaultWebhook(ctx context.Context, id int64) (*Webhook, error)
} }
// GetSystemWebhooks returns all admin system webhooks. // GetSystemWebhooks returns all admin system webhooks.
func GetSystemWebhooks(ctx context.Context, isActive util.OptionalBool) ([]*Webhook, error) { func GetSystemWebhooks(ctx context.Context, isActive optional.Option[bool]) ([]*Webhook, error) {
webhooks := make([]*Webhook, 0, 5) webhooks := make([]*Webhook, 0, 5)
if isActive.IsNone() { if !isActive.Has() {
return webhooks, db.GetEngine(ctx). return webhooks, db.GetEngine(ctx).
Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, true). Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, true).
Find(&webhooks) Find(&webhooks)
} }
return webhooks, db.GetEngine(ctx). return webhooks, db.GetEngine(ctx).
Where("repo_id=? AND owner_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.IsTrue()). Where("repo_id=? AND owner_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.Value()).
Find(&webhooks) Find(&webhooks)
} }

View file

@ -11,9 +11,8 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook" webhook_module "code.gitea.io/gitea/modules/webhook"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -35,8 +34,10 @@ func TestWebhook_History(t *testing.T) {
webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 1}) webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 1})
tasks, err := webhook.History(db.DefaultContext, 0) tasks, err := webhook.History(db.DefaultContext, 0)
assert.NoError(t, err) assert.NoError(t, err)
if assert.Len(t, tasks, 1) { if assert.Len(t, tasks, 3) {
assert.Equal(t, int64(1), tasks[0].ID) assert.Equal(t, int64(3), tasks[0].ID)
assert.Equal(t, int64(2), tasks[1].ID)
assert.Equal(t, int64(1), tasks[2].ID)
} }
webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2}) webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2})
@ -123,7 +124,7 @@ func TestGetWebhookByOwnerID(t *testing.T) {
func TestGetActiveWebhooksByRepoID(t *testing.T) { func TestGetActiveWebhooksByRepoID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{RepoID: 1, IsActive: util.OptionalBoolTrue}) hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{RepoID: 1, IsActive: optional.Some(true)})
assert.NoError(t, err) assert.NoError(t, err)
if assert.Len(t, hooks, 1) { if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(1), hooks[0].ID) assert.Equal(t, int64(1), hooks[0].ID)
@ -143,7 +144,7 @@ func TestGetWebhooksByRepoID(t *testing.T) {
func TestGetActiveWebhooksByOwnerID(t *testing.T) { func TestGetActiveWebhooksByOwnerID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{OwnerID: 3, IsActive: util.OptionalBoolTrue}) hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{OwnerID: 3, IsActive: optional.Some(true)})
assert.NoError(t, err) assert.NoError(t, err)
if assert.Len(t, hooks, 1) { if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(3), hooks[0].ID) assert.Equal(t, int64(3), hooks[0].ID)
@ -197,8 +198,10 @@ func TestHookTasks(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
hookTasks, err := HookTasks(db.DefaultContext, 1, 1) hookTasks, err := HookTasks(db.DefaultContext, 1, 1)
assert.NoError(t, err) assert.NoError(t, err)
if assert.Len(t, hookTasks, 1) { if assert.Len(t, hookTasks, 3) {
assert.Equal(t, int64(1), hookTasks[0].ID) assert.Equal(t, int64(3), hookTasks[0].ID)
assert.Equal(t, int64(2), hookTasks[1].ID)
assert.Equal(t, int64(1), hookTasks[2].ID)
} }
hookTasks, err = HookTasks(db.DefaultContext, unittest.NonexistentID, 1) hookTasks, err = HookTasks(db.DefaultContext, unittest.NonexistentID, 1)
@ -210,7 +213,7 @@ func TestCreateHookTask(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{ hookTask := &HookTask{
HookID: 3, HookID: 3,
Payloader: &api.PushPayload{}, PayloadVersion: 2,
} }
unittest.AssertNotExistsBean(t, hookTask) unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask) _, err := CreateHookTask(db.DefaultContext, hookTask)
@ -233,9 +236,9 @@ func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{ hookTask := &HookTask{
HookID: 3, HookID: 3,
Payloader: &api.PushPayload{},
IsDelivered: true, IsDelivered: true,
Delivered: timeutil.TimeStampNanoNow(), Delivered: timeutil.TimeStampNanoNow(),
PayloadVersion: 2,
} }
unittest.AssertNotExistsBean(t, hookTask) unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask) _, err := CreateHookTask(db.DefaultContext, hookTask)
@ -250,8 +253,8 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{ hookTask := &HookTask{
HookID: 4, HookID: 4,
Payloader: &api.PushPayload{},
IsDelivered: false, IsDelivered: false,
PayloadVersion: 2,
} }
unittest.AssertNotExistsBean(t, hookTask) unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask) _, err := CreateHookTask(db.DefaultContext, hookTask)
@ -266,9 +269,9 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{ hookTask := &HookTask{
HookID: 4, HookID: 4,
Payloader: &api.PushPayload{},
IsDelivered: true, IsDelivered: true,
Delivered: timeutil.TimeStampNanoNow(), Delivered: timeutil.TimeStampNanoNow(),
PayloadVersion: 2,
} }
unittest.AssertNotExistsBean(t, hookTask) unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask) _, err := CreateHookTask(db.DefaultContext, hookTask)
@ -283,9 +286,9 @@ func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{ hookTask := &HookTask{
HookID: 3, HookID: 3,
Payloader: &api.PushPayload{},
IsDelivered: true, IsDelivered: true,
Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -8).UnixNano()), Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -8).UnixNano()),
PayloadVersion: 2,
} }
unittest.AssertNotExistsBean(t, hookTask) unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask) _, err := CreateHookTask(db.DefaultContext, hookTask)
@ -300,8 +303,8 @@ func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{ hookTask := &HookTask{
HookID: 4, HookID: 4,
Payloader: &api.PushPayload{},
IsDelivered: false, IsDelivered: false,
PayloadVersion: 2,
} }
unittest.AssertNotExistsBean(t, hookTask) unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask) _, err := CreateHookTask(db.DefaultContext, hookTask)
@ -316,9 +319,9 @@ func TestCleanupHookTaskTable_OlderThan_LeavesTaskEarlierThanAgeToDelete(t *test
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{ hookTask := &HookTask{
HookID: 4, HookID: 4,
Payloader: &api.PushPayload{},
IsDelivered: true, IsDelivered: true,
Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -6).UnixNano()), Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -6).UnixNano()),
PayloadVersion: 2,
} }
unittest.AssertNotExistsBean(t, hookTask) unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask) _, err := CreateHookTask(db.DefaultContext, hookTask)

View file

@ -35,6 +35,9 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep {
} else if task.Status.IsDone() { } else if task.Status.IsDone() {
preStep.Stopped = task.Stopped preStep.Stopped = task.Stopped
preStep.Status = actions_model.StatusFailure preStep.Status = actions_model.StatusFailure
if task.Status.IsSkipped() {
preStep.Status = actions_model.StatusSkipped
}
} }
logIndex += preStep.LogLength logIndex += preStep.LogLength

View file

@ -406,6 +406,9 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
// all acts conditions should be satisfied // all acts conditions should be satisfied
for cond, vals := range acts { for cond, vals := range acts {
switch cond { switch cond {
case "types":
// types have been checked
continue
case "branches": case "branches":
refName := git.RefName(prPayload.PullRequest.Base.Ref) refName := git.RefName(prPayload.PullRequest.Base.Ref)
patterns, err := workflowpattern.CompilePatterns(vals...) patterns, err := workflowpattern.CompilePatterns(vals...)

View file

@ -12,6 +12,7 @@ import (
"io" "io"
"os" "os"
"os/exec" "os/exec"
"runtime"
"strings" "strings"
"time" "time"
@ -344,6 +345,17 @@ func (c *Command) Run(opts *RunOpts) error {
log.Debug("slow git.Command.Run: %s (%s)", c, elapsed) log.Debug("slow git.Command.Run: %s (%s)", c, elapsed)
} }
// We need to check if the context is canceled by the program on Windows.
// This is because Windows does not have signal checking when terminating the process.
// It always returns exit code 1, unlike Linux, which has many exit codes for signals.
if runtime.GOOS == "windows" &&
err != nil &&
err.Error() == "" &&
cmd.ProcessState.ExitCode() == 1 &&
ctx.Err() == context.Canceled {
return ctx.Err()
}
if err != nil && ctx.Err() != context.DeadlineExceeded { if err != nil && ctx.Err() != context.DeadlineExceeded {
return err return err
} }

View file

@ -55,15 +55,8 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) {
}, nil }, nil
} }
// SetDefaultBranch sets default branch of repository. func GetDefaultBranch(ctx context.Context, repoPath string) (string, error) {
func (repo *Repository) SetDefaultBranch(name string) error { stdout, _, err := NewCommand(ctx, "symbolic-ref", "HEAD").RunStdString(&RunOpts{Dir: repoPath})
_, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD").AddDynamicArguments(BranchPrefix + name).RunStdString(&RunOpts{Dir: repo.Path})
return err
}
// GetDefaultBranch gets default branch of repository.
func (repo *Repository) GetDefaultBranch() (string, error) {
stdout, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD").RunStdString(&RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return "", err return "", err
} }

View file

@ -30,3 +30,20 @@ func GetBranchCommitID(ctx context.Context, repo Repository, branch string) (str
return gitRepo.GetBranchCommitID(branch) return gitRepo.GetBranchCommitID(branch)
} }
// SetDefaultBranch sets default branch of repository.
func SetDefaultBranch(ctx context.Context, repo Repository, name string) error {
_, _, err := git.NewCommand(ctx, "symbolic-ref", "HEAD").
AddDynamicArguments(git.BranchPrefix + name).
RunStdString(&git.RunOpts{Dir: repoPath(repo)})
return err
}
// GetDefaultBranch gets default branch of repository.
func GetDefaultBranch(ctx context.Context, repo Repository) (string, error) {
return git.GetDefaultBranch(ctx, repoPath(repo))
}
func GetWikiDefaultBranch(ctx context.Context, repo Repository) (string, error) {
return git.GetDefaultBranch(ctx, wikiPath(repo))
}

View file

@ -59,7 +59,15 @@ func (g *Manager) start() {
go func() { go func() {
defer close(startupDone) defer close(startupDone)
// Wait till we're done getting all the listeners and then close the unused ones // Wait till we're done getting all the listeners and then close the unused ones
func() {
// FIXME: there is a fundamental design problem of the "manager" and the "wait group".
// If nothing has started, the "Wait" just panics: sync: WaitGroup is reused before previous Wait has returned
// There is no clear solution besides a complete rewriting of the "manager"
defer func() {
_ = recover()
}()
g.createServerWaitGroup.Wait() g.createServerWaitGroup.Wait()
}()
// Ignore the error here there's not much we can do with it, they're logged in the CloseProvidedListeners function // Ignore the error here there's not much we can do with it, they're logged in the CloseProvidedListeners function
_ = CloseProvidedListeners() _ = CloseProvidedListeners()
g.notify(readyMsg) g.notify(readyMsg)

View file

@ -150,8 +150,16 @@ func (g *Manager) awaitServer(limit time.Duration) bool {
c := make(chan struct{}) c := make(chan struct{})
go func() { go func() {
defer close(c) defer close(c)
func() {
// FIXME: there is a fundamental design problem of the "manager" and the "wait group".
// If nothing has started, the "Wait" just panics: sync: WaitGroup is reused before previous Wait has returned
// There is no clear solution besides a complete rewriting of the "manager"
defer func() {
_ = recover()
}()
g.createServerWaitGroup.Wait() g.createServerWaitGroup.Wait()
}() }()
}()
if limit > 0 { if limit > 0 {
select { select {
case <-c: case <-c:

View file

@ -233,21 +233,21 @@ func (b *Indexer) Delete(_ context.Context, repoID int64) error {
// Search searches for files in the specified repo. // Search searches for files in the specified repo.
// Returns the matching file-paths // Returns the matching file-paths
func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isMatch bool) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) { func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) {
var ( var (
indexerQuery query.Query indexerQuery query.Query
keywordQuery query.Query keywordQuery query.Query
) )
if isMatch { if isFuzzy {
prefixQuery := bleve.NewPrefixQuery(keyword)
prefixQuery.FieldVal = "Content"
keywordQuery = prefixQuery
} else {
phraseQuery := bleve.NewMatchPhraseQuery(keyword) phraseQuery := bleve.NewMatchPhraseQuery(keyword)
phraseQuery.FieldVal = "Content" phraseQuery.FieldVal = "Content"
phraseQuery.Analyzer = repoIndexerAnalyzer phraseQuery.Analyzer = repoIndexerAnalyzer
keywordQuery = phraseQuery keywordQuery = phraseQuery
} else {
prefixQuery := bleve.NewPrefixQuery(keyword)
prefixQuery.FieldVal = "Content"
keywordQuery = prefixQuery
} }
if len(repoIDs) > 0 { if len(repoIDs) > 0 {

View file

@ -281,10 +281,10 @@ func extractAggs(searchResult *elastic.SearchResult) []*internal.SearchResultLan
} }
// Search searches for codes and language stats by given conditions. // Search searches for codes and language stats by given conditions.
func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isMatch bool) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) { func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) {
searchType := esMultiMatchTypeBestFields searchType := esMultiMatchTypePhrasePrefix
if isMatch { if isFuzzy {
searchType = esMultiMatchTypePhrasePrefix searchType = esMultiMatchTypeBestFields
} }
kwQuery := elastic.NewMultiMatchQuery(keyword, "content").Type(searchType) kwQuery := elastic.NewMultiMatchQuery(keyword, "content").Type(searchType)

View file

@ -70,7 +70,7 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
for _, kw := range keywords { for _, kw := range keywords {
t.Run(kw.Keyword, func(t *testing.T) { t.Run(kw.Keyword, func(t *testing.T) {
total, res, langs, err := indexer.Search(context.TODO(), kw.RepoIDs, "", kw.Keyword, 1, 10, false) total, res, langs, err := indexer.Search(context.TODO(), kw.RepoIDs, "", kw.Keyword, 1, 10, true)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, kw.IDs, int(total)) assert.Len(t, kw.IDs, int(total))
assert.Len(t, langs, kw.Langs) assert.Len(t, langs, kw.Langs)

View file

@ -16,7 +16,7 @@ type Indexer interface {
internal.Indexer internal.Indexer
Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *RepoChanges) error Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *RepoChanges) error
Delete(ctx context.Context, repoID int64) error Delete(ctx context.Context, repoID int64) error
Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isMatch bool) (int64, []*SearchResult, []*SearchResultLanguages, error) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int64, []*SearchResult, []*SearchResultLanguages, error)
} }
// NewDummyIndexer returns a dummy indexer // NewDummyIndexer returns a dummy indexer
@ -38,6 +38,6 @@ func (d *dummyIndexer) Delete(ctx context.Context, repoID int64) error {
return fmt.Errorf("indexer is not ready") return fmt.Errorf("indexer is not ready")
} }
func (d *dummyIndexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isMatch bool) (int64, []*SearchResult, []*SearchResultLanguages, error) { func (d *dummyIndexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int64, []*SearchResult, []*SearchResultLanguages, error) {
return 0, nil, nil, fmt.Errorf("indexer is not ready") return 0, nil, nil, fmt.Errorf("indexer is not ready")
} }

Some files were not shown because too many files have changed in this diff Show more