diff --git a/.deadcode-out b/.deadcode-out
index 8fca0d96bb..fd7b3ad858 100644
--- a/.deadcode-out
+++ b/.deadcode-out
@@ -162,9 +162,6 @@ package "code.gitea.io/gitea/modules/cache"
package "code.gitea.io/gitea/modules/charset"
func (*BreakWriter).Write
-package "code.gitea.io/gitea/modules/context"
- func GetPrivateContext
-
package "code.gitea.io/gitea/modules/emoji"
func ReplaceCodes
@@ -192,6 +189,7 @@ package "code.gitea.io/gitea/modules/gitgraph"
package "code.gitea.io/gitea/modules/gitrepo"
func GetBranchCommitID
+ func GetWikiDefaultBranch
package "code.gitea.io/gitea/modules/graceful"
func (*Manager).TerminateContext
@@ -296,7 +294,6 @@ package "code.gitea.io/gitea/modules/translation"
package "code.gitea.io/gitea/modules/util"
func UnsafeStringToBytes
- func OptionalBoolFromGeneric
package "code.gitea.io/gitea/modules/util/filebuffer"
func CreateFromReader
@@ -316,6 +313,9 @@ package "code.gitea.io/gitea/routers/web/org"
func getActionIssues
func UpdateIssueProject
+package "code.gitea.io/gitea/services/context"
+ func GetPrivateContext
+
package "code.gitea.io/gitea/services/convert"
func ToSecret
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 9e290fb6a5..8563aafd04 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -8,7 +8,9 @@
},
"ghcr.io/devcontainers/features/git-lfs:1.1.0": {},
"ghcr.io/devcontainers-contrib/features/poetry:2": {},
- "ghcr.io/devcontainers/features/python:1": {}
+ "ghcr.io/devcontainers/features/python:1": {
+ "version": "3.12"
+ }
},
"customizations": {
"vscode": {
diff --git a/.forgejo/cascading-pr-end-to-end b/.forgejo/cascading-pr-end-to-end
index 975888b245..2350394f2c 100755
--- a/.forgejo/cascading-pr-end-to-end
+++ b/.forgejo/cascading-pr-end-to-end
@@ -5,17 +5,26 @@ set -ex
end_to_end=$1
end_to_end_pr=$2
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
-echo $head_url $branch 7.0.0+0-gitea-1.22.0 > forgejo/sources/1.22
date > last-upgrade
-base_url=$(jq --raw-output .base.repo.html_url < $forgejo_pr)
-test "$base_url" != null
+if test -f "$forgejo_pr_or_ref" ; then
+ 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"
-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
diff --git a/.forgejo/workflows/cascade-setup-end-to-end.yml b/.forgejo/workflows/cascade-setup-end-to-end.yml
index 235211f18a..85871ec31d 100644
--- a/.forgejo/workflows/cascade-setup-end-to-end.yml
+++ b/.forgejo/workflows/cascade-setup-end-to-end.yml
@@ -1,5 +1,23 @@
+# Copyright 2024 The Forgejo Authors
# 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:
+ push:
+ branches:
+ - 'wip-ci-end-to-end'
pull_request_target:
types:
- labeled
@@ -20,9 +38,18 @@ jobs:
cat <<'EOF'
${{ toJSON(github.event.pull_request.labels.*.name) }}
EOF
+ cat <<'EOF'
+ ${{ toJSON(github.event) }}
+ EOF
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
container:
image: 'docker.io/node:20-bookworm'
@@ -55,19 +82,29 @@ jobs:
path: forgejo
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]
runs-on: docker
container:
image: node:20-bookworm
steps:
- uses: actions/checkout@v4
- - uses: actions/cascading-pr@v1
+ with:
+ fetch-depth: '0'
+ show-progress: 'false'
+ - uses: actions/cascading-pr@v2
with:
origin-url: ${{ env.GITHUB_SERVER_URL }}
origin-repo: ${{ github.repository }}
origin-token: ${{ secrets.END_TO_END_CASCADING_PR_ORIGIN }}
origin-pr: ${{ github.event.pull_request.number }}
+ origin-ref: ${{ github.event_name == 'push' && github.event.ref || '' }}
destination-url: https://code.forgejo.org
destination-fork-repo: cascading-pr/end-to-end
destination-repo: forgejo/end-to-end
diff --git a/.forgejo/workflows/testing.yml b/.forgejo/workflows/testing.yml
index 80fd87152e..85d7691cd1 100644
--- a/.forgejo/workflows/testing.yml
+++ b/.forgejo/workflows/testing.yml
@@ -21,8 +21,6 @@ jobs:
check-latest: true
- run: make deps-backend deps-tools
- 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:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker
@@ -43,8 +41,11 @@ jobs:
image: 'docker.io/node:20-bookworm'
services:
minio:
- image: 'docker.io/bitnami/minio:2023.8.31'
+ image: bitnami/minio:2024.2.26
+ options: >-
+ --hostname gitea.minio
env:
+ MINIO_DOMAIN: minio
MINIO_ROOT_USER: 123456
MINIO_ROOT_PASSWORD: 12345678
steps:
@@ -130,10 +131,10 @@ jobs:
image: 'docker.io/node:20-bookworm'
services:
minio:
- image: bitnami/minio:2021.3.17
+ image: bitnami/minio:2024.2.26
env:
- MINIO_ACCESS_KEY: 123456
- MINIO_SECRET_KEY: 12345678
+ MINIO_ROOT_USER: 123456
+ MINIO_ROOT_PASSWORD: 12345678
pgsql:
image: 'docker.io/postgres:15'
env:
diff --git a/.gitignore b/.gitignore
index 12014d405b..34c71b6973 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,7 +18,7 @@ _test
# MS VSCode
.vscode
-__debug_bin
+__debug_bin*
*.cgo1.go
*.cgo2.c
diff --git a/.stylelintrc.yaml b/.stylelintrc.yaml
index 7dd0a566f2..c7725159f1 100644
--- a/.stylelintrc.yaml
+++ b/.stylelintrc.yaml
@@ -64,6 +64,7 @@ rules:
"@stylistic/media-query-list-comma-newline-before": null
"@stylistic/media-query-list-comma-space-after": null
"@stylistic/media-query-list-comma-space-before": null
+ "@stylistic/named-grid-areas-alignment": null
"@stylistic/no-empty-first-line": null
"@stylistic/no-eol-whitespace": true
"@stylistic/no-extra-semicolons": true
diff --git a/Makefile b/Makefile
index 9d0a92bdfb..7a19be9ffc 100644
--- a/Makefile
+++ b/Makefile
@@ -93,6 +93,14 @@ ifneq ($(STORED_VERSION),)
else
FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//')+${GITEA_COMPATIBILITY}
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}
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
-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/))
+ifeq ($(HAS_GO), yes)
+ 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
@@ -140,7 +150,9 @@ GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/optio
GO_SOURCES += $(GENERATED_GO_DEST)
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)
GO_SOURCES += $(BINDATA_DEST)
@@ -219,6 +231,8 @@ help:
@echo " - checks-frontend check frontend files"
@echo " - checks-backend check backend files"
@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-backend test backend files"
@echo " - test-e2e[\#TestSpecificName] test end to end using playwright"
@@ -299,12 +313,8 @@ fmt:
.PHONY: fmt-check
fmt-check: fmt
- @diff=$$(git diff --color=always $(GO_SOURCES) templates $(WEB_DIRS)); \
- if [ -n "$$diff" ]; then \
- echo "Please run 'make fmt' and commit the result:"; \
- echo "$${diff}"; \
- exit 1; \
- fi
+ @git diff --exit-code --color=always $(GO_SOURCES) templates $(WEB_DIRS) \
+ || (code=$$?; echo "Please run 'make fmt' and commit the result"; exit $${code})
.PHONY: $(TAGS_EVIDENCE)
$(TAGS_EVIDENCE):
@@ -325,12 +335,8 @@ generate-forgejo-api: $(FORGEJO_API_SPEC)
.PHONY: forgejo-api-check
forgejo-api-check: generate-forgejo-api
- @diff=$$(git diff $(FORGEJO_API_SERVER) ; \
- if [ -n "$$diff" ]; then \
- echo "Please run 'make generate-forgejo-api' and commit the result:"; \
- echo "$${diff}"; \
- exit 1; \
- fi
+ @git diff --exit-code --color=always $(FORGEJO_API_SERVER) \
+ || (code=$$?; echo "Please run 'make generate-forgejo-api' and commit the result"; exit $${code})
.PHONY: forgejo-api-validate
forgejo-api-validate:
@@ -347,12 +353,8 @@ $(SWAGGER_SPEC): $(GO_SOURCES_NO_BINDATA)
.PHONY: swagger-check
swagger-check: generate-swagger
- @diff=$$(git diff --color=always '$(SWAGGER_SPEC)'); \
- if [ -n "$$diff" ]; then \
- echo "Please run 'make generate-swagger' and commit the result:"; \
- echo "$${diff}"; \
- exit 1; \
- fi
+ @git diff --exit-code --color=always '$(SWAGGER_SPEC)' \
+ || (code=$$?; echo "Please run 'make generate-swagger' and commit the result"; exit $${code})
.PHONY: swagger-validate
swagger-validate:
@@ -423,11 +425,8 @@ lint-spell-fix:
lint-go:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS)
$(GO) run $(DEADCODE_PACKAGE) -generated=false -test code.gitea.io/gitea > .cur-deadcode-out
- @$(DIFF) .deadcode-out .cur-deadcode-out; \
- if [ $$? -eq 1 ]; then \
- echo "Please run 'make lint-go-fix' and commit the result"; \
- exit 1; \
- fi
+ @$(DIFF) .deadcode-out .cur-deadcode-out \
+ || (code=$$?; echo "Please run 'make lint-go-fix' and commit the result"; exit $${code})
.PHONY: lint-go-fix
lint-go-fix:
@@ -527,12 +526,8 @@ vendor: go.mod go.sum
.PHONY: tidy-check
tidy-check: tidy
- @diff=$$(git diff --color=always go.mod go.sum $(GO_LICENSE_FILE)); \
- if [ -n "$$diff" ]; then \
- echo "Please run 'make tidy' and commit the result:"; \
- echo "$${diff}"; \
- exit 1; \
- fi
+ @git diff --exit-code --color=always go.mod go.sum $(GO_LICENSE_FILE) \
+ || (code=$$?; echo "Please run 'make tidy' and commit the result"; exit $${code})
.PHONY: go-licenses
go-licenses: $(GO_LICENSE_FILE)
@@ -947,6 +942,7 @@ fomantic:
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 -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
# 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
@@ -970,23 +966,14 @@ svg: node-check | node_modules
.PHONY: svg-check
svg-check: svg
@git add $(SVG_DEST_DIR)
- @diff=$$(git diff --color=always --cached $(SVG_DEST_DIR)); \
- if [ -n "$$diff" ]; then \
- echo "Please run 'make svg' and 'git add $(SVG_DEST_DIR)' and commit the result:"; \
- echo "$${diff}"; \
- exit 1; \
- fi
+ @git diff --exit-code --color=always --cached $(SVG_DEST_DIR) \
+ || (code=$$?; echo "Please run 'make svg' and commit the result"; exit $${code})
.PHONY: lockfile-check
lockfile-check:
npm install --package-lock-only
- @diff=$$(git diff --color=always package-lock.json); \
- if [ -n "$$diff" ]; then \
- 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
+ @git diff --exit-code --color=always package-lock.json \
+ || (code=$$?; echo "Please run 'npm install --package-lock-only' and commit the result"; exit $${code})
.PHONY: update-translations
update-translations:
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index c72c898f67..4e87078811 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -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).
+## 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
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:
diff --git a/assets/go-licenses.json b/assets/go-licenses.json
index 2aab21595b..ad3ab6c4c1 100644
--- a/assets/go-licenses.json
+++ b/assets/go-licenses.json
@@ -34,6 +34,11 @@
"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"
},
+ {
+ "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",
"path": "git.sr.ht/~mariusor/go-xsd-duration/LICENSE",
diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go
index a257ce21c8..10965c7e8f 100644
--- a/cmd/admin_user_create.go
+++ b/cmd/admin_user_create.go
@@ -47,7 +47,8 @@ var microcmdUserCreate = &cli.Command{
},
&cli.BoolFlag{
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{
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")
}
- // always default to true
- changePassword := true
+ changePassword := c.Bool("must-change-password")
// If this is the first user being created.
// Take it as the admin and don't force a password update.
@@ -119,10 +119,6 @@ func runCreateUser(c *cli.Context) error {
changePassword = false
}
- if c.IsSet("must-change-password") {
- changePassword = c.Bool("must-change-password")
- }
-
restricted := optional.None[bool]()
if c.IsSet("restricted") {
diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 04714e5502..b3896bc31c 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -969,6 +969,12 @@ LEVEL = Info
;GO_GET_CLONE_URL_PROTOCOL = https
;;
;; 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.
;DISABLED_REPO_UNITS =
;;
@@ -1492,8 +1498,10 @@ LEVEL = Info
;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_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
+;; - manage_ssh_keys: a user cannot configure ssh keys
+;; - manage_gpg_keys: a user cannot configure gpg keys
;USER_DISABLED_FEATURES =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md
index aa2cbcee5d..1d34c78d01 100644
--- a/docs/content/administration/config-cheat-sheet.en-us.md
+++ b/docs/content/administration/config-cheat-sheet.en-us.md
@@ -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
- `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`)
@@ -829,7 +832,7 @@ Default templates for project boards:
## Issue and pull request attachments (`attachment`)
- `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_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]`
@@ -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_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_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_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`
@@ -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_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_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_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`
@@ -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_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_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_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`
@@ -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_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_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_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`
diff --git a/docs/content/administration/config-cheat-sheet.zh-cn.md b/docs/content/administration/config-cheat-sheet.zh-cn.md
index 01906930cb..f636927da6 100644
--- a/docs/content/administration/config-cheat-sheet.zh-cn.md
+++ b/docs/content/administration/config-cheat-sheet.zh-cn.md
@@ -497,6 +497,10 @@ Gitea 创建以下非唯一队列:
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**:用户电子邮件通知的默认配置(用户可配置)。选项:enabled、onmention、disabled
- `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`)
@@ -778,7 +782,7 @@ Gitea 创建以下非唯一队列:
## 工单和合并请求的附件 (`attachment`)
- `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_FILES`: **5**: 一次最多上传的附件数量。
- `STORAGE_TYPE`: **local**: 附件的存储类型,`local` 表示本地磁盘,`minio` 表示兼容 S3 的对象存储服务,如果未设置将使用默认值 `local` 或其他在 `[storage.xxx]` 中定义的名称。
@@ -788,6 +792,10 @@ Gitea 创建以下非唯一队列:
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID 以连接,仅当 STORAGE_TYPE 为 `minio` 时可用。
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey 以连接,仅当 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_BASE_PATH`: **attachments/**: Minio 存储桶上的基本路径,仅当 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_SECRET_ACCESS_KEY`:Minio 的 secretAccessKey,仅在 `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_BASE_PATH`:**lfs/**:桶上的 Minio 基本路径,仅在 `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_SECRET_ACCESS_KEY`:Minio 的 secretAccessKey,仅在 `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_USE_SSL`:**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_SECRET_ACCESS_KEY`: Minio的secretAccessKey,仅在`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_BASE_PATH`: **repo-archive/**:存储桶上的Minio基本路径,仅在`STORAGE_TYPE`为`minio`时可用。
- `MINIO_USE_SSL`: **false**:启用Minio的SSL,仅在`STORAGE_TYPE`为`minio`时可用。
diff --git a/docs/content/administration/mail-templates.en-us.md b/docs/content/administration/mail-templates.en-us.md
index 32b352da4b..4026b89975 100644
--- a/docs/content/administration/mail-templates.en-us.md
+++ b/docs/content/administration/mail-templates.en-us.md
@@ -222,9 +222,9 @@ Please check [Gitea's logs](administration/logging-config.md) for error messages
{{.Repo}}#{{.Issue.Index}} .
{{if not (eq .Body "")}}
- Message content:
+ Message content
- {{.Body | Str2html}}
+ {{.Body}}
{{end}}
@@ -245,7 +245,7 @@ This template produces something along these lines:
> [@rhonda](#) (Rhonda Myers) updated [mike/stuff#38](#).
>
-> #### Message content:
+> #### Message content
>
> \_********************************\_********************************
>
@@ -259,20 +259,20 @@ This template produces something along these lines:
The template system contains several functions that can be used to further process and format
the messages. Here's a list of some of them:
-| Name | Parameters | Available | Usage |
-| ---------------- | ----------- | --------- | --------------------------------------------------------------------------- |
-| `AppUrl` | - | Any | Gitea's URL |
-| `AppName` | - | Any | Set from `app.ini`, usually "Gitea" |
-| `AppDomain` | - | Any | Gitea's host name |
-| `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. |
-| `Safe` | string | Body only | Takes the input as HTML; can be used for `.ReviewComments.RenderedContent`. |
+| Name | Parameters | Available | Usage |
+| ---------------- | ----------- | --------- | ------------------------------------------------------------------- |
+| `AppUrl` | - | Any | Gitea's URL |
+| `AppName` | - | Any | Set from `app.ini`, usually "Gitea" |
+| `AppDomain` | - | Any | Gitea's host name |
+| `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed |
+| `SanitizeHTML` | string | Body only | Sanitizes text by removing any dangerous HTML tags from it |
+| `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:
```html
-Like this: {{Str2html "Escapetext"}}
-Or this: {{"Escapetext" | Str2html}}
+Like this: {{SanitizeHTML "Escapetext"}}
+Or this: {{"Escapetext" | SanitizeHTML}}
Or this: {{AppUrl}}
But not like this: {{.AppUrl}}
```
diff --git a/docs/content/administration/mail-templates.zh-cn.md b/docs/content/administration/mail-templates.zh-cn.md
index 588f0b2ccb..3c7c2a9397 100644
--- a/docs/content/administration/mail-templates.zh-cn.md
+++ b/docs/content/administration/mail-templates.zh-cn.md
@@ -207,7 +207,7 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
{{if not (eq .Body "")}}
消息内容:
- {{.Body | Str2html}}
+ {{.Body}}
{{end}}
@@ -228,7 +228,7 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
> [@rhonda](#)(Rhonda Myers)更新了 [mike/stuff#38](#)。
>
-> #### 消息内容:
+> #### 消息内容
>
> \_********************************\_********************************
>
@@ -242,20 +242,20 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
模板系统包含一些函数,可用于进一步处理和格式化消息。以下是其中一些函数的列表:
-| 函数名 | 参数 | 可用于 | 用法 |
-| ----------------- | ----------- | ------------ | --------------------------------------------------------------------------------- |
-| `AppUrl` | - | 任何地方 | Gitea 的 URL |
-| `AppName` | - | 任何地方 | 从 `app.ini` 中设置,通常为 "Gitea" |
-| `AppDomain` | - | 任何地方 | Gitea 的主机名 |
-| `EllipsisString` | string, int | 任何地方 | 将字符串截断为指定长度;根据需要添加省略号 |
-| `Str2html` | string | 仅正文部分 | 通过删除其中的 HTML 标签对文本进行清理 |
-| `Safe` | string | 仅正文部分 | 将输入作为 HTML 处理;可用于 `.ReviewComments.RenderedContent` 等字段 |
+| 函数名 | 参数 | 可用于 | 用法 |
+|------------------| ----------- | ------------ | ------------------------------ |
+| `AppUrl` | - | 任何地方 | Gitea 的 URL |
+| `AppName` | - | 任何地方 | 从 `app.ini` 中设置,通常为 "Gitea" |
+| `AppDomain` | - | 任何地方 | Gitea 的主机名 |
+| `EllipsisString` | string, int | 任何地方 | 将字符串截断为指定长度;根据需要添加省略号 |
+| `SanitizeHTML` | string | 仅正文部分 | 通过删除其中的危险 HTML 标签对文本进行清理 |
+| `SafeHTML` | string | 仅正文部分 | 将输入作为 HTML 处理;可用于输出原始的 HTML 内容 |
这些都是 _函数_,而不是元数据,因此必须按以下方式使用:
```html
-像这样使用: {{Str2html "Escapetext"}}
-或者这样使用: {{"Escapetext" | Str2html}}
+像这样使用: {{SanitizeHTML "Escapetext"}}
+或者这样使用: {{"Escapetext" | SanitizeHTML}}
或者这样使用: {{AppUrl}}
但不要像这样使用: {{.AppUrl}}
```
diff --git a/docs/content/contributing/guidelines-frontend.en-us.md b/docs/content/contributing/guidelines-frontend.en-us.md
index edd89e1231..2637780718 100644
--- a/docs/content/contributing/guidelines-frontend.en-us.md
+++ b/docs/content/contributing/guidelines-frontend.en-us.md
@@ -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.
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.
-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.
### Accessibility / ARIA
diff --git a/docs/content/contributing/guidelines-frontend.zh-cn.md b/docs/content/contributing/guidelines-frontend.zh-cn.md
index 66a4d4b4d6..ace0d97f49 100644
--- a/docs/content/contributing/guidelines-frontend.zh-cn.md
+++ b/docs/content/contributing/guidelines-frontend.zh-cn.md
@@ -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)。
-## Gitea 特定准则:
+## Gitea 特定准则
1. 每个功能(Fomantic-UI/jQuery 模块)应放在单独的文件/目录中。
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`,如果无法避免,添加注释解释为什么需要它。
10. 避免在一个事件监听器中混合不同的事件,优先为每个事件使用独立的事件监听器。
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
@@ -64,18 +65,21 @@ Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`ari
* Vue + Vanilla JS
* Fomantic-UI(jQuery)
+* htmx (部分页面重新加载其他静态组件)
* Vanilla JS
不推荐的实现方式:
* Vue + Fomantic-UI(jQuery)
* jQuery + Vanilla JS
+* htmx + 任何其他需要大量 JavaScript 代码或不必要的功能,如 htmx 脚本 (`hx-on`)
为了保持界面一致,Vue 组件可以使用 Fomantic-UI 的 CSS 类。
尽管不建议混合使用不同的框架,
+我们使用 htmx 进行简单的交互。您可以在此 [PR](https://github.com/go-gitea/gitea/pull/28908) 中查看一个简单交互的示例,其中应使用 htmx。如果您需要更高级的反应性,请不要使用 htmx,请使用其他框架(Vue/Vanilla JS)。
但如果混合使用是必要的,并且代码设计良好且易于维护,也可以工作。
-### async 函数
+### `async` 函数
只有当函数内部存在`await`调用或返回`Promise`时,才将函数标记为`async`。
@@ -91,6 +95,12 @@ Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`ari
这是有意为之的,我们想调用异步函数并忽略Promise。
一些 lint 规则和 IDE 也会在未处理返回的 Promise 时发出警告。
+### 获取数据
+
+要获取数据,请使用`modules/fetch.js`中的包装函数`GET`、`POST`等。他们
+接受内容的`data`选项,将自动设置 CSRF 令牌并返回
+[Response](https://developer.mozilla.org/en-US/docs/Web/API/Response)。
+
### HTML 属性和 dataset
禁止使用`dataset`,它的驼峰命名行为使得搜索属性变得困难。
@@ -132,3 +142,7 @@ Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`ari
### Vue3 和 JSX
Gitea 现在正在使用 Vue3。我们决定不引入 JSX,以保持 HTML 代码和 JavaScript 代码分离。
+
+### UI示例
+
+Gitea 使用一些自制的 UI 元素并自定义其他元素,以将它们更好地集成到通用 UI 方法中。当在开发模式(`RUN_MODE=dev`)下运行 Gitea 时,在 `http(s)://your-gitea-url:port/devtest` 下会提供一个包含一些标准化 UI 示例的页面。
diff --git a/docs/content/usage/issue-pull-request-templates.en-us.md b/docs/content/usage/issue-pull-request-templates.en-us.md
index 34475e3465..bd43131f4a 100644
--- a/docs/content/usage/issue-pull-request-templates.en-us.md
+++ b/docs/content/usage/issue-pull-request-templates.en-us.md
@@ -135,6 +135,12 @@ body:
attributes:
value: |
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
id: contact
attributes:
@@ -186,11 +192,16 @@ body:
options:
- label: I agree to follow this project's Code of Conduct
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
-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:
@@ -198,6 +209,8 @@ Attributes:
|-------|--------------------------------------------------------------|----------|--------|---------|--------------|
| value | The text that is rendered. Markdown formatting is supported. | Required | String | - | - |
+visible: Default is **[form]**
+
### 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.
@@ -218,6 +231,8 @@ Validations:
|----------|------------------------------------------------------|----------|---------|---------|--------------|
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
+visible: Default is **[form, content]**
+
### Input
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 | - |
| 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
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 | - |
+visible: Default is **[form, content]**
+
### Checkboxes
You can use the `checkboxes` element to add a set of checkboxes to your form.
@@ -265,17 +284,20 @@ You can use the `checkboxes` element to add a set of checkboxes to your form.
Attributes:
| Key | Description | Required | Type | Default | Valid values |
-|-------------|-------------------------------------------------------------------------------------------------------|----------|--------|--------------|--------------|
+| ----------- | ----------------------------------------------------------------------------------------------------- | -------- | ------ | ------------ | ------------ |
| 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 | - |
| options | An array of checkboxes that the user can select. For syntax, see below. | Required | Array | - | - |
For each value in the options array, you can set the following keys.
-| 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 | - | - |
-| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
+| 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 | - | - |
+| 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
@@ -291,15 +313,15 @@ contact_links:
### Possible Options
-| Key | Description | Type | Default |
-|----------------------|-------------------------------------------------------------------------------------------------------|--------------------|----------------|
-| 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 |
+| Key | Description | Type | Default |
+|----------------------|-------------------------------------------------------|--------------------|-------------|
+| 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 Link
-| Key | Description | Type | Required |
-|----------------------|-------------------------------------------------------------------------------------------------------|---------|----------|
-| name | the name of your link | String | true |
-| url | The URL of your Link | String | true |
-| about | A short description of your Link | String | true |
+| Key | Description | Type | Required |
+|-------|----------------------------------|--------|----------|
+| name | the name of your link | String | true |
+| url | The URL of your Link | String | true |
+| about | A short description of your Link | String | true |
diff --git a/go.mod b/go.mod
index 0924b9fdc0..837b233171 100644
--- a/go.mod
+++ b/go.mod
@@ -45,9 +45,9 @@ require (
github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.11.0
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-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/gobwas/glob v0.2.3
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/google/go-github/v57 v57.0.0
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/sessions v1.2.2
github.com/hashicorp/go-version v1.6.0
@@ -71,7 +71,7 @@ require (
github.com/lib/pq v1.10.9
github.com/markbates/goth v1.78.0
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/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.26
@@ -109,7 +109,7 @@ require (
golang.org/x/text v0.14.0
golang.org/x/tools v0.17.0
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/ini.v1 v1.67.0
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/metadata v0.2.3 // 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
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/Masterminds/goutils v1.1.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/olekukonko/tablewriter v0.0.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/pierrec/lz4/v4 v4.1.21 // 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/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
diff --git a/go.sum b/go.sum
index f8bf0567de..5f7c04bb0e 100644
--- a/go.sum
+++ b/go.sum
@@ -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/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
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/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
-gitea.com/gitea/act v0.2.51 h1:gXc/B4OlTciTTzAx9cmNyw04n2SDO7exPjAsR5Idu+c=
-gitea.com/gitea/act v0.2.51/go.mod h1:CoaX2053jqBlD6JMgu4d4UgFL/rp2I14Kt5mMqcs0Z0=
+gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw=
+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/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc=
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/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/clickhouse-go/v2 v2.17.1 h1:ZCmAYWpu75IyEi7+Yrs/uaAjiCGY5wfW5kXo64exkX4=
-github.com/ClickHouse/clickhouse-go/v2 v2.17.1/go.mod h1:rkGTvFDTLqLIm0ma+13xmcCfr/08Gvs7KmFt1tgiWHQ=
+github.com/ClickHouse/clickhouse-go/v2 v2.18.0 h1:O1LicIeg2JS2V29fKRH4+yT3f6jvvcJBm506dpVQ4mQ=
+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.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
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/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.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
-github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
+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/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewetCdvEXqgPvbc/Q=
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-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
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.9.0/go.mod h1:cdsKD2ApFBjdog9jRsz6EJqF+LClq/hrwE9K/1Dzo4s=
+github.com/go-testfixtures/testfixtures/v3 v3.10.0 h1:BrBwN7AuC+74g5qtk9D59TLGOaEa8Bw1WmIsf+SyzWc=
+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/go.mod h1:l0NiauXhL6usIKqNLCUM3Qir43GK7ORg8ggold0Uv/Y=
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.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.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
-github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+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.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
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/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
+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/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0=
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/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU=
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.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
+github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
+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/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
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/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.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+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/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/models/actions/runner.go b/models/actions/runner.go
index b646146ee6..67f003387b 100644
--- a/models/actions/runner.go
+++ b/models/actions/runner.go
@@ -13,6 +13,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/shared/types"
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/translation"
"code.gitea.io/gitea/modules/util"
@@ -159,7 +160,7 @@ type FindRunnerOptions struct {
OwnerID int64
Sort string
Filter string
- IsOnline util.OptionalBool
+ IsOnline optional.Option[bool]
WithAvailable bool // not only runners belong to, but also runners can be used
}
@@ -186,10 +187,12 @@ func (opts FindRunnerOptions) ToConds() builder.Cond {
cond = cond.And(builder.Like{"name", opts.Filter})
}
- if opts.IsOnline.IsTrue() {
- cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
- } else if opts.IsOnline.IsFalse() {
- cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
+ if opts.IsOnline.Has() {
+ if opts.IsOnline.Value() {
+ cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
+ } else {
+ cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
+ }
}
return cond
}
diff --git a/models/actions/variable.go b/models/actions/variable.go
index 12717e0ae4..14ded60fac 100644
--- a/models/actions/variable.go
+++ b/models/actions/variable.go
@@ -10,6 +10,7 @@ import (
"strings"
"code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -82,3 +83,35 @@ func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error)
})
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
+}
diff --git a/models/activities/action.go b/models/activities/action.go
index 8cb32f6889..6593767679 100644
--- a/models/activities/action.go
+++ b/models/activities/action.go
@@ -227,8 +227,8 @@ func (a *Action) ShortActUserName(ctx context.Context) string {
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.
-func (a *Action) GetDisplayName(ctx context.Context) string {
+// 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) GetActDisplayName(ctx context.Context) string {
if setting.UI.DefaultShowFullName {
trimmedFullName := strings.TrimSpace(a.GetActFullName(ctx))
if len(trimmedFullName) > 0 {
@@ -238,8 +238,8 @@ func (a *Action) GetDisplayName(ctx context.Context) string {
return a.ShortActUserName(ctx)
}
-// GetDisplayNameTitle 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 {
+// GetActDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME
+func (a *Action) GetActDisplayNameTitle(ctx context.Context) string {
if setting.UI.DefaultShowFullName {
return a.ShortActUserName(ctx)
}
@@ -395,10 +395,14 @@ func (a *Action) GetCreate() time.Time {
return a.CreatedUnix.AsTime()
}
-// GetIssueInfos returns a list of issues associated with
-// the action.
+// GetIssueInfos returns a list of associated information with the action.
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.
diff --git a/models/auth/source.go b/models/auth/source.go
index 53920f0597..1a3a1b20a6 100644
--- a/models/auth/source.go
+++ b/models/auth/source.go
@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -243,14 +244,14 @@ func CreateSource(ctx context.Context, source *Source) error {
type FindSourcesOptions struct {
db.ListOptions
- IsActive util.OptionalBool
+ IsActive optional.Option[bool]
LoginType Type
}
func (opts FindSourcesOptions) ToConds() builder.Cond {
conds := builder.NewCond()
- if !opts.IsActive.IsNone() {
- conds = conds.And(builder.Eq{"is_active": opts.IsActive.IsTrue()})
+ if opts.IsActive.Has() {
+ conds = conds.And(builder.Eq{"is_active": opts.IsActive.Value()})
}
if opts.LoginType != NoType {
conds = conds.And(builder.Eq{"`type`": opts.LoginType})
@@ -262,7 +263,7 @@ func (opts FindSourcesOptions) ToConds() builder.Cond {
// source of type LoginSSPI
func IsSSPIEnabled(ctx context.Context) bool {
exist, err := db.Exist[Source](ctx, FindSourcesOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
LoginType: SSPI,
}.ToConds())
if err != nil {
diff --git a/models/db/collation.go b/models/db/collation.go
index 2f5ff2bf05..c128cf5029 100644
--- a/models/db/collation.go
+++ b/models/db/collation.go
@@ -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)
// 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 && x.Dialect().URI().DBType == schemas.MYSQL {
+ if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) && r.ExistingTableNumber == 0 {
if err = alterDatabaseCollation(x, r.ExpectedCollation); err != nil {
log.Error("Failed to change database collation to %q: %v", r.ExpectedCollation, err)
} else {
diff --git a/models/fixtures/action_run.yml b/models/fixtures/action_run.yml
index 2c2151f354..a42ab77ca5 100644
--- a/models/fixtures/action_run.yml
+++ b/models/fixtures/action_run.yml
@@ -17,3 +17,22 @@
updated: 1683636626
need_approval: 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
diff --git a/models/fixtures/action_run_job.yml b/models/fixtures/action_run_job.yml
index 071998b979..fd90f4fd5d 100644
--- a/models/fixtures/action_run_job.yml
+++ b/models/fixtures/action_run_job.yml
@@ -12,3 +12,17 @@
status: 1
started: 1683636528
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
diff --git a/models/fixtures/action_task.yml b/models/fixtures/action_task.yml
index c78fb3c5d6..443effe08c 100644
--- a/models/fixtures/action_task.yml
+++ b/models/fixtures/action_task.yml
@@ -18,3 +18,23 @@
log_length: 707
log_size: 90179
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
diff --git a/models/fixtures/hook_task.yml b/models/fixtures/hook_task.yml
index 6dbb10151a..d573406b36 100644
--- a/models/fixtures/hook_task.yml
+++ b/models/fixtures/hook_task.yml
@@ -3,3 +3,35 @@
hook_id: 1
uuid: uuid1
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"
+ }
+ }
diff --git a/models/git/branch.go b/models/git/branch.go
index 6baad65ab4..a5ee2bde66 100644
--- a/models/git/branch.go
+++ b/models/git/branch.go
@@ -162,6 +162,11 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
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 {
for _, branch := range branches {
if _, err := db.GetEngine(ctx).Insert(branch); err != nil {
diff --git a/models/issues/comment.go b/models/issues/comment.go
index 49c3159f6f..984fb9c9fc 100644
--- a/models/issues/comment.go
+++ b/models/issues/comment.go
@@ -8,6 +8,7 @@ package issues
import (
"context"
"fmt"
+ "html/template"
"strconv"
"unicode/utf8"
@@ -21,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
@@ -259,8 +261,8 @@ type Comment struct {
CommitID int64
Line int64 // - previous line / + proposed line
TreePath string
- Content string `xorm:"LONGTEXT"`
- RenderedContent string `xorm:"-"`
+ Content string `xorm:"LONGTEXT"`
+ RenderedContent template.HTML `xorm:"-"`
// Path represents the 4 lines of code cemented by this comment
Patch string `xorm:"-"`
@@ -1043,8 +1045,8 @@ type FindCommentsOptions struct {
TreePath string
Type CommentType
IssueIDs []int64
- Invalidated util.OptionalBool
- IsPull util.OptionalBool
+ Invalidated optional.Option[bool]
+ IsPull optional.Option[bool]
}
// ToConds implements FindOptions interface
@@ -1076,11 +1078,11 @@ func (opts FindCommentsOptions) ToConds() builder.Cond {
if len(opts.TreePath) > 0 {
cond = cond.And(builder.Eq{"comment.tree_path": opts.TreePath})
}
- if !opts.Invalidated.IsNone() {
- cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.IsTrue()})
+ if opts.Invalidated.Has() {
+ cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.Value()})
}
- if opts.IsPull != util.OptionalBoolNone {
- cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.IsTrue()})
+ if opts.IsPull.Has() {
+ cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.Value()})
}
return cond
}
@@ -1089,7 +1091,7 @@ func (opts FindCommentsOptions) ToConds() builder.Cond {
func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList, error) {
comments := make([]*Comment, 0, 10)
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")
}
diff --git a/models/issues/issue.go b/models/issues/issue.go
index b3b1744969..baa79b30d1 100644
--- a/models/issues/issue.go
+++ b/models/issues/issue.go
@@ -7,6 +7,7 @@ package issues
import (
"context"
"fmt"
+ "html/template"
"regexp"
"slices"
@@ -105,7 +106,7 @@ type Issue struct {
OriginalAuthorID int64 `xorm:"index"`
Title string `xorm:"name"`
Content string `xorm:"LONGTEXT"`
- RenderedContent string `xorm:"-"`
+ RenderedContent template.HTML `xorm:"-"`
Labels []*Label `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"`
diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go
index 7dc277327a..c5c9cecdb9 100644
--- a/models/issues/issue_search.go
+++ b/models/issues/issue_search.go
@@ -13,7 +13,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
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/xorm"
@@ -34,8 +34,8 @@ type IssuesOptions struct { //nolint
MilestoneIDs []int64
ProjectID int64
ProjectBoardID int64
- IsClosed util.OptionalBool
- IsPull util.OptionalBool
+ IsClosed optional.Option[bool]
+ IsPull optional.Option[bool]
LabelIDs []int64
IncludedLabelNames []string
ExcludedLabelNames []string
@@ -46,7 +46,7 @@ type IssuesOptions struct { //nolint
UpdatedBeforeUnix int64
// prioritize issues from this repo
PriorityRepoID int64
- IsArchived util.OptionalBool
+ IsArchived optional.Option[bool]
Org *organization.Organization // issues permission scope
Team *organization.Team // 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)
- if !opts.IsClosed.IsNone() {
- sess.And("issue.is_closed=?", opts.IsClosed.IsTrue())
+ if opts.IsClosed.Has() {
+ sess.And("issue.is_closed=?", opts.IsClosed.Value())
}
if opts.AssigneeID > 0 {
@@ -260,21 +260,18 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
applyProjectBoardCondition(sess, opts)
- switch opts.IsPull {
- case util.OptionalBoolTrue:
- sess.And("issue.is_pull=?", true)
- case util.OptionalBoolFalse:
- sess.And("issue.is_pull=?", false)
+ if opts.IsPull.Has() {
+ sess.And("issue.is_pull=?", opts.IsPull.Value())
}
- if opts.IsArchived != util.OptionalBoolNone {
- sess.And(builder.Eq{"repository.is_archived": opts.IsArchived.IsTrue()})
+ if opts.IsArchived.Has() {
+ sess.And(builder.Eq{"repository.is_archived": opts.IsArchived.Value()})
}
applyLabelsCondition(sess, opts)
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
diff --git a/models/issues/issue_stats.go b/models/issues/issue_stats.go
index 99ca19f804..32c5674fc9 100644
--- a/models/issues/issue_stats.go
+++ b/models/issues/issue_stats.go
@@ -8,7 +8,6 @@ import (
"fmt"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/util"
"xorm.io/builder"
"xorm.io/xorm"
@@ -170,11 +169,8 @@ func applyIssuesOptions(sess *xorm.Session, opts *IssuesOptions, issueIDs []int6
applyReviewedCondition(sess, opts.ReviewedID)
}
- switch opts.IsPull {
- case util.OptionalBoolTrue:
- sess.And("issue.is_pull=?", true)
- case util.OptionalBoolFalse:
- sess.And("issue.is_pull=?", false)
+ if opts.IsPull.Has() {
+ sess.And("issue.is_pull=?", opts.IsPull.Value())
}
return sess
diff --git a/models/issues/label.go b/models/issues/label.go
index 527d8d7853..f6ecc68cd1 100644
--- a/models/issues/label.go
+++ b/models/issues/label.go
@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/label"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -126,7 +127,7 @@ func (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{
RepoIDs: []int64{repoID},
LabelIDs: []int64{labelID},
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
})
for _, count := range counts {
diff --git a/models/issues/milestone.go b/models/issues/milestone.go
index 15987f2225..4b3cb0e858 100644
--- a/models/issues/milestone.go
+++ b/models/issues/milestone.go
@@ -6,10 +6,12 @@ package issues
import (
"context"
"fmt"
+ "html/template"
"strings"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -47,8 +49,8 @@ type Milestone struct {
RepoID int64 `xorm:"INDEX"`
Repo *repo_model.Repository `xorm:"-"`
Name string
- Content string `xorm:"TEXT"`
- RenderedContent string `xorm:"-"`
+ Content string `xorm:"TEXT"`
+ RenderedContent template.HTML `xorm:"-"`
IsClosed bool
NumIssues int
NumClosedIssues int
@@ -313,7 +315,7 @@ func DeleteMilestoneByRepoID(ctx context.Context, repoID, id int64) error {
}
numClosedMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{
RepoID: repo.ID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
})
if err != nil {
return err
diff --git a/models/issues/milestone_list.go b/models/issues/milestone_list.go
index a73bf73c17..d1b3f0301b 100644
--- a/models/issues/milestone_list.go
+++ b/models/issues/milestone_list.go
@@ -8,7 +8,7 @@ import (
"strings"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
"xorm.io/builder"
)
@@ -28,7 +28,7 @@ func (milestones MilestoneList) getMilestoneIDs() []int64 {
type FindMilestoneOptions struct {
db.ListOptions
RepoID int64
- IsClosed util.OptionalBool
+ IsClosed optional.Option[bool]
Name string
SortType string
RepoCond builder.Cond
@@ -40,8 +40,8 @@ func (opts FindMilestoneOptions) ToConds() builder.Cond {
if opts.RepoID != 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
- if opts.IsClosed != util.OptionalBoolNone {
- cond = cond.And(builder.Eq{"is_closed": opts.IsClosed.IsTrue()})
+ if opts.IsClosed.Has() {
+ cond = cond.And(builder.Eq{"is_closed": opts.IsClosed.Value()})
}
if opts.RepoCond != nil && opts.RepoCond.IsValid() {
cond = cond.And(builder.In("repo_id", builder.Select("id").From("repository").Where(opts.RepoCond)))
diff --git a/models/issues/milestone_test.go b/models/issues/milestone_test.go
index 7477af92c8..e5f6f15ca2 100644
--- a/models/issues/milestone_test.go
+++ b/models/issues/milestone_test.go
@@ -11,10 +11,10 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
)
@@ -39,10 +39,10 @@ func TestGetMilestoneByRepoID(t *testing.T) {
func TestGetMilestonesByRepoID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
test := func(repoID int64, state api.StateType) {
- var isClosed util.OptionalBool
+ var isClosed optional.Option[bool]
switch state {
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})
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{
RepoID: unittest.NonexistentID,
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
})
assert.NoError(t, err)
assert.Len(t, milestones, 0)
@@ -101,7 +101,7 @@ func TestGetMilestones(t *testing.T) {
PageSize: setting.UI.IssuePagingNum,
},
RepoID: repo.ID,
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
SortType: sortType,
})
assert.NoError(t, err)
@@ -118,7 +118,7 @@ func TestGetMilestones(t *testing.T) {
PageSize: setting.UI.IssuePagingNum,
},
RepoID: repo.ID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
Name: "",
SortType: sortType,
})
@@ -178,7 +178,7 @@ func TestCountRepoClosedMilestones(t *testing.T) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
count, err := db.Count[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
RepoID: repoID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
})
assert.NoError(t, err)
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{
RepoID: unittest.NonexistentID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
})
assert.NoError(t, err)
assert.EqualValues(t, 0, count)
@@ -206,7 +206,7 @@ func TestCountMilestonesByRepoIDs(t *testing.T) {
openCounts, err := issues_model.CountMilestonesMap(db.DefaultContext, issues_model.FindMilestoneOptions{
RepoIDs: []int64{1, 2},
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
})
assert.NoError(t, err)
assert.EqualValues(t, repo1OpenCount, openCounts[1])
@@ -215,7 +215,7 @@ func TestCountMilestonesByRepoIDs(t *testing.T) {
closedCounts, err := issues_model.CountMilestonesMap(db.DefaultContext,
issues_model.FindMilestoneOptions{
RepoIDs: []int64{1, 2},
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
})
assert.NoError(t, err)
assert.EqualValues(t, repo1ClosedCount, closedCounts[1])
@@ -234,7 +234,7 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
PageSize: setting.UI.IssuePagingNum,
},
RepoIDs: []int64{repo1.ID, repo2.ID},
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
SortType: sortType,
})
assert.NoError(t, err)
@@ -252,7 +252,7 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
PageSize: setting.UI.IssuePagingNum,
},
RepoIDs: []int64{repo1.ID, repo2.ID},
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
SortType: sortType,
})
assert.NoError(t, err)
diff --git a/models/issues/pull.go b/models/issues/pull.go
index 98d1617380..f1baa9b5e5 100644
--- a/models/issues/pull.go
+++ b/models/issues/pull.go
@@ -891,6 +891,14 @@ func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullReque
return nil
}
+ if err := pull.LoadRepo(ctx); err != nil {
+ return err
+ }
+
+ if pull.Repo.IsFork {
+ return nil
+ }
+
if err := pr.LoadBaseRepo(ctx); err != nil {
return err
}
@@ -901,12 +909,7 @@ func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullReque
}
defer repo.Close()
- branch, err := repo.GetDefaultBranch()
- if err != nil {
- return err
- }
-
- commit, err := repo.GetBranchCommit(branch)
+ commit, err := repo.GetBranchCommit(pr.BaseRepo.DefaultBranch)
if err != nil {
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
// 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 {
return err
}
diff --git a/models/issues/review_list.go b/models/issues/review_list.go
index 282f18b4f7..ec6cb07988 100644
--- a/models/issues/review_list.go
+++ b/models/issues/review_list.go
@@ -9,7 +9,7 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
"xorm.io/builder"
)
@@ -68,7 +68,7 @@ type FindReviewOptions struct {
IssueID int64
ReviewerID int64
OfficialOnly bool
- Dismissed util.OptionalBool
+ Dismissed optional.Option[bool]
}
func (opts *FindReviewOptions) toCond() builder.Cond {
@@ -85,8 +85,8 @@ func (opts *FindReviewOptions) toCond() builder.Cond {
if opts.OfficialOnly {
cond = cond.And(builder.Eq{"official": true})
}
- if !opts.Dismissed.IsNone() {
- cond = cond.And(builder.Eq{"dismissed": opts.Dismissed.IsTrue()})
+ if opts.Dismissed.Has() {
+ cond = cond.And(builder.Eq{"dismissed": opts.Dismissed.Value()})
}
return cond
}
diff --git a/models/issues/tracked_time.go b/models/issues/tracked_time.go
index 91c4832e49..4063ca043b 100644
--- a/models/issues/tracked_time.go
+++ b/models/issues/tracked_time.go
@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db"
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/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.
-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 {
return getIssueTotalTrackedTimeChunk(ctx, opts, isClosed, opts.IssueIDs)
}
@@ -363,7 +364,7 @@ func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed
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 {
sess := db.GetEngine(ctx).
Table("tracked_time").
@@ -378,8 +379,8 @@ func getIssueTotalTrackedTimeChunk(ctx context.Context, opts *IssuesOptions, isC
}
session := sumSession(opts, issueIDs)
- if !isClosed.IsNone() {
- session = session.And("issue.is_closed = ?", isClosed.IsTrue())
+ if isClosed.Has() {
+ session = session.And("issue.is_closed = ?", isClosed.Value())
}
return session.SumInt(new(trackedTime), "tracked_time.time")
}
diff --git a/models/issues/tracked_time_test.go b/models/issues/tracked_time_test.go
index 9beb862ffb..d82bff967a 100644
--- a/models/issues/tracked_time_test.go
+++ b/models/issues/tracked_time_test.go
@@ -11,7 +11,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
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"
)
@@ -120,15 +120,15 @@ func TestTotalTimesForEachUser(t *testing.T) {
func TestGetIssueTotalTrackedTime(t *testing.T) {
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.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.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.EqualValues(t, 3682, ttt)
}
diff --git a/models/migrations/base/db_test.go b/models/migrations/base/db_test.go
index 4d61b758cf..80bf00b22a 100644
--- a/models/migrations/base/db_test.go
+++ b/models/migrations/base/db_test.go
@@ -36,12 +36,14 @@ func Test_DropTableColumns(t *testing.T) {
"updated_unix",
}
+ x.SetMapper(names.GonicMapper{})
+
for i := range columns {
- x.SetMapper(names.GonicMapper{})
if err := x.Sync(new(DropTest)); err != nil {
t.Errorf("unable to create DropTest table: %v", err)
return
}
+
sess := x.NewSession()
if err := sess.Begin(); err != nil {
sess.Close()
@@ -64,7 +66,6 @@ func Test_DropTableColumns(t *testing.T) {
return
}
for j := range columns[i+1:] {
- x.SetMapper(names.GonicMapper{})
if err := x.Sync(new(DropTest)); err != nil {
t.Errorf("unable to create DropTest table: %v", err)
return
diff --git a/models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml b/models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml
new file mode 100644
index 0000000000..f95d47916b
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml
@@ -0,0 +1,4 @@
+-
+ id: 1
+ repo_id: 1
+ index: 1
diff --git a/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml b/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml
new file mode 100644
index 0000000000..716a2a017d
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml
@@ -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
diff --git a/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml b/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml
new file mode 100644
index 0000000000..913d927d91
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml
@@ -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
diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml b/models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml
new file mode 100644
index 0000000000..056236ba9e
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml
@@ -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
diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml b/models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml
new file mode 100644
index 0000000000..7f3255096d
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ repo_id: 1
diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml b/models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml
new file mode 100644
index 0000000000..7f3255096d
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ repo_id: 1
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/comment.yml b/models/migrations/fixtures/Test_RepositoryFormat/comment.yml
new file mode 100644
index 0000000000..1197b086e3
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/comment.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/commit_status.yml b/models/migrations/fixtures/Test_RepositoryFormat/commit_status.yml
new file mode 100644
index 0000000000..ca0aaec4cc
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/commit_status.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ context_hash: 19fe5caf872476db265596eaac1dc35ad1c6422d
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/pull_request.yml b/models/migrations/fixtures/Test_RepositoryFormat/pull_request.yml
new file mode 100644
index 0000000000..380cc079ee
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/pull_request.yml
@@ -0,0 +1,5 @@
+-
+ id: 1
+ commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
+ merge_base: 19fe5caf872476db265596eaac1dc35ad1c6422d
+ merged_commit_id: 19fe5caf872476db265596eaac1dc35ad1c6422d
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/release.yml b/models/migrations/fixtures/Test_RepositoryFormat/release.yml
new file mode 100644
index 0000000000..ffabe4ab9e
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/release.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ sha1: 19fe5caf872476db265596eaac1dc35ad1c6422d
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml b/models/migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml
new file mode 100644
index 0000000000..f04cb3b340
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ commit_id: 19fe5caf872476db265596eaac1dc35ad1c6422d
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml b/models/migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml
new file mode 100644
index 0000000000..1197b086e3
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/review_state.yml b/models/migrations/fixtures/Test_RepositoryFormat/review_state.yml
new file mode 100644
index 0000000000..1197b086e3
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/review_state.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
diff --git a/models/migrations/fixtures/Test_UpdateBadgeColName/badge.yml b/models/migrations/fixtures/Test_UpdateBadgeColName/badge.yml
new file mode 100644
index 0000000000..7025144106
--- /dev/null
+++ b/models/migrations/fixtures/Test_UpdateBadgeColName/badge.yml
@@ -0,0 +1,4 @@
+-
+ id: 1
+ description: the badge
+ image_url: https://gitea.com/myimage.png
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 1c8563cebe..173d37234a 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -560,6 +560,14 @@ var migrations = []Migration{
NewMigration("Add PreviousDuration to ActionRun", v1_22.AddPreviousDurationToActionRun),
// v286 -> v287
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
diff --git a/models/migrations/v1_16/v193_test.go b/models/migrations/v1_16/v193_test.go
index 17669a012e..d99bbc2962 100644
--- a/models/migrations/v1_16/v193_test.go
+++ b/models/migrations/v1_16/v193_test.go
@@ -15,7 +15,6 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
type Attachment struct {
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"`
@@ -44,12 +43,21 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
return
}
- var issueAttachments []*Attachment
- err := x.Where("issue_id > 0").Find(&issueAttachments)
+ type NewAttachment struct {
+ 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)
for _, attach := range issueAttachments {
- assert.Greater(t, attach.RepoID, 0)
- assert.Greater(t, attach.IssueID, 0)
+ assert.Greater(t, attach.RepoID, int64(0))
+ assert.Greater(t, attach.IssueID, int64(0))
var issue Issue
has, err := x.ID(attach.IssueID).Get(&issue)
assert.NoError(t, err)
@@ -57,12 +65,12 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
assert.EqualValues(t, attach.RepoID, issue.RepoID)
}
- var releaseAttachments []*Attachment
- err = x.Where("release_id > 0").Find(&releaseAttachments)
+ var releaseAttachments []*NewAttachment
+ err = x.Table("attachment").Where("release_id > 0").Find(&releaseAttachments)
assert.NoError(t, err)
for _, attach := range releaseAttachments {
- assert.Greater(t, attach.RepoID, 0)
- assert.Greater(t, attach.IssueID, 0)
+ assert.Greater(t, attach.RepoID, int64(0))
+ assert.Greater(t, attach.ReleaseID, int64(0))
var release Release
has, err := x.ID(attach.ReleaseID).Get(&release)
assert.NoError(t, err)
diff --git a/models/migrations/v1_22/v283.go b/models/migrations/v1_22/v283.go
index 97b22f72a0..0a45c51245 100644
--- a/models/migrations/v1_22/v283.go
+++ b/models/migrations/v1_22/v283.go
@@ -4,10 +4,40 @@
package v1_22 //nolint
import (
+ "fmt"
+
"xorm.io/xorm"
+ "xorm.io/xorm/schemas"
)
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 {
UID int64 `xorm:"INDEX unique(uid_to_issue)"` // User ID.
IssueID int64 `xorm:"INDEX unique(uid_to_issue)"`
diff --git a/models/migrations/v1_22/v286.go b/models/migrations/v1_22/v286.go
index ef19f64221..fbbd87344f 100644
--- a/models/migrations/v1_22/v286.go
+++ b/models/migrations/v1_22/v286.go
@@ -36,9 +36,9 @@ func expandHashReferencesToSha256(x *xorm.Engine) error {
if setting.Database.Type.IsMSSQL() {
// drop indexes that need to be re-created afterwards
droppedIndexes := []string{
- "DROP INDEX commit_status.IDX_commit_status_context_hash",
- "DROP INDEX review_state.UQE_review_state_pull_commit_user",
- "DROP INDEX repo_archiver.UQE_repo_archiver_s",
+ "DROP INDEX IF EXISTS [IDX_commit_status_context_hash] ON [commit_status]",
+ "DROP INDEX IF EXISTS [UQE_review_state_pull_commit_user] ON [review_state]",
+ "DROP INDEX IF EXISTS [UQE_repo_archiver_s] ON [repo_archiver]",
}
for _, s := range droppedIndexes {
_, err := db.Exec(s)
@@ -53,7 +53,7 @@ func expandHashReferencesToSha256(x *xorm.Engine) error {
if setting.Database.Type.IsMySQL() {
_, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(64)", alts[0], alts[1]))
} 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 {
_, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` TYPE VARCHAR(64)", alts[0], alts[1]))
}
diff --git a/models/migrations/v1_22/v286_test.go b/models/migrations/v1_22/v286_test.go
index 6493bfba2f..7c353747e3 100644
--- a/models/migrations/v1_22/v286_test.go
+++ b/models/migrations/v1_22/v286_test.go
@@ -14,59 +14,75 @@ import (
func PrepareOldRepository(t *testing.T) (*xorm.Engine, func()) {
type Repository struct { // old struct
- ID int64 `xorm:"pk autoincr"`
- ObjectFormatName string `xorm:"VARCHAR(6) NOT NULL DEFAULT 'sha1'"`
+ ID int64 `xorm:"pk autoincr"`
}
- type CommitStatus struct { // old struct
- ID int64 `xorm:"pk autoincr"`
- ContextHash string `xorm:"char(40)"`
+ type CommitStatus struct {
+ ID int64
+ ContextHash string
}
- type Comment struct { // old struct
- ID int64 `xorm:"pk autoincr"`
- CommitSHA string `xorm:"VARCHAR(40)"`
+ type RepoArchiver struct {
+ ID int64
+ RepoID int64
+ Type int
+ CommitID string
}
- type PullRequest struct { // old struct
- ID int64 `xorm:"pk autoincr"`
- MergeBase string `xorm:"VARCHAR(40)"`
- MergedCommitID string `xorm:"VARCHAR(40)"`
+ type ReviewState struct {
+ ID int64
+ CommitSHA string
+ UserID int64
+ PullID int64
}
- type Review struct { // old struct
- ID int64 `xorm:"pk autoincr"`
- CommitID string `xorm:"VARCHAR(40)"`
+ type Comment struct {
+ ID int64
+ CommitSHA string
}
- type ReviewState struct { // old struct
- ID int64 `xorm:"pk autoincr"`
- CommitSHA string `xorm:"VARCHAR(40)"`
+ type PullRequest struct {
+ ID int64
+ CommitSHA string
+ MergeBase string
+ MergedCommitID string
}
- type RepoArchiver struct { // old struct
- ID int64 `xorm:"pk autoincr"`
- CommitID string `xorm:"VARCHAR(40)"`
+ type Release struct {
+ ID int64
+ Sha1 string
}
- type Release struct { // old struct
- ID int64 `xorm:"pk autoincr"`
- Sha1 string `xorm:"VARCHAR(40)"`
+ type RepoIndexerStatus struct {
+ ID int64
+ CommitSHA string
}
- type RepoIndexerStatus struct { // old struct
- ID int64 `xorm:"pk autoincr"`
- CommitSha string `xorm:"VARCHAR(40)"`
+ type Review struct {
+ ID int64
+ CommitID string
}
// 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) {
x, deferable := PrepareOldRepository(t)
defer deferable()
+ assert.NoError(t, AdjustDBForSha256(x))
+
type Repository struct {
ID int64 `xorm:"pk autoincr"`
ObjectFormatName string `xorg:"not null default('sha1')"`
@@ -79,12 +95,10 @@ func Test_RepositoryFormat(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, 4, count)
- assert.NoError(t, AdjustDBForSha256(x))
-
- repo.ID = 20
repo.ObjectFormatName = "sha256"
_, err = x.Insert(repo)
assert.NoError(t, err)
+ id := repo.ID
count, err = x.Count(new(Repository))
assert.NoError(t, err)
@@ -97,7 +111,7 @@ func Test_RepositoryFormat(t *testing.T) {
assert.EqualValues(t, "sha1", repo.ObjectFormatName)
repo = new(Repository)
- ok, err = x.ID(20).Get(repo)
+ ok, err = x.ID(id).Get(repo)
assert.NoError(t, err)
assert.EqualValues(t, true, ok)
assert.EqualValues(t, "sha256", repo.ObjectFormatName)
diff --git a/models/migrations/v1_22/v287.go b/models/migrations/v1_22/v287.go
new file mode 100644
index 0000000000..c8b1593286
--- /dev/null
+++ b/models/migrations/v1_22/v287.go
@@ -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()
+}
diff --git a/models/migrations/v1_22/v288.go b/models/migrations/v1_22/v288.go
new file mode 100644
index 0000000000..7c93bfcc66
--- /dev/null
+++ b/models/migrations/v1_22/v288.go
@@ -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{})
+}
diff --git a/models/migrations/v1_22/v289.go b/models/migrations/v1_22/v289.go
new file mode 100644
index 0000000000..e2dfc48715
--- /dev/null
+++ b/models/migrations/v1_22/v289.go
@@ -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
+}
diff --git a/models/migrations/v1_22/v290.go b/models/migrations/v1_22/v290.go
new file mode 100644
index 0000000000..e9c471b3dd
--- /dev/null
+++ b/models/migrations/v1_22/v290.go
@@ -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))
+}
diff --git a/models/migrations/v1_22/v290_test.go b/models/migrations/v1_22/v290_test.go
new file mode 100644
index 0000000000..24a1c0b0a5
--- /dev/null
+++ b/models/migrations/v1_22/v290_test.go
@@ -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)
+ })
+ }
+}
diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go
index f849ab5c04..b8ef698d38 100644
--- a/models/packages/descriptor.go
+++ b/models/packages/descriptor.go
@@ -70,16 +70,26 @@ type PackageFileDescriptor struct {
Properties PackagePropertyList
}
-// PackageWebLink returns the package web link
+// PackageWebLink returns the relative package web link
func (pd *PackageDescriptor) PackageWebLink() string {
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
-func (pd *PackageDescriptor) FullWebLink() string {
+// VersionWebLink returns the relative package version web link
+func (pd *PackageDescriptor) VersionWebLink() string {
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
func (pd *PackageDescriptor) CalculateBlobSize() int64 {
size := int64(0)
diff --git a/models/packages/nuget/search.go b/models/packages/nuget/search.go
index 53cdf2d4ad..7a505ff08f 100644
--- a/models/packages/nuget/search.go
+++ b/models/packages/nuget/search.go
@@ -55,7 +55,7 @@ func CountPackages(ctx context.Context, opts *packages_model.PackageSearchOption
func toConds(opts *packages_model.PackageSearchOptions) builder.Cond {
var cond builder.Cond = builder.Eq{
- "package.is_internal": opts.IsInternal.IsTrue(),
+ "package.is_internal": opts.IsInternal.Value(),
"package.owner_id": opts.OwnerID,
"package.type": packages_model.TypeNuGet,
}
diff --git a/models/packages/package_version.go b/models/packages/package_version.go
index 8fc475691b..505dbaa0a5 100644
--- a/models/packages/package_version.go
+++ b/models/packages/package_version.go
@@ -9,6 +9,7 @@ import (
"strings"
"code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -105,7 +106,7 @@ func getVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType
ExactMatch: true,
Value: version,
},
- IsInternal: util.OptionalBoolOf(isInternal),
+ IsInternal: optional.Some(isInternal),
Paginator: db.NewAbsoluteListOptions(0, 1),
})
if err != nil {
@@ -122,7 +123,7 @@ func GetVersionsByPackageType(ctx context.Context, ownerID int64, packageType Ty
pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{
OwnerID: ownerID,
Type: packageType,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
return pvs, err
}
@@ -136,7 +137,7 @@ func GetVersionsByPackageName(ctx context.Context, ownerID int64, packageType Ty
ExactMatch: true,
Value: name,
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
return pvs, err
}
@@ -182,18 +183,18 @@ type PackageSearchOptions struct {
Name SearchValue // only results with the specific name 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
- IsInternal util.OptionalBool
- 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
+ IsInternal optional.Option[bool]
+ HasFileWithName string // only results are found which are associated with a file with the specific name
+ HasFiles optional.Option[bool] // only results are found which have associated files
Sort VersionSort
db.Paginator
}
func (opts *PackageSearchOptions) ToConds() builder.Cond {
cond := builder.NewCond()
- if !opts.IsInternal.IsNone() {
+ if opts.IsInternal.Has() {
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)))
}
- 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")))
- if opts.HasFiles.IsFalse() {
+ if !opts.HasFiles.Value() {
filesCond = builder.Not{filesCond}
}
@@ -307,8 +308,8 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
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))")
- if !opts.IsInternal.IsNone() {
- joinCond = joinCond.And(builder.Eq{"pv2.is_internal": opts.IsInternal.IsTrue()})
+ if opts.IsInternal.Has() {
+ joinCond = joinCond.And(builder.Eq{"pv2.is_internal": opts.IsInternal.Value()})
}
sess := db.GetEngine(ctx).
diff --git a/models/project/board.go b/models/project/board.go
index 3e2d8e0472..c0e6529880 100644
--- a/models/project/board.go
+++ b/models/project/board.go
@@ -232,7 +232,7 @@ func UpdateBoard(ctx context.Context, board *Board) error {
func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
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
}
diff --git a/models/project/project.go b/models/project/project.go
index d2fca6cdc8..8f9ee2a99e 100644
--- a/models/project/project.go
+++ b/models/project/project.go
@@ -6,11 +6,13 @@ package project
import (
"context"
"fmt"
+ "html/template"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -100,7 +102,7 @@ type Project struct {
CardType CardType
Type Type
- RenderedContent string `xorm:"-"`
+ RenderedContent template.HTML `xorm:"-"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
@@ -195,7 +197,7 @@ type SearchOptions struct {
db.ListOptions
OwnerID int64
RepoID int64
- IsClosed util.OptionalBool
+ IsClosed optional.Option[bool]
OrderBy db.SearchOrderBy
Type Type
Title string
@@ -206,11 +208,8 @@ func (opts SearchOptions) ToConds() builder.Cond {
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
- switch opts.IsClosed {
- case util.OptionalBoolTrue:
- cond = cond.And(builder.Eq{"is_closed": true})
- case util.OptionalBoolFalse:
- cond = cond.And(builder.Eq{"is_closed": false})
+ if opts.IsClosed.Has() {
+ cond = cond.And(builder.Eq{"is_closed": opts.IsClosed.Value()})
}
if opts.Type > 0 {
diff --git a/models/repo/release.go b/models/repo/release.go
index 1f37f11b2e..a9f65f6c3e 100644
--- a/models/repo/release.go
+++ b/models/repo/release.go
@@ -7,6 +7,7 @@ package repo
import (
"context"
"fmt"
+ "html/template"
"net/url"
"sort"
"strconv"
@@ -15,6 +16,7 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -79,7 +81,7 @@ type Release struct {
NumCommits int64
NumCommitsBehind int64 `xorm:"-"`
Note string `xorm:"TEXT"`
- RenderedNote string `xorm:"-"`
+ RenderedNote template.HTML `xorm:"-"`
IsDraft 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
@@ -228,10 +230,10 @@ type FindReleasesOptions struct {
RepoID int64
IncludeDrafts bool
IncludeTags bool
- IsPreRelease util.OptionalBool
- IsDraft util.OptionalBool
+ IsPreRelease optional.Option[bool]
+ IsDraft optional.Option[bool]
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 {
@@ -246,14 +248,14 @@ func (opts FindReleasesOptions) ToConds() builder.Cond {
if len(opts.TagNames) > 0 {
cond = cond.And(builder.In("tag_name", opts.TagNames))
}
- if !opts.IsPreRelease.IsNone() {
- cond = cond.And(builder.Eq{"is_prerelease": opts.IsPreRelease.IsTrue()})
+ if opts.IsPreRelease.Has() {
+ cond = cond.And(builder.Eq{"is_prerelease": opts.IsPreRelease.Value()})
}
- if !opts.IsDraft.IsNone() {
- cond = cond.And(builder.Eq{"is_draft": opts.IsDraft.IsTrue()})
+ if opts.IsDraft.Has() {
+ cond = cond.And(builder.Eq{"is_draft": opts.IsDraft.Value()})
}
- if !opts.HasSha1.IsNone() {
- if opts.HasSha1.IsTrue() {
+ if opts.HasSha1.Has() {
+ if opts.HasSha1.Value() {
cond = cond.And(builder.Neq{"sha1": ""})
} else {
cond = cond.And(builder.Eq{"sha1": ""})
@@ -275,7 +277,7 @@ func GetTagNamesByRepoID(ctx context.Context, repoID int64) ([]string, error) {
ListOptions: listOptions,
IncludeDrafts: true,
IncludeTags: true,
- HasSha1: util.OptionalBoolTrue,
+ HasSha1: optional.Some(true),
RepoID: repoID,
}
diff --git a/models/repo/repo.go b/models/repo/repo.go
index b24e5c1dbf..81ed6efa24 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
@@ -873,7 +874,7 @@ func (repo *Repository) TemplateRepo(ctx context.Context) *Repository {
type CountRepositoryOptions struct {
OwnerID int64
- Private util.OptionalBool
+ Private optional.Option[bool]
}
// CountRepositories returns number of repositories.
@@ -885,8 +886,8 @@ func CountRepositories(ctx context.Context, opts CountRepositoryOptions) (int64,
if opts.OwnerID > 0 {
sess.And("owner_id = ?", opts.OwnerID)
}
- if !opts.Private.IsNone() {
- sess.And("is_private=?", opts.Private.IsTrue())
+ if opts.Private.Has() {
+ sess.And("is_private=?", opts.Private.Value())
}
count, err := sess.Count(new(Repository))
diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go
index 533ca5251f..6b452291ea 100644
--- a/models/repo/repo_list.go
+++ b/models/repo/repo_list.go
@@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
@@ -125,11 +126,11 @@ type SearchRepoOptions struct {
// None -> include public and private
// True -> include just private
// False -> include just public
- IsPrivate util.OptionalBool
+ IsPrivate optional.Option[bool]
// None -> include collaborative AND non-collaborative
// True -> include just collaborative
// False -> include just non-collaborative
- Collaborate util.OptionalBool
+ Collaborate optional.Option[bool]
// What type of unit the user can be collaborative in,
// it is ignored if Collaborate is False.
// TypeInvalid means any unit type.
@@ -137,19 +138,19 @@ type SearchRepoOptions struct {
// None -> include forks AND non-forks
// True -> include just forks
// False -> include just non-forks
- Fork util.OptionalBool
+ Fork optional.Option[bool]
// None -> include templates AND non-templates
// True -> include just templates
// False -> include just non-templates
- Template util.OptionalBool
+ Template optional.Option[bool]
// None -> include mirrors AND non-mirrors
// True -> include just mirrors
// False -> include just non-mirrors
- Mirror util.OptionalBool
+ Mirror optional.Option[bool]
// None -> include archived AND non-archived
// True -> include just archived
// False -> include just non-archived
- Archived util.OptionalBool
+ Archived optional.Option[bool]
// only search topic name
TopicOnly bool
// only search repositories with specified primary language
@@ -159,7 +160,7 @@ type SearchRepoOptions struct {
// None -> include has milestones AND has no milestone
// True -> include just has milestones
// False -> include just has no milestone
- HasMilestones util.OptionalBool
+ HasMilestones optional.Option[bool]
// LowerNames represents valid lower names to restrict to
LowerNames []string
// When specified true, apply some filters over the conditions:
@@ -359,12 +360,12 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
)))
}
- if opts.IsPrivate != util.OptionalBoolNone {
- cond = cond.And(builder.Eq{"is_private": opts.IsPrivate.IsTrue()})
+ if opts.IsPrivate.Has() {
+ cond = cond.And(builder.Eq{"is_private": opts.IsPrivate.Value()})
}
- if opts.Template != util.OptionalBoolNone {
- cond = cond.And(builder.Eq{"is_template": opts.Template == util.OptionalBoolTrue})
+ if opts.Template.Has() {
+ cond = cond.And(builder.Eq{"is_template": opts.Template.Value()})
}
// 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
if opts.OwnerID > 0 {
accessCond := builder.NewCond()
- if opts.Collaborate != util.OptionalBoolTrue {
+ if !opts.Collaborate.Value() {
accessCond = builder.Eq{"owner_id": opts.OwnerID}
}
- if opts.Collaborate != util.OptionalBoolFalse {
+ if opts.Collaborate.ValueOrDefault(true) {
// A Collaboration is:
collaborateCond := builder.NewCond()
@@ -472,31 +473,32 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
Where(builder.Eq{"language": opts.Language}).And(builder.Eq{"is_primary": true})))
}
- if opts.Fork != util.OptionalBoolNone || opts.OnlyShowRelevant {
- if opts.OnlyShowRelevant && opts.Fork == util.OptionalBoolNone {
+ if opts.Fork.Has() || opts.OnlyShowRelevant {
+ if opts.OnlyShowRelevant && !opts.Fork.Has() {
cond = cond.And(builder.Eq{"is_fork": false})
} 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 {
- cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue})
+ if opts.Mirror.Has() {
+ cond = cond.And(builder.Eq{"is_mirror": opts.Mirror.Value()})
}
if opts.Actor != nil && opts.Actor.IsRestricted {
cond = cond.And(AccessibleRepositoryCondition(opts.Actor, unit.TypeInvalid))
}
- if opts.Archived != util.OptionalBoolNone {
- cond = cond.And(builder.Eq{"is_archived": opts.Archived == util.OptionalBoolTrue})
+ if opts.Archived.Has() {
+ cond = cond.And(builder.Eq{"is_archived": opts.Archived.Value()})
}
- switch opts.HasMilestones {
- case util.OptionalBoolTrue:
- cond = cond.And(builder.Gt{"num_milestones": 0})
- case util.OptionalBoolFalse:
- cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"}))
+ if opts.HasMilestones.Has() {
+ if opts.HasMilestones.Value() {
+ cond = cond.And(builder.Gt{"num_milestones": 0})
+ } else {
+ cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"}))
+ }
}
if opts.OnlyShowRelevant {
diff --git a/models/repo/repo_list_test.go b/models/repo/repo_list_test.go
index 8006289460..ca6007f6c7 100644
--- a/models/repo/repo_list_test.go
+++ b/models/repo/repo_list_test.go
@@ -10,7 +10,7 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert"
)
@@ -27,62 +27,62 @@ func getTestCases() []struct {
}{
{
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,
},
{
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,
},
{
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,
},
{
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,
},
{
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,
},
{
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,
},
{
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,
},
{
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,
},
{
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,
},
{
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,
},
{
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,
},
{
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,
},
{
@@ -117,32 +117,32 @@ func getTestCases() []struct {
},
{
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,
},
{
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,
},
{
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,
},
{
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,
},
{
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,
},
{
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,
},
{
@@ -157,12 +157,12 @@ func getTestCases() []struct {
},
{
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,
},
{
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,
},
{
@@ -190,7 +190,7 @@ func TestSearchRepository(t *testing.T) {
PageSize: 10,
},
Keyword: "repo_12",
- Collaborate: util.OptionalBoolFalse,
+ Collaborate: optional.Some(false),
})
assert.NoError(t, err)
@@ -205,7 +205,7 @@ func TestSearchRepository(t *testing.T) {
PageSize: 10,
},
Keyword: "test_repo",
- Collaborate: util.OptionalBoolFalse,
+ Collaborate: optional.Some(false),
})
assert.NoError(t, err)
@@ -220,7 +220,7 @@ func TestSearchRepository(t *testing.T) {
},
Keyword: "repo_13",
Private: true,
- Collaborate: util.OptionalBoolFalse,
+ Collaborate: optional.Some(false),
})
assert.NoError(t, err)
@@ -236,7 +236,7 @@ func TestSearchRepository(t *testing.T) {
},
Keyword: "test_repo",
Private: true,
- Collaborate: util.OptionalBoolFalse,
+ Collaborate: optional.Some(false),
})
assert.NoError(t, err)
@@ -257,7 +257,7 @@ func TestSearchRepository(t *testing.T) {
PageSize: 10,
},
Keyword: "description_14",
- Collaborate: util.OptionalBoolFalse,
+ Collaborate: optional.Some(false),
IncludeDescription: true,
})
@@ -274,7 +274,7 @@ func TestSearchRepository(t *testing.T) {
PageSize: 10,
},
Keyword: "description_14",
- Collaborate: util.OptionalBoolFalse,
+ Collaborate: optional.Some(false),
IncludeDescription: false,
})
@@ -327,30 +327,25 @@ func TestSearchRepository(t *testing.T) {
assert.False(t, repo.IsPrivate)
}
- if testCase.opts.Fork == util.OptionalBoolTrue && testCase.opts.Mirror == util.OptionalBoolTrue {
- assert.True(t, repo.IsFork || repo.IsMirror)
+ if testCase.opts.Fork.Value() && testCase.opts.Mirror.Value() {
+ assert.True(t, repo.IsFork && repo.IsMirror)
} else {
- switch testCase.opts.Fork {
- case util.OptionalBoolFalse:
- assert.False(t, repo.IsFork)
- case util.OptionalBoolTrue:
- assert.True(t, repo.IsFork)
+ if testCase.opts.Fork.Has() {
+ assert.Equal(t, testCase.opts.Fork.Value(), repo.IsFork)
}
- switch testCase.opts.Mirror {
- case util.OptionalBoolFalse:
- assert.False(t, repo.IsMirror)
- case util.OptionalBoolTrue:
- assert.True(t, repo.IsMirror)
+ if testCase.opts.Mirror.Has() {
+ assert.Equal(t, testCase.opts.Mirror.Value(), repo.IsMirror)
}
}
if testCase.opts.OwnerID > 0 && !testCase.opts.AllPublic {
- switch testCase.opts.Collaborate {
- case util.OptionalBoolFalse:
- assert.Equal(t, testCase.opts.OwnerID, repo.Owner.ID)
- case util.OptionalBoolTrue:
- assert.NotEqual(t, testCase.opts.OwnerID, repo.Owner.ID)
+ if testCase.opts.Collaborate.Has() {
+ if testCase.opts.Collaborate.Value() {
+ assert.NotEqual(t, testCase.opts.OwnerID, repo.Owner.ID)
+ } else {
+ assert.Equal(t, testCase.opts.OwnerID, repo.Owner.ID)
+ }
}
}
}
diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go
index ca9209d751..1a870224bf 100644
--- a/models/repo/repo_test.go
+++ b/models/repo/repo_test.go
@@ -12,17 +12,17 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
- "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
)
var (
countRepospts = repo_model.CountRepositoryOptions{OwnerID: 10}
- countReposptsPublic = repo_model.CountRepositoryOptions{OwnerID: 10, Private: util.OptionalBoolFalse}
- countReposptsPrivate = repo_model.CountRepositoryOptions{OwnerID: 10, Private: util.OptionalBoolTrue}
+ countReposptsPublic = repo_model.CountRepositoryOptions{OwnerID: 10, Private: optional.Some(false)}
+ countReposptsPrivate = repo_model.CountRepositoryOptions{OwnerID: 10, Private: optional.Some(true)}
)
func TestGetRepositoryCount(t *testing.T) {
diff --git a/models/secret/secret.go b/models/secret/secret.go
index 41e860d7f6..35bed500b9 100644
--- a/models/secret/secret.go
+++ b/models/secret/secret.go
@@ -9,7 +9,10 @@ import (
"fmt"
"strings"
+ actions_model "code.gitea.io/gitea/models/actions"
"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"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
@@ -112,3 +115,39 @@ func UpdateSecret(ctx context.Context, secretID int64, data string) error {
}
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
+}
diff --git a/models/unittest/mock_http.go b/models/unittest/mock_http.go
index afdc5bed21..e2c181408b 100644
--- a/models/unittest/mock_http.go
+++ b/models/unittest/mock_http.go
@@ -34,7 +34,7 @@ func NewMockWebServer(t *testing.T, liveServerBaseURL, testDataDir string, liveM
path := NormalizedFullPath(r.URL)
log.Info("Mock HTTP Server: got request for path %s", r.URL.Path)
// 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 {
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)
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)
assert.NoError(t, err, "failed to open the fixture file %s for writing", fixturePath)
diff --git a/models/user/email_address.go b/models/user/email_address.go
index cc62620e0f..1d90b127bf 100644
--- a/models/user/email_address.go
+++ b/models/user/email_address.go
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"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])?)*$")
-// ValidateEmail check if email is a allowed address
+// ValidateEmail check if email is a valid & allowed address
func ValidateEmail(email string) error {
- if len(email) == 0 {
- return ErrEmailInvalid{email}
+ if err := validateEmailBasic(email); err != nil {
+ return err
}
+ return validateEmailDomain(email)
+}
- if !emailRegexp.MatchString(email) {
- return ErrEmailCharIsNotSupported{email}
- }
-
- 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
+// ValidateEmailForAdmin check if email is a valid address when admins manually add or edit users
+func ValidateEmailForAdmin(email string) error {
+ return validateEmailBasic(email)
+ // In this case we do not need to check the email domain
}
func GetEmailAddressByEmail(ctx context.Context, email string) (*EmailAddress, error) {
@@ -425,8 +407,8 @@ type SearchEmailOptions struct {
db.ListOptions
Keyword string
SortType SearchEmailOrderBy
- IsPrimary util.OptionalBool
- IsActivated util.OptionalBool
+ IsPrimary optional.Option[bool]
+ IsActivated optional.Option[bool]
}
// 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 {
- case opts.IsPrimary.IsTrue():
- cond = cond.And(builder.Eq{"email_address.is_primary": true})
- case opts.IsPrimary.IsFalse():
- cond = cond.And(builder.Eq{"email_address.is_primary": false})
+ if opts.IsPrimary.Has() {
+ cond = cond.And(builder.Eq{"email_address.is_primary": opts.IsPrimary.Value()})
}
- switch {
- case opts.IsActivated.IsTrue():
- cond = cond.And(builder.Eq{"email_address.is_activated": true})
- case opts.IsActivated.IsFalse():
- cond = cond.And(builder.Eq{"email_address.is_activated": false})
+ if opts.IsActivated.Has() {
+ cond = cond.And(builder.Eq{"email_address.is_activated": opts.IsActivated.Value()})
}
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()
}
+
+// 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
+}
diff --git a/models/user/email_address_test.go b/models/user/email_address_test.go
index be1ccea544..65befa5660 100644
--- a/models/user/email_address_test.go
+++ b/models/user/email_address_test.go
@@ -10,7 +10,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
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"
)
@@ -138,14 +138,14 @@ func TestListEmails(t *testing.T) {
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)
- opts = &user_model.SearchEmailOptions{IsPrimary: util.OptionalBoolTrue}
+ opts = &user_model.SearchEmailOptions{IsPrimary: optional.Some(true)}
emails, _, err = user_model.SearchEmails(db.DefaultContext, opts)
assert.NoError(t, err)
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 }))
// 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)
assert.NoError(t, err)
assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsActivated }))
diff --git a/models/user/search.go b/models/user/search.go
index 0fa278c257..45b051187e 100644
--- a/models/user/search.go
+++ b/models/user/search.go
@@ -9,8 +9,9 @@ import (
"strings"
"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/util"
"xorm.io/builder"
"xorm.io/xorm"
@@ -30,11 +31,13 @@ type SearchUserOptions struct {
Actor *User // The user doing the search
SearchByEmail bool // Search by email as well as username/full name
- IsActive util.OptionalBool
- IsAdmin util.OptionalBool
- IsRestricted util.OptionalBool
- IsTwoFactorEnabled util.OptionalBool
- IsProhibitLogin util.OptionalBool
+ SupportedSortOrders container.Set[string] // if not nil, only allow to use the sort orders in this set
+
+ IsActive optional.Option[bool]
+ IsAdmin optional.Option[bool]
+ IsRestricted optional.Option[bool]
+ IsTwoFactorEnabled optional.Option[bool]
+ IsProhibitLogin optional.Option[bool]
IncludeReserved bool
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})
}
- if !opts.IsActive.IsNone() {
- cond = cond.And(builder.Eq{"is_active": opts.IsActive.IsTrue()})
+ if opts.IsActive.Has() {
+ cond = cond.And(builder.Eq{"is_active": opts.IsActive.Value()})
}
- if !opts.IsAdmin.IsNone() {
- cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.IsTrue()})
+ if opts.IsAdmin.Has() {
+ cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.Value()})
}
- if !opts.IsRestricted.IsNone() {
- cond = cond.And(builder.Eq{"is_restricted": opts.IsRestricted.IsTrue()})
+ if opts.IsRestricted.Has() {
+ cond = cond.And(builder.Eq{"is_restricted": opts.IsRestricted.Value()})
}
- if !opts.IsProhibitLogin.IsNone() {
- cond = cond.And(builder.Eq{"prohibit_login": opts.IsProhibitLogin.IsTrue()})
+ if opts.IsProhibitLogin.Has() {
+ cond = cond.And(builder.Eq{"prohibit_login": opts.IsProhibitLogin.Value()})
}
e := db.GetEngine(ctx)
- if opts.IsTwoFactorEnabled.IsNone() {
+ if !opts.IsTwoFactorEnabled.Has() {
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.
// 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)
- if opts.IsTwoFactorEnabled.IsTrue() {
+ if opts.IsTwoFactorEnabled.Value() {
cond = cond.And(builder.Expr("two_factor.uid IS NOT NULL"))
} else {
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()
count, err := sessCount.Count(new(User))
if err != nil {
- return nil, 0, fmt.Errorf("Count: %w", err)
+ return nil, 0, fmt.Errorf("count: %w", err)
}
if len(opts.OrderBy) == 0 {
diff --git a/models/user/user.go b/models/user/user.go
index 9ff4881ec9..6d8eea6585 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -598,6 +598,16 @@ type CreateUserOverwriteOptions struct {
// CreateUser creates record of a new user.
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 {
return err
}
@@ -651,8 +661,14 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
return err
}
- if err := ValidateEmail(u.Email); err != nil {
- return err
+ if createdByAdmin {
+ if err := ValidateEmailForAdmin(u.Email); err != nil {
+ return err
+ }
+ } else {
+ if err := ValidateEmail(u.Email); err != nil {
+ return err
+ }
}
ctx, committer, err := db.TxContext(ctx)
@@ -727,7 +743,7 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
// IsLastAdminUser check whether user is the last admin
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 false
@@ -736,7 +752,7 @@ func IsLastAdminUser(ctx context.Context, user *User) bool {
// CountUserFilter represent optional filters for CountUsers
type CountUserFilter struct {
LastLoginSince *int64
- IsAdmin util.OptionalBool
+ IsAdmin optional.Option[bool]
}
// 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})
}
- if !opts.IsAdmin.IsNone() {
- cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.IsTrue()})
+ if opts.IsAdmin.Has() {
+ cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.Value()})
}
}
diff --git a/models/user/user_test.go b/models/user/user_test.go
index 87a842a4d3..bb72743301 100644
--- a/models/user/user_test.go
+++ b/models/user/user_test.go
@@ -16,10 +16,10 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"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/structs"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
"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}},
[]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})
- 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})
- 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})
// 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})
- 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})
- 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})
- 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})
- 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})
}
diff --git a/models/webhook/hooktask.go b/models/webhook/hooktask.go
index 2fb655ebca..ff3fdbadb2 100644
--- a/models/webhook/hooktask.go
+++ b/models/webhook/hooktask.go
@@ -5,13 +5,13 @@ package webhook
import (
"context"
+ "errors"
"time"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
- api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -31,6 +31,7 @@ type HookRequest struct {
URL string `json:"url"`
HTTPMethod string `json:"http_method"`
Headers map[string]string `json:"headers"`
+ Body string `json:"body"`
}
// HookResponse represents hook task response information.
@@ -45,11 +46,15 @@ type HookTask struct {
ID int64 `xorm:"pk autoincr"`
HookID int64 `xorm:"index"`
UUID string `xorm:"unique"`
- api.Payloader `xorm:"-"`
PayloadContent string `xorm:"LONGTEXT"`
- EventType webhook_module.HookEventType
- IsDelivered bool
- Delivered timeutil.TimeStampNano
+ // 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
+ IsDelivered bool
+ Delivered timeutil.TimeStampNano
// History info.
IsSucceed bool
@@ -115,16 +120,12 @@ func HookTasks(ctx context.Context, hookID int64, page int) ([]*HookTask, error)
// it handles conversion from Payload to PayloadContent.
func CreateHookTask(ctx context.Context, t *HookTask) (*HookTask, error) {
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 {
t.Delivered = timeutil.TimeStampNanoNow()
}
+ if t.PayloadVersion == 0 {
+ return nil, errors.New("missing HookTask.PayloadVersion")
+ }
return t, db.Insert(ctx, t)
}
@@ -165,6 +166,7 @@ func ReplayHookTask(ctx context.Context, hookID int64, uuid string) (*HookTask,
HookID: task.HookID,
PayloadContent: task.PayloadContent,
EventType: task.EventType,
+ PayloadVersion: task.PayloadVersion,
})
}
diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go
index 4a84a3d411..894357e36a 100644
--- a/models/webhook/webhook.go
+++ b/models/webhook/webhook.go
@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/secret"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
@@ -433,7 +434,7 @@ type ListWebhookOptions struct {
db.ListOptions
RepoID int64
OwnerID int64
- IsActive util.OptionalBool
+ IsActive optional.Option[bool]
}
func (opts ListWebhookOptions) ToConds() builder.Cond {
@@ -444,8 +445,8 @@ func (opts ListWebhookOptions) ToConds() builder.Cond {
if opts.OwnerID != 0 {
cond = cond.And(builder.Eq{"webhook.owner_id": opts.OwnerID})
}
- if !opts.IsActive.IsNone() {
- cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.IsTrue()})
+ if opts.IsActive.Has() {
+ cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.Value()})
}
return cond
}
diff --git a/models/webhook/webhook_system.go b/models/webhook/webhook_system.go
index 2e89f9547b..a2a9ee321a 100644
--- a/models/webhook/webhook_system.go
+++ b/models/webhook/webhook_system.go
@@ -8,7 +8,7 @@ import (
"fmt"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
)
// 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.
-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)
- if isActive.IsNone() {
+ if !isActive.Has() {
return webhooks, db.GetEngine(ctx).
Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, true).
Find(&webhooks)
}
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)
}
diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go
index 694fd7a873..f4403776ce 100644
--- a/models/webhook/webhook_test.go
+++ b/models/webhook/webhook_test.go
@@ -11,9 +11,8 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"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/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
"github.com/stretchr/testify/assert"
@@ -35,8 +34,10 @@ func TestWebhook_History(t *testing.T) {
webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 1})
tasks, err := webhook.History(db.DefaultContext, 0)
assert.NoError(t, err)
- if assert.Len(t, tasks, 1) {
- assert.Equal(t, int64(1), tasks[0].ID)
+ if assert.Len(t, tasks, 3) {
+ 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})
@@ -123,7 +124,7 @@ func TestGetWebhookByOwnerID(t *testing.T) {
func TestGetActiveWebhooksByRepoID(t *testing.T) {
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)
if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(1), hooks[0].ID)
@@ -143,7 +144,7 @@ func TestGetWebhooksByRepoID(t *testing.T) {
func TestGetActiveWebhooksByOwnerID(t *testing.T) {
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)
if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(3), hooks[0].ID)
@@ -197,8 +198,10 @@ func TestHookTasks(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTasks, err := HookTasks(db.DefaultContext, 1, 1)
assert.NoError(t, err)
- if assert.Len(t, hookTasks, 1) {
- assert.Equal(t, int64(1), hookTasks[0].ID)
+ if assert.Len(t, hookTasks, 3) {
+ 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)
@@ -209,8 +212,8 @@ func TestHookTasks(t *testing.T) {
func TestCreateHookTask(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{
- HookID: 3,
- Payloader: &api.PushPayload{},
+ HookID: 3,
+ PayloadVersion: 2,
}
unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask)
@@ -232,10 +235,10 @@ func TestUpdateHookTask(t *testing.T) {
func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{
- HookID: 3,
- Payloader: &api.PushPayload{},
- IsDelivered: true,
- Delivered: timeutil.TimeStampNanoNow(),
+ HookID: 3,
+ IsDelivered: true,
+ Delivered: timeutil.TimeStampNanoNow(),
+ PayloadVersion: 2,
}
unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask)
@@ -249,9 +252,9 @@ func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) {
func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{
- HookID: 4,
- Payloader: &api.PushPayload{},
- IsDelivered: false,
+ HookID: 4,
+ IsDelivered: false,
+ PayloadVersion: 2,
}
unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask)
@@ -265,10 +268,10 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) {
func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{
- HookID: 4,
- Payloader: &api.PushPayload{},
- IsDelivered: true,
- Delivered: timeutil.TimeStampNanoNow(),
+ HookID: 4,
+ IsDelivered: true,
+ Delivered: timeutil.TimeStampNanoNow(),
+ PayloadVersion: 2,
}
unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask)
@@ -282,10 +285,10 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) {
func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{
- HookID: 3,
- Payloader: &api.PushPayload{},
- IsDelivered: true,
- Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -8).UnixNano()),
+ HookID: 3,
+ IsDelivered: true,
+ Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -8).UnixNano()),
+ PayloadVersion: 2,
}
unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask)
@@ -299,9 +302,9 @@ func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) {
func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{
- HookID: 4,
- Payloader: &api.PushPayload{},
- IsDelivered: false,
+ HookID: 4,
+ IsDelivered: false,
+ PayloadVersion: 2,
}
unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask)
@@ -315,10 +318,10 @@ func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) {
func TestCleanupHookTaskTable_OlderThan_LeavesTaskEarlierThanAgeToDelete(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{
- HookID: 4,
- Payloader: &api.PushPayload{},
- IsDelivered: true,
- Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -6).UnixNano()),
+ HookID: 4,
+ IsDelivered: true,
+ Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -6).UnixNano()),
+ PayloadVersion: 2,
}
unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask)
diff --git a/modules/actions/task_state.go b/modules/actions/task_state.go
index cbbc0b357d..fe925bbb5d 100644
--- a/modules/actions/task_state.go
+++ b/modules/actions/task_state.go
@@ -35,6 +35,9 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep {
} else if task.Status.IsDone() {
preStep.Stopped = task.Stopped
preStep.Status = actions_model.StatusFailure
+ if task.Status.IsSkipped() {
+ preStep.Status = actions_model.StatusSkipped
+ }
}
logIndex += preStep.LogLength
diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go
index 81ab26bc27..8317f16dbd 100644
--- a/modules/actions/workflows.go
+++ b/modules/actions/workflows.go
@@ -406,6 +406,9 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
// all acts conditions should be satisfied
for cond, vals := range acts {
switch cond {
+ case "types":
+ // types have been checked
+ continue
case "branches":
refName := git.RefName(prPayload.PullRequest.Base.Ref)
patterns, err := workflowpattern.CompilePatterns(vals...)
diff --git a/modules/git/command.go b/modules/git/command.go
index 9305ef6f92..371109730a 100644
--- a/modules/git/command.go
+++ b/modules/git/command.go
@@ -12,6 +12,7 @@ import (
"io"
"os"
"os/exec"
+ "runtime"
"strings"
"time"
@@ -344,6 +345,17 @@ func (c *Command) Run(opts *RunOpts) error {
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 {
return err
}
diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go
index 979c5dec91..552ae2bb8c 100644
--- a/modules/git/repo_branch.go
+++ b/modules/git/repo_branch.go
@@ -55,15 +55,8 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) {
}, nil
}
-// SetDefaultBranch sets default branch of repository.
-func (repo *Repository) SetDefaultBranch(name string) error {
- _, _, 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})
+func GetDefaultBranch(ctx context.Context, repoPath string) (string, error) {
+ stdout, _, err := NewCommand(ctx, "symbolic-ref", "HEAD").RunStdString(&RunOpts{Dir: repoPath})
if err != nil {
return "", err
}
diff --git a/modules/gitrepo/branch.go b/modules/gitrepo/branch.go
index dcaf92668d..e13a4c82e1 100644
--- a/modules/gitrepo/branch.go
+++ b/modules/gitrepo/branch.go
@@ -30,3 +30,20 @@ func GetBranchCommitID(ctx context.Context, repo Repository, branch string) (str
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))
+}
diff --git a/modules/graceful/manager_unix.go b/modules/graceful/manager_unix.go
index f4af4993d9..edf5fc248f 100644
--- a/modules/graceful/manager_unix.go
+++ b/modules/graceful/manager_unix.go
@@ -59,7 +59,15 @@ func (g *Manager) start() {
go func() {
defer close(startupDone)
// Wait till we're done getting all the listeners and then close the unused ones
- g.createServerWaitGroup.Wait()
+ 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()
+ }()
// Ignore the error here there's not much we can do with it, they're logged in the CloseProvidedListeners function
_ = CloseProvidedListeners()
g.notify(readyMsg)
diff --git a/modules/graceful/manager_windows.go b/modules/graceful/manager_windows.go
index 0248dcb24d..ecf30af3f3 100644
--- a/modules/graceful/manager_windows.go
+++ b/modules/graceful/manager_windows.go
@@ -150,7 +150,15 @@ func (g *Manager) awaitServer(limit time.Duration) bool {
c := make(chan struct{})
go func() {
defer close(c)
- g.createServerWaitGroup.Wait()
+ 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()
+ }()
}()
if limit > 0 {
select {
diff --git a/modules/indexer/code/bleve/bleve.go b/modules/indexer/code/bleve/bleve.go
index 8ba50ed77c..107dd23598 100644
--- a/modules/indexer/code/bleve/bleve.go
+++ b/modules/indexer/code/bleve/bleve.go
@@ -233,21 +233,21 @@ func (b *Indexer) Delete(_ context.Context, repoID int64) error {
// Search searches for files in the specified repo.
// 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 (
indexerQuery query.Query
keywordQuery query.Query
)
- if isMatch {
- prefixQuery := bleve.NewPrefixQuery(keyword)
- prefixQuery.FieldVal = "Content"
- keywordQuery = prefixQuery
- } else {
+ if isFuzzy {
phraseQuery := bleve.NewMatchPhraseQuery(keyword)
phraseQuery.FieldVal = "Content"
phraseQuery.Analyzer = repoIndexerAnalyzer
keywordQuery = phraseQuery
+ } else {
+ prefixQuery := bleve.NewPrefixQuery(keyword)
+ prefixQuery.FieldVal = "Content"
+ keywordQuery = prefixQuery
}
if len(repoIDs) > 0 {
diff --git a/modules/indexer/code/elasticsearch/elasticsearch.go b/modules/indexer/code/elasticsearch/elasticsearch.go
index 0f70f13485..065b0b2061 100644
--- a/modules/indexer/code/elasticsearch/elasticsearch.go
+++ b/modules/indexer/code/elasticsearch/elasticsearch.go
@@ -281,10 +281,10 @@ func extractAggs(searchResult *elastic.SearchResult) []*internal.SearchResultLan
}
// 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) {
- searchType := esMultiMatchTypeBestFields
- if isMatch {
- searchType = esMultiMatchTypePhrasePrefix
+func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) {
+ searchType := esMultiMatchTypePhrasePrefix
+ if isFuzzy {
+ searchType = esMultiMatchTypeBestFields
}
kwQuery := elastic.NewMultiMatchQuery(keyword, "content").Type(searchType)
diff --git a/modules/indexer/code/indexer_test.go b/modules/indexer/code/indexer_test.go
index 5eb8e61e3d..23dbd63410 100644
--- a/modules/indexer/code/indexer_test.go
+++ b/modules/indexer/code/indexer_test.go
@@ -70,7 +70,7 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
for _, kw := range keywords {
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.Len(t, kw.IDs, int(total))
assert.Len(t, langs, kw.Langs)
diff --git a/modules/indexer/code/internal/indexer.go b/modules/indexer/code/internal/indexer.go
index da3ac3623c..c92419deb2 100644
--- a/modules/indexer/code/internal/indexer.go
+++ b/modules/indexer/code/internal/indexer.go
@@ -16,7 +16,7 @@ type Indexer interface {
internal.Indexer
Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *RepoChanges) 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
@@ -38,6 +38,6 @@ func (d *dummyIndexer) Delete(ctx context.Context, repoID int64) error {
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")
}
diff --git a/modules/indexer/code/search.go b/modules/indexer/code/search.go
index e19e22eea0..89a62a8d3e 100644
--- a/modules/indexer/code/search.go
+++ b/modules/indexer/code/search.go
@@ -16,14 +16,18 @@ import (
// Result a search result to display
type Result struct {
- RepoID int64
- Filename string
- CommitID string
- UpdatedUnix timeutil.TimeStamp
- Language string
- Color string
- LineNumbers []int
- FormattedLines template.HTML
+ RepoID int64
+ Filename string
+ CommitID string
+ UpdatedUnix timeutil.TimeStamp
+ Language string
+ Color string
+ Lines []ResultLine
+}
+
+type ResultLine struct {
+ Num int
+ FormattedContent template.HTML
}
type SearchResultLanguages = internal.SearchResultLanguages
@@ -70,7 +74,7 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res
var formattedLinesBuffer bytes.Buffer
contentLines := strings.SplitAfter(result.Content[startIndex:endIndex], "\n")
- lineNumbers := make([]int, len(contentLines))
+ lines := make([]ResultLine, 0, len(contentLines))
index := startIndex
for i, line := range contentLines {
var err error
@@ -93,31 +97,40 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res
return nil, err
}
- lineNumbers[i] = startLineNum + i
+ lines = append(lines, ResultLine{Num: startLineNum + i})
index += len(line)
}
- highlighted, _ := highlight.Code(result.Filename, "", formattedLinesBuffer.String())
+ // we should highlight the whole code block first, otherwise it doesn't work well with multiple line highlighting
+ hl, _ := highlight.Code(result.Filename, "", formattedLinesBuffer.String())
+ highlightedLines := strings.Split(string(hl), "\n")
+
+ // The lines outputted by highlight.Code might not match the original lines, because "highlight" removes the last `\n`
+ lines = lines[:min(len(highlightedLines), len(lines))]
+ highlightedLines = highlightedLines[:len(lines)]
+ for i := 0; i < len(lines); i++ {
+ lines[i].FormattedContent = template.HTML(highlightedLines[i])
+ }
return &Result{
- RepoID: result.RepoID,
- Filename: result.Filename,
- CommitID: result.CommitID,
- UpdatedUnix: result.UpdatedUnix,
- Language: result.Language,
- Color: result.Color,
- LineNumbers: lineNumbers,
- FormattedLines: highlighted,
+ RepoID: result.RepoID,
+ Filename: result.Filename,
+ CommitID: result.CommitID,
+ UpdatedUnix: result.UpdatedUnix,
+ Language: result.Language,
+ Color: result.Color,
+ Lines: lines,
}, nil
}
// PerformSearch perform a search on a repository
-func PerformSearch(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isMatch bool) (int, []*Result, []*internal.SearchResultLanguages, error) {
+// if isFuzzy is true set the Damerau-Levenshtein distance from 0 to 2
+func PerformSearch(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isFuzzy bool) (int, []*Result, []*internal.SearchResultLanguages, error) {
if len(keyword) == 0 {
return 0, nil, nil, nil
}
- total, results, resultLanguages, err := (*globalIndexer.Load()).Search(ctx, repoIDs, language, keyword, page, pageSize, isMatch)
+ total, results, resultLanguages, err := (*globalIndexer.Load()).Search(ctx, repoIDs, language, keyword, page, pageSize, isFuzzy)
if err != nil {
return 0, nil, nil, err
}
diff --git a/modules/indexer/internal/bleve/query.go b/modules/indexer/internal/bleve/query.go
index c7d66538c1..2a427c4020 100644
--- a/modules/indexer/internal/bleve/query.go
+++ b/modules/indexer/internal/bleve/query.go
@@ -25,6 +25,13 @@ func MatchPhraseQuery(matchPhrase, field, analyzer string) *query.MatchPhraseQue
return q
}
+// PrefixQuery generates a match prefix query for the given prefix and field
+func PrefixQuery(matchPrefix, field string) *query.PrefixQuery {
+ q := bleve.NewPrefixQuery(matchPrefix)
+ q.FieldVal = field
+ return q
+}
+
// BoolFieldQuery generates a bool field query for the given value and field
func BoolFieldQuery(value bool, field string) *query.BoolFieldQuery {
q := bleve.NewBoolFieldQuery(value)
diff --git a/modules/indexer/issues/bleve/bleve.go b/modules/indexer/issues/bleve/bleve.go
index 7c82cfbb79..aaea854efa 100644
--- a/modules/indexer/issues/bleve/bleve.go
+++ b/modules/indexer/issues/bleve/bleve.go
@@ -156,12 +156,19 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
var queries []query.Query
if options.Keyword != "" {
- keywordQueries := []query.Query{
- inner_bleve.MatchPhraseQuery(options.Keyword, "title", issueIndexerAnalyzer),
- inner_bleve.MatchPhraseQuery(options.Keyword, "content", issueIndexerAnalyzer),
- inner_bleve.MatchPhraseQuery(options.Keyword, "comments", issueIndexerAnalyzer),
+ if options.IsFuzzyKeyword {
+ queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{
+ inner_bleve.MatchPhraseQuery(options.Keyword, "title", issueIndexerAnalyzer),
+ inner_bleve.MatchPhraseQuery(options.Keyword, "content", issueIndexerAnalyzer),
+ inner_bleve.MatchPhraseQuery(options.Keyword, "comments", issueIndexerAnalyzer),
+ }...))
+ } else {
+ queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{
+ inner_bleve.PrefixQuery(options.Keyword, "title"),
+ inner_bleve.PrefixQuery(options.Keyword, "content"),
+ inner_bleve.PrefixQuery(options.Keyword, "comments"),
+ }...))
}
- queries = append(queries, bleve.NewDisjunctionQuery(keywordQueries...))
}
if len(options.RepoIDs) > 0 || options.AllPublic {
@@ -175,11 +182,11 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
queries = append(queries, bleve.NewDisjunctionQuery(repoQueries...))
}
- if !options.IsPull.IsNone() {
- queries = append(queries, inner_bleve.BoolFieldQuery(options.IsPull.IsTrue(), "is_pull"))
+ if options.IsPull.Has() {
+ queries = append(queries, inner_bleve.BoolFieldQuery(options.IsPull.Value(), "is_pull"))
}
- if !options.IsClosed.IsNone() {
- queries = append(queries, inner_bleve.BoolFieldQuery(options.IsClosed.IsTrue(), "is_closed"))
+ if options.IsClosed.Has() {
+ queries = append(queries, inner_bleve.BoolFieldQuery(options.IsClosed.Value(), "is_closed"))
}
if options.NoLabelOnly {
diff --git a/modules/indexer/issues/db/options.go b/modules/indexer/issues/db/options.go
index 5406715bbc..69146573a8 100644
--- a/modules/indexer/issues/db/options.go
+++ b/modules/indexer/issues/db/options.go
@@ -11,6 +11,7 @@ import (
issue_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/indexer/issues/internal"
+ "code.gitea.io/gitea/modules/optional"
)
func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_model.IssuesOptions, error) {
@@ -75,7 +76,7 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
UpdatedAfterUnix: convertInt64(options.UpdatedAfterUnix),
UpdatedBeforeUnix: convertInt64(options.UpdatedBeforeUnix),
PriorityRepoID: 0,
- IsArchived: 0,
+ IsArchived: optional.None[bool](),
Org: nil,
Team: nil,
User: nil,
diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go
index d059f76b32..0077da263a 100644
--- a/modules/indexer/issues/elasticsearch/elasticsearch.go
+++ b/modules/indexer/issues/elasticsearch/elasticsearch.go
@@ -19,6 +19,10 @@ import (
const (
issueIndexerLatestVersion = 1
+ // multi-match-types, currently only 2 types are used
+ // Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types
+ esMultiMatchTypeBestFields = "best_fields"
+ esMultiMatchTypePhrasePrefix = "phrase_prefix"
)
var _ internal.Indexer = &Indexer{}
@@ -141,7 +145,13 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
query := elastic.NewBoolQuery()
if options.Keyword != "" {
- query.Must(elastic.NewMultiMatchQuery(options.Keyword, "title", "content", "comments"))
+
+ searchType := esMultiMatchTypePhrasePrefix
+ if options.IsFuzzyKeyword {
+ searchType = esMultiMatchTypeBestFields
+ }
+
+ query.Must(elastic.NewMultiMatchQuery(options.Keyword, "title", "content", "comments").Type(searchType))
}
if len(options.RepoIDs) > 0 {
@@ -153,11 +163,11 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
query.Must(q)
}
- if !options.IsPull.IsNone() {
- query.Must(elastic.NewTermQuery("is_pull", options.IsPull.IsTrue()))
+ if options.IsPull.Has() {
+ query.Must(elastic.NewTermQuery("is_pull", options.IsPull.Value()))
}
- if !options.IsClosed.IsNone() {
- query.Must(elastic.NewTermQuery("is_closed", options.IsClosed.IsTrue()))
+ if options.IsClosed.Has() {
+ query.Must(elastic.NewTermQuery("is_closed", options.IsClosed.Value()))
}
if options.NoLabelOnly {
diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go
index 57037d2947..e3bc21b49d 100644
--- a/modules/indexer/issues/indexer.go
+++ b/modules/indexer/issues/indexer.go
@@ -20,10 +20,10 @@ import (
"code.gitea.io/gitea/modules/indexer/issues/internal"
"code.gitea.io/gitea/modules/indexer/issues/meilisearch"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
)
// IndexerMetadata is used to send data to the queue, so it contains only the ids.
@@ -220,7 +220,7 @@ func PopulateIssueIndexer(ctx context.Context) error {
ListOptions: db_model.ListOptions{Page: page, PageSize: repo_model.RepositoryListDefaultPageSize},
OrderBy: db_model.SearchOrderByID,
Private: true,
- Collaborate: util.OptionalBoolFalse,
+ Collaborate: optional.Some(false),
})
if err != nil {
log.Error("SearchRepositoryByName: %v", err)
diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go
index 3b96686d98..10ffa7cbe6 100644
--- a/modules/indexer/issues/indexer_test.go
+++ b/modules/indexer/issues/indexer_test.go
@@ -10,8 +10,8 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/indexer/issues/internal"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
_ "code.gitea.io/gitea/models"
_ "code.gitea.io/gitea/models/actions"
@@ -210,13 +210,13 @@ func searchIssueIsPull(t *testing.T) {
}{
{
SearchOptions{
- IsPull: util.OptionalBoolFalse,
+ IsPull: optional.Some(false),
},
[]int64{17, 16, 15, 14, 13, 6, 5, 18, 10, 7, 4, 1},
},
{
SearchOptions{
- IsPull: util.OptionalBoolTrue,
+ IsPull: optional.Some(true),
},
[]int64{22, 21, 12, 11, 20, 19, 9, 8, 3, 2},
},
@@ -237,13 +237,13 @@ func searchIssueIsClosed(t *testing.T) {
}{
{
SearchOptions{
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
},
[]int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 19, 18, 10, 7, 9, 8, 3, 2, 1},
},
{
SearchOptions{
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
},
[]int64{5, 4},
},
diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go
index 031745dd2f..d41fec4aba 100644
--- a/modules/indexer/issues/internal/model.go
+++ b/modules/indexer/issues/internal/model.go
@@ -5,8 +5,8 @@ package internal
import (
"code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
)
// IndexerData data stored in the issue indexer
@@ -74,11 +74,13 @@ type SearchResult struct {
type SearchOptions struct {
Keyword string // keyword to search
+ IsFuzzyKeyword bool // if false the levenshtein distance is 0
+
RepoIDs []int64 // repository IDs which the issues belong to
AllPublic bool // if include all public repositories
- IsPull util.OptionalBool // if the issues is a pull request
- IsClosed util.OptionalBool // if the issues is closed
+ IsPull optional.Option[bool] // if the issues is a pull request
+ IsClosed optional.Option[bool] // if the issues is closed
IncludedLabelIDs []int64 // labels the issues have
ExcludedLabelIDs []int64 // labels the issues don't have
diff --git a/modules/indexer/issues/internal/tests/tests.go b/modules/indexer/issues/internal/tests/tests.go
index 06fddeb65b..6724471539 100644
--- a/modules/indexer/issues/internal/tests/tests.go
+++ b/modules/indexer/issues/internal/tests/tests.go
@@ -16,8 +16,8 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/indexer/issues/internal"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -166,7 +166,7 @@ var cases = []*testIndexerCase{
Paginator: &db.ListOptions{
PageSize: 5,
},
- IsPull: util.OptionalBoolFalse,
+ IsPull: optional.Some(false),
},
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits))
@@ -182,7 +182,7 @@ var cases = []*testIndexerCase{
Paginator: &db.ListOptions{
PageSize: 5,
},
- IsPull: util.OptionalBoolTrue,
+ IsPull: optional.Some(true),
},
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits))
@@ -198,7 +198,7 @@ var cases = []*testIndexerCase{
Paginator: &db.ListOptions{
PageSize: 5,
},
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
},
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits))
@@ -214,7 +214,7 @@ var cases = []*testIndexerCase{
Paginator: &db.ListOptions{
PageSize: 5,
},
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
},
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits))
diff --git a/modules/indexer/issues/meilisearch/meilisearch.go b/modules/indexer/issues/meilisearch/meilisearch.go
index ab8dcd0af4..c429920065 100644
--- a/modules/indexer/issues/meilisearch/meilisearch.go
+++ b/modules/indexer/issues/meilisearch/meilisearch.go
@@ -5,6 +5,7 @@ package meilisearch
import (
"context"
+ "errors"
"strconv"
"strings"
@@ -16,12 +17,15 @@ import (
)
const (
- issueIndexerLatestVersion = 2
+ issueIndexerLatestVersion = 3
// TODO: make this configurable if necessary
maxTotalHits = 10000
)
+// ErrMalformedResponse is never expected as we initialize the indexer ourself and so define the types.
+var ErrMalformedResponse = errors.New("meilisearch returned unexpected malformed content")
+
var _ internal.Indexer = &Indexer{}
// Indexer implements Indexer interface
@@ -47,6 +51,9 @@ func NewIndexer(url, apiKey, indexerName string) *Indexer {
},
DisplayedAttributes: []string{
"id",
+ "title",
+ "content",
+ "comments",
},
FilterableAttributes: []string{
"repo_id",
@@ -131,11 +138,11 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
query.And(q)
}
- if !options.IsPull.IsNone() {
- query.And(inner_meilisearch.NewFilterEq("is_pull", options.IsPull.IsTrue()))
+ if options.IsPull.Has() {
+ query.And(inner_meilisearch.NewFilterEq("is_pull", options.IsPull.Value()))
}
- if !options.IsClosed.IsNone() {
- query.And(inner_meilisearch.NewFilterEq("is_closed", options.IsClosed.IsTrue()))
+ if options.IsClosed.Has() {
+ query.And(inner_meilisearch.NewFilterEq("is_closed", options.IsClosed.Value()))
}
if options.NoLabelOnly {
@@ -221,11 +228,9 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
return nil, err
}
- hits := make([]internal.Match, 0, len(searchRes.Hits))
- for _, hit := range searchRes.Hits {
- hits = append(hits, internal.Match{
- ID: int64(hit.(map[string]any)["id"].(float64)),
- })
+ hits, err := nonFuzzyWorkaround(searchRes, options.Keyword, options.IsFuzzyKeyword)
+ if err != nil {
+ return nil, err
}
return &internal.SearchResult{
@@ -241,3 +246,77 @@ func parseSortBy(sortBy internal.SortBy) string {
}
return field + ":asc"
}
+
+// nonFuzzyWorkaround is needed as meilisearch does not have an exact search
+// and you can only change "typo tolerance" per index. So we have to post-filter the results
+// https://www.meilisearch.com/docs/learn/configuration/typo_tolerance#configuring-typo-tolerance
+// TODO: remove once https://github.com/orgs/meilisearch/discussions/377 is addressed
+func nonFuzzyWorkaround(searchRes *meilisearch.SearchResponse, keyword string, isFuzzy bool) ([]internal.Match, error) {
+ hits := make([]internal.Match, 0, len(searchRes.Hits))
+ for _, hit := range searchRes.Hits {
+ hit, ok := hit.(map[string]any)
+ if !ok {
+ return nil, ErrMalformedResponse
+ }
+
+ if !isFuzzy {
+ keyword = strings.ToLower(keyword)
+
+ // declare a anon func to check if the title, content or at least one comment contains the keyword
+ found, err := func() (bool, error) {
+ // check if title match first
+ title, ok := hit["title"].(string)
+ if !ok {
+ return false, ErrMalformedResponse
+ } else if strings.Contains(strings.ToLower(title), keyword) {
+ return true, nil
+ }
+
+ // check if content has a match
+ content, ok := hit["content"].(string)
+ if !ok {
+ return false, ErrMalformedResponse
+ } else if strings.Contains(strings.ToLower(content), keyword) {
+ return true, nil
+ }
+
+ // now check for each comment if one has a match
+ // so we first try to cast and skip if there are no comments
+ comments, ok := hit["comments"].([]any)
+ if !ok {
+ return false, ErrMalformedResponse
+ } else if len(comments) == 0 {
+ return false, nil
+ }
+
+ // now we iterate over all and report as soon as we detect one match
+ for i := range comments {
+ comment, ok := comments[i].(string)
+ if !ok {
+ return false, ErrMalformedResponse
+ }
+ if strings.Contains(strings.ToLower(comment), keyword) {
+ return true, nil
+ }
+ }
+
+ // we got no match
+ return false, nil
+ }()
+
+ if err != nil {
+ return nil, err
+ } else if !found {
+ continue
+ }
+ }
+ issueID, ok := hit["id"].(float64)
+ if !ok {
+ return nil, ErrMalformedResponse
+ }
+ hits = append(hits, internal.Match{
+ ID: int64(issueID),
+ })
+ }
+ return hits, nil
+}
diff --git a/modules/indexer/issues/meilisearch/meilisearch_test.go b/modules/indexer/issues/meilisearch/meilisearch_test.go
index 8a6b0a61d3..1a9bbeef10 100644
--- a/modules/indexer/issues/meilisearch/meilisearch_test.go
+++ b/modules/indexer/issues/meilisearch/meilisearch_test.go
@@ -10,7 +10,11 @@ import (
"testing"
"time"
+ "code.gitea.io/gitea/modules/indexer/issues/internal"
"code.gitea.io/gitea/modules/indexer/issues/internal/tests"
+
+ "github.com/meilisearch/meilisearch-go"
+ "github.com/stretchr/testify/assert"
)
func TestMeilisearchIndexer(t *testing.T) {
@@ -49,3 +53,44 @@ func TestMeilisearchIndexer(t *testing.T) {
tests.TestIndexer(t, indexer)
}
+
+func TestNonFuzzyWorkaround(t *testing.T) {
+ // get unexpected return
+ _, err := nonFuzzyWorkaround(&meilisearch.SearchResponse{
+ Hits: []any{"aa", "bb", "cc", "dd"},
+ }, "bowling", false)
+ assert.ErrorIs(t, err, ErrMalformedResponse)
+
+ validResponse := &meilisearch.SearchResponse{
+ Hits: []any{
+ map[string]any{
+ "id": float64(11),
+ "title": "a title",
+ "content": "issue body with no match",
+ "comments": []any{"hey whats up?", "I'm currently bowling", "nice"},
+ },
+ map[string]any{
+ "id": float64(22),
+ "title": "Bowling as title",
+ "content": "",
+ "comments": []any{},
+ },
+ map[string]any{
+ "id": float64(33),
+ "title": "Bowl-ing as fuzzy match",
+ "content": "",
+ "comments": []any{},
+ },
+ },
+ }
+
+ // nonFuzzy
+ hits, err := nonFuzzyWorkaround(validResponse, "bowling", false)
+ assert.NoError(t, err)
+ assert.EqualValues(t, []internal.Match{{ID: 11}, {ID: 22}}, hits)
+
+ // fuzzy
+ hits, err = nonFuzzyWorkaround(validResponse, "bowling", true)
+ assert.NoError(t, err)
+ assert.EqualValues(t, []internal.Match{{ID: 11}, {ID: 22}, {ID: 33}}, hits)
+}
diff --git a/modules/issue/template/template.go b/modules/issue/template/template.go
index 4e813fc91f..3be48b9edc 100644
--- a/modules/issue/template/template.go
+++ b/modules/issue/template/template.go
@@ -122,7 +122,13 @@ func validateRequired(field *api.IssueFormField, idx int) error {
// The label is not required for a markdown or checkboxes field
return nil
}
- return validateBoolItem(newErrorPosition(idx, field.Type), field.Validations, "required")
+ if err := validateBoolItem(newErrorPosition(idx, field.Type), field.Validations, "required"); err != nil {
+ return err
+ }
+ if required, _ := field.Validations["required"].(bool); required && !field.VisibleOnForm() {
+ return newErrorPosition(idx, field.Type).Errorf("can not require a hidden field")
+ }
+ return nil
}
func validateID(field *api.IssueFormField, idx int, ids container.Set[string]) error {
@@ -172,10 +178,38 @@ func validateOptions(field *api.IssueFormField, idx int) error {
return position.Errorf("'label' is required and should be a string")
}
+ if visibility, ok := opt["visible"]; ok {
+ visibilityList, ok := visibility.([]any)
+ if !ok {
+ return position.Errorf("'visible' should be list")
+ }
+ for _, visibleType := range visibilityList {
+ visibleType, ok := visibleType.(string)
+ if !ok || !(visibleType == "form" || visibleType == "content") {
+ return position.Errorf("'visible' list can only contain strings of 'form' and 'content'")
+ }
+ }
+ }
+
if required, ok := opt["required"]; ok {
if _, ok := required.(bool); !ok {
return position.Errorf("'required' should be a bool")
}
+
+ // validate if hidden field is required
+ if visibility, ok := opt["visible"]; ok {
+ visibilityList, _ := visibility.([]any)
+ isVisible := false
+ for _, v := range visibilityList {
+ if vv, _ := v.(string); vv == "form" {
+ isVisible = true
+ break
+ }
+ }
+ if !isVisible {
+ return position.Errorf("can not require a hidden checkbox")
+ }
+ }
}
}
}
@@ -238,7 +272,7 @@ func RenderToMarkdown(template *api.IssueTemplate, values url.Values) string {
IssueFormField: field,
Values: values,
}
- if f.ID == "" {
+ if f.ID == "" || !f.VisibleInContent() {
continue
}
f.WriteTo(builder)
@@ -253,11 +287,6 @@ type valuedField struct {
}
func (f *valuedField) WriteTo(builder *strings.Builder) {
- if f.Type == api.IssueFormFieldTypeMarkdown {
- // markdown blocks do not appear in output
- return
- }
-
// write label
if !f.HideLabel() {
_, _ = fmt.Fprintf(builder, "### %s\n\n", f.Label())
@@ -269,6 +298,9 @@ func (f *valuedField) WriteTo(builder *strings.Builder) {
switch f.Type {
case api.IssueFormFieldTypeCheckboxes:
for _, option := range f.Options() {
+ if !option.VisibleInContent() {
+ continue
+ }
checked := " "
if option.IsChecked() {
checked = "x"
@@ -302,6 +334,10 @@ func (f *valuedField) WriteTo(builder *strings.Builder) {
} else {
_, _ = fmt.Fprintf(builder, "%s\n", value)
}
+ case api.IssueFormFieldTypeMarkdown:
+ if value, ok := f.Attributes["value"].(string); ok {
+ _, _ = fmt.Fprintf(builder, "%s\n", value)
+ }
}
_, _ = fmt.Fprintln(builder)
}
@@ -314,6 +350,9 @@ func (f *valuedField) Label() string {
}
func (f *valuedField) HideLabel() bool {
+ if f.Type == api.IssueFormFieldTypeMarkdown {
+ return true
+ }
if label, ok := f.Attributes["hide_label"].(bool); ok {
return label
}
@@ -385,6 +424,22 @@ func (o *valuedOption) IsChecked() bool {
return false
}
+func (o *valuedOption) VisibleInContent() bool {
+ if o.field.Type == api.IssueFormFieldTypeCheckboxes {
+ if vs, ok := o.data.(map[string]any); ok {
+ if vl, ok := vs["visible"].([]any); ok {
+ for _, v := range vl {
+ if vv, _ := v.(string); vv == "content" {
+ return true
+ }
+ }
+ return false
+ }
+ }
+ }
+ return true
+}
+
var minQuotesRegex = regexp.MustCompilePOSIX("^`{3,}")
// minQuotes return 3 or more back-quotes.
diff --git a/modules/issue/template/template_test.go b/modules/issue/template/template_test.go
index 06e6b70d35..e24b962d61 100644
--- a/modules/issue/template/template_test.go
+++ b/modules/issue/template/template_test.go
@@ -10,6 +10,7 @@ import (
"code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
+ "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -318,6 +319,42 @@ body:
`,
wantErr: "body[0](checkboxes), option[0]: 'required' should be a bool",
},
+ {
+ name: "field is required but hidden",
+ content: `
+name: "test"
+about: "this is about"
+body:
+ - type: "input"
+ id: "1"
+ attributes:
+ label: "a"
+ validations:
+ required: true
+ visible: [content]
+`,
+ wantErr: "body[0](input): can not require a hidden field",
+ },
+ {
+ name: "checkboxes is required but hidden",
+ content: `
+name: "test"
+about: "this is about"
+body:
+ - type: checkboxes
+ id: "1"
+ attributes:
+ label: Label of checkboxes
+ description: Description of checkboxes
+ options:
+ - label: Option 1
+ required: false
+ - label: Required and hidden
+ required: true
+ visible: [content]
+`,
+ wantErr: "body[0](checkboxes), option[1]: can not require a hidden checkbox",
+ },
{
name: "valid",
content: `
@@ -374,8 +411,11 @@ body:
required: true
- label: Option 2 of checkboxes
required: false
- - label: Option 3 of checkboxes
+ - label: Hidden Option 3 of checkboxes
+ visible: [content]
+ - label: Required but not submitted
required: true
+ visible: [form]
`,
want: &api.IssueTemplate{
Name: "Name",
@@ -390,6 +430,7 @@ body:
Attributes: map[string]any{
"value": "Value of the markdown",
},
+ Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm},
},
{
Type: "textarea",
@@ -404,6 +445,7 @@ body:
Validations: map[string]any{
"required": true,
},
+ Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm, api.IssueFormFieldVisibleContent},
},
{
Type: "input",
@@ -419,6 +461,7 @@ body:
"is_number": true,
"regex": "[a-zA-Z0-9]+",
},
+ Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm, api.IssueFormFieldVisibleContent},
},
{
Type: "dropdown",
@@ -436,6 +479,7 @@ body:
Validations: map[string]any{
"required": true,
},
+ Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm, api.IssueFormFieldVisibleContent},
},
{
Type: "checkboxes",
@@ -446,9 +490,11 @@ body:
"options": []any{
map[string]any{"label": "Option 1 of checkboxes", "required": true},
map[string]any{"label": "Option 2 of checkboxes", "required": false},
- map[string]any{"label": "Option 3 of checkboxes", "required": true},
+ map[string]any{"label": "Hidden Option 3 of checkboxes", "visible": []string{"content"}},
+ map[string]any{"label": "Required but not submitted", "required": true, "visible": []string{"form"}},
},
},
+ Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm, api.IssueFormFieldVisibleContent},
},
},
FileName: "test.yaml",
@@ -467,7 +513,12 @@ body:
- type: markdown
id: id1
attributes:
- value: Value of the markdown
+ value: Value of the markdown shown in form
+ - type: markdown
+ id: id2
+ attributes:
+ value: Value of the markdown shown in created issue
+ visible: [content]
`,
want: &api.IssueTemplate{
Name: "Name",
@@ -480,8 +531,17 @@ body:
Type: "markdown",
ID: "id1",
Attributes: map[string]any{
- "value": "Value of the markdown",
+ "value": "Value of the markdown shown in form",
},
+ Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm},
+ },
+ {
+ Type: "markdown",
+ ID: "id2",
+ Attributes: map[string]any{
+ "value": "Value of the markdown shown in created issue",
+ },
+ Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleContent},
},
},
FileName: "test.yaml",
@@ -515,6 +575,7 @@ body:
Attributes: map[string]any{
"value": "Value of the markdown",
},
+ Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm},
},
},
FileName: "test.yaml",
@@ -548,6 +609,7 @@ body:
Attributes: map[string]any{
"value": "Value of the markdown",
},
+ Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm},
},
},
FileName: "test.yaml",
@@ -622,9 +684,14 @@ body:
- type: markdown
id: id1
attributes:
- value: Value of the markdown
- - type: textarea
+ value: Value of the markdown shown in form
+ - type: markdown
id: id2
+ attributes:
+ value: Value of the markdown shown in created issue
+ visible: [content]
+ - type: textarea
+ id: id3
attributes:
label: Label of textarea
description: Description of textarea
@@ -634,7 +701,7 @@ body:
validations:
required: true
- type: input
- id: id3
+ id: id4
attributes:
label: Label of input
description: Description of input
@@ -646,7 +713,7 @@ body:
is_number: true
regex: "[a-zA-Z0-9]+"
- type: dropdown
- id: id4
+ id: id5
attributes:
label: Label of dropdown
description: Description of dropdown
@@ -658,7 +725,7 @@ body:
validations:
required: true
- type: checkboxes
- id: id5
+ id: id6
attributes:
label: Label of checkboxes
description: Description of checkboxes
@@ -669,20 +736,26 @@ body:
required: false
- label: Option 3 of checkboxes
required: true
+ visible: [form]
+ - label: Hidden Option of checkboxes
+ visible: [content]
`,
values: map[string][]string{
- "form-field-id2": {"Value of id2"},
"form-field-id3": {"Value of id3"},
- "form-field-id4": {"0,1"},
- "form-field-id5-0": {"on"},
- "form-field-id5-2": {"on"},
+ "form-field-id4": {"Value of id4"},
+ "form-field-id5": {"0,1"},
+ "form-field-id6-0": {"on"},
+ "form-field-id6-2": {"on"},
},
},
- want: `### Label of textarea
-` + "```bash\nValue of id2\n```" + `
+ want: `Value of the markdown shown in created issue
-Value of id3
+### Label of textarea
+
+` + "```bash\nValue of id3\n```" + `
+
+Value of id4
### Label of dropdown
@@ -692,7 +765,7 @@ Option 1 of dropdown, Option 2 of dropdown
- [x] Option 1 of checkboxes
- [ ] Option 2 of checkboxes
-- [x] Option 3 of checkboxes
+- [ ] Hidden Option of checkboxes
`,
},
@@ -704,7 +777,7 @@ Option 1 of dropdown, Option 2 of dropdown
t.Fatal(err)
}
if got := RenderToMarkdown(template, tt.args.values); got != tt.want {
- t.Errorf("RenderToMarkdown() = %v, want %v", got, tt.want)
+ assert.EqualValues(t, tt.want, got)
}
})
}
diff --git a/modules/issue/template/unmarshal.go b/modules/issue/template/unmarshal.go
index 8cae8d4c42..0fc13d7ddf 100644
--- a/modules/issue/template/unmarshal.go
+++ b/modules/issue/template/unmarshal.go
@@ -128,9 +128,18 @@ func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
}
}
for i, v := range it.Fields {
+ // set default id value
if v.ID == "" {
v.ID = strconv.Itoa(i)
}
+ // set default visibility
+ if v.Visible == nil {
+ v.Visible = []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm}
+ // markdown is not submitted by default
+ if v.Type != api.IssueFormFieldTypeMarkdown {
+ v.Visible = append(v.Visible, api.IssueFormFieldVisibleContent)
+ }
+ }
}
}
diff --git a/modules/markup/csv/csv.go b/modules/markup/csv/csv.go
index 7af34a6cbc..12458e954a 100644
--- a/modules/markup/csv/csv.go
+++ b/modules/markup/csv/csv.go
@@ -93,8 +93,10 @@ func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Wri
if _, err := tmpBlock.WriteString(html.EscapeString(string(rawBytes))); err != nil {
return err
}
- _, err = tmpBlock.WriteString("")
- return err
+ if _, err := tmpBlock.WriteString(""); err != nil {
+ return err
+ }
+ return tmpBlock.Flush()
}
rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, bytes.NewReader(rawBytes))
diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go
index 89ecfc036b..132955c019 100644
--- a/modules/markup/html_test.go
+++ b/modules/markup/html_test.go
@@ -388,7 +388,7 @@ func TestRender_ShortLinks(t *testing.T) {
},
}, input)
assert.NoError(t, err)
- assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
+ assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
buffer, err = markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
Links: markup.Links{
@@ -398,7 +398,7 @@ func TestRender_ShortLinks(t *testing.T) {
IsWiki: true,
}, input)
assert.NoError(t, err)
- assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer))
+ assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
}
mediatree := util.URLJoin(markup.TestRepoURL, "media", "master")
@@ -501,7 +501,7 @@ func TestRender_RelativeImages(t *testing.T) {
Metas: localMetas,
}, input)
assert.NoError(t, err)
- assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
+ assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
buffer, err = markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
Links: markup.Links{
@@ -511,7 +511,7 @@ func TestRender_RelativeImages(t *testing.T) {
IsWiki: true,
}, input)
assert.NoError(t, err)
- assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer))
+ assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
}
rawwiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw")
diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go
index 00d01a2f55..7750279ef2 100644
--- a/modules/markup/markdown/markdown.go
+++ b/modules/markup/markdown/markdown.go
@@ -6,6 +6,7 @@ package markdown
import (
"fmt"
+ "html/template"
"io"
"strings"
"sync"
@@ -266,12 +267,12 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
}
// RenderString renders Markdown string to HTML with all specific handling stuff and return string
-func RenderString(ctx *markup.RenderContext, content string) (string, error) {
+func RenderString(ctx *markup.RenderContext, content string) (template.HTML, error) {
var buf strings.Builder
if err := Render(ctx, strings.NewReader(content), &buf); err != nil {
return "", err
}
- return buf.String(), nil
+ return template.HTML(buf.String()), nil
}
// RenderRaw renders Markdown to HTML without handling special links.
diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
index 1be8c6a273..f591a5057e 100644
--- a/modules/markup/markdown/markdown_test.go
+++ b/modules/markup/markdown/markdown_test.go
@@ -5,6 +5,7 @@ package markdown_test
import (
"context"
+ "html/template"
"os"
"strings"
"testing"
@@ -22,12 +23,11 @@ import (
)
const (
- AppURL = "http://localhost:3000/"
- Repo = "gogits/gogs"
- AppSubURL = AppURL + Repo + "/"
+ AppURL = "http://localhost:3000/"
+ FullURL = AppURL + "gogits/gogs/"
)
-// these values should match the Repo const above
+// these values should match the const above
var localMetas = map[string]string{
"user": "gogits",
"repo": "gogs",
@@ -49,34 +49,33 @@ func TestMain(m *testing.M) {
func TestRender_StandardLinks(t *testing.T) {
setting.AppURL = AppURL
- setting.AppSubURL = AppSubURL
test := func(input, expected, expectedWiki string) {
buffer, err := markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
Links: markup.Links{
- Base: setting.AppSubURL,
+ Base: FullURL,
},
}, input)
assert.NoError(t, err)
- assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
+ assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
buffer, err = markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
Links: markup.Links{
- Base: setting.AppSubURL,
+ Base: FullURL,
},
IsWiki: true,
}, input)
assert.NoError(t, err)
- assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer))
+ assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
}
googleRendered := `https://google.com/
`
test(" ", googleRendered, googleRendered)
- lnk := util.URLJoin(AppSubURL, "WikiPage")
- lnkWiki := util.URLJoin(AppSubURL, "wiki", "WikiPage")
+ lnk := util.URLJoin(FullURL, "WikiPage")
+ lnkWiki := util.URLJoin(FullURL, "wiki", "WikiPage")
test("[WikiPage](WikiPage)",
`WikiPage
`,
`WikiPage
`)
@@ -84,23 +83,22 @@ func TestRender_StandardLinks(t *testing.T) {
func TestRender_Images(t *testing.T) {
setting.AppURL = AppURL
- setting.AppSubURL = AppSubURL
test := func(input, expected string) {
buffer, err := markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
Links: markup.Links{
- Base: setting.AppSubURL,
+ Base: FullURL,
},
}, input)
assert.NoError(t, err)
- assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
+ assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
}
url := "../../.images/src/02/train.jpg"
title := "Train"
href := "https://gitea.io"
- result := util.URLJoin(AppSubURL, url)
+ result := util.URLJoin(FullURL, url)
// hint: With Markdown v2.5.2, there is a new syntax: [link](URL){:target="_blank"} , but we do not support it now
test(
@@ -290,33 +288,32 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno
func TestTotal_RenderWiki(t *testing.T) {
setting.AppURL = AppURL
- setting.AppSubURL = AppSubURL
- answers := testAnswers(util.URLJoin(AppSubURL, "wiki"), util.URLJoin(AppSubURL, "wiki", "raw"))
+ answers := testAnswers(util.URLJoin(FullURL, "wiki"), util.URLJoin(FullURL, "wiki", "raw"))
for i := 0; i < len(sameCases); i++ {
line, err := markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
Links: markup.Links{
- Base: setting.AppSubURL,
+ Base: FullURL,
},
Metas: localMetas,
IsWiki: true,
}, sameCases[i])
assert.NoError(t, err)
- assert.Equal(t, answers[i], line)
+ assert.Equal(t, template.HTML(answers[i]), line)
}
testCases := []string{
// Guard wiki sidebar: special syntax
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
// rendered
- `Guardfile-DSL / Configuring-Guard
+ `Guardfile-DSL / Configuring-Guard
`,
// special syntax
`[[Name|Link]]`,
// rendered
- `Name
+ `Name
`,
}
@@ -324,32 +321,31 @@ func TestTotal_RenderWiki(t *testing.T) {
line, err := markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
Links: markup.Links{
- Base: setting.AppSubURL,
+ Base: FullURL,
},
IsWiki: true,
}, testCases[i])
assert.NoError(t, err)
- assert.Equal(t, testCases[i+1], line)
+ assert.Equal(t, template.HTML(testCases[i+1]), line)
}
}
func TestTotal_RenderString(t *testing.T) {
setting.AppURL = AppURL
- setting.AppSubURL = AppSubURL
- answers := testAnswers(util.URLJoin(AppSubURL, "src", "master"), util.URLJoin(AppSubURL, "media", "master"))
+ answers := testAnswers(util.URLJoin(FullURL, "src", "master"), util.URLJoin(FullURL, "media", "master"))
for i := 0; i < len(sameCases); i++ {
line, err := markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
Links: markup.Links{
- Base: AppSubURL,
+ Base: FullURL,
BranchPath: "master",
},
Metas: localMetas,
}, sameCases[i])
assert.NoError(t, err)
- assert.Equal(t, answers[i], line)
+ assert.Equal(t, template.HTML(answers[i]), line)
}
testCases := []string{}
@@ -358,11 +354,11 @@ func TestTotal_RenderString(t *testing.T) {
line, err := markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
Links: markup.Links{
- Base: AppSubURL,
+ Base: FullURL,
},
}, testCases[i])
assert.NoError(t, err)
- assert.Equal(t, testCases[i+1], line)
+ assert.Equal(t, template.HTML(testCases[i+1]), line)
}
}
@@ -429,7 +425,7 @@ func TestRenderEmojiInLinks_Issue12331(t *testing.T) {
`
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase)
assert.NoError(t, err)
- assert.Equal(t, expected, res)
+ assert.Equal(t, template.HTML(expected), res)
}
func TestColorPreview(t *testing.T) {
@@ -463,7 +459,7 @@ func TestColorPreview(t *testing.T) {
for _, test := range positiveTests {
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
- assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase)
+ assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
}
@@ -542,7 +538,7 @@ func TestMathBlock(t *testing.T) {
for _, test := range testcases {
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
- assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase)
+ assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
}
}
@@ -741,7 +737,7 @@ Citation needed[^0].`,
for _, test := range testcases {
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
- assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase)
+ assert.Equal(t, test.expected, string(res), "Unexpected result in testcase %q", test.testcase)
}
}
@@ -778,12 +774,12 @@ foo: bar
for _, test := range testcases {
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
- assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase)
+ assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
}
}
func TestRenderLinks(t *testing.T) {
- input := ` space @mention-user
+ input := ` space @mention-user${SPACE}${SPACE}
/just/a/path.bin
https://example.com/file.bin
[local link](file.bin)
@@ -804,8 +800,9 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
mail@domain.com
@mention-user test
#123
- space
+ space${SPACE}${SPACE}
`
+ input = strings.ReplaceAll(input, "${SPACE}", " ") // replace ${SPACE} with " ", to avoid some editor's auto-trimming
cases := []struct {
Links markup.Links
IsWiki bool
@@ -1168,26 +1165,24 @@ space
for i, c := range cases {
result, err := markdown.RenderString(&markup.RenderContext{Ctx: context.Background(), Links: c.Links, IsWiki: c.IsWiki}, input)
assert.NoError(t, err, "Unexpected error in testcase: %v", i)
- assert.Equal(t, c.Expected, result, "Unexpected result in testcase %v", i)
+ assert.Equal(t, template.HTML(c.Expected), result, "Unexpected result in testcase %v", i)
}
}
func TestCustomMarkdownURL(t *testing.T) {
defer test.MockVariableValue(&setting.Markdown.CustomURLSchemes, []string{"abp"})()
-
setting.AppURL = AppURL
- setting.AppSubURL = AppSubURL
test := func(input, expected string) {
buffer, err := markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
Links: markup.Links{
- Base: setting.AppSubURL,
+ Base: FullURL,
BranchPath: "branch/main",
},
}, input)
assert.NoError(t, err)
- assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
+ assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
}
test("[test](abp:subscribe?location=https://codeberg.org/filters.txt&title=joy)",
diff --git a/modules/optional/option_test.go b/modules/optional/option_test.go
index 410fd73577..4f55608004 100644
--- a/modules/optional/option_test.go
+++ b/modules/optional/option_test.go
@@ -27,6 +27,16 @@ func TestOption(t *testing.T) {
assert.Equal(t, int(1), some.Value())
assert.Equal(t, int(1), some.ValueOrDefault(2))
+ noneBool := optional.None[bool]()
+ assert.False(t, noneBool.Has())
+ assert.False(t, noneBool.Value())
+ assert.True(t, noneBool.ValueOrDefault(true))
+
+ someBool := optional.Some(true)
+ assert.True(t, someBool.Has())
+ assert.True(t, someBool.Value())
+ assert.True(t, someBool.ValueOrDefault(false))
+
var ptr *int
assert.False(t, optional.FromPtr(ptr).Has())
diff --git a/modules/queue/workergroup.go b/modules/queue/workergroup.go
index 147a4f335e..e3801ef2b2 100644
--- a/modules/queue/workergroup.go
+++ b/modules/queue/workergroup.go
@@ -60,6 +60,9 @@ func (q *WorkerPoolQueue[T]) doDispatchBatchToWorker(wg *workerGroup[T], flushCh
full = true
}
+ // TODO: the logic could be improved in the future, to avoid a data-race between "doStartNewWorker" and "workerNum"
+ // The root problem is that if we skip "doStartNewWorker" here, the "workerNum" might be decreased by other workers later
+ // So ideally, it should check whether there are enough workers by some approaches, and start new workers if necessary.
q.workerNumMu.Lock()
noWorker := q.workerNum == 0
if full || noWorker {
@@ -143,7 +146,11 @@ func (q *WorkerPoolQueue[T]) doStartNewWorker(wp *workerGroup[T]) {
log.Debug("Queue %q starts new worker", q.GetName())
defer log.Debug("Queue %q stops idle worker", q.GetName())
+ atomic.AddInt32(&q.workerStartedCounter, 1) // Only increase counter, used for debugging
+
t := time.NewTicker(workerIdleDuration)
+ defer t.Stop()
+
keepWorking := true
stopWorking := func() {
q.workerNumMu.Lock()
@@ -158,13 +165,18 @@ func (q *WorkerPoolQueue[T]) doStartNewWorker(wp *workerGroup[T]) {
case batch, ok := <-q.batchChan:
if !ok {
stopWorking()
- } else {
- q.doWorkerHandle(batch)
- t.Reset(workerIdleDuration)
+ continue
+ }
+ q.doWorkerHandle(batch)
+ // reset the idle ticker, and drain the tick after reset in case a tick is already triggered
+ t.Reset(workerIdleDuration)
+ select {
+ case <-t.C:
+ default:
}
case <-t.C:
q.workerNumMu.Lock()
- keepWorking = q.workerNum <= 1
+ keepWorking = q.workerNum <= 1 // keep the last worker running
if !keepWorking {
q.workerNum--
}
diff --git a/modules/queue/workerqueue.go b/modules/queue/workerqueue.go
index b28fd88027..4160622d81 100644
--- a/modules/queue/workerqueue.go
+++ b/modules/queue/workerqueue.go
@@ -40,6 +40,8 @@ type WorkerPoolQueue[T any] struct {
workerMaxNum int
workerActiveNum int
workerNumMu sync.Mutex
+
+ workerStartedCounter int32
}
type flushType chan struct{}
diff --git a/modules/queue/workerqueue_test.go b/modules/queue/workerqueue_test.go
index e60120162a..e09669c542 100644
--- a/modules/queue/workerqueue_test.go
+++ b/modules/queue/workerqueue_test.go
@@ -11,6 +11,7 @@ import (
"time"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
@@ -175,11 +176,7 @@ func testWorkerPoolQueuePersistence(t *testing.T, queueSetting setting.QueueSett
}
func TestWorkerPoolQueueActiveWorkers(t *testing.T) {
- oldWorkerIdleDuration := workerIdleDuration
- workerIdleDuration = 300 * time.Millisecond
- defer func() {
- workerIdleDuration = oldWorkerIdleDuration
- }()
+ defer test.MockVariableValue(&workerIdleDuration, 300*time.Millisecond)()
handler := func(items ...int) (unhandled []int) {
time.Sleep(100 * time.Millisecond)
@@ -250,3 +247,25 @@ func TestWorkerPoolQueueShutdown(t *testing.T) {
q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", qs, handler, false)
assert.EqualValues(t, 20, q.GetQueueItemNumber())
}
+
+func TestWorkerPoolQueueWorkerIdleReset(t *testing.T) {
+ defer test.MockVariableValue(&workerIdleDuration, 10*time.Millisecond)()
+
+ handler := func(items ...int) (unhandled []int) {
+ time.Sleep(50 * time.Millisecond)
+ return nil
+ }
+
+ q, _ := newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 2, Length: 100}, handler, false)
+ stop := runWorkerPoolQueue(q)
+ for i := 0; i < 20; i++ {
+ assert.NoError(t, q.Push(i))
+ }
+
+ time.Sleep(500 * time.Millisecond)
+ assert.EqualValues(t, 2, q.GetWorkerNumber())
+ assert.EqualValues(t, 2, q.GetWorkerActiveNumber())
+ // when the queue never becomes empty, the existing workers should keep working
+ assert.EqualValues(t, 2, q.workerStartedCounter)
+ stop()
+}
diff --git a/modules/references/references.go b/modules/references/references.go
index fce893cf5b..fd10992e8e 100644
--- a/modules/references/references.go
+++ b/modules/references/references.go
@@ -31,9 +31,9 @@ var (
// mentionPattern matches all mentions in the form of "@user" or "@org/team"
mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_]+\/?[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_][0-9a-zA-Z-_.]+\/?[0-9a-zA-Z-_.]+[0-9a-zA-Z-_])(?:'|\s|[:,;.?!]\s|[:,;.?!]?$|\)|\])`)
// issueNumericPattern matches string that references to a numeric issue, e.g. #1287
- issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\')([#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
+ issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\'|\")([#!][0-9]+)(?:\s|$|\)|\]|\'|\"|[:;,.?!]\s|[:;,.?!]$)`)
// issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234
- issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$))`)
+ issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\"|\')([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$)|\"|\')`)
// crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository
// e.g. org/repo#12345
crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
diff --git a/modules/references/references_test.go b/modules/references/references_test.go
index 3f1f52401a..498374b2a7 100644
--- a/modules/references/references_test.go
+++ b/modules/references/references_test.go
@@ -432,6 +432,8 @@ func TestRegExp_issueNumericPattern(t *testing.T) {
" #12",
"#12:",
"ref: #12: msg",
+ "\"#1234\"",
+ "'#1234'",
}
falseTestCases := []string{
"# 1234",
@@ -462,6 +464,8 @@ func TestRegExp_issueAlphanumericPattern(t *testing.T) {
"(ABC-123)",
"[ABC-123]",
"ABC-123:",
+ "\"ABC-123\"",
+ "'ABC-123'",
}
falseTestCases := []string{
"RC-08",
diff --git a/modules/repository/init.go b/modules/repository/init.go
index b90b234a73..5f500c5233 100644
--- a/modules/repository/init.go
+++ b/modules/repository/init.go
@@ -6,22 +6,18 @@ package repository
import (
"context"
"fmt"
- "os"
"path/filepath"
"sort"
"strings"
- "time"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
- user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
- asymkey_service "code.gitea.io/gitea/services/asymkey"
)
type OptionFile struct {
@@ -124,70 +120,6 @@ func LoadRepoConfig() error {
return nil
}
-// InitRepoCommit temporarily changes with work directory.
-func InitRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Repository, u *user_model.User, defaultBranch string) (err error) {
- commitTimeStr := time.Now().Format(time.RFC3339)
-
- sig := u.NewGitSig()
- // Because this may call hooks we should pass in the environment
- env := append(os.Environ(),
- "GIT_AUTHOR_NAME="+sig.Name,
- "GIT_AUTHOR_EMAIL="+sig.Email,
- "GIT_AUTHOR_DATE="+commitTimeStr,
- "GIT_COMMITTER_DATE="+commitTimeStr,
- )
- committerName := sig.Name
- committerEmail := sig.Email
-
- if stdout, _, err := git.NewCommand(ctx, "add", "--all").
- SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)).
- RunStdString(&git.RunOpts{Dir: tmpPath}); err != nil {
- log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err)
- return fmt.Errorf("git add --all: %w", err)
- }
-
- cmd := git.NewCommand(ctx, "commit", "--message=Initial commit").
- AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)
-
- sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u)
- if sign {
- cmd.AddOptionFormat("-S%s", keyID)
-
- if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
- // need to set the committer to the KeyID owner
- committerName = signer.Name
- committerEmail = signer.Email
- }
- } else {
- cmd.AddArguments("--no-gpg-sign")
- }
-
- env = append(env,
- "GIT_COMMITTER_NAME="+committerName,
- "GIT_COMMITTER_EMAIL="+committerEmail,
- )
-
- if stdout, _, err := cmd.
- SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)).
- RunStdString(&git.RunOpts{Dir: tmpPath, Env: env}); err != nil {
- log.Error("Failed to commit: %v: Stdout: %s\nError: %v", cmd.String(), stdout, err)
- return fmt.Errorf("git commit: %w", err)
- }
-
- if len(defaultBranch) == 0 {
- defaultBranch = setting.Repository.DefaultBranch
- }
-
- if stdout, _, err := git.NewCommand(ctx, "push", "origin").AddDynamicArguments("HEAD:" + defaultBranch).
- SetDescription(fmt.Sprintf("initRepoCommit (git push): %s", tmpPath)).
- RunStdString(&git.RunOpts{Dir: tmpPath, Env: InternalPushingEnvironment(u, repo)}); err != nil {
- log.Error("Failed to push back to HEAD: Stdout: %s\nError: %v", stdout, err)
- return fmt.Errorf("git push: %w", err)
- }
-
- return nil
-}
-
func CheckInitRepository(ctx context.Context, owner, name, objectFormatName string) (err error) {
// Somehow the directory could exist.
repoPath := repo_model.RepoPath(owner, name)
diff --git a/modules/repository/repo.go b/modules/repository/repo.go
index 2f076c5286..a863bec996 100644
--- a/modules/repository/repo.go
+++ b/modules/repository/repo.go
@@ -6,16 +6,13 @@ package repository
import (
"context"
- "errors"
"fmt"
"io"
- "net/http"
"strings"
"time"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
- "code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
@@ -23,10 +20,8 @@ import (
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/migration"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
)
/*
@@ -48,267 +43,6 @@ func WikiRemoteURL(ctx context.Context, remote string) string {
return ""
}
-// MigrateRepositoryGitData starts migrating git related data after created migrating repository
-func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
- repo *repo_model.Repository, opts migration.MigrateOptions,
- httpTransport *http.Transport,
-) (*repo_model.Repository, error) {
- repoPath := repo_model.RepoPath(u.Name, opts.RepoName)
-
- if u.IsOrganization() {
- t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx)
- if err != nil {
- return nil, err
- }
- repo.NumWatches = t.NumMembers
- } else {
- repo.NumWatches = 1
- }
-
- migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second
-
- var err error
- if err = util.RemoveAll(repoPath); err != nil {
- return repo, fmt.Errorf("Failed to remove %s: %w", repoPath, err)
- }
-
- if err = git.Clone(ctx, opts.CloneAddr, repoPath, git.CloneRepoOptions{
- Mirror: true,
- Quiet: true,
- Timeout: migrateTimeout,
- SkipTLSVerify: setting.Migrations.SkipTLSVerify,
- }); err != nil {
- if errors.Is(err, context.DeadlineExceeded) {
- return repo, fmt.Errorf("Clone timed out. Consider increasing [git.timeout] MIGRATE in app.ini. Underlying Error: %w", err)
- }
- return repo, fmt.Errorf("Clone: %w", err)
- }
-
- if err := git.WriteCommitGraph(ctx, repoPath); err != nil {
- return repo, err
- }
-
- if opts.Wiki {
- wikiPath := repo_model.WikiPath(u.Name, opts.RepoName)
- wikiRemotePath := WikiRemoteURL(ctx, opts.CloneAddr)
- if len(wikiRemotePath) > 0 {
- if err := util.RemoveAll(wikiPath); err != nil {
- return repo, fmt.Errorf("Failed to remove %s: %w", wikiPath, err)
- }
-
- if err := git.Clone(ctx, wikiRemotePath, wikiPath, git.CloneRepoOptions{
- Mirror: true,
- Quiet: true,
- Timeout: migrateTimeout,
- SkipTLSVerify: setting.Migrations.SkipTLSVerify,
- }); err != nil {
- log.Warn("Clone wiki: %v", err)
- if err := util.RemoveAll(wikiPath); err != nil {
- return repo, fmt.Errorf("Failed to remove %s: %w", wikiPath, err)
- }
- } else {
- // Figure out the branch of the wiki we just cloned. We assume
- // that the default branch is to be used, and we'll use the same
- // name as the source.
- gitRepo, err := git.OpenRepository(ctx, wikiPath)
- if err != nil {
- log.Warn("Failed to open wiki repository during migration: %v", err)
- if err := util.RemoveAll(wikiPath); err != nil {
- return repo, fmt.Errorf("Failed to remove %s: %w", wikiPath, err)
- }
- return repo, err
- }
- defer gitRepo.Close()
-
- branch, err := gitRepo.GetDefaultBranch()
- if err != nil {
- log.Warn("Failed to get the default branch of a migrated wiki repo: %v", err)
- if err := util.RemoveAll(wikiPath); err != nil {
- return repo, fmt.Errorf("Failed to remove %s: %w", wikiPath, err)
- }
-
- return repo, err
- }
- repo.WikiBranch = branch
-
- if err := git.WriteCommitGraph(ctx, wikiPath); err != nil {
- return repo, err
- }
- }
- }
- }
-
- if repo.OwnerID == u.ID {
- repo.Owner = u
- }
-
- if err = CheckDaemonExportOK(ctx, repo); err != nil {
- return repo, fmt.Errorf("checkDaemonExportOK: %w", err)
- }
-
- if stdout, _, err := git.NewCommand(ctx, "update-server-info").
- SetDescription(fmt.Sprintf("MigrateRepositoryGitData(git update-server-info): %s", repoPath)).
- RunStdString(&git.RunOpts{Dir: repoPath}); err != nil {
- log.Error("MigrateRepositoryGitData(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
- return repo, fmt.Errorf("error in MigrateRepositoryGitData(git update-server-info): %w", err)
- }
-
- gitRepo, err := git.OpenRepository(ctx, repoPath)
- if err != nil {
- return repo, fmt.Errorf("OpenRepository: %w", err)
- }
- defer gitRepo.Close()
-
- repo.IsEmpty, err = gitRepo.IsEmpty()
- if err != nil {
- return repo, fmt.Errorf("git.IsEmpty: %w", err)
- }
-
- if !repo.IsEmpty {
- if len(repo.DefaultBranch) == 0 {
- // Try to get HEAD branch and set it as default branch.
- headBranch, err := gitRepo.GetHEADBranch()
- if err != nil {
- return repo, fmt.Errorf("GetHEADBranch: %w", err)
- }
- if headBranch != nil {
- repo.DefaultBranch = headBranch.Name
- }
- }
-
- if _, err := SyncRepoBranchesWithRepo(ctx, repo, gitRepo, u.ID); err != nil {
- return repo, fmt.Errorf("SyncRepoBranchesWithRepo: %v", err)
- }
-
- if !opts.Releases {
- // note: this will greatly improve release (tag) sync
- // for pull-mirrors with many tags
- repo.IsMirror = opts.Mirror
- if err = SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
- log.Error("Failed to synchronize tags to releases for repository: %v", err)
- }
- }
-
- if opts.LFS {
- endpoint := lfs.DetermineEndpoint(opts.CloneAddr, opts.LFSEndpoint)
- lfsClient := lfs.NewClient(endpoint, httpTransport)
- if err = StoreMissingLfsObjectsInRepository(ctx, repo, gitRepo, lfsClient); err != nil {
- log.Error("Failed to store missing LFS objects for repository: %v", err)
- }
- }
- }
-
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return nil, err
- }
- defer committer.Close()
-
- if opts.Mirror {
- remoteAddress, err := util.SanitizeURL(opts.CloneAddr)
- if err != nil {
- return repo, err
- }
- mirrorModel := repo_model.Mirror{
- RepoID: repo.ID,
- Interval: setting.Mirror.DefaultInterval,
- EnablePrune: true,
- NextUpdateUnix: timeutil.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval),
- LFS: opts.LFS,
- RemoteAddress: remoteAddress,
- }
- if opts.LFS {
- mirrorModel.LFSEndpoint = opts.LFSEndpoint
- }
-
- if opts.MirrorInterval != "" {
- parsedInterval, err := time.ParseDuration(opts.MirrorInterval)
- if err != nil {
- log.Error("Failed to set Interval: %v", err)
- return repo, err
- }
- if parsedInterval == 0 {
- mirrorModel.Interval = 0
- mirrorModel.NextUpdateUnix = 0
- } else if parsedInterval < setting.Mirror.MinInterval {
- err := fmt.Errorf("interval %s is set below Minimum Interval of %s", parsedInterval, setting.Mirror.MinInterval)
- log.Error("Interval: %s is too frequent", opts.MirrorInterval)
- return repo, err
- } else {
- mirrorModel.Interval = parsedInterval
- mirrorModel.NextUpdateUnix = timeutil.TimeStampNow().AddDuration(parsedInterval)
- }
- }
-
- if err = repo_model.InsertMirror(ctx, &mirrorModel); err != nil {
- return repo, fmt.Errorf("InsertOne: %w", err)
- }
-
- repo.IsMirror = true
- if err = UpdateRepository(ctx, repo, false); err != nil {
- return nil, err
- }
-
- // this is necessary for sync local tags from remote
- configName := fmt.Sprintf("remote.%s.fetch", mirrorModel.GetRemoteName())
- if stdout, _, err := git.NewCommand(ctx, "config").
- AddOptionValues("--add", configName, `+refs/tags/*:refs/tags/*`).
- RunStdString(&git.RunOpts{Dir: repoPath}); err != nil {
- log.Error("MigrateRepositoryGitData(git config --add +refs/tags/*:refs/tags/*) in %v: Stdout: %s\nError: %v", repo, stdout, err)
- return repo, fmt.Errorf("error in MigrateRepositoryGitData(git config --add +refs/tags/*:refs/tags/*): %w", err)
- }
- } else {
- if err = UpdateRepoSize(ctx, repo); err != nil {
- log.Error("Failed to update size for repository: %v", err)
- }
- if repo, err = CleanUpMigrateInfo(ctx, repo); err != nil {
- return nil, err
- }
- }
-
- return repo, committer.Commit()
-}
-
-// cleanUpMigrateGitConfig removes mirror info which prevents "push --all".
-// This also removes possible user credentials.
-func cleanUpMigrateGitConfig(ctx context.Context, repoPath string) error {
- cmd := git.NewCommand(ctx, "remote", "rm", "origin")
- // if the origin does not exist
- _, stderr, err := cmd.RunStdString(&git.RunOpts{
- Dir: repoPath,
- })
- if err != nil && !strings.HasPrefix(stderr, "fatal: No such remote") {
- return err
- }
- return nil
-}
-
-// CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors.
-func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo_model.Repository, error) {
- repoPath := repo.RepoPath()
- if err := CreateDelegateHooks(repoPath); err != nil {
- return repo, fmt.Errorf("createDelegateHooks: %w", err)
- }
- if repo.HasWiki() {
- if err := CreateDelegateHooks(repo.WikiPath()); err != nil {
- return repo, fmt.Errorf("createDelegateHooks.(wiki): %w", err)
- }
- }
-
- _, _, err := git.NewCommand(ctx, "remote", "rm", "origin").RunStdString(&git.RunOpts{Dir: repoPath})
- if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") {
- return repo, fmt.Errorf("CleanUpMigrateInfo: %w", err)
- }
-
- if repo.HasWiki() {
- if err := cleanUpMigrateGitConfig(ctx, repo.WikiPath()); err != nil {
- return repo, fmt.Errorf("cleanUpMigrateGitConfig (wiki): %w", err)
- }
- }
-
- return repo, UpdateRepository(ctx, repo, false)
-}
-
// SyncRepoTags synchronizes releases table with repository tags
func SyncRepoTags(ctx context.Context, repoID int64) error {
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
diff --git a/modules/setting/admin.go b/modules/setting/admin.go
index 502efd0eb9..35ffa9efbf 100644
--- a/modules/setting/admin.go
+++ b/modules/setting/admin.go
@@ -21,5 +21,7 @@ func loadAdminFrom(rootCfg ConfigProvider) {
}
const (
- UserFeatureDeletion = "deletion"
+ UserFeatureDeletion = "deletion"
+ UserFeatureManageSSHKeys = "manage_ssh_keys"
+ UserFeatureManageGPGKeys = "manage_gpg_keys"
)
diff --git a/modules/setting/attachment.go b/modules/setting/attachment.go
index 934d4d7f46..0fdabb5032 100644
--- a/modules/setting/attachment.go
+++ b/modules/setting/attachment.go
@@ -12,7 +12,7 @@ var Attachment = struct {
Enabled bool
}{
Storage: &Storage{},
- AllowedTypes: ".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",
+ AllowedTypes: ".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",
MaxSize: 2048,
MaxFiles: 5,
Enabled: true,
@@ -25,7 +25,7 @@ func loadAttachmentFrom(rootCfg ConfigProvider) (err error) {
return err
}
- Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".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")
+ Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".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")
Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(2048)
Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5)
Attachment.Enabled = sec.Key("ENABLED").MustBool(true)
diff --git a/modules/setting/repository.go b/modules/setting/repository.go
index 7a07fec85c..65f8d11b8d 100644
--- a/modules/setting/repository.go
+++ b/modules/setting/repository.go
@@ -22,8 +22,12 @@ const (
var RecognisedRepositoryDownloadOrCloneMethods = []string{"download-zip", "download-targz", "download-bundle", "vscode-clone", "vscodium-clone", "cite"}
-// ItemsPerPage maximum items per page in forks, watchers and stars of a repo
-const ItemsPerPage = 40
+// MaxUserCardsPerPage sets maximum amount of watchers and stargazers shown per page
+// those pages use 2 or 3 column layout, so the value should be divisible by 2 and 3
+var MaxUserCardsPerPage = 36
+
+// MaxForksPerPage sets maximum amount of forks shown per page
+var MaxForksPerPage = 40
// Repository settings
var (
diff --git a/modules/setting/session.go b/modules/setting/session.go
index 664c66f869..e9637fdfc5 100644
--- a/modules/setting/session.go
+++ b/modules/setting/session.go
@@ -21,7 +21,7 @@ var SessionConfig = struct {
ProviderConfig string
// Cookie name to save session ID. Default is "MacaronSession".
CookieName string
- // Cookie path to store. Default is "/". HINT: there was a bug, the old value doesn't have trailing slash, and could be empty "".
+ // Cookie path to store. Default is "/".
CookiePath string
// GC interval time in seconds. Default is 3600.
Gclifetime int64
@@ -49,7 +49,10 @@ func loadSessionFrom(rootCfg ConfigProvider) {
SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig)
}
SessionConfig.CookieName = sec.Key("COOKIE_NAME").MustString("i_like_gitea")
- SessionConfig.CookiePath = AppSubURL + "/" // there was a bug, old code only set CookePath=AppSubURL, no trailing slash
+ SessionConfig.CookiePath = AppSubURL
+ if SessionConfig.CookiePath == "" {
+ SessionConfig.CookiePath = "/"
+ }
SessionConfig.Secure = sec.Key("COOKIE_SECURE").MustBool(strings.HasPrefix(strings.ToLower(AppURL), "https://"))
SessionConfig.Gclifetime = sec.Key("GC_INTERVAL_TIME").MustInt64(86400)
SessionConfig.Maxlifetime = sec.Key("SESSION_LIFE_TIME").MustInt64(86400)
diff --git a/modules/setting/storage.go b/modules/setting/storage.go
index f937c7cff3..1e2d28a88b 100644
--- a/modules/setting/storage.go
+++ b/modules/setting/storage.go
@@ -41,6 +41,7 @@ type MinioStorageConfig struct {
AccessKeyID string `ini:"MINIO_ACCESS_KEY_ID" json:",omitempty"`
SecretAccessKey string `ini:"MINIO_SECRET_ACCESS_KEY" json:",omitempty"`
Bucket string `ini:"MINIO_BUCKET" json:",omitempty"`
+ BucketLookup string `ini:"MINIO_BUCKET_LOOKUP" json:",omitempty"`
Location string `ini:"MINIO_LOCATION" json:",omitempty"`
BasePath string `ini:"MINIO_BASE_PATH" json:",omitempty"`
UseSSL bool `ini:"MINIO_USE_SSL"`
@@ -78,6 +79,7 @@ func getDefaultStorageSection(rootCfg ConfigProvider) ConfigSection {
storageSec.Key("MINIO_ACCESS_KEY_ID").MustString("")
storageSec.Key("MINIO_SECRET_ACCESS_KEY").MustString("")
storageSec.Key("MINIO_BUCKET").MustString("gitea")
+ storageSec.Key("MINIO_BUCKET_LOOKUP").MustString("auto")
storageSec.Key("MINIO_LOCATION").MustString("us-east-1")
storageSec.Key("MINIO_USE_SSL").MustBool(false)
storageSec.Key("MINIO_INSECURE_SKIP_VERIFY").MustBool(false)
diff --git a/modules/storage/minio.go b/modules/storage/minio.go
index b58ab67dc7..0b65577cb5 100644
--- a/modules/storage/minio.go
+++ b/modules/storage/minio.go
@@ -82,14 +82,26 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage,
if config.ChecksumAlgorithm != "" && config.ChecksumAlgorithm != "default" && config.ChecksumAlgorithm != "md5" {
return nil, fmt.Errorf("invalid minio checksum algorithm: %s", config.ChecksumAlgorithm)
}
+ var lookup minio.BucketLookupType
+ switch config.BucketLookup {
+ case "auto", "":
+ lookup = minio.BucketLookupAuto
+ case "dns":
+ lookup = minio.BucketLookupDNS
+ case "path":
+ lookup = minio.BucketLookupPath
+ default:
+ return nil, fmt.Errorf("invalid minio bucket lookup type %s", config.BucketLookup)
+ }
log.Info("Creating Minio storage at %s:%s with base path %s", config.Endpoint, config.Bucket, config.BasePath)
minioClient, err := minio.New(config.Endpoint, &minio.Options{
- Creds: credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, ""),
- Secure: config.UseSSL,
- Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}},
- Region: config.Location,
+ Creds: credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, ""),
+ Secure: config.UseSSL,
+ Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}},
+ Region: config.Location,
+ BucketLookup: lookup,
})
if err != nil {
return nil, convertMinioErr(err)
diff --git a/modules/storage/minio_test.go b/modules/storage/minio_test.go
index 2364ced0ef..2e1a3028c7 100644
--- a/modules/storage/minio_test.go
+++ b/modules/storage/minio_test.go
@@ -31,6 +31,23 @@ func TestMinioStorageIterator(t *testing.T) {
})
}
+func TestVirtualHostMinioStorage(t *testing.T) {
+ if os.Getenv("CI") == "" {
+ t.Skip("minioStorage not present outside of CI")
+ return
+ }
+ testStorageIterator(t, setting.MinioStorageType, &setting.Storage{
+ MinioConfig: setting.MinioStorageConfig{
+ Endpoint: "minio:9000",
+ AccessKeyID: "123456",
+ SecretAccessKey: "12345678",
+ Bucket: "gitea",
+ Location: "us-east-1",
+ BucketLookup: "dns",
+ },
+ })
+}
+
func TestMinioStoragePath(t *testing.T) {
m := &MinioStorage{basePath: ""}
assert.Equal(t, "", m.buildMinioPath("/"))
diff --git a/modules/structs/issue.go b/modules/structs/issue.go
index 331dbc1f8e..e2b49e94c5 100644
--- a/modules/structs/issue.go
+++ b/modules/structs/issue.go
@@ -6,6 +6,7 @@ package structs
import (
"fmt"
"path"
+ "slices"
"strings"
"time"
@@ -143,12 +144,37 @@ const (
// IssueFormField represents a form field
// swagger:model
type IssueFormField struct {
- Type IssueFormFieldType `json:"type" yaml:"type"`
- ID string `json:"id" yaml:"id"`
- Attributes map[string]any `json:"attributes" yaml:"attributes"`
- Validations map[string]any `json:"validations" yaml:"validations"`
+ Type IssueFormFieldType `json:"type" yaml:"type"`
+ ID string `json:"id" yaml:"id"`
+ Attributes map[string]any `json:"attributes" yaml:"attributes"`
+ Validations map[string]any `json:"validations" yaml:"validations"`
+ Visible []IssueFormFieldVisible `json:"visible,omitempty"`
}
+func (iff IssueFormField) VisibleOnForm() bool {
+ if len(iff.Visible) == 0 {
+ return true
+ }
+ return slices.Contains(iff.Visible, IssueFormFieldVisibleForm)
+}
+
+func (iff IssueFormField) VisibleInContent() bool {
+ if len(iff.Visible) == 0 {
+ // we have our markdown exception
+ return iff.Type != IssueFormFieldTypeMarkdown
+ }
+ return slices.Contains(iff.Visible, IssueFormFieldVisibleContent)
+}
+
+// IssueFormFieldVisible defines issue form field visible
+// swagger:model
+type IssueFormFieldVisible string
+
+const (
+ IssueFormFieldVisibleForm IssueFormFieldVisible = "form"
+ IssueFormFieldVisibleContent IssueFormFieldVisible = "content"
+)
+
// IssueTemplate represents an issue template for a repository
// swagger:model
type IssueTemplate struct {
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 708de57c91..7ef051cc0b 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -33,16 +33,16 @@ func NewFuncMap() template.FuncMap {
// -----------------------------------------------------------------
// html/template related functions
- "dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
- "Eval": Eval,
- "SafeHTML": SafeHTML,
- "HTMLFormat": HTMLFormat,
- "HTMLEscape": HTMLEscape,
- "QueryEscape": url.QueryEscape,
- "JSEscape": JSEscapeSafe,
- "Str2html": Str2html, // TODO: rename it to SanitizeHTML
- "URLJoin": util.URLJoin,
- "DotEscape": DotEscape,
+ "dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
+ "Eval": Eval,
+ "SafeHTML": SafeHTML,
+ "HTMLFormat": HTMLFormat,
+ "HTMLEscape": HTMLEscape,
+ "QueryEscape": url.QueryEscape,
+ "JSEscape": JSEscapeSafe,
+ "SanitizeHTML": SanitizeHTML,
+ "URLJoin": util.URLJoin,
+ "DotEscape": DotEscape,
"PathEscape": url.PathEscape,
"PathEscapeSegments": util.PathEscapeSegments,
@@ -210,15 +210,9 @@ func SafeHTML(s any) template.HTML {
panic(fmt.Sprintf("unexpected type %T", s))
}
-// Str2html sanitizes the input by pre-defined markdown rules
-func Str2html(s any) template.HTML {
- switch v := s.(type) {
- case string:
- return template.HTML(markup.Sanitize(v))
- case template.HTML:
- return template.HTML(markup.Sanitize(string(v)))
- }
- panic(fmt.Sprintf("unexpected type %T", s))
+// SanitizeHTML sanitizes the input by pre-defined markdown rules
+func SanitizeHTML(s string) template.HTML {
+ return template.HTML(markup.Sanitize(s))
}
func HTMLEscape(s any) template.HTML {
diff --git a/modules/templates/helper_test.go b/modules/templates/helper_test.go
index 8f5d633d4f..64f29d033e 100644
--- a/modules/templates/helper_test.go
+++ b/modules/templates/helper_test.go
@@ -61,3 +61,7 @@ func TestJSEscapeSafe(t *testing.T) {
func TestHTMLFormat(t *testing.T) {
assert.Equal(t, template.HTML("< < 1 "), HTMLFormat("%s %s %d ", "<", template.HTML("<"), 1))
}
+
+func TestSanitizeHTML(t *testing.T) {
+ assert.Equal(t, template.HTML(`link xss inline
`), SanitizeHTML(`link xss inline
`))
+}
diff --git a/modules/templates/mailer.go b/modules/templates/mailer.go
index 54d857a8f6..f1832cba0e 100644
--- a/modules/templates/mailer.go
+++ b/modules/templates/mailer.go
@@ -5,6 +5,7 @@ package templates
import (
"context"
+ "fmt"
"html/template"
"regexp"
"strings"
@@ -33,7 +34,7 @@ func mailSubjectTextFuncMap() texttmpl.FuncMap {
}
}
-func buildSubjectBodyTemplate(stpl *texttmpl.Template, btpl *template.Template, name string, content []byte) {
+func buildSubjectBodyTemplate(stpl *texttmpl.Template, btpl *template.Template, name string, content []byte) error {
// Split template into subject and body
var subjectContent []byte
bodyContent := content
@@ -42,14 +43,13 @@ func buildSubjectBodyTemplate(stpl *texttmpl.Template, btpl *template.Template,
subjectContent = content[0:loc[0]]
bodyContent = content[loc[1]:]
}
- if _, err := stpl.New(name).
- Parse(string(subjectContent)); err != nil {
- log.Warn("Failed to parse template [%s/subject]: %v", name, err)
+ if _, err := stpl.New(name).Parse(string(subjectContent)); err != nil {
+ return fmt.Errorf("failed to parse template [%s/subject]: %w", name, err)
}
- if _, err := btpl.New(name).
- Parse(string(bodyContent)); err != nil {
- log.Warn("Failed to parse template [%s/body]: %v", name, err)
+ if _, err := btpl.New(name).Parse(string(bodyContent)); err != nil {
+ return fmt.Errorf("failed to parse template [%s/body]: %w", name, err)
}
+ return nil
}
// Mailer provides the templates required for sending notification mails.
@@ -81,7 +81,13 @@ func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) {
if firstRun {
log.Trace("Adding mail template %s: %s by %s", tmplName, assetPath, layerName)
}
- buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, tmplName, content)
+ if err = buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, tmplName, content); err != nil {
+ if firstRun {
+ log.Fatal("Failed to parse mail template, err: %v", err)
+ } else {
+ log.Error("Failed to parse mail template, err: %v", err)
+ }
+ }
}
}
diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go
index 1d9635410b..cdff31698c 100644
--- a/modules/templates/util_render.go
+++ b/modules/templates/util_render.go
@@ -208,7 +208,7 @@ func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //n
if err != nil {
log.Error("RenderString: %v", err)
}
- return template.HTML(output)
+ return output
}
func RenderLabels(ctx context.Context, labels []*issues_model.Label, repoLink string) template.HTML {
diff --git a/modules/templates/util_string.go b/modules/templates/util_string.go
index 3f51c122b1..f23b74786a 100644
--- a/modules/templates/util_string.go
+++ b/modules/templates/util_string.go
@@ -4,6 +4,7 @@
package templates
import (
+ "fmt"
"html/template"
"strings"
@@ -28,6 +29,19 @@ func (su *StringUtils) HasPrefix(s any, prefix string) bool {
return false
}
+func (su *StringUtils) ToString(v any) string {
+ switch v := v.(type) {
+ case string:
+ return v
+ case template.HTML:
+ return string(v)
+ case fmt.Stringer:
+ return v.String()
+ default:
+ return fmt.Sprint(v)
+ }
+}
+
func (su *StringUtils) Contains(s, substr string) bool {
return strings.Contains(s, substr)
}
diff --git a/modules/translation/mock.go b/modules/translation/mock.go
index 1f0559f38d..18fbc1044a 100644
--- a/modules/translation/mock.go
+++ b/modules/translation/mock.go
@@ -9,7 +9,9 @@ import (
)
// MockLocale provides a mocked locale without any translations
-type MockLocale struct{}
+type MockLocale struct {
+ Lang, LangName string // these fields are used directly in templates: ctx.Locale.Lang
+}
var _ Locale = (*MockLocale)(nil)
diff --git a/modules/translation/translation.go b/modules/translation/translation.go
index b7c18f610a..36ae58a9f1 100644
--- a/modules/translation/translation.go
+++ b/modules/translation/translation.go
@@ -144,7 +144,7 @@ func Match(tags ...language.Tag) language.Tag {
// locale represents the information of localization.
type locale struct {
i18n.Locale
- Lang, LangName string // these fields are used directly in templates: .i18n.Lang
+ Lang, LangName string // these fields are used directly in templates: ctx.Locale.Lang
msgPrinter *message.Printer
}
diff --git a/modules/util/util.go b/modules/util/util.go
index 28b549f405..5c75158196 100644
--- a/modules/util/util.go
+++ b/modules/util/util.go
@@ -17,64 +17,13 @@ import (
"golang.org/x/text/language"
)
-// OptionalBool a boolean that can be "null"
-type OptionalBool byte
-
-const (
- // OptionalBoolNone a "null" boolean value
- OptionalBoolNone OptionalBool = iota
- // OptionalBoolTrue a "true" boolean value
- OptionalBoolTrue
- // OptionalBoolFalse a "false" boolean value
- OptionalBoolFalse
-)
-
-// IsTrue return true if equal to OptionalBoolTrue
-func (o OptionalBool) IsTrue() bool {
- return o == OptionalBoolTrue
-}
-
-// IsFalse return true if equal to OptionalBoolFalse
-func (o OptionalBool) IsFalse() bool {
- return o == OptionalBoolFalse
-}
-
-// IsNone return true if equal to OptionalBoolNone
-func (o OptionalBool) IsNone() bool {
- return o == OptionalBoolNone
-}
-
-// ToGeneric converts OptionalBool to optional.Option[bool]
-func (o OptionalBool) ToGeneric() optional.Option[bool] {
- if o.IsNone() {
+// OptionalBoolParse get the corresponding optional.Option[bool] of a string using strconv.ParseBool
+func OptionalBoolParse(s string) optional.Option[bool] {
+ v, e := strconv.ParseBool(s)
+ if e != nil {
return optional.None[bool]()
}
- return optional.Some[bool](o.IsTrue())
-}
-
-// OptionalBoolFromGeneric converts optional.Option[bool] to OptionalBool
-func OptionalBoolFromGeneric(o optional.Option[bool]) OptionalBool {
- if o.Has() {
- return OptionalBoolOf(o.Value())
- }
- return OptionalBoolNone
-}
-
-// OptionalBoolOf get the corresponding OptionalBool of a bool
-func OptionalBoolOf(b bool) OptionalBool {
- if b {
- return OptionalBoolTrue
- }
- return OptionalBoolFalse
-}
-
-// OptionalBoolParse get the corresponding OptionalBool of a string using strconv.ParseBool
-func OptionalBoolParse(s string) OptionalBool {
- b, e := strconv.ParseBool(s)
- if e != nil {
- return OptionalBoolNone
- }
- return OptionalBoolOf(b)
+ return optional.Some(v)
}
// IsEmptyString checks if the provided string is empty
diff --git a/modules/util/util_test.go b/modules/util/util_test.go
index c5830ce01c..819e12ee91 100644
--- a/modules/util/util_test.go
+++ b/modules/util/util_test.go
@@ -8,6 +8,8 @@ import (
"strings"
"testing"
+ "code.gitea.io/gitea/modules/optional"
+
"github.com/stretchr/testify/assert"
)
@@ -173,17 +175,17 @@ func Test_RandomBytes(t *testing.T) {
assert.NotEqual(t, bytes3, bytes4)
}
-func Test_OptionalBool(t *testing.T) {
- assert.Equal(t, OptionalBoolNone, OptionalBoolParse(""))
- assert.Equal(t, OptionalBoolNone, OptionalBoolParse("x"))
+func TestOptionalBoolParse(t *testing.T) {
+ assert.Equal(t, optional.None[bool](), OptionalBoolParse(""))
+ assert.Equal(t, optional.None[bool](), OptionalBoolParse("x"))
- assert.Equal(t, OptionalBoolFalse, OptionalBoolParse("0"))
- assert.Equal(t, OptionalBoolFalse, OptionalBoolParse("f"))
- assert.Equal(t, OptionalBoolFalse, OptionalBoolParse("False"))
+ assert.Equal(t, optional.Some(false), OptionalBoolParse("0"))
+ assert.Equal(t, optional.Some(false), OptionalBoolParse("f"))
+ assert.Equal(t, optional.Some(false), OptionalBoolParse("False"))
- assert.Equal(t, OptionalBoolTrue, OptionalBoolParse("1"))
- assert.Equal(t, OptionalBoolTrue, OptionalBoolParse("t"))
- assert.Equal(t, OptionalBoolTrue, OptionalBoolParse("True"))
+ assert.Equal(t, optional.Some(true), OptionalBoolParse("1"))
+ assert.Equal(t, optional.Some(true), OptionalBoolParse("t"))
+ assert.Equal(t, optional.Some(true), OptionalBoolParse("True"))
}
// Test case for any function which accepts and returns a single string.
diff --git a/options/gitignore/Janet b/options/gitignore/Janet
new file mode 100644
index 0000000000..9c181fe604
--- /dev/null
+++ b/options/gitignore/Janet
@@ -0,0 +1,4 @@
+# Binaries
+build/
+# Janet Project Manager dependency directory
+jpm_tree/
diff --git a/options/license/MIT-Khronos-old b/options/license/MIT-Khronos-old
new file mode 100644
index 0000000000..430863bc98
--- /dev/null
+++ b/options/license/MIT-Khronos-old
@@ -0,0 +1,23 @@
+Copyright (c) 2014-2020 The Khronos Group Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and/or associated documentation files (the "Materials"),
+to deal in the Materials without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Materials, and to permit persons to whom the
+Materials are furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Materials.
+
+MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS
+STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND
+HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/
+
+THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS
+IN THE MATERIALS.
diff --git a/options/locale/locale_ar.ini b/options/locale/locale_ar.ini
index 7f0f9791c7..c60ed5c42c 100644
--- a/options/locale/locale_ar.ini
+++ b/options/locale/locale_ar.ini
@@ -64,7 +64,7 @@ copy_url = انسخ الرابط
admin_panel = إدارة الموقع
copy_error = فشل النسخ
new_mirror = مرآة جديدة
-re_type = أكّد كلمة المرور الجديدة
+re_type = تأكيد كلمة المرور
webauthn_unsupported_browser = متصفحك لا يدعم ويب آوثن حالياً.
copy = انسخ
enabled = مُفَعَّل
@@ -210,8 +210,8 @@ admin_email = عنوان البريد الإلكتروني
install_btn_confirm = تثبت فورجيو
secret_key_failed = لم يتم توليد مفتاح سري: %v
save_config_failed = فشل في حفظ الإعداد: %s
-sqlite3_not_available = هذا الأصدار من فورجيو لا يدعم SQLite3. من فضلك قم بتحميل الاصدار الملفي الرسمي من %s (ليس اصدار 'gobuild').
-test_git_failed = لم يتمكن من أختبار أمر جِت: %v
+sqlite3_not_available = هذا الإصدار من فورجيو لا يدعم SQLite3. من فضلك قم بتنزيل الإصدار الرسمي من %s (ليس إصدار 'gobuild').
+test_git_failed = يتعذر اختبار أمر جِت: %v
confirm_password = أكّد كلمة المرور
invalid_admin_setting = إعداد حساب المدير غير صالح: %v
invalid_log_root_path = مسار السجل غير صالح: %v
@@ -1375,6 +1375,7 @@ network_error = خطأ في الشبكة
invalid_csrf = طلب سيئ: رمز CSRF غير صالح
occurred = حدث خطأ
missing_csrf = طلب سيئ: لا يوجد رمز CSRF
+server_internal = خطأ داخلي في الخادم
[startpage]
install = سهلة التثبيت
@@ -1492,6 +1493,7 @@ openid_signin_desc = أدخل مسار الـOpenID الخاص بك. مثلاً:
openid_register_desc = مسار الـOpenID المختار مجهول. اربطه مع حساب جديد هنا.
remember_me = تذكر هذا الجهاز
remember_me.compromised = رمز الاحتفاظ بتسجيل الدخول لم يعد صالحا، مما قد يعني اختراق الحساب. نرجو مراجعة حسابك لرؤية أي نشاط غير مألوف.
+authorization_failed_desc = فشل التفويض لأننا اكتشفنا طلبًا غير صالح. يرجى الاتصال بمشرف التطبيق الذي حاولت ترخيصه.
[packages]
rpm.repository.multiple_groups = هذه الحزمة متوفرة في مجموعات متعددة.
@@ -1656,7 +1658,7 @@ config.default_enable_timetracking = فعّل تتبع الوقت مبدئيا
config.default_allow_only_contributors_to_track_time = اسمح للمشتركين في المستودع موحدهم بتتبع الوقت
[form]
-username_error_no_dots = ` يُمكن أن يحتوي فقط على أرقام "0-9 "، أبجدية "A-Z" ،"a-z"، شرطة "-"، وخط أسفل "_" ولا يمكن أن تبدأ أو تنتهي بغير الأبجدية الرقمية، كما يحظر تتالي رموز غير أبجدية رقمية.`
+username_error_no_dots = ` يُمكنه أن يحتوي على حروف إنجليزية وأرقام وشرطة ("-") وشرطة سفلية ("_") فقط. ويمكنه ان يبدأ وينتهي بحرف او برقم.`
Password = كلمة المرور
admin_cannot_delete_self = لا يمكنك أن تحذف نفسك عندما تكون مدير من فضلك ازيل امتيازاتك الإدارية اولا.
enterred_invalid_repo_name = اسم المستودع الذي أدخلته خطأ.
@@ -1684,8 +1686,8 @@ username_password_incorrect = اسم المستخدم أو كلمة المرور
org_still_own_repo = "لدى هذه المنظمة مستودع واحد أو أكثر؛ احذفهم أو انقل ملكيتهم أولا."
enterred_invalid_org_name = اسم المنظمة التي أدخلته خطأ.
lang_select_error = اختر لغة من القائمة.
-alpha_dash_error = ` لا يجب أن يحتوي إلا على الحروف الإنجليزية والأرقام والشرطة ('-') والشرطة السفلية ('_').`
-alpha_dash_dot_error = ` لا يجب أن يحتوي إلا على الحروف الإنجليزية والأرقام والشرطة ('-') والشرطة السفلية ('_') والنقطة ('.').`
+alpha_dash_error = ` لا يجب أن يحتوي إلا على الحروف الإنجليزية والأرقام والشرطة ("-") والشرطة السفلية ("_").`
+alpha_dash_dot_error = ` لا يجب أن يحتوي إلا على الحروف الإنجليزية والأرقام والشرطة ("-") والشرطة السفلية ("_") والنقطة (".").`
repo_name_been_taken = اسم المستودع مستعمل بالفعل.
Email = البريد الإلكتروني
auth_failed = فشل الاستيثاق: %v
@@ -1733,6 +1735,9 @@ git_ref_name_error = `يجب أن يكون اسمًا مرجعيًا جيدًا
include_error = ` يجب أن يحتوي على سلسلة فرعية "%s".`
size_error = `يجب أن يكون بالحجم %s.'
glob_pattern_error = `النمط الشامل غير صالح: %s.`
+CommitChoice = إختيار الإداع
+regex_pattern_error = ` نمط التعبير النمطي غير صالح: %s.`
+username_error = ` يُمكنه أن يحتوي على حروف إنجليزية وأرقام وشرطة ("-") وشرطة سفلية ("_") و نقطة (".") فقط. ويمكنه ان يبدأ وينتهي بحرف او برقم.`
[home]
filter = تصفيات أخرى
diff --git a/options/locale/locale_be.ini b/options/locale/locale_be.ini
new file mode 100644
index 0000000000..f9d8e738c3
--- /dev/null
+++ b/options/locale/locale_be.ini
@@ -0,0 +1,19 @@
+
+
+
+[common]
+dashboard = Панэль кіравання
+explore = Агляд
+help = Дапамога
+logo = Лагатып
+sign_in = Увайсці
+sign_in_or = або
+sign_out = Выйсці
+sign_up = Зарэгістравацца
+link_account = Звязаць Уліковы запіс
+register = Рэгістрацыя
+version = Версія
+powered_by = Працуе на ℅s
+page = Старонка
+home = Галоўная Старонка
+sign_in_with_provider = Увайсці з %s
\ No newline at end of file
diff --git a/options/locale/locale_bg.ini b/options/locale/locale_bg.ini
index de2c40c174..3eca60f551 100644
--- a/options/locale/locale_bg.ini
+++ b/options/locale/locale_bg.ini
@@ -128,6 +128,7 @@ profile_desc = Контролирайте как вашият профил се
permission_write = Четене и Писане
twofa_disable = Изключване на двуфакторното удостоверяване
twofa_enroll = Включване на двуфакторно удостоверяване
+ssh_key_name_used = Вече съществува SSH ключ със същото име във вашия акаунт.
[packages]
container.labels.value = Стойност
@@ -147,6 +148,9 @@ keywords = Ключови думи
details.author = Автор
about = Относно този пакет
settings.delete.success = Пакетът бе изтрит.
+settings.delete = Изтриване на пакета
+container.details.platform = Платформа
+settings.delete.error = Неуспешно изтриване на пакет.
[tool]
hours = %d часа
@@ -243,7 +247,7 @@ email = Адрес на ел. поща
issues = Задачи
retry = Повторен опит
remove = Премахване
-admin_panel = Администриране на сайта
+admin_panel = Управление на сайта
account_settings = Настройки на акаунта
powered_by = Осъществено от %s
pull_requests = Заявки за сливане
@@ -451,15 +455,15 @@ activity.period.quarterly = 3 месеца
activity.period.semiyearly = 6 месеца
activity.title.user_1 = %d потребител
activity.title.user_n = %d потребители
-activity.title.prs_n = %d Заявки за сливане
+activity.title.prs_n = %d заявки за сливане
activity.merged_prs_count_1 = Слята заявка за сливане
activity.merged_prs_count_n = Слети заявки за сливане
-activity.active_issues_count_1 = %d Активна задача
-activity.active_issues_count_n = %d Активни задачи
+activity.active_issues_count_1 = %d активна задача
+activity.active_issues_count_n = %d активни задачи
activity.closed_issues_count_1 = Затворена задача
activity.closed_issues_count_n = Затворени задачи
-activity.title.issues_1 = %d Задача
-activity.title.issues_n = %d Задачи
+activity.title.issues_1 = %d задача
+activity.title.issues_n = %d задачи
wiki.pages = Страници
activity.git_stats_author_1 = %d автор
activity.git_stats_and_deletions = и
@@ -467,14 +471,14 @@ project_board = Проекти
wiki.save_page = Запазване на страницата
activity.git_stats_author_n = %d автори
wiki.delete_page_button = Изтриване на страницата
-activity.title.prs_1 = %d Заявка за сливане
-activity.active_prs_count_n = %d Активни заявки за сливане
+activity.title.prs_1 = %d заявка за сливане
+activity.active_prs_count_n = %d активни заявки за сливане
activity.period.filter_label = Период:
activity.period.daily = 1 ден
activity.period.halfweekly = 3 дни
activity.period.weekly = 1 седмица
activity.period.yearly = 1 година
-activity.active_prs_count_1 = %d Активна заявка за сливане
+activity.active_prs_count_1 = %d активна заявка за сливане
wiki.page_title = Заглавие на страницата
wiki.page_content = Съдържание на страницата
wiki.filter_page = Филтриране на страница
@@ -613,7 +617,7 @@ issues.filter_milestone_all = Всички етапи
issues.filter_milestone_open = Отворени етапи
issues.filter_milestone_none = Без етапи
issues.filter_project = Проект
-issues.num_participants = %d Участващи
+issues.num_participants = %d участващи
issues.filter_assignee = Изпълнител
issues.filter_milestone_closed = Затворени етапи
issues.filter_assginee_no_select = Всички изпълнители
@@ -626,8 +630,8 @@ activity.opened_prs_label = Предложена
activity.title.issues_closed_from = %s затворена от %s
activity.closed_issue_label = Затворена
activity.new_issue_label = Отворена
-activity.title.releases_1 = %d Издание
-activity.title.releases_n = %d Издания
+activity.title.releases_1 = %d издание
+activity.title.releases_n = %d издания
milestones.completeness = %d%% Завършен
activity.title.prs_opened_by = %s предложена от %s
issues.action_milestone_no_select = Без етап
@@ -686,18 +690,18 @@ more_operations = Още операции
download_archive = Изтегляне на хранилището
branch = Клон
tree = Дърво
-branches = Клонове
-tags = Тагове
-tag = Таг
-filter_branch_and_tag = Филтриране на клон или таг
+branches = Клони
+tags = Маркери
+tag = Маркер
+filter_branch_and_tag = Филтриране на клон или маркер
symbolic_link = Символна връзка
executable_file = Изпълним файл
blame = Авторство
editor.patch = Прилагане на кръпка
editor.new_patch = Нова кръпка
signing.wont_sign.not_signed_in = Не сте влезли.
-settings.tags = Тагове
-release.tags = Тагове
+settings.tags = Маркери
+release.tags = Маркери
star_guest_user = Влезте, за отбелязване на това хранилище със звезда.
download_bundle = Изтегляне на BUNDLE
desc.private = Частно
@@ -746,17 +750,196 @@ issues.label_modify = Редактиране на етикета
issues.due_date_added = добави крайния срок %s %s
issues.due_date_remove = премахна крайния срок %s %s
release.new_release = Ново издание
-release.tag_helper_existing = Съществуващ таг.
-release.tag_name = Име на тага
-issues.no_ref = Няма указан Клон/Таг
+release.tag_helper_existing = Съществуващ маркер.
+release.tag_name = Име на маркера
+issues.no_ref = Няма указан клон/маркер
issues.lock.reason = Причина за заключването
pulls.create = Създаване на заявка за сливане
issues.label.filter_sort.reverse_by_size = Най-голям размер
issues.unlock = Отключване на обсъждането
issues.due_date_form_add = Добавяне на краен срок
-release.save_draft = Запазване като чернова
-release.add_tag = Създаване само на таг
+release.save_draft = Запазване на чернова
+release.add_tag = Създаване само на маркер
release.publish = Публикуване на издание
+file_view_source = Преглед на изходния код
+diff.parent = родител
+issues.unlock_comment = отключи това обсъждане %s
+release.edit_subheader = Изданията ви позволяват да управлявате версиите на проекта.
+branch.already_exists = Вече съществува клон на име "%s".
+contributors.contribution_type.deletions = Изтривания
+contributors.contribution_type.additions = Добавяния
+diff.browse_source = Разглеждане на изходния код
+file_view_rendered = Преглед на визуализация
+issues.lock_with_reason = заключи като %s и ограничи обсъждането до сътрудници %s
+milestones.new_subheader = Етапите ви помагат да управлявате задачите и да проследявате напредъка им.
+release.edit = редактиране
+activity.published_release_label = Публикувано
+activity.navbar.contributors = Допринесли
+pulls.recently_pushed_new_branches = Изтласкахте в клона %[1]s %[2]s
+branch.branch_name_conflict = Името на клон "%s" е в конфликт с вече съществуващия клон "%s".
+all_branches = Всички клонове
+file_raw = Директно
+file_history = История
+file_permalink = Постоянна връзка
+projects.edit_subheader = Проектите ви позволяват да управлявате задачите и да проследявате напредъка.
+release.compare = Сравняване
+released_this = публикува това
+file_too_large = Файлът е твърде голям, за да бъде показан.
+commits = Подавания
+commit = Подаване
+editor.commit_changes = Подаване на промените
+editor.add_tmpl = Добавяне на "<име на файла>"
+editor.add = Добавяне на %s
+editor.delete = Изтриване на %s
+editor.update = Обновяване на %s
+editor.commit_message_desc = Добавете опционално разширено описание…
+commit_graph.monochrome = Моно
+commit.contained_in = Това подаване се съдържа в:
+editor.new_branch_name_desc = Име на новия клон…
+editor.propose_file_change = Предлагане на промяна на файла
+editor.create_new_branch = Създаване на нов клон за това подаване и започване на заявка за сливане.
+editor.create_new_branch_np = Създаване на нов клон за това подаване.
+editor.filename_is_invalid = Името на файла е невалидно: "%s".
+editor.commit_directly_to_this_branch = Подаване директно към клона %s .
+editor.branch_already_exists = Клонът "%s" вече съществува в това хранилище.
+editor.file_already_exists = Файл с име "%s" вече съществува в това хранилище.
+editor.commit_empty_file_header = Подаване на празен файл
+editor.commit_empty_file_text = Файлът, който сте на път да подадете, е празен. Продължаване?
+editor.fail_to_update_file_summary = Съобщение за грешка:
+editor.fail_to_update_file = Неуспешно обновяване/създаване на файл "%s".
+editor.add_subdir = Добавяне на директория…
+commits.commits = Подавания
+commits.find = Търсене
+commits.search_all = Всички клони
+commits.search = Потърсете подавания…
+commit.operations = Операции
+issues.deleted_milestone = `(изтрит)`
+issues.deleted_project = `(изтрит)`
+milestones.edit_subheader = Етапите ви позволяват да управлявате задачите и да проследявате напредъка.
+activity.navbar.recent_commits = Скорошни подавания
+activity.git_stats_deletion_n = %d изтривания
+activity.git_stats_addition_n = %d добавяния
+release.draft = Чернова
+release.detail = Подробности за изданието
+releases.desc = Проследявайте версиите на проекта и изтеглянията.
+release.ahead.target = в %s след това издание
+release.prerelease = Предварително издание
+release.target = Цел
+release.new_subheader = Изданията ви позволяват да управлявате версиите на проекта.
+release.tag_helper = Изберете съществуващ маркер или създайте нов маркер.
+release.tag_helper_new = Нов маркер. Този маркер ще бъде създаден от целта.
+release.message = Опишете това издание
+release.prerelease_desc = Отбелязване като предварително издание
+release.delete_release = Изтриване на изданието
+release.delete_tag = Изтриване на маркера
+release.edit_release = Обновяване на изданието
+diff.committed_by = подадено от
+release.downloads = Изтегляния
+issues.sign_in_require_desc = Влезте за да се присъедините към това обсъждане.
+activity.git_stats_push_to_all_branches = към всички клони.
+release.deletion_tag_success = Маркерът е изтрит.
+release.cancel = Отказ
+release.deletion = Изтриване на изданието
+release.download_count = Изтегляния: %s
+release.tag_name_invalid = Името на маркера не е валидно.
+diff.stats_desc = %d променени файла с %d добавяния и %d изтривания
+release.tag_name_already_exist = Вече съществува издание с това име на маркер.
+branch.branch_already_exists = Клонът "%s" вече съществува в това хранилище.
+diff.download_patch = Изтегляне на файл-кръпка
+diff.show_diff_stats = Показване на статистика
+diff.commit = подаване
+diff.download_diff = Изтегляне на файл-разлики
+diff.whitespace_show_everything = Показване на всички промени
+diff.show_split_view = Разделен изглед
+diff.show_unified_view = Обединен изглед
+issues.review.self.approval = Не можете да одобрите собствената си заявка за сливане.
+fork_repo = Разклоняване на хранилището
+pulls.merged = Слети
+issues.push_commits_n = добави %d подавания %s
+pulls.num_conflicting_files_n = %d конфликтни файла
+issues.push_commit_1 = добави %d подаване %s
+fork_visibility_helper = Видимостта на разклонено хранилище не може да бъде променена.
+language_other = Други
+stars_remove_warning = Това ще премахне всички звезди от това хранилище.
+tree_path_not_found_tag = Пътят %[1]s не съществува в маркер %[2]s
+tree_path_not_found_commit = Пътят %[1]s не съществува в подаване %[2]s
+tree_path_not_found_branch = Пътят %[1]s не съществува в клон %[2]s
+transfer.accept = Приемане на прехвърлянето
+transfer.reject = Отхвърляне на прехвърлянето
+archive.issue.nocomment = Това хранилище е архивирано. Не можете да коментирате в задачите.
+forked_from = разклонено от
+issues.delete_branch_at = `изтри клон %s %s`
+pulls.has_viewed_file = Прегледано
+pulls.viewed_files_label = %[1]d / %[2]d прегледани файла
+pulls.approve_count_n = %d одобрения
+activity.git_stats_commit_1 = %d подаване
+activity.git_stats_deletion_1 = %d изтриване
+diff.review.approve = Одобряване
+diff.review.comment = Коментиране
+issues.stop_tracking = Спиране на таймера
+issues.stop_tracking_history = `спря работа %s`
+issues.cancel_tracking = Отхвърляне
+issues.add_time = Ръчно добавяне на време
+issues.start_tracking_history = `започна работа %s`
+issues.start_tracking_short = Пускане на таймера
+issues.review.approve = одобри тези промени %s
+pulls.tab_conversation = Обсъждане
+pulls.close = Затваряне на заявката за сливане
+issues.add_time_short = Добавяне на време
+issues.add_time_hours = Часове
+issues.add_time_minutes = Минути
+issues.add_time_cancel = Отказ
+pulls.tab_commits = Подавания
+pulls.tab_files = Променени файлове
+pulls.approve_count_1 = %d одобрение
+pulls.can_auto_merge_desc = Тази заявка за сливане може да бъде слята автоматично.
+pulls.num_conflicting_files_1 = %d конфликтен файл
+activity.git_stats_commit_n = %d подавания
+settings.event_issues = Задачи
+branch.delete_head = Изтриване
+branch.delete = Изтриване на клона "%s"
+branch.delete_html = Изтриване на клона
+tag.create_success = Маркерът "%s" е създаден.
+branch.new_branch_from = Създаване на нов клон от "%s"
+branch.new_branch = Създаване на нов клон
+branch.confirm_rename_branch = Преименуване на клона
+branch.create_from = от "%s"
+settings.add_team_duplicate = Екипът вече разполага с това хранилище
+settings.slack_domain = Домейн
+editor.directory_is_a_file = Името на директорията "%s" вече се използва като име на файл в това хранилище.
+editor.filename_is_a_directory = Името на файла "%s" вече се използва като име на директория в това хранилище.
+editor.file_editing_no_longer_exists = Файлът, който се редактира, "%s", вече не съществува в това хранилище.
+editor.file_deleting_no_longer_exists = Файлът, който се изтрива, "%s", вече не съществува в това хранилище.
+editor.unable_to_upload_files = Неуспешно качване на файлове в "%s" с грешка: %v
+settings.web_hook_name_slack = Slack
+settings.web_hook_name_discord = Discord
+settings.web_hook_name_telegram = Telegram
+settings.web_hook_name_matrix = Matrix
+settings.web_hook_name_gogs = Gogs
+settings.web_hook_name_feishu_or_larksuite = Feishu / Lark Suite
+settings.web_hook_name_feishu = Feishu
+settings.web_hook_name_larksuite = Lark Suite
+settings.web_hook_name_wechatwork = WeCom (Wechat Work)
+settings.web_hook_name_packagist = Packagist
+diff.file_byte_size = Размер
+branch.create_success = Клонът "%s" е създаден.
+branch.deletion_success = Клонът "%s" е изтрит.
+branch.deletion_failed = Неуспешно изтриване на клон "%s".
+branch.rename_branch_to = Преименуване от "%s" на:
+settings.web_hook_name_msteams = Microsoft Teams
+settings.web_hook_name_dingtalk = DingTalk
+issues.error_removing_due_date = Неуспешно премахване на крайния срок.
+branch.renamed = Клонът %s е преименуван на %s.
+settings.teams = Екипи
+settings.add_team = Добавяне на екип
+settings.web_hook_name_gitea = Gitea
+settings.web_hook_name_forgejo = Forgejo
+release.tag_already_exist = Вече съществува маркер с това име.
+branch.name = Име на клона
+settings.rename_branch = Преименуване на клона
+branch.restore_failed = Неуспешно възстановяване на клон "%s".
+branch.download = Изтегляне на клона "%s"
+branch.rename = Преименуване на клона "%s"
[modal]
confirm = Потвърждаване
@@ -834,10 +1017,23 @@ follow_blocked_user = Не можете да следвате тази орга
settings.delete_prompt = Организацията ще бъде премахната завинаги. Това НЕ МОЖЕ да бъде отменено!
settings.labels_desc = Добавете етикети, които могат да се използват за задачи за всички хранилища в тази организация.
teams.none_access = Без достъп
-teams.members.none = Няма участници в този екип.
+teams.members.none = Няма членове в този екип.
repo_updated = Обновено
teams.delete_team_success = Екипът е изтрит.
teams.search_repo_placeholder = Потърсете хранилище…
+teams.delete_team_title = Изтриване на екипа
+teams.add_team_member = Добавяне на член на екипа
+teams.read_access_helper = Членовете могат да преглеждат и клонират хранилищата на екипа.
+teams.invite.description = Моля, щракнете върху бутона по-долу, за да се присъедините към екипа.
+teams.invite.title = Поканени сте да се присъедините към екип %s в организация %s .
+team_permission_desc = Разрешение
+members.public_helper = да е скрит
+teams.members = Членове на екипа
+teams.delete_team = Изтриване на екипа
+members.owner = Притежател
+members.member_role = Роля на участника:
+members.member = Участник
+members.private_helper = да е видим
[install]
admin_password = Парола
@@ -896,9 +1092,10 @@ register_notify = Добре дошли във Forgejo
issue.action.new = @%[1]s създаде #%[2]d.
issue.action.review = @%[1]s коментира в тази заявка за сливане.
issue.action.reopen = @%[1]s отвори наново #%[2]d.
+issue.action.approve = @%[1]s одобри тази заявка за сливане.
[user]
-joined_on = Присъединен на %s
+joined_on = Присъединени на %s
user_bio = Биография
repositories = Хранилища
activity = Публична дейност
@@ -977,6 +1174,42 @@ users.deletion_success = Потребителският акаунт бе изт
last_page = Последна
config.test_email_placeholder = Ел. поща (напр. test@example.com)
users.cannot_delete_self = Не можете да изтриете себе си
+repos.owner = Притежател
+auths.domain = Домейн
+auths.host = Хост
+auths.port = Порт
+auths.type = Тип
+config.ssh_config = SSH Конфигурация
+monitor.stats = Статистика
+monitor.queue = Опашка: %s
+config = Конфигурация
+config.mailer_user = Потребител
+config.enable_captcha = Включване на CAPTCHA
+repos.size = Размер
+auths.enabled = Включено
+config.git_config = Git Конфигурация
+config.mailer_protocol = Протокол
+users.bot = Бот
+config.db_path = Път
+monitor.queues = Опашки
+config.server_config = Сървърна конфигурация
+packages.size = Размер
+settings = Админ. настройки
+users = Потребителски акаунти
+emails.duplicate_active = Този адрес на ел. поща вече е активен за друг потребител.
+config.app_ver = Версия на Forgejo
+config.custom_conf = Път на конфигурационния файл
+config.git_version = Версия на Git
+config.lfs_config = LFS Конфигурация
+config.db_ssl_mode = SSL
+users.admin = Админ
+auths.name = Име
+repos.issues = Задачи
+packages.owner = Притежател
+packages.creator = Създател
+packages.type = Тип
+orgs.teams = Екипи
+orgs.members = Участници
[error]
not_found = Целта не може да бъде намерена.
@@ -1006,6 +1239,10 @@ team_not_exist = Екипът не съществува.
TeamName = Име на екипа
email_error = ` не е валиден адрес на ел. поща.`
email_invalid = Адресът на ел. поща е невалиден.
+SSHTitle = Име на SSH ключ
+repo_name_been_taken = Името на хранилището вече е използвано.
+team_name_been_taken = Името на екипа вече е заето.
+org_name_been_taken = Името на организацията вече е заето.
[action]
close_issue = `затвори задача %[3]s#%[2]s `
@@ -1024,9 +1261,13 @@ comment_pull = `коментира в заявка за сливане %[3]s#%[2]s `
auto_merge_pull_request = `сля автоматично заявка за сливане %[3]s#%[2]s `
watched_repo = започна да наблюдава %[2]s
-delete_tag = изтри таг %[2]s от %[3]s
+delete_tag = изтри маркер %[2]s от %[3]s
delete_branch = изтри клон %[2]s от %[3]s
create_branch = създаде клон %[3]s на %[4]s
+publish_release = `публикува издание "%[4]s" на %[3]s `
+push_tag = изтласка маркер %[3]s към %[4]s
+approve_pull_request = `одобри %[3]s#%[2]s `
+reject_pull_request = `предложи промени за %[3]s#%[2]s `
[auth]
login_openid = OpenID
@@ -1080,6 +1321,7 @@ read = Прочетени
watching = Наблюдавани
no_unread = Няма непрочетени известия.
mark_all_as_read = Отбелязване на всички като прочетени
+pin = Закачване на известието
[explore]
code_search_results = Резултати от търсенето на "%s"
@@ -1099,6 +1341,18 @@ runners.version = Версия
variables = Променливи
runners.labels = Етикети
actions = Действия
+variables.none = Все още няма променливи.
+variables.creation.failed = Неуспешно добавяне на променлива.
+variables.update.failed = Неуспешно редактиране на променлива.
+variables.creation.success = Променливата "%s" е добавена.
+variables.deletion.success = Променливата е премахната.
+variables.edit = Редактиране на променливата
+variables.deletion = Премахване на променливата
+variables.update.success = Променливата е редактирана.
+variables.creation = Добавяне на променлива
+variables.deletion.failed = Неуспешно премахване на променлива.
+runners.task_list.repository = Хранилище
+runners.description = Описание
[heatmap]
less = По-малко
@@ -1116,4 +1370,16 @@ submodule = Подмодул
[dropzone]
-default_message = Пуснете файлове тук или щракнете, за качване.
\ No newline at end of file
+default_message = Пуснете файлове тук или щракнете, за качване.
+remove_file = Премахване на файла
+file_too_big = Размерът на файла ({{filesize}} MB) надвишава максималния размер от ({{maxFilesize}} MB).
+invalid_input_type = Не можете да качвате файлове от този тип.
+
+[graphs]
+component_loading_failed = Неуспешно зареждане на %s
+contributors.what = приноси
+recent_commits.what = скорошни подавания
+component_loading = Зареждане на %s...
+
+[projects]
+type-1.display_name = Индивидуален проект
\ No newline at end of file
diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini
index fdb91f987f..9c61881754 100644
--- a/options/locale/locale_cs-CZ.ini
+++ b/options/locale/locale_cs-CZ.ini
@@ -18,7 +18,7 @@ template=Šablona
language=Jazyk
notifications=Oznámení
active_stopwatch=Aktivní sledování času
-tracked_time_summary=Shrnutí sledovaného času na základě filtrů v seznamu úkolů
+tracked_time_summary=Shrnutí sledovaného času na základě filtrů v seznamu problémů
create_new=Vytvořit…
user_profile_and_more=Profily a nastavení…
signed_in_as=Přihlášen jako
@@ -31,7 +31,7 @@ username=Uživatelské jméno
email=E-mailová adresa
password=Heslo
access_token=Přístupový token
-re_type=Potvrdit heslo
+re_type=Potvrzení hesla
captcha=CAPTCHA
twofa=Dvoufaktorové ověřování
twofa_scratch=Dvoufaktorový pomocný kód
@@ -124,6 +124,7 @@ pin=Připnout
unpin=Odepnout
artifacts=Artefakty
+confirm_delete_artifact=Jste si jisti, že chcete odstranit artefakt „%s“?
archived=Archivováno
@@ -142,6 +143,20 @@ confirm_delete_selected=Potvrdit odstranění všech vybraných položek?
name=Název
value=Hodnota
sign_in_with_provider = Přihlásit se přes %s
+confirm_delete_artifact = Opravdu chcete odstranit artefakt „%s“?
+toggle_menu = Přepnout nabídku
+filter = Filtr
+filter.is_fork = Forknuto
+filter.not_fork = Není forkuto
+filter.is_mirror = Zrcadleno
+filter.is_template = Šablona
+filter.not_template = Není šablona
+filter.public = Veřejné
+filter.private = Soukromé
+filter.is_archived = Archivováno
+filter.not_mirror = Není zrcadleno
+filter.not_archived = Není archivováno
+filter.clear = Vymazat filtr
[aria]
navbar=Navigační lišta
@@ -182,13 +197,14 @@ missing_csrf=Špatný požadavek: Neexistuje CSRF token
invalid_csrf=Špatný požadavek: Neplatný CSRF token
not_found=Cíl nebyl nalezen.
network_error=Chyba sítě
+server_internal = Interní chyba serveru
[startpage]
app_desc=Snadno přístupný vlastní Git
install=Jednoduchá na instalaci
install_desc=Jednoduše spusťte jako binární program pro vaši platformu, nasaďte jej pomocí Docker , nebo jej stáhněte jako balíček .
platform=Multiplatformní
-platform_desc=Forgejo běží všude, kde Go může kompilovat: Windows, macOS, Linux, ARM, atd. Vyberte si ten, který milujete!
+platform_desc=Forgejo běží na všech platformách, na které může kompilovat jazyk Go : Windows, macOS, Linux, ARM, atd. Výběr je opravdu velký!
lightweight=Lehká
lightweight_desc=Forgejo má minimální požadavky a může běžet na Raspberry Pi. Šetřete energii vašeho stroje!
license=Open Source
@@ -196,7 +212,7 @@ license_desc=Vše je na dokumentaci , než budete měnit jakákoliv nastavení.
require_db_desc=Forgejo requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol).
db_title=Nastavení databáze
@@ -278,13 +294,13 @@ admin_password=Heslo
confirm_password=Potvrdit heslo
admin_email=E-mailová adresa
install_btn_confirm=Nainstalovat Forgejo
-test_git_failed=Chyba při testu příkazu 'git': %v
-sqlite3_not_available=Tato verze Forgejo nepodporuje SQLite3. Stáhněte si oficiální binární verzi od %s (nikoli verzi „gobuild“).
+test_git_failed=Chyba při testu příkazu „git“: %v
+sqlite3_not_available=Tato verze Forgejo nepodporuje SQLite3. Stáhněte si oficiální binární verzi z %s (nikoli verzi „gobuild“).
invalid_db_setting=Nastavení databáze je neplatné: %v
invalid_db_table=Databázová tabulka „%s“ je neplatná: %v
invalid_repo_path=Kořenový adresář repozitářů není správný: %v
invalid_app_data_path=Cesta k datům aplikace je neplatná: %v
-run_user_not_match=`"Run as" uživatelské jméno není aktuální uživatelské jméno: %s -> %s`
+run_user_not_match=Uživatelské jméno v poli „Spustit jako“ není aktuální uživatelské jméno: %s -> %s
internal_token_failed=Nepodařilo se vytvořit interní token: %v
secret_key_failed=Nepodařilo se vytvořit tajný klíč: %v
save_config_failed=Uložení konfigurace se nezdařilo: %v
@@ -297,7 +313,7 @@ default_allow_create_organization_popup=Povolit novým uživatelským účtům v
default_enable_timetracking=Povolit sledování času ve výchozím nastavení
default_enable_timetracking_popup=Povolí sledování času pro nové repozitáře.
no_reply_address=Skrytá e-mailová doména
-no_reply_address_helper=Název domény pro uživatele se skrytou e-mailovou adresou. Příklad: Pokud je název skryté e-mailové domény nastaven na „noreply.example.org“, uživatelské jméno „joe“ bude zaznamenáno v Gitu jako „joe@noreply.example.org“.
+no_reply_address_helper=Název domény pro uživatele se skrytou e-mailovou adresou. Příklad: pokud je název skryté e-mailové domény nastaven na „noreply.example.org“, uživatelské jméno „joe“ bude zaznamenáno v Gitu jako „joe@noreply.example.org“.
password_algorithm=Hash algoritmus hesla
invalid_password_algorithm=Neplatný algoritmus hash hesla
password_algorithm_helper=Nastavte algoritmus hashování hesla. Algoritmy mají odlišné požadavky a sílu. Algoritmus argon2 je poměrně bezpečný, ale používá spoustu paměti a může být nevhodný pro malé systémy.
@@ -305,6 +321,9 @@ enable_update_checker=Povolit kontrolu aktualizací
enable_update_checker_helper=Kontroluje vydání nových verzí pravidelně připojením ke gitea.io.
env_config_keys=Konfigurace prostředí
env_config_keys_prompt=Následující proměnné prostředí budou také použity pro váš konfigurační soubor:
+enable_update_checker_helper_forgejo = Pravidelně kontroluje nové verze Forgejo kontrolou DNS TXT záznamu na adrese release.forgejo.org.
+allow_dots_in_usernames = Povolit uživatelům používat tečky ve svých uživatelských jménech. Neovlivní stávající účty.
+smtp_from_invalid = Adresa v poli „Poslat e-mail jako“ je neplatná
[home]
uname_holder=Uživatelské jméno nebo e-mailová adresa
@@ -425,11 +444,15 @@ authorization_failed_desc=Autorizace selhala, protože jsme detekovali neplatný
sspi_auth_failed=SSPI autentizace selhala
password_pwned=Heslo, které jste zvolili, je na seznamu odcizených hesel , která byla dříve odhalena při narušení veřejných dat. Zkuste to prosím znovu s jiným heslem.
password_pwned_err=Nelze dokončit požadavek na HaveIBeenPwned
+change_unconfirmed_email = Pokud jste při registraci zadali nesprávnou e-mailovou adresu, můžete ji změnit níže. Potvrzovací e-mail bude místo toho odeslán na novou adresu.
+change_unconfirmed_email_error = Nepodařilo se změnit e-mailovou adresu: %v
+change_unconfirmed_email_summary = Změna e-mailové adresy, na kterou bude odeslán aktivační e-mail.
+last_admin=Nelze odstranit posledního správce. Musí existovat alespoň jeden správce.
[mail]
view_it_on=Zobrazit na %s
reply=nebo přímo odpovědět na tento e-mail
-link_not_working_do_paste=Nefunguje? Zkuste jej zkopírovat a vložit do svého prohlížeče.
+link_not_working_do_paste=Odkaz nefunguje? Zkuste jej zkopírovat a vložit do adresního řádku svého prohlížeče.
hi_user_x=Ahoj %s ,
activate_account=Prosíme, aktivujte si váš účet
@@ -444,12 +467,12 @@ activate_email.text=Pro aktivaci vašeho účtu do %s klikněte na násle
register_notify=Vítejte v Forgejo
register_notify.title=%[1]s vítejte v %[2]s
register_notify.text_1=toto je váš potvrzovací e-mail pro %s!
-register_notify.text_2=Nyní se můžete přihlásit přes uživatelské jméno: %s.
-register_notify.text_3=Pokud pro vás byl vytvořen tento účet, nejprve nastavte své heslo .
+register_notify.text_2=Do svého účtu se můžete přihlásit svým uživatelským jménem: %s
+register_notify.text_3=Pokud vám tento účet vytvořil někdo jiný, musíte si nejprve nastavit své heslo .
reset_password=Obnovit váš účet
-reset_password.title=%s, požádal jste o obnovení vašeho účtu
-reset_password.text=Klikněte prosím na následující odkaz pro obnovení vašeho účtu v rámci %s :
+reset_password.title=Uživateli %s, obdrželi jsme žádost o obnovu vašeho účtu
+reset_password.text=Pokud jste to byli vy, klikněte na následující odkaz pro obnovení vašeho účtu do %s :
register_success=Registrace byla úspěšná
@@ -491,6 +514,9 @@ team_invite.subject=%[1]s vás pozval/a, abyste se připojili k organizaci %[2]s
team_invite.text_1=%[1]s vás pozval/a do týmu %[2]s v organizaci %[3]s.
team_invite.text_2=Pro připojení k týmu klikněte na následující odkaz:
team_invite.text_3=Poznámka: Tato pozvánka byla určena pro %[1]s. Pokud jste neočekávali tuto pozvánku, můžete tento e-mail ignorovat.
+admin.new_user.user_info = Informace o uživateli
+admin.new_user.text = Klikněte sem pro správu tohoto uživatele z administrátorského panelu.
+admin.new_user.subject = Právě se zaregistroval nový uživatel %s
[modal]
yes=Ano
@@ -523,8 +549,8 @@ SSPISeparatorReplacement=Oddělovač
SSPIDefaultLanguage=Výchozí jazyk
require_error=` nemůže být prázdný.`
-alpha_dash_error=` by měl obsahovat pouze alfanumerické znaky, pomlčku („-“) a podtržítka („_“). `
-alpha_dash_dot_error=` by měl obsahovat pouze alfanumerické znaky, pomlčku („-“), podtržítka („_“) nebo tečku („.“). `
+alpha_dash_error=` by měl obsahovat pouze alfanumerické znaky, pomlčky („-“) a podtržítka („_“). `
+alpha_dash_dot_error=` by měl obsahovat pouze alfanumerické znaky, pomlčky („-“), podtržítka („_“) nebo tečky („.“). `
git_ref_name_error=` musí být správný název odkazu Git.`
size_error=` musí být minimálně velikosti %s.`
min_size_error=` musí obsahovat nejméně %s znaků.`
@@ -534,7 +560,7 @@ url_error=`„%s“ není platná adresa URL.`
include_error=` musí obsahovat substring „%s“.`
glob_pattern_error=`zástupný vzor je neplatný: %s.`
regex_pattern_error=` regex vzor je neplatný: %s.`
-username_error=` může obsahovat pouze alfanumerické znaky („0-9“, „a-z“, „A-Z“), pomlčku („-“), podtržítka („_“) a tečka („.“). Nemůže začínat nebo končit nealfanumerickými znaky a po sobě jdoucí nealfanumerické znaky jsou také zakázány.`
+username_error=` může obsahovat pouze alfanumerické znaky („0-9“, „a-z“, „A-Z“), pomlčky („-“), podtržítka („_“) a tečky („.“). Nemůže začínat nebo končit nealfanumerickými znaky. Jsou také zakázány po sobě jdoucí nealfanumerické znaky.`
invalid_group_team_map_error=` mapování je neplatné: %s`
unknown_error=Neznámá chyba:
captcha_incorrect=CAPTCHA kód není správný.
@@ -570,7 +596,7 @@ enterred_invalid_owner_name=Nové jméno vlastníka není správné.
enterred_invalid_password=Zadané heslo není správné.
user_not_exist=Tento uživatel neexistuje.
team_not_exist=Tento tým neexistuje.
-last_org_owner=Nemůžete odstranit posledního uživatele z týmu „vlastníci“. Musí existovat alespoň jeden vlastník pro organizaci.
+last_org_owner=Nemůžete odebrat posledního uživatele z týmu „vlastníci“. Organizace musí obsahovat alespoň jednoho vlastníka.
cannot_add_org_to_team=Organizace nemůže být přidána jako člen týmu.
duplicate_invite_to_team=Uživatel byl již pozván jako člen týmu.
organization_leave_success=Úspěšně jste opustili organizaci %s.
@@ -579,17 +605,20 @@ invalid_ssh_key=Nelze ověřit váš SSH klíč: %s
invalid_gpg_key=Nelze ověřit váš GPG klíč: %s
invalid_ssh_principal=Neplatný SSH Principal certifikát: %s
must_use_public_key=Zadaný klíč je soukromý klíč. Nenahrávejte svůj soukromý klíč nikde. Místo toho použijte váš veřejný klíč.
-unable_verify_ssh_key=Nelze ověřit váš SSH klíč.
+unable_verify_ssh_key=Nepodařilo se ověřit klíč SSH, zkontrolujte, zda neobsahuje chyby.
auth_failed=Ověření selhalo: %v
-still_own_repo=Váš účet vlastní jeden nebo více repozitářů. Nejprve je smažte nebo převeďte.
+still_own_repo=Váš účet vlastní jeden nebo více repozitářů. Nejprve je odstraňte nebo přesuňte.
still_has_org=Váš účet je členem jedné nebo více organizací. Nejdříve je musíte opustit.
still_own_packages=Váš účet vlastní jeden nebo více balíčků. Nejprve je musíte odstranit.
-org_still_own_repo=Organizace stále vlastní jeden nebo více repozitářů. Nejdříve je smažte nebo převeďte.
-org_still_own_packages=Organizace stále vlastní jeden nebo více balíčků. Nejdříve je smažte.
+org_still_own_repo=Organizace stále vlastní jeden nebo více repozitářů. Nejdříve je odstraňte nebo přesuňte.
+org_still_own_packages=Organizace stále vlastní jeden nebo více balíčků. Nejdříve je odstraňte.
target_branch_not_exist=Cílová větev neexistuje.
+admin_cannot_delete_self = Nemůžete odstranit sami sebe, když jste administrátorem. Nejprve prosím odeberte svá práva administrátora.
+username_error_no_dots = ` může obsahovat pouze alfanumerické znaky („0-9“, „a-z“, „A-Z“), pomlčky („-“) a podtržítka („_“). Nemůže začínat nebo končit nealfanumerickými znaky. Jsou také zakázány po sobě jdoucí nealfanumerické znaky.`
+admin_cannot_delete_self=Nemůžete se smazat, dokud jste správce. Nejdříve prosím odeberte svá administrátorská oprávnění.
[user]
change_avatar=Změnit váš avatar…
@@ -615,6 +644,14 @@ settings=Uživatelská nastavení
form.name_reserved=Uživatelské jméno „%s“ je rezervováno.
form.name_pattern_not_allowed=Vzor „%s“ není povolen v uživatelském jméně.
form.name_chars_not_allowed=Uživatelské jméno „%s“ obsahuje neplatné znaky.
+block_user = Zablokovat uživatele
+block_user.detail = Pokud zablokujete tohoto uživatele, budou provedeny i další akce. Například:
+block_user.detail_1 = Tento uživatel vás nebude moci sledovat.
+block_user.detail_2 = Tento uživatel nebude moci interagovat s vašimi repozitáři, vytvářet problémy a komentáře.
+block_user.detail_3 = Tento uživatel vás nebude moci přidat jako spolupracovníka a naopak.
+follow_blocked_user = Tohoto uživatele nemůžete sledovat, protože jste si jej zablokovali nebo si on zablokoval vás.
+block = Zablokovat
+unblock = Odblokovat
[settings]
profile=Profil
@@ -658,7 +695,7 @@ language=Jazyk
ui=Motiv vzhledu
hidden_comment_types=Skryté typy komentářů
hidden_comment_types_description=Zde zkontrolované typy komentářů nebudou zobrazeny na stránkách problémů. Zaškrtnutí „Štítek“ například odstraní všechny komentáře „ přidal/odstranil “.
-hidden_comment_types.ref_tooltip=Komentáře, na které se odkazovalo z jiného úkolu/commitu/…
+hidden_comment_types.ref_tooltip=Komentáře, kde byl tento problém odkázán u jiného problému/commitu/…
hidden_comment_types.issue_ref_tooltip=Komentáře, kde uživatel změní větev/značku spojenou s problémem
comment_type_group_reference=Reference
comment_type_group_label=Štítek
@@ -693,7 +730,7 @@ update_user_avatar_success=Uživatelův avatar byl aktualizován.
update_password=Aktualizovat heslo
old_password=Stávající heslo
new_password=Nové heslo
-retype_new_password=Potvrdit nové heslo
+retype_new_password=Potvrzení nového hesla
password_incorrect=Zadané heslo není správné.
change_password_success=Vaše heslo bylo aktualizováno. Od teď se přihlašujte novým heslem.
password_change_disabled=Externě ověřovaní uživatelé nemohou aktualizovat své heslo prostřednictvím webového rozhraní Forgejo.
@@ -720,7 +757,7 @@ theme_update_error=Vybraný motiv vzhledu neexistuje.
openid_deletion=Odstranit OpenID adresu
openid_deletion_desc=Pokud odstraníte OpenID adresu, nebudete ji moci použít k přihlašování. Pokračovat?
openid_deletion_success=OpenID adresa byla odstraněna.
-add_new_email=Přidat novou e-mailovou adresu
+add_new_email=Přidat e-mailovou adresu
add_new_openid=Přidat novou OpenID URI
add_email=Přidat e-mailovou adresu
add_openid=Přidat OpenID URI
@@ -728,22 +765,22 @@ add_email_confirmation_sent=Potvrzovací e-mail byl odeslán na „%s“. Prosí
add_email_success=Nová e-mailová adresa byla přidána.
email_preference_set_success=Nastavení e-mailu bylo úspěšně nastaveno.
add_openid_success=Nová OpenID adresa byla přidána.
-keep_email_private=Schovat e-mailovou adresu
+keep_email_private=Skrýt e-mailovou adresu
keep_email_private_popup=Toto skryje vaši e-mailovou adresu z vašeho profilu, stejně jako při vytvoření pull requestu nebo úpravě souboru pomocí webového rozhraní. Odeslané commity nebudou změněny. Použijte %s v commitech pro jejich přiřazení k vašemu účtu.
openid_desc=OpenID vám umožní delegovat ověřování na externího poskytovatele.
manage_ssh_keys=Správa klíčů SSH
manage_ssh_principals=Spravovat SSH Principal certifikáty
-manage_gpg_keys=Správa GPG klíčů
+manage_gpg_keys=Správa klíčů GPG
add_key=Přidat klíč
-ssh_desc=Tyto veřejné SSH klíče jsou propojeny s vaším účtem. Odpovídající soukromé klíče umožní plný přístup k vašim repozitářům.
+ssh_desc=Tyto veřejné klíče SSH jsou propojeny s vaším účtem. Odpovídající soukromé klíče umožní plný přístup k vašim repozitářům. Klíče SSH, které byly ověřeny, mohou být použity pro ověření Git commitů podepsaných přes SSH.
principal_desc=Tyto SSH Principal certifikáty jsou přidruženy k vašemu účtu a umožňují plný přístup do vašich repozitářů.
-gpg_desc=Tyto veřejné GPG klíče jsou propojeny s vaším účtem. Uchovejte vaše soukromé klíče, protože umožňují ověření commitů.
+gpg_desc=Tyto veřejné klíče GPG jsou propojeny s vaším účtem a používají se k ověření vašich commitů. Uložte je na bezpečné místo, jelikož umožňují podepsat commity vaší identitou.
ssh_helper=Potřebujete pomoct? Podívejte se do příručky GitHubu na to vytvoření vlastních klíčů SSH nebo vyřešte běžné problémy , se kterými se můžete potkat při použití SSH.
gpg_helper=Potřebujete pomoct? Podívejte se do příručky GitHubu o GPG .
add_new_key=Přidat klíč SSH
add_new_gpg_key=Přidat GPG klíč
-key_content_ssh_placeholder=Začíná s „ssh-ed25519“, „ssh-rsa“, „ecdsa-sha2-nistp256“, „ecdsa-sha2-nistp384“, „ecdsa-sha2-nistp521“, „sk-ecdsa-sha2-nistp256@openssh.com“, nebo „sk-ssh-ed25519@openssh.com“
+key_content_ssh_placeholder=Začíná s „ssh-ed25519“, „ssh-rsa“, „ecdsa-sha2-nistp256“, „ecdsa-sha2-nistp384“, „ecdsa-sha2-nistp521“, „sk-ecdsa-sha2-nistp256@openssh.com“ nebo „sk-ssh-ed25519@openssh.com“
key_content_gpg_placeholder=Začíná s „-----BEGIN PGP PUBLIC KEY BLOCK-----“
add_new_principal=Přidat SSH Principal certifikát
ssh_key_been_used=Tento SSH klíč byl na server již přidán.
@@ -835,13 +872,13 @@ permission_write=čtení i zápis
at_least_one_permission=Musíte vybrat alespoň jedno oprávnění pro vytvoření tokenu
permissions_list=Oprávnění:
-manage_oauth2_applications=Spravovat OAuth2 aplikace
+manage_oauth2_applications=Spravovat aplikace OAuth2
edit_oauth2_application=Upravit OAuth2 aplikaci
oauth2_applications_desc=OAuth2 aplikace umožní aplikacím třetích stran bezpečně ověřit uživatele v této instanci Forgejo.
remove_oauth2_application=Odstranit OAuth2 aplikaci
remove_oauth2_application_desc=Odstraněním OAuth2 aplikace odeberete přístup všem podepsaným přístupovým tokenům. Pokračovat?
remove_oauth2_application_success=Aplikace byla odstraněna.
-create_oauth2_application=Vytvořit novou OAuth2 aplikaci
+create_oauth2_application=Vytvořit novou aplikaci OAuth2
create_oauth2_application_button=Vytvořit aplikaci
create_oauth2_application_success=Úspěšně jste vytvořili novou OAuth2 aplikaci.
update_oauth2_application_success=Úspěšně jste aktualizovali OAuth2 aplikaci.
@@ -859,8 +896,8 @@ oauth2_application_create_description=OAuth2 aplikace poskytuje přístup aplika
oauth2_application_remove_description=Odebráním OAuth2 aplikace zabrání přístupu ověřeným uživatelům na této instanci. Pokračovat?
oauth2_application_locked=Gitea předregistruje některé OAuth2 aplikace při spuštění, pokud je to povoleno v konfiguraci. Aby se zabránilo neočekávanému chování, nelze je upravovat ani odstranit. Více informací naleznete v dokumentaci OAuth2.
-authorized_oauth2_applications=Autorizovat OAuth2 aplikaci
-authorized_oauth2_applications_description=Úspěšně jste povolili přístup k vašemu osobnímu účtu této aplikaci třetí strany. Zrušte prosím přístup aplikacím, které již nadále nepotřebujete.
+authorized_oauth2_applications=Autorizovat aplikaci OAuth2
+authorized_oauth2_applications_description=Úspěšně jste povolili přístup k vašemu osobnímu účtu této aplikaci třetí strany. Zrušte prosím přístup aplikacím, které již nejsou používány.
revoke_key=Zrušit
revoke_oauth2_grant=Zrušit přístup
revoke_oauth2_grant_description=Zrušením přístupu této aplikaci třetí strany ji zabráníte v přístupu k vašim datům. Jste si jisti?
@@ -926,6 +963,13 @@ visibility.limited=Omezený
visibility.limited_tooltip=Viditelné pouze pro ověřené uživatele
visibility.private=Soukromý
visibility.private_tooltip=Viditelné pouze pro členy organizací, ke kterým jste se připojili
+blocked_users = Zablokovaní uživatelé
+change_password = Změnit heslo
+user_block_success = Uživatel byl úspěšně zablokován.
+user_unblock_success = Uživatel byl úspěšně odblokován.
+access_token_desc = Oprávnění vybraného tokenu omezují autorizaci pouze na příslušné cesty API . Pro více informací si přečtěte dokumentaci .
+blocked_users_none = Nemáte žádné zablokované uživatele.
+blocked_since = Zablokován od %s
[repo]
new_repo_helper=Repozitář obsahuje všechny projektové soubory, včetně historie revizí. Už jej hostujete jinde? Migrovat repozitář.
@@ -969,6 +1013,8 @@ issue_labels_helper=Vyberte sadu štítků úkolů.
license=Licence
license_helper=Vyberte licenční soubor.
license_helper_desc=Licence řídí, co ostatní mohou a nemohou dělat s vaším kódem. Nejste si jisti, která je pro váš projekt správná? Podívejte se na Zvolte licenci
+object_format=Formát objektu
+object_format_helper=Objektový formát repozitáře. Nelze později změnit. SHA1 je nejvíce kompatibilní.
readme=README
readme_helper=Vyberte šablonu souboru README.
readme_helper_desc=Toto je místo, kde můžete napsat úplný popis vašeho projektu.
@@ -1035,6 +1081,7 @@ desc.public=Veřejný
desc.template=Šablona
desc.internal=Interní
desc.archived=Archivováno
+desc.sha256=SHA256
template.items=Položky šablony
template.git_content=Obsah gitu (výchozí větev)
@@ -1047,7 +1094,7 @@ template.issue_labels=Štítky úkolů
template.one_item=Musíte vybrat alespoň jednu položku šablony
template.invalid=Musíte vybrat repositář šablony
-archive.title=Tento repozitář je archivovaný. Můžete prohlížet soubory, klonovat, ale nemůžete nahrávat a vytvářet nové úkoly nebo požadavky na natažení.
+archive.title=Tento repozitář je archivovaný. Můžete prohlížet soubory, klonovat, ale nemůžete nahrávat a vytvářet nové problémy nebo žádosti o sloučení.
archive.title_date=Tento repositář byl archivován %s. Můžete zobrazit soubory a klonovat je, ale nemůžete nahrávat ani otevírat problémy nebo požadavky na natažení.
archive.issue.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat úkoly.
archive.pull.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat požadavky na natažení.
@@ -1076,7 +1123,7 @@ migrate_items_merge_requests=Sloučit požadavky
migrate_items_releases=Vydání
migrate_repo=Migrovat repozitář
migrate.clone_address=Migrovat / klonovat z URL
-migrate.clone_address_desc=HTTP(S) nebo URL pro klonování existujícího repozitáře
+migrate.clone_address_desc=HTTP(S) nebo URL Git „clone“ existujícího repozitáře
migrate.github_token_desc=Můžete sem vložit jeden nebo více tokenů oddělených čárkou, abyste urychlili migraci kvůli omezení rychlosti rozhraní GitHub API. VAROVÁNÍ: Zneužití této funkce může vést k porušení zásad poskytovatele služeb a zablokování účtu.
migrate.clone_local_path=nebo místní cesta serveru
migrate.permission_denied=Není dovoleno importovat místní repozitáře.
@@ -1180,11 +1227,13 @@ escape_control_characters=Escape sekvence
unescape_control_characters=Bez escape sekvencí
file_copy_permalink=Kopírovat trvalý odkaz
view_git_blame=Zobrazit Git Blame
-video_not_supported_in_browser=Váš prohlížeč nepodporuje značku pro HTML5 video.
-audio_not_supported_in_browser=Váš prohlížeč nepodporuje značku pro HTML5 audio.
+video_not_supported_in_browser=Váš prohlížeč nepodporuje značku HTML5 „video“.
+audio_not_supported_in_browser=Váš prohlížeč nepodporuje značku HTML5 „audio“.
stored_lfs=Uloženo pomocí Git LFS
symbolic_link=Symbolický odkaz
executable_file=Spustitelný soubor
+vendored=Vendorováno
+generated=Generováno
commit_graph=Graf commitů
commit_graph.select=Vybrat větve
commit_graph.hide_pr_refs=Skrýt požadavky na natažení
@@ -1215,12 +1264,12 @@ editor.delete_this_file=Smazat soubor
editor.must_have_write_access=Musíte mít přístup pro zápis pro dělání či navrhování změn tohoto souboru.
editor.file_delete_success=Soubor „%s“ byl odstraněn.
editor.name_your_file=Pojmenujte váš soubor…
-editor.filename_help=Přidejte adresář pomocí zapsání jeho jména následovaného lomítkem („/“). Smažte adresář pomocí stisku backspace na začátku vstupního pole.
+editor.filename_help=Přidejte adresář zapsáním jeho jména následovaného lomítkem („/“). Adresář odeberete stiskem backspace na začátku vstupního pole.
editor.or=nebo
editor.cancel_lower=Zrušit
editor.commit_signed_changes=Odevzdat podepsané změny
editor.commit_changes=Odevzdat změny
-editor.add_tmpl=Přidán „“
+editor.add_tmpl=Přidat „“
editor.add=Přidat %s
editor.update=Aktualizovat %s
editor.delete=Odstranit %s
@@ -1322,8 +1371,8 @@ projects.edit_success=Projekt „%s“ byl aktualizován.
projects.type.none=Žádný
projects.type.basic_kanban=Základní Kanban
projects.type.bug_triage=Třídění chyb
-projects.template.desc=Šablona projektu
-projects.template.desc_helper=Vyberte šablonu projektu pro začátek
+projects.template.desc=Šablona
+projects.template.desc_helper=Začněte vybráním šablony projektu
projects.type.uncategorized=Nezařazené
projects.column.edit=Upravit sloupec
projects.column.edit_title=Název
@@ -1331,11 +1380,11 @@ projects.column.new_title=Název
projects.column.new_submit=Vytvořit sloupec
projects.column.new=Nový sloupec
projects.column.set_default=Nastavit jako výchozí
-projects.column.set_default_desc=Nastavit tento sloupec jako výchozí pro nekategorizované úkoly a požadavky na natažení
+projects.column.set_default_desc=Nastavit tento sloupec jako výchozí pro nekategorizované problémy a požadavky na sloučení
projects.column.unset_default=Zrušit nastavení jako výchozí
projects.column.unset_default_desc=Zrušit nastavení tohoto sloupce jako výchozí
-projects.column.delete=Smazat sloupec
-projects.column.deletion_desc=Smazání projektového sloupce přesune všechny související problémy do kategorie „Nezařazené“. Pokračovat?
+projects.column.delete=Odstranit sloupec
+projects.column.deletion_desc=Odstranění projektového sloupce přesune všechny související problémy do kategorie „Nezařazené“. Pokračovat?
projects.column.color=Barva
projects.open=Otevřít
projects.close=Zavřít
@@ -1384,7 +1433,7 @@ issues.new_label_placeholder=Název štítku
issues.new_label_desc_placeholder=Popis
issues.create_label=Vytvořit štítek
issues.label_templates.title=Nahrát předdefinovanou sadu značek
-issues.label_templates.info=Neexistují žádné štítky. Vytvořte štítek pomocí „Nový štítek“ nebo použijte přednastavenou sadu štítků:
+issues.label_templates.info=Zatím nebyly vytvořeny žádné štítky. Vytvořte štítek kliknutím na „Nový štítek“ nebo použijte přednastavenou sadu štítků:
issues.label_templates.helper=Vyberte sadu značek
issues.label_templates.use=Použít sadu štítků
issues.label_templates.fail_to_load_file=Nepodařilo se načíst soubor šablony popisku „%s“: %v
@@ -1520,7 +1569,11 @@ issues.label_title=Název štítku
issues.label_description=Popis štítku
issues.label_color=Barva štítku
issues.label_exclusive=Exkluzivní
+issues.label_archive=Archivovat štítek
issues.label_archived_filter=Zobrazit archivované popisky
+issues.label_archive_tooltip=Archivované štítky jsou ve výchozím nastavení vyloučeny z návrhů při hledání podle popisku.
+issues.label_exclusive_desc=Pojmenujte štítek rozsah/položka
, aby se stal vzájemně exkluzivním s jinými štítky rozsah/
.
+issues.label_exclusive_warning=Jakékoliv protichůdné rozsahy štítků budou odstraněny při úpravě štítků u úkolů nebo u požadavku na natažení.
issues.label_count=%d štítků
issues.label_open_issues=%d otevřených úkolů
issues.label_edit=Upravit
@@ -1539,8 +1592,8 @@ issues.attachment.download=`Klikněte pro stažení „%s“`
issues.subscribe=Odebírat
issues.unsubscribe=Zrušit odběr
issues.unpin_issue=Odepnout problém
-issues.max_pinned=Nemůžete připnout další úkoly
-issues.pin_comment=připnuto %s
+issues.max_pinned=Nemůžete připnout další problémy
+issues.pin_comment=připnul/a tento %s
issues.unpin_comment=odepnul/a tento %s
issues.lock=Uzamknout konverzaci
issues.unlock=Odemknout konverzaci
@@ -1552,7 +1605,7 @@ issues.lock_no_reason=uzamkl/a a omezil/a konverzaci na spolupracovníky %s
issues.unlock_comment=odemkl/a tuto konverzaci %s
issues.lock_confirm=Uzamknout
issues.unlock_confirm=Odemknout
-issues.lock.notice_1=- Další uživatelé nemohou komentovat tento úkol.
+issues.lock.notice_1=- Další uživatelé nemohou komentovat tento problém.
issues.lock.notice_2=- Vy a ostatní spolupracovníci s přístupem k tomuto repozitáři můžete stále přidávat komentáře, které ostatní uvidí.
issues.lock.notice_3=- V budoucnu budete moci vždy znovu tento úkol odemknout.
issues.unlock.notice_1=- Všichni budou moci znovu komentovat tento úkol.
@@ -1586,7 +1639,7 @@ issues.add_time_sum_to_small=Čas nebyl zadán.
issues.time_spent_total=Celkový strávený čas
issues.time_spent_from_all_authors=`Celkový strávený čas: %s`
issues.due_date=Termín dokončení
-issues.invalid_due_date_format=Termín dokončení musí být ve formátu 'rrrr-mm-dd'.
+issues.invalid_due_date_format=Termín dokončení musí být ve formátu „rrrr-mm-dd“.
issues.error_modifying_due_date=Změna termínu dokončení selhala.
issues.error_removing_due_date=Odstranění termínu dokončení selhalo.
issues.push_commit_1=přidal/a %d commit %s
@@ -1597,18 +1650,18 @@ issues.due_date_form=rrrr-mm-dd
issues.due_date_form_add=Přidat termín dokončení
issues.due_date_form_edit=Upravit
issues.due_date_form_remove=Odstranit
-issues.due_date_not_writer=Potřebujete přístup k zápisu do tohoto repozitáře, abyste mohli aktualizovat datum dokončení problému.
+issues.due_date_not_writer=Pro aktualizaci data dokončení problému potřebujete přístup k zápisu do tohoto repozitáře.
issues.due_date_not_set=Žádný termín dokončení.
issues.due_date_added=přidal/a termín dokončení %s %s
-issues.due_date_modified=upravil/a termín termínu z %[2]s na %[1]s %[3]s
+issues.due_date_modified=změnil/a datum termínu z %[2]s na %[1]s %[3]s
issues.due_date_remove=odstranil/a termín dokončení %s %s
issues.due_date_overdue=Zpožděné
issues.due_date_invalid=Termín dokončení není platný nebo je mimo rozsah. Použijte prosím formát „rrrr-mm-dd“.
issues.dependency.title=Závislosti
issues.dependency.issue_no_dependencies=Nejsou nastaveny žádné závislosti.
issues.dependency.pr_no_dependencies=Nejsou nastaveny žádné závislosti.
-issues.dependency.no_permission_1=Nemáte oprávnění ke čtení závislosti %d
-issues.dependency.no_permission_n=Nemáte oprávnění ke čtení závislostí %d
+issues.dependency.no_permission_1=Nemáte oprávnění ke čtení %d závislosti
+issues.dependency.no_permission_n=Nemáte oprávnění ke čtení %d závislostí
issues.dependency.no_permission.can_remove=Nemáte oprávnění ke čtení této závislosti, ale můžete ji odstranit
issues.dependency.add=Přidat závislost…
issues.dependency.cancel=Zrušit
@@ -1621,6 +1674,7 @@ issues.dependency.issue_closing_blockedby=Uzavření tohoto úkolu je blokováno
issues.dependency.issue_close_blocks=Tento úkol blokuje uzavření následujících úkolů
issues.dependency.pr_close_blocks=Tento požadavek na natažení blokuje uzavření následujících úkolů
issues.dependency.issue_close_blocked=Musíte zavřít všechny úkoly, které blokují tento úkol, aby jej bylo možné zavřít.
+issues.dependency.issue_batch_close_blocked=Nelze uzavřít úkoly, které jste vybrali, protože úkol #%d má stále otevřené závislosti
issues.dependency.pr_close_blocked=Musíte zavřít všechny úkoly, které blokují tento požadavek na natažení, aby jej bylo možné sloučit.
issues.dependency.blocks_short=Blokuje
issues.dependency.blocked_by_short=Závisí na
@@ -1636,17 +1690,17 @@ issues.dependency.add_error_cannot_create_circular=Nemůžete vytvořit závislo
issues.dependency.add_error_dep_not_same_repo=Oba úkoly musí být ve stejném repozitáři.
issues.review.self.approval=Nemůžete schválit svůj požadavek na natažení.
issues.review.self.rejection=Nemůžete požadovat změny ve svém vlastním požadavku na natažení.
-issues.review.approve=schválil tyto změny %s
-issues.review.comment=posoudil %s
-issues.review.dismissed=zamítl/a posouzení od %s %s
+issues.review.approve=schválil/a tyto změny %s
+issues.review.comment=posoudil/a %s
+issues.review.dismissed=zamítl/a posouzení uživatele %s %s
issues.review.dismissed_label=Zamítnuto
issues.review.left_comment=zanechal komentář
issues.review.content.empty=Je potřeba zanechat poznámku s uvedením požadované změny (požadovaných změn).
-issues.review.reject=požadované změny %s
-issues.review.wait=byl požádán o posouzení %s
-issues.review.add_review_request=vyžádal posouzení od %s %s
-issues.review.remove_review_request=odstranil žádost o posouzení na %s %s
-issues.review.remove_review_request_self=odmítl posoudit %s
+issues.review.reject=požádal/a o změny %s
+issues.review.wait=byl/a požádán/a o posouzení %s
+issues.review.add_review_request=požádal/a o posouzení od %s %s
+issues.review.remove_review_request=odstranil/a žádost o posouzení na %s %s
+issues.review.remove_review_request_self=odmítl/a posoudit %s
issues.review.pending=Čekající
issues.review.pending.tooltip=Tento komentář není momentálně viditelný pro ostatní uživatele. Chcete-li odeslat Vaše čekající komentáře, vyberte „%s“ → „%s/%s/%s“ v horní části stránky.
issues.review.review=Posouzení
@@ -1702,6 +1756,7 @@ pulls.select_commit_hold_shift_for_range=Vyberte commit. Podržte klávesu shift
pulls.review_only_possible_for_full_diff=Posouzení je možné pouze při zobrazení plného rozlišení
pulls.filter_changes_by_commit=Filtrovat podle commitu
pulls.nothing_to_compare=Tyto větve jsou stejné. Není potřeba vytvářet požadavek na natažení.
+pulls.nothing_to_compare_have_tag=Vybraná větev/značka je stejná.
pulls.nothing_to_compare_and_allow_empty_pr=Tyto větve jsou stejné. Tento požadavek na natažení bude prázdný.
pulls.has_pull_request=`Požadavek na natažení mezi těmito větvemi již existuje: %[2]s#%[3]d `
pulls.create=Vytvořit požadavek na natažení
@@ -1732,12 +1787,12 @@ pulls.is_empty=Změny na této větvi jsou již na cílové větvi. Toto bude pr
pulls.required_status_check_failed=Některé požadované kontroly nebyly úspěšné.
pulls.required_status_check_missing=Některé požadované kontroly chybí.
pulls.required_status_check_administrator=Jako administrátor stále můžete sloučit tento požadavek na natažení.
-pulls.blocked_by_approvals=Tento požadavek na natažení ještě nemá dostatek schválení. Uděleno %d z %d schválení.
-pulls.blocked_by_rejection=Tento požadavek na natažení obsahuje změny požadované oficiálním posuzovatelem.
-pulls.blocked_by_official_review_requests=Tento požadavek na natažení obsahuje oficiální žádosti o posouzení.
-pulls.blocked_by_outdated_branch=Tento požadavek na natažení je zablokován, protože je zastaralý.
-pulls.blocked_by_changed_protected_files_1=Tento požadavek na natažení je zablokován, protože mění chráněný soubor:
-pulls.blocked_by_changed_protected_files_n=Tento požadavek na natažení je zablokován, protože mění chráněné soubory:
+pulls.blocked_by_approvals=Tato žádost o sloučení ještě nemá dostatek schválení. Uděleno %d z %d schválení.
+pulls.blocked_by_rejection=Tato žádost o sloučení obsahuje změny požadované oficiálním posuzovatelem.
+pulls.blocked_by_official_review_requests=Tato žádost o sloučení je zablokována, protože jí chybí schválení oficiálních posuzovatelů.
+pulls.blocked_by_outdated_branch=Tato žádost o sloučení je zablokována, protože je zastaralá.
+pulls.blocked_by_changed_protected_files_1=Tato žádost o sloučení je zablokována, protože mění chráněný soubor:
+pulls.blocked_by_changed_protected_files_n=Tato žádost o sloučení je zablokována, protože mění chráněné soubory:
pulls.can_auto_merge_desc=Tento požadavek na natažení může být automaticky sloučen.
pulls.cannot_auto_merge_desc=Tento požadavek na natažení nemůže být automaticky sloučen, neboť se v něm nachází konflikty.
pulls.cannot_auto_merge_helper=Pro vyřešení konfliktů proveďte ruční sloučení.
@@ -1746,10 +1801,10 @@ pulls.num_conflicting_files_n=%d konfliktních souborů
pulls.approve_count_1=%d schválení
pulls.approve_count_n=%d schválení
pulls.reject_count_1=%d žádost o změnu
-pulls.reject_count_n=%d žádosti o změnu
+pulls.reject_count_n=%d žádostí o změnu
pulls.waiting_count_1=%d čekající posouzení
-pulls.waiting_count_n=%d čekající posouzení
-pulls.wrong_commit_id=ID commitu musí být ID commitu v cílové větvi
+pulls.waiting_count_n=%d čekajících posouzení
+pulls.wrong_commit_id=id commitu musí být id commitu v cílové větvi
pulls.no_merge_desc=Tento požadavek na natažení nemůže být sloučen, protože všechny možnosti repozitáře na sloučení jsou zakázány.
pulls.no_merge_helper=Povolte možnosti sloučení v nastavení repozitáře nebo proveďte sloučení požadavku na natažení ručně.
@@ -1773,9 +1828,9 @@ pulls.unrelated_histories=Sloučení selhalo: Hlavní a základní revize nesdí
pulls.merge_out_of_date=Sloučení selhalo: Základ byl aktualizován při generování sloučení. Tip: Zkuste to znovu.
pulls.head_out_of_date=Sloučení selhalo: Hlavní revize byla aktualizován při generování sloučení. Tip: Zkuste to znovu.
pulls.has_merged=Chyba: Požadavek na natažení byl sloučen, nelze znovu sloučit nebo změnit cílovou větev.
-pulls.push_rejected=Sloučení selhalo: Nahrání bylo zamítnuto. Zkontrolujte háčky Gitu pro tento repozitář.
+pulls.push_rejected=Pushnutí selhalo: Nahrání bylo zamítnuto. Zkontrolujte Git Hooky pro tento repozitář.
pulls.push_rejected_summary=Úplná zpráva o odmítnutí
-pulls.push_rejected_no_message=Sloučení se nezdařilo: Nahrání bylo odmítnuto, ale nebyla nalezena žádná vzdálená zpráva. Zkontrolujte háčky gitu pro tento repozitář
+pulls.push_rejected_no_message=Pushnutí selhalo: Nahrání bylo odmítnuto, ale nebyla nalezena žádná vzdálená zpráva. Zkontrolujte Git Hooky pro tento repozitář
pulls.open_unmerged_pull_exists=`Nemůžete provést operaci znovuotevření protože je tu čekající požadavek na natažení (#%d) s identickými vlastnostmi.`
pulls.status_checking=Některé kontroly jsou nedořešeny
pulls.status_checks_success=Všechny kontroly byly úspěšné
@@ -1824,13 +1879,14 @@ milestones.update_ago=Aktualizováno %s
milestones.no_due_date=Bez lhůty dokončení
milestones.open=Otevřít
milestones.close=Zavřít
+milestones.new_subheader=Milníky vám pomohou organizovat úkoly a sledovat jejich pokrok.
milestones.completeness=%d%% Dokončeno
milestones.create=Vytvořit milník
milestones.title=Název
milestones.desc=Popis
milestones.due_date=Termín (volitelný)
milestones.clear=Zrušit
-milestones.invalid_due_date_format=Termín dokončení musí být ve formátu 'rrrr-mm-dd'.
+milestones.invalid_due_date_format=Termín dokončení musí být ve formátu „rrrr-mm-dd“.
milestones.create_success=Milník „%s“ byl vytvořen.
milestones.edit=Upravit milník
milestones.edit_subheader=Milník organizuje úkoly a sledují pokrok.
@@ -1957,6 +2013,7 @@ activity.git_stats_and_deletions=a
activity.git_stats_deletion_1=%d odebrání
activity.git_stats_deletion_n=%d odebrání
+contributors.contribution_type.filter_label=Typ příspěvku:
contributors.contribution_type.commits=Commity
search=Vyhledat
@@ -2144,7 +2201,7 @@ settings.webhook.body=Tělo zprávy
settings.webhook.replay.description=Zopakovat tento webový háček.
settings.webhook.replay.description_disabled=Chcete-li znovu spustit tento webový háček, aktivujte jej.
settings.webhook.delivery.success=Událost byla přidána do fronty doručení. Může to trvat několik sekund, než se zobrazí v historii doručení.
-settings.githooks_desc=Jelikož háčky Gitu jsou spravovány Gitem samotným, můžete upravit soubory háčků k provádění uživatelských operací.
+settings.githooks_desc=Jelikož Git háčky jsou spravovány Gitem samotným, můžete upravit soubory háčků níže, k provádění libovolných operací.
settings.githook_edit_desc=Je-li háček neaktivní, bude zobrazen vzorový obsah. Nebude-li zadán žádný obsah, háček bude vypnut.
settings.githook_name=Název háčku
settings.githook_content=Obsah háčku
@@ -2261,7 +2318,7 @@ settings.protected_branch.delete_rule=Odstranit pravidlo
settings.protected_branch_can_push=Povolit nahrání?
settings.protected_branch_can_push_yes=Můžete nahrávat
settings.protected_branch_can_push_no=Nemůžete nahrávat
-settings.branch_protection=Ochrana větví pro větev „%s “
+settings.branch_protection=Pravidla ochrany větví pro větev „%s “
settings.protect_this_branch=Povolit ochranu větví
settings.protect_this_branch_desc=Zabraňuje smazání a omezuje gitu nahrávání a slučování do větve.
settings.protect_disable_push=Zakázat nahrávání
@@ -2298,12 +2355,12 @@ settings.dismiss_stale_approvals_desc=Pokud budou do větve nahrány nové reviz
settings.require_signed_commits=Vyžadovat podepsané revize
settings.require_signed_commits_desc=Odmítnout nahrání do této větve pokud nejsou podepsaná nebo jsou neověřitelná.
settings.protect_branch_name_pattern=Vzor jména chráněných větví
-settings.protect_branch_name_pattern_desc=Vzory jmen chráněných větví. Pro vzorovou syntaxi viz dokumentace . Příklady: main, release/**
+settings.protect_branch_name_pattern_desc=Vzory názvů chráněných větví. Pro vzorovou syntaxi viz dokumentace . Příklady: main, release/**
settings.protect_patterns=Vzory
settings.protect_protected_file_patterns=Vzory chráněných souborů (oddělené středníkem „;“):
-settings.protect_protected_file_patterns_desc=Chráněné soubory, které nemají povoleno být měněny přímo, i když uživatel má právo přidávat, upravovat nebo mazat soubory v této větvi. Více vzorů lze oddělit pomocí středníku („;“). Podívejte se na github.com/gobwas/glob dokumentaci pro syntaxi vzoru. Příklady: .drone.yml
, /docs/**/*.txt
.
+settings.protect_protected_file_patterns_desc=Chráněné soubory, které nemají povoleno být měněny přímo, i když uživatel má právo přidávat, upravovat nebo mazat soubory v této větvi. Více vzorů lze oddělit pomocí středníku („;“). Podívejte se na github.com/gobwas/glob dokumentaci pro syntaxi vzoru. Příklady: .drone.yml
, /docs/**/*.txt
.
settings.protect_unprotected_file_patterns=Vzory nechráněných souborů (oddělené středníkem „;“):
-settings.protect_unprotected_file_patterns_desc=Nechráněné soubory, které je možné měnit přímo, pokud má uživatel právo zápisu, čímž se obejde omezení push. Více vzorů lze oddělit pomocí středníku („;“). Podívejte se na github.com/gobwas/glob dokumentaci pro syntaxi vzoru. Příklady: .drone.yml
, /docs/**/*.txt
.
+settings.protect_unprotected_file_patterns_desc=Nechráněné soubory, které je možné měnit přímo, pokud má uživatel právo zápisu, čímž se obejde omezení push. Více vzorů lze oddělit pomocí středníku („;“). Podívejte se na github.com/gobwas/glob dokumentaci pro syntaxi vzoru. Příklady: .drone.yml
, /docs/**/*.txt
.
settings.add_protected_branch=Zapnout ochranu
settings.delete_protected_branch=Vypnout ochranu
settings.update_protect_branch_success=Ochrana větví pro větev „%s“ byla aktualizována.
@@ -2324,7 +2381,7 @@ settings.choose_branch=Vyberte větev…
settings.no_protected_branch=Nejsou tu žádné chráněné větve.
settings.edit_protected_branch=Upravit
settings.protected_branch_required_rule_name=Požadovaný název pravidla
-settings.protected_branch_duplicate_rule_name=Duplikovat název pravidla
+settings.protected_branch_duplicate_rule_name=Již existuje pravidlo pro tuto sadu větví
settings.protected_branch_required_approvals_min=Požadovaná schválení nesmí být záporné číslo.
settings.tags=Značky
settings.tags.protection=Ochrana značek
@@ -2344,12 +2401,13 @@ settings.matrix.room_id=ID místnosti
settings.matrix.message_type=Typ zprávy
settings.archive.button=Archivovat repozitář
settings.archive.header=Archivovat tento repozitář
+settings.archive.text=Archivace repozitáře způsobí, že bude zcela určen pouze pro čtení. Bude skryt z ovládacího panelu. Nikdo (ani vy!) nebude moci vytvářet nové revize ani otevírat nové úkoly nebo žádosti o natažení.
settings.archive.success=Repozitář byl úspěšně archivován.
settings.archive.error=Nastala chyba při archivování repozitáře. Prohlédněte si záznam pro více detailů.
settings.archive.error_ismirror=Nemůžete archivovat zrcadlený repozitář.
settings.archive.branchsettings_unavailable=Nastavení větví není dostupné, pokud je repozitář archivovaný.
settings.archive.tagsettings_unavailable=Nastavení značek není k dispozici, pokud je repozitář archivován.
-settings.unarchive.button=Obnovit repozitář
+settings.unarchive.button=Zrušit archivaci repozitáře
settings.unarchive.header=Obnovit tento repozitář
settings.unarchive.text=Obnovení repozitáře vrátí možnost přijímání commitů a nahrávání. Stejně tak se obnoví i možnost zadávání nových úkolů a požadavků na natažení.
settings.unarchive.success=Repozitář byl úspěšně obnoven.
@@ -2362,7 +2420,7 @@ settings.lfs_findcommits=Najít revize
settings.lfs_lfs_file_no_commits=Pro tento LFS soubor nebyly nalezeny žádné revize
settings.lfs_noattribute=Tato cesta nemá uzamykatelný atribut ve výchozí větvi
settings.lfs_delete=Odstranit LFS soubor s OID %s
-settings.lfs_delete_warning=Smazání LFS souboru může při checkout způsobit „objekt neexistuje“. Jste si jisti?
+settings.lfs_delete_warning=Odstranění souboru LFS může při kontrole způsobit chybu „objekt neexistuje“. Jste si jisti?
settings.lfs_findpointerfiles=Najít soubory ukazatelů
settings.lfs_locks=Zámky
settings.lfs_invalid_locking_path=Neplatná cesta: %s
@@ -2546,8 +2604,94 @@ find_file.no_matching=Nebyl nalezen žádný odpovídající soubor
error.csv.too_large=Tento soubor nelze vykreslit, protože je příliš velký.
error.csv.unexpected=Tento soubor nelze vykreslit, protože obsahuje neočekávaný znak na řádku %d ve sloupci %d.
error.csv.invalid_field_count=Soubor nelze vykreslit, protože má nesprávný počet polí na řádku %d.
+pulls.made_using_agit = AGit
+settings.confirm_wiki_branch_rename = Přejmenovat větev Wiki
+issues.comment.blocked_by_user = U tohoto problému nemůžete vytvořit komentář, protože jste byl/a zablokován/a majitelem repozitáře nebo autorem problému.
+contributors.contribution_type.filter_label = Typ přispění:
+contributors.contribution_type.additions = Přidání
+admin.manage_flags = Spravovat vlajky
+admin.enabled_flags = Vlajky povolené v repozitáři:
+admin.update_flags = Upravit vlajky
+admin.failed_to_replace_flags = Nepodařilo se nahradit vlajky repozitáře
+admin.flags_replaced = Vlajky repozitáře nahrazeny
+desc.sha256 = SHA256
+issues.label_exclusive_warning = Při úpravě štítků problému nebo žádosti o sloučení budou odstraněny všechny konfliktní štítky.
+pulls.cmd_instruction_checkout_title = Kontrola
+settings.mirror_settings.docs.disabled_push_mirror.info = Push zrcadla byla zakázána administrátorem vašeho webu.
+generated = Generováno
+clone_in_vscodium = Klonovat do VSCodium
+settings.wiki_rename_branch_main_notices_1 = Tato operace je NEVRATNÁ .
+settings.wiki_branch_rename_success = Název větve Wiki repozitáře byl úspěšně normalizován.
+object_format = Objektový formát
+rss.must_be_on_branch = Abyste mohli mít zdroj RSS, musíte se nacházet ve větvi.
+object_format_helper = Objektový formát repozitáře. Později jej nelze změnit. Nejkompatibilnější je SHA1.
+issues.blocked_by_user = V tomto repozitáři nemůžete vytvořit problém, protože jste byl/a jeho majitelem zablokován/a.
+migrate.forgejo.description = Migrovat data z codeberg.org nebo jiných instancí Forgejo.
+mirror_sync = synchronizováno
+blame.ignore_revs = Ignorování revizí v souboru .git-blame-ignore-revs . Klikněte sem pro udělení výjimky a zobrazení normálního přehledu blame.
+commits.browse_further = Procházet dále
+issues.role.first_time_contributor = První přispěvatel
+vendored = Vendorováno
+editor.invalid_commit_mail = Neplatný e-mail pro vytvoření commitu.
+commits.renamed_from = Přejmenováno z %s
+issues.label_exclusive_desc = Pojmenujte štítek scope/item
, aby se vzájemně vylučoval s ostatními štítky scope/
.
+issues.label_archive_tooltip = Štítek Archivováno jsou ve výchozím nastavení vyloučeny z návrhů při vyhledávání podle štítků.
+issues.label_archive = Štítek archivu
+milestones.new_subheader = Milníky vám pomohou zorganizovat problémy a sledovat jejich pokrok.
+pulls.nothing_to_compare_have_tag = Vybraná větev a značka jsou shodné.
+activity.navbar.recent_commits = Nedávné commity
+settings.units.units = Jednotky repozitáře
+pulls.blocked_by_user = V tomto repozitáři nemůžete vytvořit žádost o sloučení, protože jste byli zablokováni jeho majitelem.
+pulls.clear_merge_message_hint = Vymazáním zprávy o sloučení pouze odstraníte obsah zprávy commitu a ponecháte vygenerované git trailery, jako „Co-Authored-By …“.
+pulls.agit_explanation = Vytvořeno pomocí workflow AGit. AGit umožňuje přispěvatelům navrhovat změny pomocí „git push“ bez vytváření forku nebo nové větve.
+contributors.contribution_type.deletions = Odstranění
+settings.pull_mirror_sync_in_progress = Probíhá načítání změn ze vzdáleného %s.
+settings.enter_repo_name = Zadejte majitele a repozitář přesně tak, jak je vidíte níže:
+settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning = Tuto akci lze v současné chvíli provést pouze v nabídce „Nová migrace“. Pro více informací viz:
+settings.new_owner_blocked_doer = Nový majitel vás zablokoval.
+settings.mirror_settings.pushed_repository = Pushnutý repozitář
+settings.add_collaborator_blocked_our = Nepodařilo se přidat spolupracovníka, jelikož byl zablokován majitelem repozitáře.
+pulls.commit_ref_at = `se odkázal na tuto žádost o sloučení z commitu %[2]s `
+settings.wiki_rename_branch_main = Normalizovat název větve Wiki
+settings.wiki_rename_branch_main_desc = Přejmenovat větev interně používanou pro Wiki na „%s“. Tato změna je trvalá a nelze ji vrátit.
+pulls.fast_forward_only_merge_pull_request = Pouze zrychlené
+pulls.reopen_failed.head_branch = Tuto žádost o sloučení nelze znovu otevřít, protože hlavní větev již neexistuje.
+pulls.reopen_failed.base_branch = Tuto žádost o sloučení nelze znovu otevřít, protože základní větev již neexistuje.
+issues.dependency.issue_batch_close_blocked = Nepodařilo se hromadně zavřít vybrané problémy, protože problém #%d má stále otevřené závislosti
+pulls.recently_pushed_new_branches = Pushnuli jste do větve %[1]s %[2]s
+wiki.cancel = Zrušit
+activity.navbar.pulse = Pulz
+activity.navbar.code_frequency = Frekvence kódu
+activity.navbar.contributors = Přispěvatelé
+settings.mirror_settings.docs.pull_mirror_instructions = Pro nastavení pull zrcadla viz:
+settings.mirror_settings.docs.doc_link_pull_section = sekci „Pulling from a remote repository“ v dokumentaci.
+settings.units.overview = Přehled
+settings.units.add_more = Přidat další...
+settings.push_mirror_sync_in_progress = Probíhá odesílání změn na vzdálený %s.
+settings.wiki_globally_editable = Umožnit komukoli editovat Wiki
+settings.confirmation_string = Potvrzovací řetězec
+settings.wiki_rename_branch_main_notices_2 = Touto akcí trvale přejmenujete interní větev Wiki repozitáře %s. Existující kontroly budou muset být aktualizovány.
+settings.wiki_branch_rename_failure = Nepodařilo se normalizovat název větve Wiki repozitáře.
+settings.add_collaborator_blocked_them = Nepodařilo se přidat spolupracovníka, jelikož má zablokovaného majitele repozitáře.
+settings.ignore_stale_approvals = Ignorovat zastaralá schválení
+settings.event_pull_request_merge = Sloučení žádosti o sloučení
+settings.event_pull_request_approvals = Schválení žádostí o sloučení
+settings.ignore_stale_approvals_desc = Nepočítat schválení udělená u starších commitů (zastaralá schválení) do celkového počtu schválení u ŽS. Není relevantní, pokud byla zastaralá schválení již zrušena.
+file_follow = Následovat symbolický odkaz
+settings.protect_status_check_patterns_desc = Zadejte vzorce pro upřesnění kontrol, které musí projít před sloučením větví do větve, která se shoduje s tímto pravidlem. Na každý řádek zadejte jeden vzorec. Vzorce nesmí být prázdné.
+settings.archive.mirrors_unavailable = Zrcadla nejsou dostupná, když je repozitář archivován.
+settings.protect_enable_merge_desc = Kdokoli s přístupem k zápisu bude moci slučovat žádosti o sloučení do této větve.
+settings.archive.text = Archivováním repozitáře jej celý převedete do stavu pouze pro čtení. Bude skryt z nástěnky. Nikdo (ani vy!) nebude moci vytvářet nové commity ani otevírat problémy a žádosti o sloučení.
+settings.event_pull_request_review_request_desc = Bylo požádáno o posouzení žádosti o sloučení nebo bylo toto požádání odstraněno.
[graphs]
+component_loading_info = Tohle může chvíli trvat…
+component_failed_to_load = Došlo k neočekávané chybě.
+code_frequency.what = frekvence kódu
+contributors.what = příspěvky
+recent_commits.what = nedávné commity
+component_loading = Načítání %s...
+component_loading_failed = Nepodařilo se načíst %s
[org]
org_name_holder=Název organizace
@@ -2658,7 +2802,7 @@ teams.remove_all_repos_title=Odstranit všechny repozitáře týmu
teams.remove_all_repos_desc=Tímto odeberete všechny repozitáře z týmu.
teams.add_all_repos_title=Přidat všechny repozitáře
teams.add_all_repos_desc=Tímto přidáte do týmu všechny repozitáře organizace.
-teams.add_nonexistent_repo=Repositář, který se snažíte přidat, neexistuje. Nejdříve jej vytvořte, prosím.
+teams.add_nonexistent_repo=Repositář, který se snažíte přidat, neexistuje. Nejdříve jej prosím vytvořte.
teams.add_duplicate_users=Uživatel je již členem týmu.
teams.repos.none=Tento tým nemůže přistoupit k žádným repozitářům.
teams.members.none=Žádní členové v tomto týmu.
@@ -2672,6 +2816,7 @@ teams.all_repositories_admin_permission_desc=Tomuto týmu je udělen Adm
teams.invite.title=Byli jste pozváni do týmu %s v organizaci %s .
teams.invite.by=Pozvání od %s
teams.invite.description=Pro připojení k týmu klikněte na tlačítko níže.
+follow_blocked_user = Tuto organizaci nemůžete sledovat, protože jste v ní zablokovaní.
[admin]
dashboard=Přehled
@@ -2694,7 +2839,7 @@ settings=Nastavení správce
dashboard.new_version_hint=Gitea %s je nyní k dispozici, právě u vás běži %s. Podívej se na blogu pro více informací.
dashboard.statistic=Souhrn
dashboard.operations=Operace údržby
-dashboard.system_status=Status systému
+dashboard.system_status=Stav systému
dashboard.operation_name=Název operace
dashboard.operation_switch=Přepnout
dashboard.operation_run=Spustit
@@ -2713,11 +2858,12 @@ dashboard.cron.error=Chyba v naplánované úloze: %s: %[3]s
dashboard.cron.finished=Naplánovaná úloha: %[1]s skončila
dashboard.delete_inactive_accounts=Smazat všechny neaktivované účty
dashboard.delete_inactive_accounts.started=Spuštěna úloha mazání všech neaktivovaných účtů.
-dashboard.delete_repo_archives=Odstranit všechny archivy repozitáře (ZIP, TAR.GZ, atd.)
+dashboard.delete_repo_archives=Odstranit všechny archivy repozitářů (ZIP, TAR.GZ, atd.)
dashboard.delete_repo_archives.started=Spuštěna úloha smazání všech archivovaných repozitářů.
dashboard.delete_missing_repos=Smazat všechny repozitáře, které nemají Git soubory
dashboard.delete_missing_repos.started=Spuštěna úloha mazání všech repozitářů, které nemají Git soubory.
dashboard.delete_generated_repository_avatars=Odstranit vygenerované avatary repozitářů
+dashboard.sync_repo_tags=Synchronizovat značky z git dat do databáze
dashboard.update_mirrors=Aktualizovat zrcadla
dashboard.repo_health_check=Kontrola stavu všech repozitářů
dashboard.check_repo_stats=Zkontrolovat všechny statistiky repositáře
@@ -2726,37 +2872,37 @@ dashboard.deleted_branches_cleanup=Vyčistit odstraněné větve
dashboard.update_migration_poster_id=Aktualizovat ID autora migrace
dashboard.git_gc_repos=Provést úklid všech repozitářů
dashboard.resync_all_sshkeys=Aktualizovat soubor „.ssh/authorized_keys“ pomocí SSH klíčů Forgejo.
-dashboard.resync_all_sshprincipals=Aktualizovat soubor '.ssh/authorized_principals' pomocí Forgejo SSH Principal certifikátů.
-dashboard.resync_all_hooks=Znovu synchronizovat háčky před přijetím, aktualizace a po přijetí všech repozitářů.
+dashboard.resync_all_sshprincipals=Aktualizovat soubor „.ssh/authorized_principals“ pomocí Forgejo SSH Principal certifikátů.
+dashboard.resync_all_hooks=Znovu synchronizovat hooky „před přijetím“, „aktualizace“ a „po přijetí“ u všech repozitářů
dashboard.reinit_missing_repos=Znovu inicializovat všechny chybějící repozitáře, pro které existují záznamy
dashboard.sync_external_users=Synchronizovat externí uživatelská data
dashboard.cleanup_hook_task_table=Vyčistit tabulku hook_task
dashboard.cleanup_packages=Vyčistit prošlé balíčky
dashboard.server_uptime=Doba provozu serveru
-dashboard.current_goroutine=Aktuální Goroutines
+dashboard.current_goroutine=Aktuální goroutines
dashboard.current_memory_usage=Aktuální využití paměti
-dashboard.total_memory_allocated=Přidělené paměti celkem
-dashboard.memory_obtained=Celkem získané paměti
+dashboard.total_memory_allocated=Celková přidělená paměť
+dashboard.memory_obtained=Získaná paměť
dashboard.pointer_lookup_times=Časy vyhledávání ukazatelů
dashboard.memory_allocate_times=Alokace paměti
dashboard.memory_free_times=Uvolnění paměti
dashboard.current_heap_usage=Aktuální využití paměti zásobníku
dashboard.heap_memory_obtained=Získaná paměť zásobníku
dashboard.heap_memory_idle=Nečinná paměť zásobníku
-dashboard.heap_memory_in_use=Používána paměť zásobníku
+dashboard.heap_memory_in_use=Používaná paměť zásobníku
dashboard.heap_memory_released=Uvolněná paměť zásobníku
dashboard.heap_objects=Objekty zásobníku
dashboard.bootstrap_stack_usage=Využití zásobníku prvotního zavedení
-dashboard.stack_memory_obtained=Celkem získané paměti zásobníku
+dashboard.stack_memory_obtained=Celková získaná pamět zásobníku
dashboard.mspan_structures_usage=Užití struktur MSpan
dashboard.mspan_structures_obtained=Získané struktury MSpan
-dashboard.mcache_structures_usage=Užití struktur MCache
+dashboard.mcache_structures_usage=Využití struktur MCache
dashboard.mcache_structures_obtained=Získané struktury MCache
-dashboard.profiling_bucket_hash_table_obtained=Získaná analytická tabulka
-dashboard.gc_metadata_obtained=Získané metadata GC
+dashboard.profiling_bucket_hash_table_obtained=Získaná profilovací bucket hash tabulka
+dashboard.gc_metadata_obtained=Získaná metadata GC
dashboard.other_system_allocation_obtained=Získaná alokace ostatních systémových prostředků
dashboard.next_gc_recycle=Příští recyklace GC
-dashboard.last_gc_time=Doba od poslední recyklace GC
+dashboard.last_gc_time=Doba od posledního GC
dashboard.total_gc_time=Celková pauza GC
dashboard.total_gc_pause=Celková pauza GC
dashboard.last_gc_pause=Poslední pauza GC
@@ -2765,11 +2911,14 @@ dashboard.delete_old_actions=Odstranit všechny staré akce z databáze
dashboard.delete_old_actions.started=Začalo odstraňování všech starých akcí z databáze.
dashboard.update_checker=Kontrola aktualizací
dashboard.delete_old_system_notices=Odstranit všechna stará systémová upozornění z databáze
+dashboard.gc_lfs=Úklid LFS meta objektů
dashboard.stop_zombie_tasks=Zastavit zombie úlohy
dashboard.stop_endless_tasks=Zastavit nekonečné úlohy
dashboard.cancel_abandoned_jobs=Zrušit opuštěné úlohy
dashboard.start_schedule_tasks=Spustit naplánované úlohy
dashboard.sync_branch.started=Synchronizace větví byla spuštěna
+dashboard.sync_tag.started=Synchronizace značek spuštěna
+dashboard.rebuild_issue_indexer=Znovu sestavit index úkolů
users.user_manage_panel=Správa uživatelských účtů
users.new_account=Vytvořit uživatelský účet
@@ -2807,11 +2956,11 @@ users.allow_import_local=Může importovat lokální repozitáře
users.allow_create_organization=Může vytvářet organizace
users.update_profile=Aktualizovat uživatelský účet
users.delete_account=Smazat uživatelský účet
-users.cannot_delete_self=Nemůžete smazat sami sebe
+users.cannot_delete_self=Nemůžete odstranit sami sebe
users.still_own_repo=Tento uživatel stále vlastní jeden nebo více repozitářů. Tyto repozitáře nejprve smažte nebo je převeďte.
users.still_has_org=Uživatel je člen organizace. Nejprve odstraňte uživatele ze všech organizací.
users.purge=Vymazat uživatele
-users.purge_help=Vynuceně smazat uživatele a všechny repositáře, organizace a balíčky vlastněné uživatelem. Všechny komentáře budou také smazány.
+users.purge_help=Vynuceně odstranit uživatele a všechny repositáře, organizace a balíčky vlastněné uživatelem. Budou také smazány všechny komentáře a problémy uživatele.
users.still_own_packages=Tento uživatel stále vlastní jeden nebo více balíčků, nejprve odstraňte tyto balíčky.
users.deletion_success=Uživatelský účet byl smazán.
users.reset_2fa=Resetovat 2FA
@@ -2829,7 +2978,7 @@ users.list_status_filter.is_2fa_enabled=2FA povoleno
users.list_status_filter.not_2fa_enabled=2FA zakázáno
users.details=Detaily uživatele
-emails.email_manage_panel=Správa e-mailů uživatele
+emails.email_manage_panel=Správa e-mailů uživatelů
emails.primary=Hlavní
emails.activated=Aktivován
emails.filter_sort.email=E-mail
@@ -2848,7 +2997,7 @@ orgs.teams=Týmy
orgs.members=Členové
orgs.new_orga=Nová organizace
-repos.repo_manage_panel=Správa repozitáře
+repos.repo_manage_panel=Správa repozitářů
repos.unadopted=Nepřijaté repozitáře
repos.unadopted.no_more=Nebyly nalezeny žádné další nepřijaté repositáře
repos.owner=Vlastník
@@ -2874,15 +3023,15 @@ packages.repository=Repozitář
packages.size=Velikost
packages.published=Publikováno
-defaulthooks=Výchozí webové háčky
+defaulthooks=Výchozí webhooky
defaulthooks.add_webhook=Přidat výchozí webový háček
defaulthooks.update_webhook=Aktualizovat výchozí webový háček
-systemhooks=Systémové webové háčky
+systemhooks=Systémové webhooky
systemhooks.add_webhook=Přidat systémový webový háček
systemhooks.update_webhook=Aktualizovat systémový webový háček
-auths.auth_manage_panel=Správa zdroje ověřování
+auths.auth_manage_panel=Správa zdrojů ověřování
auths.new=Přidat zdroj ověřování
auths.name=Název
auths.type=Typ
@@ -2926,7 +3075,7 @@ auths.smtp_auth=Typ ověření SMTP
auths.smtphost=Server SMTP
auths.smtpport=Port SMTP
auths.allowed_domains=Povolené domény
-auths.allowed_domains_helper=Nechte prázdné k povolení všech domén. Oddělte více domén pomocí čárky („,“).
+auths.allowed_domains_helper=Nechte prázdné k povolení všech domén. Více domén oddělte čárkou („,“).
auths.skip_tls_verify=Přeskočit ověření TLS
auths.force_smtps=Vynutit SMTPS
auths.force_smtps_helper=SMTPS se vždy používá na portu 465. Nastavením této hodnoty vynutíte použití SMTPS na jiných portech. (V opačném případě se na ostatních portech použije STARTTLS, pokud je podporován hostiteslkým serverem.)
@@ -2972,7 +3121,7 @@ auths.tips=Tipy
auths.tips.oauth2.general=Ověřování OAuth2
auths.tips.oauth2.general.tip=Při registraci nové OAuth2 autentizace by URL callbacku/přesměrování měla být:
auths.tip.oauth2_provider=Poskytovatel OAuth2
-auths.tip.bitbucket=Vytvořte nového OAuth konzumenta na https://bitbucket.org/account/user//oauth-consumers/new a přidejte oprávnění „Account“ - „Read“
+auths.tip.bitbucket=Vytvořte nového OAuth uživatele na stránce https://bitbucket.org/account/user//oauth-consumers/new a přidejte oprávnění „Account“ - „Read“
auths.tip.nextcloud=Zaregistrujte nového OAuth konzumenta na vaší instanci pomocí následujícího menu „Nastavení -> Zabezpečení -> OAuth 2.0 klient“
auths.tip.dropbox=Vytvořte novou aplikaci na https://www.dropbox.com/developers/apps
auths.tip.facebook=Registrujte novou aplikaci na https://developers.facebook.com/apps a přidejte produkt „Facebook Login“
@@ -3005,7 +3154,7 @@ config.app_name=Název stránky
config.app_ver=Verze Forgejo
config.app_url=Základní URL Forgejo
config.custom_conf=Cesta ke konfiguračnímu souboru
-config.custom_file_root_path=Kořenový adresář vlastních souborů
+config.custom_file_root_path=Vlastní kořenový adresář souborů
config.domain=Doména serveru
config.offline_mode=Lokální režim
config.disable_router_log=Vypnout log směrovače
@@ -3027,14 +3176,14 @@ config.ssh_port=Port
config.ssh_listen_port=Port pro naslouchání
config.ssh_root_path=Kořenová cesta
config.ssh_key_test_path=Cesta testu klíčů
-config.ssh_keygen_path=Cesta ke generátoru klíčů ('ssh-keygen')
+config.ssh_keygen_path=Cesta ke generátoru klíčů („ssh-keygen“)
config.ssh_minimum_key_size_check=Kontrola minimální velikosti klíčů
config.ssh_minimum_key_sizes=Minimální velikost klíčů
config.lfs_config=Nastavení LFS
config.lfs_enabled=Povoleno
config.lfs_content_path=Cesta k obsahu LFS
-config.lfs_http_auth_expiry=Vypršení autorizace LFS HTTPS
+config.lfs_http_auth_expiry=Vypršení autorizace LFS HTTP
config.db_config=Nastavení databáze
config.db_type=Typ
@@ -3050,27 +3199,27 @@ config.register_email_confirm=Pro registraci vyžadovat potvrzení e-mailu
config.disable_register=Vypnout možnost uživatelské registrace
config.allow_only_internal_registration=Povolit registraci pouze prostřednictvím Forgejo
config.allow_only_external_registration=Povolit registraci pouze prostřednictvím externích služeb
-config.enable_openid_signup=Povolit automatickou registraci pomocí OpenID
+config.enable_openid_signup=Povolit uživatelskou registraci pomocí OpenID
config.enable_openid_signin=Povolit přihlášení pomocí OpenID
-config.show_registration_button=Ukázat tlačítko registrace
-config.require_sign_in_view=Vyžadovat přihlášení k zobrazení stránek
+config.show_registration_button=Zobrazit tlačítko registrace
+config.require_sign_in_view=Vyžadovat přihlášení pro zobrazení obsahu
config.mail_notify=Povolit e-mailová oznámení
config.enable_captcha=Povolit CAPTCHA
-config.active_code_lives=Doba života aktivního kódu
-config.reset_password_code_lives=Čas vypršení platnosti kódu pro obnovení účtu
-config.default_keep_email_private=Jako počáteční nastavení skrýt e-mailové adresy
-config.default_allow_create_organization=Dovolí novým uživatelům zakládat organizace
+config.active_code_lives=Doba expirace aktivačního kódu
+config.reset_password_code_lives=Čas vypršení platnosti obnovovacího kódu
+config.default_keep_email_private=Ve výchozím nastavení skrýt e-mailové adresy
+config.default_allow_create_organization=Povolit ve výchozím nastavení vytvářet organizace
config.enable_timetracking=Povolit sledování času
-config.default_enable_timetracking=Povolit sledování času ve výchozím nastavení
+config.default_enable_timetracking=Povolit ve výchozím nastavení sledování času
config.default_allow_only_contributors_to_track_time=Povolit sledování času pouze přispěvatelům
config.no_reply_address=Skrytá e-mailová doména
-config.default_visibility_organization=Výchozí viditelnost pro nové organizace
-config.default_enable_dependencies=Povolit závislosti úkolů ve výchozím stavu
+config.default_visibility_organization=Výchozí viditelnost nových organizací
+config.default_enable_dependencies=Povolit ve výchozím nastavení závislosti úkolů
-config.webhook_config=Nastavení webových háčků
+config.webhook_config=Nastavení webhooků
config.queue_length=Délka fronty
config.deliver_timeout=Časový limit doručení
-config.skip_tls_verify=Přeskočit verifikaci TLS
+config.skip_tls_verify=Přeskočit ověření TLS
config.mailer_config=Nastavení odesílání e-mailů
config.mailer_enabled=Zapnutý
@@ -3106,37 +3255,37 @@ config.provider_config=Nastavení poskytovatele
config.cookie_name=Název souboru cookie
config.gc_interval_time=Čas intervalu GC
config.session_life_time=Doba trvání relace
-config.https_only=Pouze protokol HTTPS
+config.https_only=Pouze HTTPS
config.cookie_life_time=Doba života souboru cookie
-config.picture_config=Nastavení obrázku a avataru
-config.picture_service=Služba ikon uživatelů
+config.picture_config=Nastavení obrázků a avatarů
+config.picture_service=Služba obrázků
config.disable_gravatar=Zakázat službu Gravatar
-config.enable_federated_avatar=Povolit avatary z veřejných zdrojů
+config.enable_federated_avatar=Povolit federované avatary
config.git_config=Konfigurace Gitu
-config.git_disable_diff_highlight=Zakázat zvýraznění syntaxe v rozdílovém zobrazení
-config.git_max_diff_lines=Maximální počet rozdílových řádků jednoho souboru
-config.git_max_diff_line_characters=Maximální počet zobrazených rozdílových znaků
-config.git_max_diff_files=Maximální počet zobrazených rozdílových souborů
-config.git_gc_args=Parametry GC
+config.git_disable_diff_highlight=Zakázat zvýraznění syntaxe v zobrazení rozdílů
+config.git_max_diff_lines=Maximální počet rozdílových řádků na soubor
+config.git_max_diff_line_characters=Maximální počet zobrazených rozdílných znaků
+config.git_max_diff_files=Maximální počet zobrazených rozdílných souborů
+config.git_gc_args=Argumenty GC
config.git_migrate_timeout=Časový limit migrace
config.git_mirror_timeout=Časový limit aktualizace zrcadla
-config.git_clone_timeout=Časový limit operace naklonování
-config.git_pull_timeout=Časový limit operace stažení
+config.git_clone_timeout=Časový limit operace klonování
+config.git_pull_timeout=Časový limit operace Pull
config.git_gc_timeout=Časový limit operace GC
-config.log_config=Nastavení logů
+config.log_config=Nastavení protokolů
config.disabled_logger=Zakázané
-config.access_log_mode=Režim logování přístupu
-config.access_log_template=Šablona záznamu přístupu
+config.access_log_mode=Režim protokolování přístupu
+config.access_log_template=Šablona protokolu přístupu
config.xorm_log_sql=Logovat SQL
config.set_setting_failed=Nastavení %s se nezdařilo
monitor.stats=Statistiky
-monitor.cron=Naplánované úlohy
+monitor.cron=Úlohy Cron
monitor.name=Název
monitor.schedule=Rozvrh
monitor.next=Příští čas spuštění
@@ -3173,7 +3322,7 @@ monitor.queue.settings.remove_all_items=Odstranit vše
monitor.queue.settings.remove_all_items_done=Všechny položky ve frontě byly odstraněny.
notices.system_notice_list=Systémová oznámení
-notices.view_detail_header=Zobrazit detaily oznámení
+notices.view_detail_header=Podrobnosti oznámení
notices.operations=Operace
notices.select_all=Vybrat vše
notices.deselect_all=Zrušit výběr všech
@@ -3186,7 +3335,35 @@ notices.type_2=Úloha
notices.desc=Popis
notices.op=Akce
notices.delete_success=Systémové upozornění bylo smazáno.
+dashboard.sync_repo_branches = Synchronizovat zmeškané větve z dat gitu do databáze
+dashboard.sync_repo_tags = Synchronizovat značky z dat gitu do databáze
+dashboard.gc_lfs = Sbírat garbage z LFS meta objektů
+monitor.queue.activeworkers = Aktivní workery
+defaulthooks.desc = Webhooky automaticky vytvářejí žádosti HTTP POST na server, kde se spustí určité události Forgejo. Webhooky zde definované jsou výchozí a budou zkopírovány do všech nových repozitářů. Více informací zjistíte v návodu webhooků .
+systemhooks.desc = Webhooky automaticky vytvářejí žádosti HTTP POST na server, kde se spustí určité události Forgejo. Webhooky zde definované budou aktivní u všech repozitářů v systému, zvažte tedy prosím všechny vlivy na výkon, které může tato funkce způsobit. Více informací zjistíte v návodu webhooků .
+assets = Assety kódu
+dashboard.cleanup_actions = Vymazat prošlé protokoly a artefakty z akcí
+packages.cleanup.success = Prošlá data úspěšně vymazána
+config.logger_name_fmt = Logger: %S
+monitor.download_diagnosis_report = Stáhnout hlášení o diagnóze
+self_check.no_problem_found = Zatím nenalezen žádný problém.
+self_check.database_collation_mismatch = Očekává se, že databáze použije collation: %s
+self_check.database_inconsistent_collation_columns = Databáze používá collation %s, tyto sloupce nicméně používají rozdílné collationy. Toto může způsobit neočekávané problémy.
+self_check.database_fix_mysql = Uživatelé MySQL/MariaDB mohou použít příkaz „gitea doctor convert“ pro automatické opravení problémů s collation. Problém také můžete vyřešit ručně SQL příkazy „ALTER ... COLLATE ...“.
+self_check = Vlastní kontrola
+dashboard.sync_tag.started = Synchronizace značek spuštěna
+dashboard.rebuild_issue_indexer = Přestavit indexer vydání
+self_check.database_collation_case_insensitive = Databáze používá collation %s. Jedná se o intenzivní collation. Ačkoli s ní Forgejo nejspíše bude pracovat, mohou nastat určité vzácné případy, kdy nebude pracovat tak, jak má.
+self_check.database_fix_mssql = Uživatelé MSSQL mohou tento problém vyřešit pouze ručními SQL příkazy „ALTER ... COLLATE ...“.
+auths.oauth2_map_group_to_team = Zmapovat zabrané skupiny u týmů organizací (volitelné - vyžaduje název zabrání výše)
+monitor.queue.settings.desc = Pooly dynamicky rostou podle blokování fronty jejich workerů.
+self_check.no_problem_found=Zatím nebyl nalezen žádný problém.
+self_check.database_collation_mismatch=Očekávejte, že databáze použije collation: %s
+self_check.database_collation_case_insensitive=Databáze používá collation %s, což je collation nerozlišující velká a malá písmena. Ačkoli s ní Gitea může pracovat, mohou se vyskytnout vzácné případy, kdy nebude fungovat podle očekávání.
+self_check.database_inconsistent_collation_columns=Databáze používá collation %s, ale tyto sloupce používají chybné collation. To může způsobit neočekávané problémy.
+self_check.database_fix_mysql=Pro uživatele MySQL/MariaDB můžete použít příkaz "gitea doctor convert", který opraví problémy s collation, nebo můžete také problém vyřešit příkazem "ALTER ... COLLATE ..." SQL ručně.
+self_check.database_fix_mssql=Uživatelé MSSQL mohou problém vyřešit pouze pomocí příkazu "ALTER ... COLLATE ..." SQL ručně.
[action]
create_repo=vytvořil/a repozitář %s
@@ -3267,10 +3444,10 @@ error.extract_sign=Selhalo získání podpisu
error.generate_hash=Selhalo vygenerování hash revize
error.no_committer_account=Žádný účet není propojen s e-mailovou adresou přispěvatele
error.no_gpg_keys_found=V databázi nebyl nalezen žádný známý klíč pro tento podpis
-error.not_signed_commit=Nepodepsaná revize
-error.failed_retrieval_gpg_keys=Nelze získat žádný klíč propojený s účtem přispěvatele
-error.probable_bad_signature=VAROVÁNÍ! Přestože v databázi existuje klíč s tímto ID, tuto revizi neověřuje! Tato revize je PODEZŘELÁ.
-error.probable_bad_default_signature=VAROVÁNÍ! Ačkoli výchozí klíč má toto ID, neověřuje tuto revizi! Tato revize je PODEZŘELÁ.
+error.not_signed_commit=Nepodepsaný commit
+error.failed_retrieval_gpg_keys=Nepodařilo se získat žádný klíč propojený s účtem přispěvatele
+error.probable_bad_signature=VAROVÁNÍ! Přestože v databázi existuje klíč s tímto ID, tento commit neověřuje! Tento commit je PODEZŘELÝ.
+error.probable_bad_default_signature=VAROVÁNÍ! Ačkoli výchozí klíč má toto ID, neověřuje tento commit! Tento commit je PODEZŘELÝ.
[units]
unit=Jednotka
@@ -3374,6 +3551,7 @@ rpm.distros.suse=na distribuce založené na SUSE
rpm.install=Pro instalaci balíčku spusťte následující příkaz:
rpm.repository=Informace o repozitáři
rpm.repository.architectures=Architektury
+rpm.repository.multiple_groups=Tento balíček je k dispozici ve více skupinách.
rubygems.install=Pro instalaci balíčku pomocí gem spusťte následující příkaz:
rubygems.install2=nebo ho přidejte do Gemfie:
rubygems.dependencies.runtime=Běhové závislosti
@@ -3403,10 +3581,10 @@ owner.settings.cargo.initialize.success=Index Cargo byl úspěšně vytvořen.
owner.settings.cargo.rebuild=Znovu vytvořit Index
owner.settings.cargo.rebuild.error=Obnovení Cargo indexu se nezdařilo: %v
owner.settings.cargo.rebuild.success=Cargo Index byl úspěšně obnoven.
-owner.settings.cleanuprules.title=Spravovat pravidla pro čištění
+owner.settings.cleanuprules.title=Správa pravidel čištění
owner.settings.cleanuprules.add=Přidat pravidlo pro čištění
owner.settings.cleanuprules.edit=Upravit pravidlo pro čištění
-owner.settings.cleanuprules.none=Nejsou k dispozici žádná pravidla čištění. Prohlédněte si prosím dokumentaci.
+owner.settings.cleanuprules.none=Zatím nejsou k dispozici žádná pravidla čištění.
owner.settings.cleanuprules.preview=Náhled pravidla pro čištění
owner.settings.cleanuprules.preview.overview=%d balíčků má být odstraněno.
owner.settings.cleanuprules.preview.none=Pravidlo čištění neodpovídá žádným balíčkům.
@@ -3426,6 +3604,8 @@ owner.settings.cleanuprules.success.delete=Pravidlo pro čištění bylo odstran
owner.settings.chef.title=Registr Chef
owner.settings.chef.keypair=Generovat pár klíčů
owner.settings.chef.keypair.description=Pro autentizaci do registru Chef je zapotřebí pár klíčů. Pokud jste předtím vytvořili pár klíčů, nově vygenerovaný pár klíčů vyřadí starý pár klíčů.
+rpm.repository.multiple_groups = Tento balíček je dostupný v několika skupinách.
+owner.settings.cargo.rebuild.description = Opětovné sestavení může být užitečné, pokud není index synchronizován s uloženými balíčky Cargo.
[secrets]
secrets=Tajné klíče
@@ -3501,13 +3681,15 @@ runs.actors_no_select=Všichni aktéři
runs.status_no_select=Všechny stavy
runs.no_results=Nebyly nalezeny žádné výsledky.
runs.no_workflows=Zatím neexistují žádné pracovní postupy.
+runs.no_workflows.quick_start=Nevíte jak začít s Gitea Actions? Podívejte se na průvodce rychlým startem .
+runs.no_workflows.documentation=Další informace o Gitea Actions naleznete v dokumentaci .
runs.no_runs=Pracovní postup zatím nebyl spuštěn.
runs.empty_commit_message=(prázdná zpráva commitu)
workflow.disable=Zakázat pracovní postup
-workflow.disable_success=Pracovní postup „%s“ byl úspěšně deaktivován.
+workflow.disable_success=Workflow „%s“ byl úspěšně deaktivován.
workflow.enable=Povolit pracovní postup
-workflow.enable_success=Pracovní postup „%s“ byl úspěšně aktivován.
+workflow.enable_success=Workflow „%s“ byl úspěšně aktivován.
workflow.disabled=Pracovní postup je zakázán.
@@ -3518,6 +3700,7 @@ variables.none=Zatím nejsou žádné proměnné.
variables.deletion=Odstranit proměnnou
variables.deletion.description=Odstranění proměnné je trvalé a nelze jej vrátit zpět. Pokračovat?
variables.description=Proměnné budou předány určitým akcím a nelze je přečíst jinak.
+variables.id_not_exist=Proměnná s ID %d neexistuje.
variables.edit=Upravit proměnnou
variables.deletion.failed=Nepodařilo se odstranit proměnnou.
variables.deletion.success=Proměnná byla odstraněna.
@@ -3528,6 +3711,12 @@ variables.update.success=Proměnná byla upravena.
runs.no_workflows.quick_start = Nevíte jak začít s Gitea Action? Podívejte se na průvodce rychlým startem .
variables.id_not_exist = Proměnná s id %d neexistuje.
runs.no_workflows.documentation = Další informace o Gitea Action, viz dokumentace .
+runners.none = Nejsou dostupné žádné runnery
+runs.workflow = Workflow
+runners = Runnery
+runs.pushed_by = pushnuto uživatelem
+need_approval_desc = Potřebovat schválení pro spouštění workflowů pro žádosti o sloučení forků.
+runners.runner_manage_panel = Správa runnerů
[projects]
type-1.display_name=Samostatný projekt
diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini
index 5b288c0090..3056e7f2ff 100644
--- a/options/locale/locale_de-DE.ini
+++ b/options/locale/locale_de-DE.ini
@@ -89,7 +89,7 @@ add=Hinzufügen
add_all=Alle hinzufügen
remove=Löschen
remove_all=Alle entfernen
-remove_label_str=Element "%s " entfernen
+remove_label_str=Element „%s“ entfernen
edit=Bearbeiten
enabled=Aktiviert
@@ -141,7 +141,20 @@ name=Name
value=Wert
view = Ansehen
tracked_time_summary = Zusammenfassung erfasster Zeit, basierend auf Filtern der Issue-Liste
-confirm_delete_artifact = Bist du sicher, das Artefakt '%s' löschen zu wollen?
+confirm_delete_artifact = Bist du sicher, das Artefakt „%s“ löschen zu wollen?
+toggle_menu = Menü umschalten
+filter = Filter
+filter.clear = Filter löschen
+filter.is_archived = Archiviert
+filter.not_archived = Nicht archiviert
+filter.is_fork = Geforkt
+filter.not_fork = Nicht geforkt
+filter.is_mirror = Gemirrort
+filter.not_mirror = Nicht gemirrort
+filter.is_template = Vorlage
+filter.not_template = Keine Vorlage
+filter.public = Öffentlich
+filter.private = Privat
[aria]
navbar=Navigationsleiste
@@ -168,8 +181,8 @@ buttons.list.task.tooltip=Aufgabenliste hinzufügen
buttons.mention.tooltip=Benutzer oder Team erwähnen
buttons.ref.tooltip=Issue oder Pull-Request referenzieren
buttons.switch_to_legacy.tooltip=Legacy-Editor verwenden
-buttons.enable_monospace_font=Monospace-Schrift aktivieren
-buttons.disable_monospace_font=Monospace-Schrift deaktivieren
+buttons.enable_monospace_font=Festbreitenschrift aktivieren
+buttons.disable_monospace_font=Festbreitenschrift deaktivieren
[filter]
string.asc=A–Z
@@ -177,11 +190,12 @@ string.desc=Z–A
[error]
occurred=Ein Fehler ist aufgetreten
-report_message=Wenn du glaubst, dass dies ein Fehler von Forgejo ist, suche bitte auf Codeberg nach diesem Fehler und erstelle gegebenenfalls einen neuen Bugreport.
-missing_csrf=Fehlerhafte Anfrage: Kein CSRF Token verfügbar
-invalid_csrf=Fehlerhafte Anfrage: Ungültiger CSRF Token
+report_message=Wenn du glaubst, dass dies ein Fehler von Forgejo ist, such bitte auf Codeberg nach Issues oder erstelle gegebenenfalls ein neues Issue.
+missing_csrf=Fehlerhafte Anfrage: Kein CSRF-Token verfügbar
+invalid_csrf=Fehlerhafte Anfrage: Ungültiger CSRF-Token
not_found=Das Ziel konnte nicht gefunden werden.
network_error=Netzwerkfehler
+server_internal = Interner Serverfehler
[startpage]
app_desc=Ein einfacher, selbst gehosteter Git-Service
@@ -224,7 +238,7 @@ err_admin_name_pattern_not_allowed=Administrator-Benutzername ist ungültig, der
err_admin_name_is_invalid=Administratornutzername ist ungültig
general_title=Allgemeine Einstellungen
-app_name=Seitentitel
+app_name=Instanzname
app_name_helper=Du kannst hier den Namen deines Unternehmens eingeben.
repo_path=Repository-Verzeichnis
repo_path_helper=Remote-Git-Repositorys werden in diesem Verzeichnis gespeichert.
@@ -236,9 +250,9 @@ domain=Server-Domain
domain_helper=Domain oder Host-Adresse für den Server.
ssh_port=SSH-Server-Port
ssh_port_helper=Der Port deines SSH-Servers. Leer lassen, um SSH zu deaktivieren.
-http_port=Forgejo-HTTP-Listen-Port
+http_port=HTTP-Listen-Port
http_port_helper=Port, unter dem der Forgejo-Webserver laufen soll.
-app_url=Forgejo-Basis-URL
+app_url=Basis-URL
app_url_helper=Adresse für HTTP(S)-Klon-URLs und E-Mail-Benachrichtigungen.
log_root_path=Logdateipfad
log_root_path_helper=Log-Dateien werden in diesem Verzeichnis gespeichert.
@@ -307,6 +321,8 @@ env_config_keys=Umgebungskonfiguration
env_config_keys_prompt=Die folgenden Umgebungsvariablen werden auch auf Ihre Konfigurationsdatei angewendet:
allow_dots_in_usernames = Erlaubt Benutzern die Verwendung von Punkten in ihren Benutzernamen. Hat keine Auswirkungen auf bestehende Konten.
enable_update_checker_helper_forgejo = Prüft regelmäßig auf neue Forgejo-Versionen, indem ein DNS-TXT-Eintrag unter release.forgejo.org überprüft wird.
+smtp_from_invalid = Die „Sende E-Mail Als“-Adresse ist ungültig
+config_location_hint = Diese Konfigurationsoptionen werden gespeichert in:
[home]
uname_holder=E-Mail-Adresse oder Benutzername
@@ -450,12 +466,12 @@ activate_email.text=Bitte klicke innerhalb von %s auf folgenden Link, um
register_notify=Willkommen bei Forgejo
register_notify.title=%[1]s, willkommen bei %[2]s
register_notify.text_1=dies ist deine Bestätigungs-E-Mail für %s!
-register_notify.text_2=Du kannst dich jetzt mit dem Benutzernamen „%s“ anmelden.
-register_notify.text_3=Wenn dieser Account von dir erstellt wurde, musst du zuerst dein Passwort setzen .
+register_notify.text_2=Du kannst dich mit dem Benutzernamen „%s“ anmelden.
+register_notify.text_3=Wenn jemand anderes diesen Account für dich erstellt hat, musst du zuerst dein Passwort setzen .
reset_password=Stelle dein Konto wieder her
-reset_password.title=%s, du hast um Wiederherstellung deines Kontos gebeten
-reset_password.text=Bitte klicke innerhalb von %s auf folgenden Link, um dein Konto wiederherzustellen:
+reset_password.title=%s, wir haben eine Anfrage zur Wiederherstellung deines Kontos erhalten
+reset_password.text=Falls du das warst, klicke bitte innerhalb von %s auf folgenden Link, um dein Konto wiederherzustellen:
register_success=Registrierung erfolgreich
@@ -587,7 +603,7 @@ organization_leave_success=Du hast die Organisation %s erfolgreich verlassen.
invalid_ssh_key=Dein SSH-Key kann nicht überprüft werden: %s
invalid_gpg_key=Dein GPG-Key kann nicht überprüft werden: %s
invalid_ssh_principal=Ungültige Identität: %s
-must_use_public_key=Der von Dir bereitgestellte Key ist ein privater Key. Bitte lade Deinen privaten Key nirgendwo hoch. Verwende stattdessen Deinen öffentlichen Key.
+must_use_public_key=Der von dir bereitgestellte Key ist ein privater Key. Bitte lade deinen privaten Key nirgendwo hoch. Verwende stattdessen deinen öffentlichen Key.
unable_verify_ssh_key=Der SSH-Key kann nicht verifiziert werden, überprüfe ihn auf Fehler.
auth_failed=Authentifizierung fehlgeschlagen: %v
@@ -752,14 +768,14 @@ keep_email_private_popup=Dies wird deine E-Mail-Adresse nicht nur in deinem Prof
openid_desc=Mit OpenID kannst du dich über einen Drittanbieter authentifizieren.
manage_ssh_keys=SSH-Schlüssel verwalten
-manage_ssh_principals=SSH-Zertifikat's Identitäten verwalten
+manage_ssh_principals=SSH-Zertifikats-Principals verwalten
manage_gpg_keys=GPG-Schlüssel verwalten
add_key=Schlüssel hinzufügen
ssh_desc=Diese öffentlichen SSH-Keys sind mit deinem Account verbunden. Der dazugehörigen privaten SSH-Keys geben dir vollen Zugriff auf deine Repositorys. Verifizierte SSH-Key können verwendet werden, um SSH-signierte Git-Commits zu signieren.
-principal_desc=Diese SSH-Zertifikat-Identitäten sind mit deinem Konto verknüpft und erlauben den vollen Zugriff auf deine Repositories.
+principal_desc=Diese SSH-Zertifikat-Principals sind mit deinem Konto verknüpft und erlauben den vollen Zugriff auf deine Repositorys.
gpg_desc=Diese öffentlichen GPG-Keys sind mit deinem Account verbunden und werden benutzt um deine Commits zu verifizieren. Halte die dazugehörigen privaten GPG-Keys geheim, da diese deine Commits signieren.
-ssh_helper=Brauchst du Hilfe? Hier ist GitHubs Anleitung zum Erzeugen von SSH-Schlüsseln oder zum Lösen einfacher SSH-Probleme .
-gpg_helper=Brauchst du Hilfe? Hier ist GitHubs Anleitung über GPG .
+ssh_helper=Brauchst du Hilfe? Sieh dir die Anleitung zum Erzeugen deiner eigenen SSH-Schlüssel an oder zum Lösen häufiger Probleme , denen du bei der Arbeit mit SSH begegnen kannst.
+gpg_helper=Brauchst du Hilfe? Sieh dir die Anleitung über GPG an.
add_new_key=SSH-Schlüssel hinzufügen
add_new_gpg_key=GPG-Schlüssel hinzufügen
key_content_ssh_placeholder=Startet mit „ssh-ed25519“, „ssh-rsa“, „ecdsa-sha2-nistp256“, „ecdsa-sha2-nistp384“, „ecdsa-sha2-nistp521“, „sk-ecdsa-sha2-nistp256@openssh.com“ oder „sk-ssh-ed25519@openssh.com“
@@ -775,12 +791,12 @@ gpg_key_matched_identities_long=Die eingebetteten Identitäten in diesem Schlüs
gpg_key_verified=Verifizierter Schlüssel
gpg_key_verified_long=Der Schlüssel wurde mit einem Token verifiziert. Er kann verwendet werden, um Commits zu verifizieren, die mit irgendeiner für diesen Nutzer aktivierten E-Mail-Adresse und irgendeiner Identität dieses Schlüssels übereinstimmen.
gpg_key_verify=Verifizieren
-gpg_invalid_token_signature=Der GPG Key, die Signatur, und das Token stimmen nicht überein, oder das Token ist veraltet.
+gpg_invalid_token_signature=Der GPG-Key, die Signatur, und das Token stimmen nicht überein, oder das Token ist veraltet.
gpg_token_required=Du musst eine Signatur für das folgende Token angeben
gpg_token=Token
gpg_token_help=Du kannst eine Signatur wie folgt generieren:
gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
-gpg_token_signature=GPG Textsignatur (armored signature)
+gpg_token_signature=GPG-Textsignatur (armored signature)
key_signature_gpg_placeholder=Beginnt mit „-----BEGIN PGP SIGNATURE-----“
verify_gpg_key_success=GPG-Schlüssel „%s“ wurde verifiziert.
ssh_key_verified=Verifizierter Schlüssel
@@ -790,7 +806,7 @@ ssh_invalid_token_signature=Der gegebene SSH-Schlüssel, Signatur oder Token sti
ssh_token_required=Du musst eine Signatur für den Token unten angeben
ssh_token=Token
ssh_token_help=Du kannst eine Signatur wie folgt generieren:
-ssh_token_signature=SSH Textsignatur (armored signature)
+ssh_token_signature=SSH-Textsignatur (armored signature)
key_signature_ssh_placeholder=Beginnt mit „-----BEGIN SSH SIGNATURE-----“
verify_ssh_key_success=SSH-Key „%s“ wurde verifiziert.
subkeys=Unterschlüssel
@@ -827,7 +843,7 @@ ssh_disabled=SSH ist deaktiviert
ssh_signonly=SSH ist derzeit deaktiviert, sodass diese Schlüssel nur zur Commit-Signaturverifizierung verwendet werden.
ssh_externally_managed=Dieser SSH-Schlüssel wird extern für diesen Benutzer verwaltet
manage_social=Verknüpfte soziale Konten verwalten
-social_desc=Diese sozialen Konten können verwendet werden, um sich bei deinem Konto anzumelden. Stelle sicher, dass du sie alle zuordnen kannst.
+social_desc=Diese sozialen Konten können verwendet werden, um sich bei deinem Konto anzumelden. Stell sicher, dass du sie alle zuordnen kannst.
unbind=Trennen
unbind_success=Das soziale Konto wurde erfolgreich entfernt.
@@ -836,7 +852,7 @@ generate_new_token=Neuen Token erzeugen
tokens_desc=Diese Tokens gewähren vollen Zugriff auf dein Konto via die Forgejo-API.
token_name=Token-Name
generate_token=Token generieren
-generate_token_success=Ein neuer Token wurde generiert. Kopiere diesen, da er nicht erneut angezeigt wird.
+generate_token_success=Ein neuer Token wurde generiert. Kopiere diesen jetzt, da er nicht erneut angezeigt wird.
generate_token_name_duplicate=%s wurde bereits als Anwendungsname verwendet. Bitte wähle einen neuen Namen.
delete_token=Löschen
access_token_deletion=Zugriffstoken löschen
@@ -891,17 +907,17 @@ twofa_is_enrolled=Für dein Konto ist die Zwei-Faktor-Authentifizierung
twofa_not_enrolled=Für dein Konto ist die Zwei-Faktor-Authentifizierung momentan nicht eingeschaltet.
twofa_disable=Zwei-Faktor-Authentifizierung deaktivieren
twofa_scratch_token_regenerate=Neues Einmalpasswort erstellen
-twofa_scratch_token_regenerated=Dein temporärer Token ist jetzt %s. Speichere ihn an einem sicheren Ort, er wird nie wieder angezeigt.
+twofa_scratch_token_regenerated=Dein einmalig verwendbarer Wiederherstellungsschlüssel ist jetzt %s. Speichere ihn an einem sicheren Ort, denn er wird nie wieder angezeigt.
twofa_enroll=Zwei-Faktor-Authentifizierung aktivieren
twofa_disable_note=Du kannst die Zwei-Faktor-Authentifizierung auch wieder deaktivieren.
twofa_disable_desc=Wenn du die Zwei-Faktor-Authentifizierung deaktivierst, wird die Sicherheit deines Kontos verringert. Fortfahren?
-regenerate_scratch_token_desc=Wenn du dein Einmalpasswort verlegt oder es bereits benutzt hast, kannst du es hier zurücksetzen.
+regenerate_scratch_token_desc=Wenn du deinen Wiederherstellungsschlüssel verlegst oder es bereits benutzt hast, kannst du es hier zurücksetzen.
twofa_disabled=Zwei-Faktor-Authentifizierung wurde deaktiviert.
scan_this_image=Scanne diese Grafik mit deiner Authentifizierungs-App:
or_enter_secret=Oder gib das Secret ein: %s
then_enter_passcode=Und gib dann die angezeigte PIN der Anwendung ein:
passcode_invalid=Die PIN ist falsch. Probiere es erneut.
-twofa_enrolled=Die Zwei-Faktor-Authentifizierung wurde für dein Konto aktiviert. Bewahre dein Einmalpasswort (%s) an einem sicheren Ort auf, da es nicht wieder angezeigt werden wird.
+twofa_enrolled=Die Zwei-Faktor-Authentifizierung wurde für dein Konto aktiviert. Bewahre deinen einmalig verwendbaren Wiederherstellungsschlüssel (%s) an einem sicheren Ort auf, da er nicht wieder angezeigt werden wird.
twofa_failed_get_secret=Fehler beim Abrufen des Secrets.
webauthn_desc=Sicherheitsschlüssel sind Geräte, die kryptografische Schlüssel beeinhalten. Diese können für die Zwei-Faktor-Authentifizierung verwendet werden. Der Sicherheitsschlüssel muss den Standard „WebAuthn “ unterstützen.
@@ -944,7 +960,7 @@ visibility.limited_tooltip=Nur für authentifizierte Benutzer sichtbar
visibility.private=Privat
visibility.private_tooltip=Sichtbar nur für Mitglieder von Organisationen, denen du beigetreten bist
user_block_success = Dieser Benutzer wurde erfolgreich blockiert.
-twofa_recovery_tip = Falls du dein Gerät verlierst, wirst du in der Lage sein, einen einmalig verwendbaren Key zu benutzen, um den auf dein Konto wiederherzustellen.
+twofa_recovery_tip = Falls du dein Gerät verlierst, wirst du in der Lage sein, einen einmalig verwendbaren Wiederherstellungsschlüssel zu benutzen, um den auf dein Konto wiederherzustellen.
webauthn_alternative_tip = Du möchtest vielleicht eine zusätzliche Authentifizierungsmethode einrichten.
blocked_users_none = Keine Benutzer blockiert.
webauthn_key_loss_warning = Falls du deine Security-Keys verlierst, wirst du Zugang zu deinem Konto verlieren.
@@ -1006,7 +1022,7 @@ trust_model_helper_default=Standard: Verwende das Standardvertrauensmodell für
create_repo=Repository erstellen
default_branch=Standardbranch
default_branch_label=Standard
-default_branch_helper=Der default-Branch ist der Basisbranch für Pull-Requests und Commits.
+default_branch_helper=Der Standard-Branch ist der Basisbranch für Pull-Requests und Commits.
mirror_prune=Entfernen
mirror_prune_desc=Entferne veraltete Remote-Tracking-Referenzen
mirror_interval=Spiegelintervall (gültige Zeiteinheiten sind „h“, „m“, „s“). 0 deaktiviert die regelmäßige Synchronisation. (Minimales Intervall: %s)
@@ -1026,7 +1042,7 @@ mirror_password_blank_placeholder=(Nicht gesetzt)
mirror_password_help=Ändere den Benutzernamen, um ein gespeichertes Passwort zu löschen.
watchers=Beobachter
stargazers=Favorisiert von
-stars_remove_warning=Dies wird alle Sterne aus diesem Repository entfernen.
+stars_remove_warning=Dies wird alle Favorisierungen aus diesem Repository entfernen.
forks=Forks
reactions_more=und %d weitere
unit_disabled=Der Administrator hat diesen Repository-Bereich deaktiviert.
@@ -1361,7 +1377,7 @@ projects.column.set_default_desc=Diese Spalte als Standard für nicht kategorisi
projects.column.unset_default=Standard entfernen
projects.column.unset_default_desc=Diese Spalte nicht als Standard verwenden
projects.column.delete=Spalte löschen
-projects.column.deletion_desc=Beim Löschen einer Projektspalte werden alle dazugehörigen Issues nach ‚Nicht kategorisiert‘ verschoben. Fortfahren?
+projects.column.deletion_desc=Beim Löschen einer Projektspalte werden alle dazugehörigen Issues nach „Nicht kategorisiert“ verschoben. Fortfahren?
projects.column.color=Farbe
projects.open=Öffnen
projects.close=Schließen
@@ -1392,7 +1408,7 @@ issues.new.no_milestone=Kein Meilenstein
issues.new.clear_milestone=Meilenstein entfernen
issues.new.open_milestone=Offene Meilensteine
issues.new.closed_milestone=Geschlossene Meilensteine
-issues.new.assignees=Zuständig
+issues.new.assignees=Zuständige
issues.new.clear_assignees=Zuständige entfernen
issues.new.no_assignees=Niemand zuständig
issues.new.no_reviewers=Keine Reviewer
@@ -1403,7 +1419,7 @@ issues.choose.blank_about=Erstelle ein Issue aus der Standardvorlage.
issues.choose.ignore_invalid_templates=Ungültige Vorlagen wurden ignoriert
issues.choose.invalid_templates=%v ungültige Vorlage(n) gefunden
issues.choose.invalid_config=Die Issue-Konfiguration enthält Fehler:
-issues.no_ref=Keine Branch/Tag angegeben
+issues.no_ref=Kein Branch/Tag angegeben
issues.create=Issue erstellen
issues.new_label=Neues Label
issues.new_label_placeholder=Labelname
@@ -1411,31 +1427,31 @@ issues.new_label_desc_placeholder=Beschreibung
issues.create_label=Label erstellen
issues.label_templates.title=Eine vordefinierte Label-Sammung laden
issues.label_templates.info=Es existieren noch keine Labels. Erstelle ein neues Label („Neues Label“) oder verwende die Standard-Label-Sammlung:
-issues.label_templates.helper=Wähle ein Label-Set
-issues.label_templates.use=Label-Set verwenden
+issues.label_templates.helper=Wähle eine Label-Sammlung
+issues.label_templates.use=Label-Sammlung verwenden
issues.label_templates.fail_to_load_file=Fehler beim Laden der Label-Vorlagen-Datei „%s“: %v
issues.add_label=hat das Label %s %s hinzugefügt
issues.add_labels=hat die Labels %s %s hinzugefügt
issues.remove_label=hat das Label %s %s entfernt
issues.remove_labels=hat die Labels %s %s entfernt
issues.add_remove_labels=hat %s hinzugefügt, und %s %s entfernt
-issues.add_milestone_at=`hat diesen Issue %[2]s zum %[1]s Meilenstein hinzugefügt`
-issues.add_project_at=`hat dieses zum %s projekt %s hinzugefügt`
+issues.add_milestone_at=`hat dieses Issue %[2]s zum Meilenstein %[1]s hinzugefügt`
+issues.add_project_at=`hat dies zum Projekt %s %s hinzugefügt`
issues.change_milestone_at=`hat den Meilenstein %[3]s von %[1]s zu %[2]s geändert`
issues.change_project_at=`hat das Projekt %[3]s von %[1]s zu %[2]s geändert`
issues.remove_milestone_at=`hat dieses Issue %[2]s vom %[1]s Meilenstein entfernt`
-issues.remove_project_at=`hat dieses vom %s Projekt %s entfernt`
+issues.remove_project_at=`hat dies vom Projekt %s %s entfernt`
issues.deleted_milestone=`(gelöscht)`
issues.deleted_project=`(gelöscht)`
issues.self_assign_at=`hat sich das Issue %s selbst zugewiesen`
issues.add_assignee_at=`wurde von %s %s zugewiesen`
-issues.remove_assignee_at=`wurde von %s %s nicht zugewiesen`
-issues.remove_self_assignment=`hat seine Zuweisung %s entfernt`
+issues.remove_assignee_at=`wurde von %s von der Zuweisung %s befreit`
+issues.remove_self_assignment=`hat die Selbstzuweisung %s entfernt`
issues.change_title_at=`hat den Titel von %s zu %s %s geändert`
issues.change_ref_at=`hat die Referenz von %s zu %s %s geändert`
issues.remove_ref_at=`hat die Referenz %s entfernt %s`
issues.add_ref_at=`hat die Referenz %s hinzugefügt %s`
-issues.delete_branch_at=`löschte die Branch %s %s`
+issues.delete_branch_at=`löschte den Branch %s %s`
issues.filter_label=Label
issues.filter_label_exclude=`Alt
+ Klick/Enter
verwenden, um Labels auszuschließen`
issues.filter_label_no_select=Alle Labels
@@ -1459,7 +1475,7 @@ issues.filter_type.assigned_to_you=Dir zugewiesen
issues.filter_type.created_by_you=Von dir erstellt
issues.filter_type.mentioning_you=Hat dich erwähnt
issues.filter_type.review_requested=Review angefordert
-issues.filter_type.reviewed_by_you=Bewertet von dir
+issues.filter_type.reviewed_by_you=Von dir gereviewt
issues.filter_sort=Sortieren
issues.filter_sort.latest=Neueste
issues.filter_sort.oldest=Älteste
@@ -1497,7 +1513,7 @@ issues.draft_title=Entwurf
issues.num_comments_1=%d Kommentar
issues.num_comments=%d Kommentare
issues.commented_at=`hat %s kommentiert`
-issues.delete_comment_confirm=Bist du sicher dass du diesen Kommentar löschen möchtest?
+issues.delete_comment_confirm=Bist du sicher, dass du diesen Kommentar löschen möchtest?
issues.context.copy_link=Link kopieren
issues.context.quote_reply=Antwort zitieren
issues.context.reference_issue=In neuem Issue referenzieren
@@ -1505,14 +1521,14 @@ issues.context.edit=Bearbeiten
issues.context.delete=Löschen
issues.no_content=Keine Beschreibung angegeben.
issues.close=Issue schließen
-issues.comment_pull_merged_at=hat Commit %[1]s in %[2]s %[3]s gemerged
-issues.comment_manually_pull_merged_at=hat Commit %[1]s in %[2]s %[3]s manuell gemerged
+issues.comment_pull_merged_at=hat Commit %[1]s in %[2]s %[3]s gemergt
+issues.comment_manually_pull_merged_at=hat Commit %[1]s in %[2]s %[3]s manuell gemergt
issues.close_comment_issue=Kommentieren und schließen
issues.reopen_issue=Wieder öffnen
issues.reopen_comment_issue=Kommentieren und wieder öffnen
issues.create_comment=Kommentieren
issues.closed_at=`hat diesen Issue %[2]s geschlossen`
-issues.reopened_at=`hat diesen Issue %[2]s wieder geöffnet`
+issues.reopened_at=`hat dieses Issue %[2]s wieder geöffnet`
issues.commit_ref_at=`hat dieses Issue %[2]s aus einem Commit referenziert`
issues.ref_issue_from=`hat %[2]s auf dieses Issue verwiesen %[4]s `
issues.ref_pull_from=`hat %[2]s auf diesen Pull-Request verwiesen %[4]s `
@@ -1550,7 +1566,7 @@ issues.label_exclusive=Exklusiv
issues.label_archive=Label archivieren
issues.label_archived_filter=Archivierte Labels anzeigen
issues.label_archive_tooltip=Archivierte Labels werden bei der Suche nach Labels standardmäßig von den Vorschlägen ausgeschlossen.
-issues.label_exclusive_desc=Nenne das Label Bereich/Element
um es gegenseitig ausschließend mit anderen Bereich/
Labels zu machen.
+issues.label_exclusive_desc=Nenn das Label Bereich/Element
, um es gegenseitig ausschließend mit anderen Bereich/
-Labels zu machen.
issues.label_exclusive_warning=Alle im Konflikt stehenden Labels werden beim Bearbeiten der Labels eines Issues oder eines Pull-Requests entfernt.
issues.label_count=%d Labels
issues.label_open_issues=%d offene Issues
@@ -1571,15 +1587,15 @@ issues.subscribe=Abonnieren
issues.unsubscribe=Abbestellen
issues.unpin_issue=Issue abheften
issues.max_pinned=Du kannst keine weiteren Issues anheften
-issues.pin_comment=hat das %s angeheftet
+issues.pin_comment=hat dies %s angeheftet
issues.unpin_comment=hat das %s abgeheftet
issues.lock=Diskussion sperren
issues.unlock=Diskussion entsperren
issues.lock.unknown_reason=Es ist nicht möglich, einen Issue mit unbekanntem Grund zu sperren.
issues.lock_duplicate=Eine Diskussion kann nicht mehrfach gesperrt werden.
issues.unlock_error=Es ist nicht möglich, einen nicht gesperrten Issue zu entsperren.
-issues.lock_with_reason=gesperrt als %s und Diskussion auf Mitarbeiter beschränkt %s
-issues.lock_no_reason=gesperrt und Diskussion auf Mitarbeiter beschränkt %s
+issues.lock_with_reason=sperrte dies %s mit der Begründung „%s “ und schränkte die Diskussion auf Mitarbeiter ein
+issues.lock_no_reason=sperrte dies %s und schränkte die Diskussion auf Mitarbeiter ein
issues.unlock_comment=hat diese Diskussion %s entsperrt
issues.lock_confirm=Sperren
issues.unlock_confirm=Entsperren
@@ -1609,15 +1625,15 @@ issues.add_time=Zeit manuell hinzufügen
issues.del_time=Diese Zeiterfassung löschen
issues.add_time_short=Zeit hinzufügen
issues.add_time_cancel=Abbrechen
-issues.add_time_history=`hat %s gearbeitete Zeit hinzugefügt`
-issues.del_time_history=`hat %s gearbeitete Zeit gelöscht`
+issues.add_time_history=`hat %s den Zeitaufwand hinzugefügt`
+issues.del_time_history=`hat %s den Zeitaufwand gelöscht`
issues.add_time_hours=Stunden
issues.add_time_minutes=Minuten
issues.add_time_sum_to_small=Es wurde keine Zeit eingegeben.
issues.time_spent_total=Zeitaufwand insgesamt
-issues.time_spent_from_all_authors=`Aufgewendete Zeit: %s`
+issues.time_spent_from_all_authors=`Gesamtzeitaufwand: %s`
issues.due_date=Fällig am
-issues.invalid_due_date_format=Das Fälligkeitsdatum muss das Format ‚JJJJ-MM-TT‘ haben.
+issues.invalid_due_date_format=Das Fälligkeitsdatum muss das Format „JJJJ-MM-TT“ haben.
issues.error_modifying_due_date=Fehler beim Ändern des Fälligkeitsdatums.
issues.error_removing_due_date=Fehler beim Entfernen des Fälligkeitsdatums.
issues.push_commit_1=hat %d Commit %s hinzugefügt
@@ -1631,20 +1647,20 @@ issues.due_date_form_remove=Entfernen
issues.due_date_not_writer=Du musst Schreibrechte für dieses Repository haben, um das Fälligkeitsdatum zu ändern.
issues.due_date_not_set=Kein Fälligkeitsdatum gesetzt.
issues.due_date_added=hat %[2]s das Fälligkeitsdatum %[1]s hinzugefügt
-issues.due_date_modified=ändert das Abgabedatum von %[2]s auf %[1]s %[3]s s
+issues.due_date_modified=hat das Fälligkeitsdatum von %[2]s auf %[1]s %[3]s geändert
issues.due_date_remove=hat %[2]s das Fälligkeitsdatum %[1]s entfernt
issues.due_date_overdue=Überfällig
issues.due_date_invalid=Das Fälligkeitsdatum ist ungültig oder außerhalb des zulässigen Bereichs. Bitte verwende das Format „jjjj-mm-tt“.
issues.dependency.title=Abhängigkeiten
issues.dependency.issue_no_dependencies=Keine Abhängigkeiten gesetzt.
issues.dependency.pr_no_dependencies=Keine Abhängigkeiten gesetzt.
-issues.dependency.no_permission_1=Du bist nicht berechtigt %d Abhängigkeit zu lesen
-issues.dependency.no_permission_n=Du bist nicht berechtigt %d Abhängigkeiten zu lesen
-issues.dependency.no_permission.can_remove=Du hast keine Berechtigung diese Abhängigkeit zu lesen, kannst diese Abhängigkeit aber entfernen
+issues.dependency.no_permission_1=Du bist nicht berechtigt, %d Abhängigkeit zu lesen
+issues.dependency.no_permission_n=Du bist nicht berechtigt, %d Abhängigkeiten zu lesen
+issues.dependency.no_permission.can_remove=Du hast keine Berechtigung, diese Abhängigkeit zu lesen, kannst diese Abhängigkeit aber entfernen
issues.dependency.add=Abhängigkeit hinzufügen …
issues.dependency.cancel=Abbrechen
issues.dependency.remove=Entfernen
-issues.dependency.remove_info=Abhängigkeit löschen
+issues.dependency.remove_info=Diese Abhängigkeit löschen
issues.dependency.added_dependency=`hat eine neue Abhängigkeit %s hinzugefügt`
issues.dependency.removed_dependency=`hat eine Abhängigkeit %s entfernt`
issues.dependency.pr_closing_blockedby=Das Schließen dieses Pull-Requests wird von den folgenden Issues blockiert
@@ -1669,7 +1685,7 @@ issues.dependency.add_error_dep_not_same_repo=Beide Issues müssen sich im selbe
issues.review.self.approval=Du kannst nicht dein eigenen Pull-Request genehmigen.
issues.review.self.rejection=Du kannst keine Änderungen an deinem eigenen Pull-Request anfragen.
issues.review.approve=hat die Änderungen %s genehmigt
-issues.review.comment=hat %s überprüft
+issues.review.comment=hat %s gereviewt
issues.review.dismissed=verwarf %ss Review %s
issues.review.dismissed_label=Verworfen
issues.review.left_comment=hat einen Kommentar hinterlassen
@@ -1689,12 +1705,12 @@ issues.review.option.show_outdated_comments=Veraltete Kommentare anzeigen
issues.review.option.hide_outdated_comments=Veraltete Kommentare ausblenden
issues.review.show_outdated=Veraltete anzeigen
issues.review.hide_outdated=Veraltete ausblenden
-issues.review.show_resolved=Gelöste anzeigen
-issues.review.hide_resolved=Gelöste ausblenden
+issues.review.show_resolved=Erledigte anzeigen
+issues.review.hide_resolved=Erledigte ausblenden
issues.review.resolve_conversation=Diskussion als „erledigt“ markieren
issues.review.un_resolve_conversation=Diskussion als „nicht erledigt“ markieren
issues.review.resolved_by=markierte diese Unterhaltung als gelöst
-issues.assignee.error=Aufgrund eines unerwarteten Fehlers konnten nicht alle Beauftragten hinzugefügt werden.
+issues.assignee.error=Aufgrund eines unerwarteten Fehlers konnten nicht alle Zuständigen hinzugefügt werden.
issues.reference_issue.body=Beschreibung
issues.content_history.deleted=gelöscht
issues.content_history.edited=bearbeitet
@@ -1717,7 +1733,7 @@ pulls.allow_edits_from_maintainers_err=Aktualisieren fehlgeschlagen
pulls.compare_changes_desc=Wähle den Ziel- und Quellbranch aus.
pulls.has_viewed_file=Gesehen
pulls.has_changed_since_last_review=Nach deinem letzten Review geändert
-pulls.viewed_files_label=%[1]d / %[2]d Dateien reviewed
+pulls.viewed_files_label=%[1]d / %[2]d Dateien betrachtet
pulls.expand_files=Alle Dateien ausklappen
pulls.collapse_files=Alle Dateien einklappen
pulls.compare_base=Ziel
@@ -1744,9 +1760,9 @@ pulls.tab_conversation=Diskussion
pulls.tab_commits=Commits
pulls.tab_files=Geänderte Dateien
pulls.reopen_to_merge=Bitte diesen Pull-Request wieder öffnen, um zu mergen.
-pulls.cant_reopen_deleted_branch=Dieser Pull-Request kann nicht wieder geöffnet werden, da die Branch bereits gelöscht wurde.
+pulls.cant_reopen_deleted_branch=Dieser Pull-Request kann nicht wieder geöffnet werden, da der Branch bereits gelöscht wurde.
pulls.merged=Zusammengeführt
-pulls.merged_success=Pull-Request erfolgreich gemerged und geschlossen
+pulls.merged_success=Pull-Request erfolgreich gemergt und geschlossen
pulls.closed=Pull-Request geschlossen
pulls.manually_merged=Manuell gemergt
pulls.merged_info_text=Der Branch %s kann jetzt gelöscht werden.
@@ -1754,8 +1770,8 @@ pulls.is_closed=Der Pull-Request wurde geschlossen.
pulls.title_wip_desc=`Beginne den Titel mit %s , um zu verhindern, dass der Pull-Request versehentlich gemergt wird.`
pulls.cannot_merge_work_in_progress=Dieser Pull Request ist als „Work in Progress“ (in Bearbeitung) markiert.
pulls.still_in_progress=Noch in Bearbeitung?
-pulls.add_prefix=%s Präfix hinzufügen
-pulls.remove_prefix=%s Präfix entfernen
+pulls.add_prefix=Präfix „%s “ hinzufügen
+pulls.remove_prefix=Präfix „%s ” entfernen
pulls.data_broken=Dieser Pull-Requests ist kaputt, da Fork-Informationen gelöscht wurden.
pulls.files_conflicted=Dieser Pull-Request hat Änderungen, die im Widerspruch zum Ziel-Branch stehen.
pulls.is_checking=Die Merge-Konfliktprüfung läuft noch. Bitte aktualisiere die Seite in wenigen Augenblicken.
@@ -1775,13 +1791,13 @@ pulls.cannot_auto_merge_desc=Dieser Pull-Request kann nicht automatisch gemergt
pulls.cannot_auto_merge_helper=Bitte manuell mergen, um die Konflikte zu beheben.
pulls.num_conflicting_files_1=%d Datei mit Konflikten
pulls.num_conflicting_files_n=%d Dateien mit Konflikten
-pulls.approve_count_1=%d Zustimmung
-pulls.approve_count_n=%d Zustimmungen
+pulls.approve_count_1=%d Genehmigung
+pulls.approve_count_n=%d Genehmigungen
pulls.reject_count_1=%d Änderungsanfrage
pulls.reject_count_n=%d Änderungsanfragen
pulls.waiting_count_1=%d wartendes Review
pulls.waiting_count_n=%d wartende Reviews
-pulls.wrong_commit_id=die Commit ID muss eine Commit ID auf dem Zielbranch sein
+pulls.wrong_commit_id=die Commit-ID muss eine Commit-ID auf dem Zielbranch sein
pulls.no_merge_desc=Dieser Pull-Request kann nicht gemergt werden, da alle Repository-Merge-Optionen deaktiviert sind.
pulls.no_merge_helper=Aktiviere Mergeoptionen in den Repositoryeinstellungen oder merge den Pull-Request manuell.
@@ -1792,7 +1808,7 @@ pulls.merge_pull_request=Merge-Commit erstellen
pulls.rebase_merge_pull_request=Rebasen und dann fast-forwarden
pulls.rebase_merge_commit_pull_request=Rebasen und dann mergen
pulls.squash_merge_pull_request=Squash-Commit erstellen
-pulls.merge_manually=Manuell mergen
+pulls.merge_manually=Manuell gemergt
pulls.merge_commit_id=Der Mergecommit ID
pulls.require_signed_wont_sign=Der Branch erfordert einen signierten Commit, aber dieser Merge wird nicht signiert
@@ -1804,10 +1820,10 @@ pulls.rebase_conflict_summary=Fehlermeldung
pulls.unrelated_histories=Merge fehlgeschlagen: Der Head des Merges und die Basis haben keinen gemeinsamen Verlauf. Hinweis: Versuche eine andere Strategie
pulls.merge_out_of_date=Merge fehlgeschlagen: Während des Mergens wurde die Basis aktualisiert. Hinweis: Versuche es erneut.
pulls.head_out_of_date=Mergen fehlgeschlagen: Der Head wurde aktualisiert während der Merge erstellt wurde. Tipp: Versuche es erneut.
-pulls.has_merged=Fehler: Der Pull-Request wurde gemerged, du kannst den Zielbranch nicht wieder mergen oder ändern.
-pulls.push_rejected=Mergen fehlgeschlagen: Der Push wurde abgelehnt. Überprüfe die Git Hooks für dieses Repository.
+pulls.has_merged=Fehler: Der Pull-Request wurde gemergt, du kannst den Zielbranch nicht wieder mergen oder ändern.
+pulls.push_rejected=Pushen fehlgeschlagen: Der Push wurde abgelehnt. Überprüfe die Git-Hooks für dieses Repository.
pulls.push_rejected_summary=Vollständige Ablehnungsmeldung
-pulls.push_rejected_no_message=Mergen fehlgeschlagen: Der Push wurde abgelehnt, aber es gab keine Fehlermeldung. Überprüfe die Git Hooks für dieses Repository
+pulls.push_rejected_no_message=Pushen fehlgeschlagen: Der Push wurde abgelehnt, aber es gab keine Fehlermeldung. Überprüfe die Git-Hooks für dieses Repository
pulls.open_unmerged_pull_exists=`Du kannst diesen Pull-Request nicht erneut öffnen, da noch ein anderer (#%d) mit identischen Eigenschaften offen ist.`
pulls.status_checking=Einige Prüfungen sind noch ausstehend
pulls.status_checks_success=Alle Prüfungen waren erfolgreich
@@ -1819,8 +1835,8 @@ pulls.status_checks_details=Details
pulls.update_branch=Branch durch Mergen aktualisieren
pulls.update_branch_rebase=Branch durch Rebase aktualisieren
pulls.update_branch_success=Branch-Aktualisierung erfolgreich
-pulls.update_not_allowed=Du hast keine Berechtigung, die Branch zu Updaten
-pulls.outdated_with_base_branch=Dieser Branch enthält nicht die neusten Commits der Basis-Branch
+pulls.update_not_allowed=Du hast keine Berechtigung, den Branch zu updaten
+pulls.outdated_with_base_branch=Dieser Branch enthält nicht die neusten Commits des Basis-Branches
pulls.close=Pull-Request schließen
pulls.closed_at=`hat diesen Pull-Request %[2]s geschlossen`
pulls.reopened_at=`hat diesen Pull-Request %[2]s wieder geöffnet`
@@ -1964,8 +1980,8 @@ activity.title.releases_1=%d Release
activity.title.releases_n=%d Releases
activity.title.releases_published_by=%s von %s veröffentlicht
activity.published_release_label=Veröffentlicht
-activity.no_git_activity=In diesem Zeitraum sind keine Commit-Aktivität vorhanden.
-activity.git_stats_exclude_merges=Merges ausgenommen,
+activity.no_git_activity=In diesem Zeitraum hat es keine Commit-Aktivität gegeben.
+activity.git_stats_exclude_merges=Von Merges abgesehen, gilt:
activity.git_stats_author_1=%d Autor
activity.git_stats_author_n=%d Autoren
activity.git_stats_pushed_1=hat
@@ -2085,7 +2101,7 @@ settings.admin_indexer_commit_sha=Zuletzt indexierter SHA
settings.admin_indexer_unindexed=Unindiziert
settings.reindex_button=Zur Warteschlange für erneutes Indexieren hinzufügen
settings.reindex_requested=Erneutes Indexieren angefordert
-settings.admin_enable_close_issues_via_commit_in_any_branch=Einen Issue mit einem Commit auf einem nicht-Standard-Branch schließen
+settings.admin_enable_close_issues_via_commit_in_any_branch=Einen Issue mit einem Commit auf einem Nicht-Standard-Branch schließen
settings.danger_zone=Gefahrenzone
settings.new_owner_has_same_repo=Der neue Besitzer hat bereits ein Repository mit dem gleichen Namen. Bitte wähle einen anderen Namen.
settings.convert=In ein normales Repository umwandeln
@@ -2126,7 +2142,7 @@ settings.trust_model.committer.long=Committer: Vertraue Signaturen, die zu Commi
settings.trust_model.committer.desc=Gültige Signaturen werden nur dann als „vertrauenswürdig“ gekennzeichnet, wenn sie mit ihrem Committer übereinstimmen. Ansonsten werden sie als „nicht übereinstimmend“ markiert. Das führt dazu, dass Forgejo auf signierten Commits, bei denen der echte Committer als „Co-authored-by:“ oder „Co-committed-by:“ in der Beschreibung eingetragen wurde, als Committer gilt. Der Forgejo-Standard-Key muss zu einem Benutzer in der Datenbank passen.
settings.trust_model.collaboratorcommitter=Mitarbeiter+Committer
settings.trust_model.collaboratorcommitter.long=Mitarbeiter+Committer: Signaturen der Mitarbeiter vertrauen die mit dem Committer übereinstimmen
-settings.trust_model.collaboratorcommitter.desc=Gültige Signaturen von Mitarbeitern dieses Projekts werden als „vertrauenswürdig“ markiert, wenn sie mit dem Committer übereinstimmen. Andernfalls werden gültige Signaturen als „nicht vertrauenswürdig“ markiert, wenn die Signatur mit dem Committer übereinstimmt. Ansonsten werden sie als als „nicht übereinstimmend“ margiert. Dies zwingt Forgejo, als Committer bei signierten Commits mit dem echten Committer als „Co-Authored-By:“ und „Co-Committed-By:“ im Commit zu markieren. Der Standard-Forgejo-Schlüssel muss mit einem Benutzer in der Datenbank übereinstimmen.
+settings.trust_model.collaboratorcommitter.desc=Gültige Signaturen von Mitarbeitern dieses Projekts werden als „vertrauenswürdig“ markiert, wenn sie mit dem Committer übereinstimmen. Andernfalls werden gültige Signaturen als „nicht vertrauenswürdig“ markiert, wenn die Signatur mit dem Committer übereinstimmt. Ansonsten werden sie als „nicht übereinstimmend“ markiert. Dies zwingt Forgejo, als Committer bei signierten Commits mit dem echten Committer als „Co-Authored-By:“ und „Co-Committed-By:“ im Commit zu markieren. Der Standard-Forgejo-Schlüssel muss mit einem Benutzer in der Datenbank übereinstimmen.
settings.wiki_delete=Wiki-Daten löschen
settings.wiki_delete_desc=Das Löschen von Wiki-Daten kann nicht rückgängig gemacht werden. Bitte sei vorsichtig.
settings.wiki_delete_notices_1=– Dies löscht und deaktiviert das Wiki für %s.
@@ -2231,8 +2247,8 @@ settings.event_pull_request_assign=Pull-Request zugewiesen
settings.event_pull_request_assign_desc=Pull-Request zugewiesen oder Zuweisung entfernt.
settings.event_pull_request_label=Pull-Request mit Label versehen
settings.event_pull_request_label_desc=Pull-Request-Labels aktualisiert oder geleert.
-settings.event_pull_request_milestone=Pull-Request zu Milestone hinzugefügt
-settings.event_pull_request_milestone_desc=Pull-Request zu Milestone hinzugefügt oder entfernt.
+settings.event_pull_request_milestone=Pull-Request zum Meilenstein hinzugefügt
+settings.event_pull_request_milestone_desc=Pull-Request zum Meilenstein hinzugefügt oder entfernt.
settings.event_pull_request_comment=Pull-Request-Kommentar
settings.event_pull_request_comment_desc=Pull-Request-Kommentar angelegt, geändert oder gelöscht.
settings.event_pull_request_review=Pull-Request überprüft
@@ -2241,7 +2257,7 @@ settings.event_pull_request_sync=Pull-Request synchronisiert
settings.event_pull_request_sync_desc=Pull-Request synchronisiert.
settings.event_pull_request_review_request=Überprüfung des Pull-Requests angefragt
settings.event_pull_request_review_request_desc=Überprüfung des Pull-Requests angefragt oder die Anfrage entfernt.
-settings.event_pull_request_approvals=Zustimmungen zum Pull-Request
+settings.event_pull_request_approvals=Genehmigungen zum Pull-Request
settings.event_pull_request_merge=Pull-Request-Merge
settings.event_package=Paket
settings.event_package_desc=Paket wurde in einem Repository erstellt oder gelöscht.
@@ -2279,7 +2295,7 @@ settings.packagist_username=Benutzername für Packagist
settings.packagist_api_token=API-Token
settings.packagist_package_url=Paket-URL
settings.deploy_keys=Deploy-Keys
-settings.add_deploy_key=Deploy-Schlüssel hinzufügen
+settings.add_deploy_key=Deploy-Key hinzufügen
settings.deploy_key_desc=Deploy-Keys haben nur Lesezugriff auf das Repository.
settings.is_writable=Erlaube Schreibzugriff
settings.is_writable_info=Erlaube diesem Deploy-Key auf das Repository zu pushen .
@@ -2288,7 +2304,7 @@ settings.title=Titel
settings.deploy_key_content=Inhalt
settings.key_been_used=Ein Deploy-Key mit identischem Inhalt wird bereits verwendet.
settings.key_name_used=Ein Deploy-Key mit diesem Namen existiert bereits.
-settings.add_key_success=Der Deploy-Key "%s" wurde erfolgreich hinzugefügt.
+settings.add_key_success=Der Deploy-Key „%s“ wurde erfolgreich hinzugefügt.
settings.deploy_key_deletion=Deploy-Key löschen
settings.deploy_key_deletion_desc=Nach dem Löschen wird dieser Deploy-Key keinen Zugriff mehr auf dieses Repository haben. Fortfahren?
settings.deploy_key_deletion_success=Der Deploy-Key wurde entfernt.
@@ -2299,40 +2315,40 @@ settings.protected_branch.delete_rule=Regel löschen
settings.protected_branch_can_push=Push erlauben?
settings.protected_branch_can_push_yes=Du kannst pushen
settings.protected_branch_can_push_no=Du kannst nicht pushen
-settings.branch_protection=Branch-Schutz für Branch „%s “
+settings.branch_protection=Branch-Schutzregeln für Branch „%s “
settings.protect_this_branch=Branch-Schutz aktivieren
settings.protect_this_branch_desc=Verhindert das Löschen und schränkt Git auf Push- und Merge-Änderungen auf dem Branch ein.
settings.protect_disable_push=Push deaktivieren
settings.protect_disable_push_desc=Kein Push auf diesen Branch erlauben.
settings.protect_enable_push=Push aktivieren
-settings.protect_enable_push_desc=Jeder, der Schreibzugriff hat, darf in diesen Branch Pushen (aber kein Force-Push).
+settings.protect_enable_push_desc=Jeder, der Schreibzugriff hat, darf in diesen Branch pushen (aber kein Force-Push).
settings.protect_enable_merge=Merge aktivieren
settings.protect_enable_merge_desc=Jeder mit Schreibzugriff darf die Pull-Requests in diesen Branch mergen.
settings.protect_whitelist_committers=Schütze gewhitelistete Commiter
settings.protect_whitelist_committers_desc=Jeder, der auf der Whitelist steht, darf in diesen Branch pushen (aber kein Force-Push).
-settings.protect_whitelist_deploy_keys=Deploy-Schlüssel mit Schreibzugriff zum Pushen whitelisten.
+settings.protect_whitelist_deploy_keys=Deploy-Key mit Schreibzugriff zum Pushen whitelisten.
settings.protect_whitelist_users=Nutzer, die pushen dürfen:
-settings.protect_whitelist_search_users=Benutzer suchen…
+settings.protect_whitelist_search_users=Benutzer suchen …
settings.protect_whitelist_teams=Teams, die pushen dürfen:
-settings.protect_whitelist_search_teams=Teams suchen…
+settings.protect_whitelist_search_teams=Teams suchen …
settings.protect_merge_whitelist_committers=Merge-Whitelist aktivieren
-settings.protect_merge_whitelist_committers_desc=Erlaube Nutzern oder Teams auf der Whitelist Pull-Requests in diesen Branch zu mergen.
+settings.protect_merge_whitelist_committers_desc=Erlaube Nutzern oder Teams auf der Whitelist, Pull-Requests in diesen Branch zu mergen.
settings.protect_merge_whitelist_users=Nutzer, die mergen dürfen:
settings.protect_merge_whitelist_teams=Teams, die mergen dürfen:
settings.protect_check_status_contexts=Statusprüfungen aktivieren
settings.protect_status_check_patterns=Statuscheck-Muster:
-settings.protect_status_check_patterns_desc=Gib Muster ein, um festzulegen, welche Statusüberprüfungen durchgeführt werden müssen, bevor Branches in einen Branch, der dieser Regel entspricht, gemerged werden können. Jede Zeile gibt ein Muster an. Muster dürfen nicht leer sein.
+settings.protect_status_check_patterns_desc=Gib Muster ein, um festzulegen, welche Statusüberprüfungen durchgeführt werden müssen, bevor Branches in einen Branch, der dieser Regel entspricht, gemergt werden können. Jede Zeile gibt ein Muster an. Muster dürfen nicht leer sein.
settings.protect_check_status_contexts_desc=Vor dem Mergen müssen Statusprüfungen bestanden werden. Wähle aus, welche Statusprüfungen erfolgreich durchgeführt werden müssen, bevor Branches in einen anderen gemergt werden können, der dieser Regel entspricht. Wenn aktiviert, müssen Commits zuerst auf einen anderen Branch gepusht werden, dann nach bestandener Statusprüfung gemergt oder direkt auf einen Branch gepusht werden, der dieser Regel entspricht. Wenn kein Kontext ausgewählt ist, muss der letzte Commit unabhängig vom Kontext erfolgreich sein.
settings.protect_check_status_contexts_list=Statusprüfungen, die in der letzten Woche für dieses Repository gefunden wurden
settings.protect_status_check_matched=Übereinstimmung
-settings.protect_invalid_status_check_pattern=Ungültiges Muster: "%s".
+settings.protect_invalid_status_check_pattern=Ungültiges Statusprüfungspattern: „%s“.
settings.protect_no_valid_status_check_patterns=Keine gültigen Statuscheck-Muster.
-settings.protect_required_approvals=Erforderliche Zustimmungen:
+settings.protect_required_approvals=Erforderliche Genehmigungen:
settings.protect_required_approvals_desc=Erlaube das Mergen des Pull-Requests nur mit genügend positiven Reviews.
-settings.protect_approvals_whitelist_enabled=Freigaben auf Benutzer oder Teams auf der Whitelist beschränken
-settings.protect_approvals_whitelist_enabled_desc=Nur Bewertungen von Benutzern auf der Whitelist oder Teams zählen zu den erforderlichen Genehmigungen. Gibt es keine Whitelist, so zählen Reviews von jedem mit Schreibzugriff zu den erforderlichen Genehmigungen.
-settings.protect_approvals_whitelist_users=Freigeschaltete Reviewer:
-settings.protect_approvals_whitelist_teams=Freigeschaltete Teams:
+settings.protect_approvals_whitelist_enabled=Genehmigungen auf Benutzer oder Teams auf der Whitelist beschränken
+settings.protect_approvals_whitelist_enabled_desc=Nur Reviews von Benutzern auf der Whitelist oder Teams zählen zu den erforderlichen Genehmigungen. Gibt es keine Whitelist, so zählen Reviews von jedem mit Schreibzugriff zu den erforderlichen Genehmigungen.
+settings.protect_approvals_whitelist_users=Reviewer auf der Whitelist:
+settings.protect_approvals_whitelist_teams=Für Reviews gewhitelistete Teams:
settings.dismiss_stale_approvals=Entferne alte Genehmigungen
settings.dismiss_stale_approvals_desc=Wenn neue Commits gepusht werden, die den Inhalt des Pull-Requests ändern, werden alte Genehmigungen entfernt.
settings.require_signed_commits=Signierte Commits erforderlich
@@ -2340,34 +2356,34 @@ settings.require_signed_commits_desc=Pushes auf diesen Branch ablehnen, wenn Com
settings.protect_branch_name_pattern=Muster für geschützte Branchnamen
settings.protect_patterns=Muster
settings.protect_protected_file_patterns=Geschützte Dateimuster (durch Semikolon „;“ getrennt):
-settings.protect_protected_file_patterns_desc=Geschützte Dateien dürfen nicht direkt geändert werden, auch wenn der Benutzer Rechte hat, Dateien in diesem Branch hinzuzufügen, zu bearbeiten oder zu löschen. Mehrere Muster können mit Semikolon (';') getrennt werden. Siehe github.com/gobwas/glob Dokumentation zur Mustersyntax. Beispiele: .drone.yml
, /docs/**/*.txt
.
+settings.protect_protected_file_patterns_desc=Geschützte Dateien dürfen nicht direkt geändert werden, auch wenn der Benutzer Rechte hat, Dateien in diesem Branch hinzuzufügen, zu bearbeiten oder zu löschen. Mehrere Muster können mit Semikolon („;“) getrennt werden. Siehe github.com/gobwas/glob Dokumentation zur Mustersyntax. Beispiele: .drone.yml
, /docs/**/*.txt
.
settings.protect_unprotected_file_patterns=Ungeschützte Dateimuster (durch Semikolon „;“ getrennt):
-settings.protect_unprotected_file_patterns_desc=Ungeschützte Dateien, die direkt geändert werden dürfen, wenn der Benutzer Schreibzugriff hat, können die Push-Beschränkung umgehen. Mehrere Muster können mit Semikolon (';') getrennt werden. Siehe github.com/gobwas/glob Dokumentation zur Mustersyntax. Beispiele: .drone.yml
, /docs/**/*.txt
.
+settings.protect_unprotected_file_patterns_desc=Ungeschützte Dateien, die direkt geändert werden dürfen, wenn der Benutzer Schreibzugriff hat, können die Push-Beschränkung umgehen. Mehrere Muster können mit Semikolon („;“) getrennt werden. Siehe github.com/gobwas/glob Dokumentation zur Mustersyntax. Beispiele: .drone.yml
, /docs/**/*.txt
.
settings.add_protected_branch=Schutz aktivieren
settings.delete_protected_branch=Schutz deaktivieren
-settings.update_protect_branch_success=Branchschutzregel "%s" wurde geändert.
-settings.remove_protected_branch_success=Branchschutzregel "%s" wurde deaktiviert.
-settings.remove_protected_branch_failed=Entfernen der Branchschutzregel "%s" fehlgeschlagen.
+settings.update_protect_branch_success=Branchschutzregel „%s“ wurde aktualisiert.
+settings.remove_protected_branch_success=Branchschutzregel „%s“ wurde entfernt.
+settings.remove_protected_branch_failed=Entfernen der Branchschutzregel „%s“ fehlgeschlagen.
settings.protected_branch_deletion=Branch-Schutz deaktivieren
settings.protected_branch_deletion_desc=Wenn du den Branch-Schutz deaktivierst, können alle Nutzer mit Schreibrechten auf den Branch pushen. Fortfahren?
settings.block_rejected_reviews=Merge bei abgelehnten Reviews blockieren
-settings.block_rejected_reviews_desc=Mergen ist nicht möglich, wenn Änderungen durch offizielle Reviewer angefragt werden, auch wenn es genügend Zustimmungen gibt.
+settings.block_rejected_reviews_desc=Mergen ist nicht möglich, wenn Änderungen durch offizielle Reviewer angefragt werden, auch wenn es genügend Genehmigungen gibt.
settings.block_on_official_review_requests=Mergen bei offiziellen Review-Anfragen blockieren
-settings.block_on_official_review_requests_desc=Mergen ist nicht möglich wenn offizielle Review-Anfrangen vorliegen, selbst wenn es genügend Zustimmungen gibt.
+settings.block_on_official_review_requests_desc=Mergen ist nicht möglich, wenn offizielle Review-Anfrangen vorliegen, selbst wenn es genügend Genehmigungen gibt.
settings.block_outdated_branch=Merge blockieren, wenn der Pull-Request veraltet ist
settings.block_outdated_branch_desc=Mergen ist nicht möglich, wenn der Head-Branch hinter dem Basis-Branch ist.
settings.default_branch_desc=Wähle einen Standardbranch für Pull-Requests und Code-Commits:
settings.merge_style_desc=Merge-Styles
-settings.default_merge_style_desc=Standard Mergeverhalten für Pull Requests:
-settings.choose_branch=Branch wählen…
+settings.default_merge_style_desc=Standard-Mergeverhalten
+settings.choose_branch=Branch wählen …
settings.no_protected_branch=Es gibt keine geschützten Branches.
settings.edit_protected_branch=Bearbeiten
settings.protected_branch_required_rule_name=Regelname erforderlich
-settings.protected_branch_duplicate_rule_name=Regelname existiert bereits
-settings.protected_branch_required_approvals_min=Die Anzahl der erforderlichen Zustimmungen darf nicht negativ sein.
+settings.protected_branch_duplicate_rule_name=Es existiert bereits eine Regel für dieses Branch-Set
+settings.protected_branch_required_approvals_min=Die Anzahl der erforderlichen Genehmigungen darf nicht negativ sein.
settings.tags=Tags
settings.tags.protection=Tag-Schutz
-settings.tags.protection.pattern=Tag Muster
+settings.tags.protection.pattern=Tag-Muster
settings.tags.protection.allowed=Erlaubt
settings.tags.protection.allowed.users=Erlaubte Benutzer
settings.tags.protection.allowed.teams=Erlaubte Teams
@@ -2383,14 +2399,14 @@ settings.matrix.room_id=Raum-ID
settings.matrix.message_type=Nachrichtentyp
settings.archive.button=Repo archivieren
settings.archive.header=Dieses Repo archivieren
-settings.archive.text=Durch das Archivieren wird ein Repo vollständig schreibgeschützt. Es wird vom Dashboard versteckt. Niemand (nicht einmal du!) wird in der Lage sein, neue Commits zu erstellen oder Issues oder Pull-Requests zu öffnen.
+settings.archive.text=Durch das Archivieren wird ein Repo vollständig schreibgeschützt. Es wird von der Übersichtsseite versteckt. Niemand (nicht einmal du!) wird in der Lage sein, neue Commits zu erstellen oder Issues oder Pull-Requests zu öffnen.
settings.archive.success=Das Repo wurde erfolgreich archiviert.
settings.archive.error=Beim Versuch, das Repository zu archivieren, ist ein Fehler aufgetreten. Weitere Details finden sich im Log.
-settings.archive.error_ismirror=Du kannst keinen Repo-Mirror archivieren.
+settings.archive.error_ismirror=Du kannst kein gespiegeltes Repo archivieren.
settings.archive.branchsettings_unavailable=Branch-Einstellungen sind nicht verfügbar wenn das Repo archiviert ist.
settings.archive.tagsettings_unavailable=Tag Einstellungen sind nicht verfügbar, wenn das Repo archiviert wurde.
settings.unarchive.button=Archivieren rückgängig machen
-settings.unarchive.header=Archivieren dieses Repositories rückgängig machen
+settings.unarchive.header=Archivieren dieses Repositorys rückgängig machen
settings.unarchive.text=Durch das Aufheben der Archivierung kann das Repo wieder Commits und Pushes sowie neue Issues und Pull-Requests empfangen.
settings.unarchive.success=Die Archivierung des Repos wurde erfolgreich wieder rückgängig gemacht.
settings.unarchive.error=Beim Versuch, die Archivierung des Repos aufzuheben, ist ein Fehler aufgetreten. Weitere Details finden sich im Log.
@@ -2402,7 +2418,7 @@ settings.lfs_findcommits=Commits finden
settings.lfs_lfs_file_no_commits=Keine Commits für diese LFS-Datei gefunden
settings.lfs_noattribute=Dieser Pfad hat nicht das sperrbare Attribut im Standard-Branch
settings.lfs_delete=LFS-Datei mit OID %s löschen
-settings.lfs_delete_warning=Das Löschen einer LFS-Datei kann dazu führen, dass 'Objekt existiert nicht'-Fehler beim Checkout auftreten. Bist du sicher?
+settings.lfs_delete_warning=Das Löschen einer LFS-Datei kann dazu führen, dass „Objekt existiert nicht“-Fehler beim Checkout auftreten. Bist du sicher?
settings.lfs_findpointerfiles=Pointer-Dateien finden
settings.lfs_locks=Sperren
settings.lfs_invalid_locking_path=Ungültiger Pfad: %s
@@ -2413,8 +2429,8 @@ settings.lfs_lock_path=Zu sperrender Dateipfad...
settings.lfs_locks_no_locks=Keine Sperren
settings.lfs_lock_file_no_exist=Gesperrte Datei existiert nicht im Standard-Branch
settings.lfs_force_unlock=Freigabe erzwingen
-settings.lfs_pointers.found=%d Blob-Zeiger gefunden - %d assoziiert, %d nicht assoziiert (%d fehlend im Speicher)
-settings.lfs_pointers.sha=Blob SHA
+settings.lfs_pointers.found=%d Blob-Zeiger gefunden – %d assoziiert, %d nicht assoziiert (%d fehlend im Speicher)
+settings.lfs_pointers.sha=Blob-SHA
settings.lfs_pointers.oid=OID
settings.lfs_pointers.inRepo=Im Repo
settings.lfs_pointers.exists=Existiert im Speicher
@@ -2459,21 +2475,21 @@ diff.too_many_files=Einige Dateien werden nicht angezeigt, da zu viele Dateien i
diff.show_more=Mehr anzeigen
diff.load=Diff laden
diff.generated=generiert
-diff.vendored=vendored
+diff.vendored=gevendort
diff.comment.add_line_comment=Einzelnen Kommentar hinzufügen
-diff.comment.placeholder=Kommentieren...
+diff.comment.placeholder=Kommentieren
diff.comment.markdown_info=Styling mit Markdown wird unterstützt.
diff.comment.add_single_comment=Einzelnen Kommentar hinzufügen
diff.comment.add_review_comment=Kommentar hinzufügen
diff.comment.start_review=Review starten
diff.comment.reply=Antworten
-diff.review=Überprüfen
+diff.review=Reviewen
diff.review.header=Review einreichen
diff.review.placeholder=Kommentar zum Review
diff.review.comment=Kommentieren
diff.review.approve=Genehmigen
diff.review.self_reject=Pull-Request-Autoren können keine Änderungen an ihren eigenen Pull-Request anfordern
-diff.review.reject=Änderung anfragen
+diff.review.reject=Änderungen anfragen
diff.review.self_approve=Pull-Request-Autoren können ihren eigenen Pull-Request nicht genehmigen
diff.committed_by=committet von
diff.protected=Geschützt
@@ -2533,28 +2549,28 @@ release.releases_for=Releases für %s
release.tags_for=Tags für %s
branch.name=Branchname
-branch.already_exists=Ein Branch mit dem Namen "%s" existiert bereits.
+branch.already_exists=Ein Branch mit dem Namen „%s“ existiert bereits.
branch.delete_head=Löschen
-branch.delete=Branch "%s" löschen
+branch.delete=Branch „%s“ löschen
branch.delete_html=Branch löschen
branch.delete_desc=Das Löschen eines Branches ist permanent. Obwohl der Branch für eine kurze Zeit weiter existieren könnte, kann diese Aktion in den meisten Fällen NICHT rückgängig gemacht werden. Fortfahren?
-branch.deletion_success=Branch "%s" wurde gelöscht.
-branch.deletion_failed=Branch "%s" konnte nicht gelöscht werden.
-branch.delete_branch_has_new_commits=Der Branch "%s" kann nicht gelöscht werden, da seit dem letzten Merge neue Commits hinzugefügt wurden.
+branch.deletion_success=Branch „%s“ wurde gelöscht.
+branch.deletion_failed=Branch „%s“ konnte nicht gelöscht werden.
+branch.delete_branch_has_new_commits=Der Branch „%s“ kann nicht gelöscht werden, da seit dem letzten Merge neue Commits hinzugefügt wurden.
branch.create_branch=Erstelle Branch %s
-branch.create_from=`von "%s"`
-branch.create_success=Branch "%s" wurde erstellt.
-branch.branch_already_exists=Branch "%s" existiert bereits in diesem Repository.
-branch.branch_name_conflict=Der Branch-Name "%s" steht in Konflikt mit dem bestehenden Branch "%s".
-branch.tag_collision=Branch "%s" kann nicht erstellt werden, da in diesem Repository bereits ein Tag mit dem selben Namen existiert.
+branch.create_from=`von „%s“`
+branch.create_success=Branch „%s“ wurde erstellt.
+branch.branch_already_exists=Branch „%s“ existiert bereits in diesem Repository.
+branch.branch_name_conflict=Der Branch-Name „%s“ steht in Konflikt mit dem bestehenden Branch „%s“.
+branch.tag_collision=Branch „%s“ kann nicht erstellt werden, da in diesem Repository bereits ein Tag mit dem selben Namen existiert.
branch.deleted_by=Von %s gelöscht
-branch.restore_success=Branch "%s" wurde wiederhergestellt.
-branch.restore_failed=Wiederherstellung vom Branch "%s" gescheitert.
-branch.protected_deletion_failed=Branch "%s" ist geschützt und kann nicht gelöscht werden.
-branch.default_deletion_failed=Branch "%s" kann nicht gelöscht werden, da dieser Branch der Standard-Branch ist.
-branch.restore=Branch "%s" wiederherstellen
-branch.download=Branch "%s" herunterladen
-branch.rename=Branch "%s" umbenennen
+branch.restore_success=Branch „%s“ wurde wiederhergestellt.
+branch.restore_failed=Wiederherstellung vom Branch „%s“ gescheitert.
+branch.protected_deletion_failed=Branch „%s“ ist geschützt und kann nicht gelöscht werden.
+branch.default_deletion_failed=Branch „%s“ kann nicht gelöscht werden, da dieser Branch der Standard-Branch ist.
+branch.restore=Branch „%s“ wiederherstellen
+branch.download=Branch „%s“ herunterladen
+branch.rename=Branch „%s“ umbenennen
branch.search=Branch suchen
branch.included_desc=Dieser Branch ist im Standard-Branch enthalten
branch.included=Enthalten
@@ -2565,20 +2581,20 @@ branch.rename_branch_to=„%s“ umbenennen in:
branch.confirm_rename_branch=Branch umbenennen
branch.create_branch_operation=Branch erstellen
branch.new_branch=Neue Branch erstellen
-branch.new_branch_from=Neuen Branch von "%s" erstellen
+branch.new_branch_from=Neuen Branch von „%s“ erstellen
branch.renamed=Branch %s wurde in %s umbenannt.
tag.create_tag=Tag %s erstellen
tag.create_tag_operation=Tag erstellen
tag.confirm_create_tag=Tag erstellen
-tag.create_tag_from=Neuen Tag von "%s" erstellen
+tag.create_tag_from=Neuen Tag von „%s“ erstellen
-tag.create_success=Tag "%s" wurde erstellt.
+tag.create_success=Tag „%s“ wurde erstellt.
topic.manage_topics=Themen verwalten
topic.done=Fertig
topic.count_prompt=Du kannst nicht mehr als 25 Themen auswählen
-topic.format_prompt=Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) und Punkte ('.') enthalten und bis zu 35 Zeichen lang sein. Nur Kleinbuchstaben sind zulässig.
+topic.format_prompt=Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) und Punkte („.“) enthalten und bis zu 35 Zeichen lang sein. Nur Kleinbuchstaben sind zulässig.
find_file.go_to_file=Datei suchen
find_file.no_matching=Keine passende Datei gefunden
@@ -2599,9 +2615,9 @@ pulls.reopen_failed.base_branch = Der Pull-Request kann nicht wieder geöffnet w
settings.mirror_settings.pushed_repository = Gepushtes Repository
settings.add_collaborator_blocked_them = Der Mitarbeiter konnte nicht hinzugefügt werden, weil er den Besitzer des Repositorys blockiert hat.
settings.wiki_rename_branch_main = Den Wiki-Branch-Namen normalisieren
-settings.enter_repo_name = Gib den Repository-Namen zur Bestätigung ein:
+settings.enter_repo_name = Gib den Besitzer- und den Repository-Namen genau wie angezeigt ein:
settings.wiki_branch_rename_success = Der Branch-Name des Repository-Wikis wurde erfolgreich normalisiert.
-settings.archive.mirrors_unavailable = Mirrors sind nicht verfügbar, wenn das Repo archiviert ist.
+settings.archive.mirrors_unavailable = Spiegel sind nicht verfügbar, wenn das Repo archiviert ist.
pulls.blocked_by_user = Du kannst keinen Pull-Request in diesem Repository erstellen, weil du vom Repository-Besitzer blockiert wurdest.
settings.add_collaborator_blocked_our = Der Mitarbeiter konnte nicht hinzugefügt werden, weil der Repository-Besitzer ihn blockiert hat.
issues.blocked_by_user = Du kannst kein Issue in diesem Repository erstellen, weil du vom Repository-Besitzer blockiert wurdest.
@@ -2647,6 +2663,11 @@ contributors.contribution_type.filter_label = Art des Beitrags:
vendored = Vendored
activity.navbar.pulse = Puls
pulls.made_using_agit = AGit
+settings.confirmation_string = Bestätigungsstring
+pulls.agit_explanation = Mittels AGit-Workflow erstellt. AGit erlaubt Mitwirkenden, Änderungen mittels „git push“ vorzuschlagen, ohne einen Fork oder neuen Branch zu erstellen.
+activity.navbar.recent_commits = Neueste Commits
+activity.navbar.code_frequency = Code-Frequenz
+file_follow = Symlink folgen
[graphs]
@@ -2660,7 +2681,7 @@ members=Mitglieder
teams=Teams
code=Quelltext
lower_members=Mitglieder
-lower_repositories=Repositories
+lower_repositories=Repositorys
create_new_team=Neues Team
create_team=Team erstellen
org_desc=Beschreibung
@@ -2673,8 +2694,8 @@ team_permission_desc=Berechtigungen
team_unit_desc=Zugriff auf Repositorybereiche erlauben
team_unit_disabled=(Deaktiviert)
-form.name_reserved=Der Organisationsname "%s" ist reserviert.
-form.name_pattern_not_allowed=Das Muster "%s" ist in Organisationsnamen nicht erlaubt.
+form.name_reserved=Der Organisationsname „%s“ ist reserviert.
+form.name_pattern_not_allowed=Das Muster „%s“ ist in Organisationsnamen nicht erlaubt.
form.create_org_not_allowed=Du bist nicht berechtigt, eine Organisation zu erstellen.
settings=Einstellungen
@@ -2703,9 +2724,9 @@ settings.delete_prompt=Die Organisation wird dauerhaft gelöscht. Dies K
settings.confirm_delete_account=Löschen bestätigen
settings.delete_org_title=Organisation löschen
settings.delete_org_desc=Diese Organisation wird dauerhaft gelöscht. Fortfahren?
-settings.hooks_desc=Webhooks hinzufügen, die für alle Repositories dieser Organisation ausgelöst werden.
+settings.hooks_desc=Webhooks hinzufügen, die für alle Repositorys dieser Organisation ausgelöst werden.
-settings.labels_desc=Labels hinzufügen, die für alle Repositories dieser Organisation genutzt werden können.
+settings.labels_desc=Labels hinzufügen, die für alle Repositorys dieser Organisation genutzt werden können.
members.membership_visibility=Sichtbarkeit der Mitgliedschaft:
members.public=Sichtbar
@@ -2725,8 +2746,8 @@ members.invite_now=Jetzt einladen
teams.join=Beitreten
teams.leave=Verlassen
teams.leave.detail=%s verlassen?
-teams.can_create_org_repo=Repositories erstellen
-teams.can_create_org_repo_helper=Mitglieder können neue Repositories in der Organisation erstellen. Der Ersteller erhält Administrator-Zugriff auf das neue Repository.
+teams.can_create_org_repo=Repositorys erstellen
+teams.can_create_org_repo_helper=Mitglieder können neue Repositorys in der Organisation erstellen. Der Ersteller erhält Administrator-Zugriff auf das neue Repository.
teams.none_access=Kein Zugriff
teams.none_access_helper=Teammitglieder haben keinen Zugriff auf diese Einheit.
teams.general_access=Allgemeiner Zugriff
@@ -2736,10 +2757,10 @@ teams.read_access_helper=Mitglieder können Teamrepositorys ansehen und klonen.
teams.write_access=Schreiben
teams.write_access_helper=Mitglieder können Teamrepositorys ansehen und auf sie pushen.
teams.admin_access=Administratorzugang
-teams.admin_access_helper=Mitglieder können auf Team-Repositories pushen, von ihnen pullen und Mitwirkende hinzufügen.
+teams.admin_access_helper=Mitglieder können auf Team-Repositorys pushen, von ihnen pullen und Mitarbeiter hinzufügen.
teams.no_desc=Dieses Team hat keine Beschreibung
teams.settings=Einstellungen
-teams.owners_permission_desc=Besitzer haben vollen Zugriff auf alle Repositories und Admin-Rechte für diese Organisation.
+teams.owners_permission_desc=Besitzer haben vollen Zugriff auf alle Repositorys und Admin-Rechte für diese Organisation.
teams.members=Teammitglieder
teams.update_settings=Einstellungen aktualisieren
teams.delete_team=Team löschen
@@ -2749,39 +2770,39 @@ teams.invite_team_member.list=Ausstehende Einladungen
teams.delete_team_title=Team löschen
teams.delete_team_desc=Das Löschen eines Teams widerruft den Repository-Zugriff für seine Mitglieder. Fortfahren?
teams.delete_team_success=Das Team wurde gelöscht.
-teams.read_permission_desc=Dieses Team hat Lesezugriff : Mitglieder können Team-Repositories einsehen und klonen.
-teams.write_permission_desc=Dieses Team hat Schreibzugriff : Mitglieder können Team-Repositories einsehen und darauf pushen.
-teams.admin_permission_desc=Dieses Team hat Adminzugriff : Mitglieder dieses Teams können Team-Repositories ansehen, auf sie pushen und Mitarbeiter hinzufügen.
-teams.create_repo_permission_desc=Zusätzlich erteilt dieses Team die Berechtigung Repository erstellen : Mitglieder können neue Repositories in der Organisation erstellen.
-teams.repositories=Team-Repositories
-teams.search_repo_placeholder=Repository durchsuchen…
-teams.remove_all_repos_title=Alle Team-Repositories entfernen
-teams.remove_all_repos_desc=Dies entfernt alle Repositories von dem Team.
-teams.add_all_repos_title=Alle Repositories hinzufügen
-teams.add_all_repos_desc=Dadurch werden alle Repositories der Organisation dem Team hinzugefügt.
+teams.read_permission_desc=Dieses Team hat Lesezugriff : Mitglieder können Team-Repositorys einsehen und klonen.
+teams.write_permission_desc=Dieses Team hat Schreibzugriff : Mitglieder können Team-Repositorys einsehen und darauf pushen.
+teams.admin_permission_desc=Dieses Team hat Adminzugriff : Mitglieder dieses Teams können Team-Repositorys ansehen, auf sie pushen und Mitarbeiter hinzufügen.
+teams.create_repo_permission_desc=Zusätzlich erteilt dieses Team die Berechtigung Repository erstellen : Mitglieder können neue Repositorys in der Organisation erstellen.
+teams.repositories=Team-Repositorys
+teams.search_repo_placeholder=Repository durchsuchen …
+teams.remove_all_repos_title=Alle Team-Repositorys entfernen
+teams.remove_all_repos_desc=Dies entfernt alle Repositorys von dem Team.
+teams.add_all_repos_title=Alle Repositorys hinzufügen
+teams.add_all_repos_desc=Dadurch werden alle Repositorys der Organisation dem Team hinzugefügt.
teams.add_nonexistent_repo=Das Repository, das du hinzufügen möchtest, existiert nicht. Bitte erstelle es zuerst.
teams.add_duplicate_users=Dieser Benutzer ist bereits ein Teammitglied.
teams.repos.none=Dieses Team hat Zugang zu keinem Repository.
teams.members.none=Keine Mitglieder in diesem Team.
-teams.specific_repositories=Bestimmte Repositories
-teams.specific_repositories_helper=Mitglieder haben nur Zugriff auf Repositories, die explizit dem Team hinzugefügt wurden. Wenn Du diese Option wählst, werden Repositories, die bereits mit Alle Repositories hinzugefügt wurden, nicht automatisch entfernt.
-teams.all_repositories=Alle Repositories
-teams.all_repositories_helper=Team hat Zugriff auf alle Repositories. Wenn dies ausgewählt wird, werden alle vorhandenen Repositories zum Team hinzugefügt.
-teams.all_repositories_read_permission_desc=Dieses Team gewährt Lese -Zugriff auf Repositories : Mitglieder können Repositories ansehen und klonen.
-teams.all_repositories_write_permission_desc=Dieses Team gewährt Schreib -Zugriff auf alle Repositories : Mitglieder können Repositories lesen und auf sie pushen.
-teams.all_repositories_admin_permission_desc=Dieses Team gewährt Administrator -Zugriff auf alle Repositories : Mitglieder können Repositories lesen, auf sie pushen und Mitwirkende zu Repositorys hinzufügen.
+teams.specific_repositories=Bestimmte Repositorys
+teams.specific_repositories_helper=Mitglieder haben nur Zugriff auf Repositorys, die explizit dem Team hinzugefügt wurden. Wenn Du diese Option wählst, werden Repositorys, die bereits mit Alle Repositorys hinzugefügt wurden, nicht automatisch entfernt.
+teams.all_repositories=Alle Repositorys
+teams.all_repositories_helper=Team hat Zugriff auf alle Repositorys. Wenn dies ausgewählt wird, werden alle vorhandenen Repositorys zum Team hinzugefügt.
+teams.all_repositories_read_permission_desc=Dieses Team gewährt Lese -Zugriff auf Repositorys : Mitglieder können Repositorys ansehen und klonen.
+teams.all_repositories_write_permission_desc=Dieses Team gewährt Schreib -Zugriff auf alle Repositorys : Mitglieder können Repositorys lesen und auf sie pushen.
+teams.all_repositories_admin_permission_desc=Dieses Team gewährt Administrator -Zugriff auf alle Repositorys : Mitglieder können Repositorys lesen, auf sie pushen und Mitwirkende zu Repositorys hinzufügen.
teams.invite.title=Du wurdest eingeladen, dem Team %s in der Organisation %s beizutreten.
teams.invite.by=Von %s eingeladen
teams.invite.description=Bitte klicke auf die folgende Schaltfläche, um dem Team beizutreten.
follow_blocked_user = Du kannst dieser Organisation nicht folgen, weil diese Organisation dich blockiert hat.
[admin]
-dashboard=Dashboard
+dashboard=Übersicht
identity_access=Identität & Zugriff
users=Benutzerkonten
organizations=Organisationen
assets=Code-Assets
-repositories=Repositories
+repositories=Repositorys
hooks=Webhooks
integrations=Integrationen
authentication=Authentifizierungsquellen
@@ -2816,23 +2837,23 @@ dashboard.cron.error=Fehler in Cron: %s: %[3]s
dashboard.cron.finished=Cron: %[1]s ist beendet
dashboard.delete_inactive_accounts=Alle nicht aktivierten Konten löschen
dashboard.delete_inactive_accounts.started=Löschen aller nicht aktivierten Account-Aufgabe gestartet.
-dashboard.delete_repo_archives=Lösche alle Repository-Archive (ZIP, TAR.GZ, …)
+dashboard.delete_repo_archives=Lösche alle Repository-Archive (ZIP, TAR.GZ, etc.)
dashboard.delete_repo_archives.started=Löschen aller Repository-Archive gestartet.
dashboard.delete_missing_repos=Alle Repository-Datensätze mit verloren gegangenen Git-Dateien löschen
-dashboard.delete_missing_repos.started=Alle Repositories löschen, die den Git-File-Task nicht gestartet haben.
+dashboard.delete_missing_repos.started=Alle Repositorys löschen, die den Git-File-Task nicht gestartet haben.
dashboard.delete_generated_repository_avatars=Generierte Repository-Avatare löschen
dashboard.sync_repo_branches=Fehlende Branches aus den Git-Daten in die Datenbank synchronisieren
-dashboard.update_mirrors=Mirrors aktualisieren
-dashboard.repo_health_check=Healthchecks für alle Repositories ausführen
+dashboard.update_mirrors=Spiegel aktualisieren
+dashboard.repo_health_check=Healthchecks für alle Repositorys ausführen
dashboard.check_repo_stats=Überprüfe alle Repository-Statistiken
dashboard.archive_cleanup=Alte Repository-Archive löschen
dashboard.deleted_branches_cleanup=Gelöschte Branches bereinigen
dashboard.update_migration_poster_id=Migration Poster-IDs updaten
-dashboard.git_gc_repos=Garbage-Collection für alle Repositories ausführen
-dashboard.resync_all_sshkeys=Die Datei '.ssh/authorized_keys' mit Forgejo SSH-Schlüsseln aktualisieren.
-dashboard.resync_all_sshprincipals=Aktualisiere die Datei '.ssh/authorized_principals' mit Forgejo SSH Identitäten.
-dashboard.resync_all_hooks=Die „pre-receive“-, „update“- und „post-receive“-Hooks für alle Repositories erneut synchronisieren.
-dashboard.reinit_missing_repos=Alle Git-Repositories neu einlesen, für die Einträge existieren
+dashboard.git_gc_repos=Garbage-Collection für alle Repositorys ausführen
+dashboard.resync_all_sshkeys=Die Datei „.ssh/authorized_keys“ mit Forgejo-SSH-Schlüsseln aktualisieren.
+dashboard.resync_all_sshprincipals=Aktualisiere die Datei „.ssh/authorized_principals“ mit Forgejo-SSH-Principals.
+dashboard.resync_all_hooks=Die „pre-receive“-, „update“- und „post-receive“-Hooks für alle Repositorys erneut synchronisieren.
+dashboard.reinit_missing_repos=Alle Git-Repositorys neu einlesen, für die Einträge existieren
dashboard.sync_external_users=Externe Benutzerdaten synchronisieren
dashboard.cleanup_hook_task_table=Hook-Task-Tabelle bereinigen
dashboard.cleanup_packages=Veraltete Pakete löschen
@@ -2865,7 +2886,7 @@ dashboard.last_gc_time=Seit letztem GC-Zyklus
dashboard.total_gc_time=Gesamte GC-Pause
dashboard.total_gc_pause=Gesamte GC-Pause
dashboard.last_gc_pause=Letzte GC-Pause
-dashboard.gc_times=Anzahl GC
+dashboard.gc_times=GC-Zeiten
dashboard.delete_old_actions=Alle alten Aktionen aus der Datenbank löschen
dashboard.delete_old_actions.started=Löschen aller alten Aktionen in der Datenbank gestartet.
dashboard.update_checker=Update-Checker
@@ -2894,7 +2915,7 @@ users.created=Registriert am
users.last_login=Letzte Anmeldung
users.never_login=Hat sich noch nie eingeloggt
users.send_register_notify=Benutzer-Registrierungsbenachrichtigung senden
-users.new_success=Der Account "%s" wurde erstellt.
+users.new_success=Der Account „%s“ wurde erstellt.
users.edit=Bearbeiten
users.auth_source=Authentifizierungsquelle
users.local=Lokal
@@ -2902,15 +2923,15 @@ users.auth_login_name=Anmeldename zur Authentifizierung
users.password_helper=Passwort leer lassen, um es nicht zu verändern.
users.update_profile_success=Das Benutzerkonto wurde aktualisiert.
users.edit_account=Benutzerkonto bearbeiten
-users.max_repo_creation=Maximale Anzahl an Repositories
+users.max_repo_creation=Maximale Anzahl an Repositorys
users.max_repo_creation_desc=(Gib -1 ein, um das globale Standardlimit zu verwenden.)
users.is_activated=Account ist aktiviert
users.prohibit_login=Anmelden deaktivieren
users.is_admin=Ist Administrator
users.is_restricted=Ist eingeschränkt
users.allow_git_hook=Darf „Git Hooks“ erstellen
-users.allow_git_hook_tooltip=Git-Hooks werden mit denselben Benutzer-Rechten ausgeführt, mit denen Forgejo läuft, und haben die gleiche Ebene von Host-Zugriff. Dadurch können Benutzer mit diesen speziellen Git-Hook-Rechten auf alle Forgejo-Repositories sowie auf die von Forgejo verwendete Datenbank zugreifen und diese ändern. Auch das Erhalten von Administratorrechten für Forgejo ist möglich.
-users.allow_import_local=Darf lokale Repositories importieren
+users.allow_git_hook_tooltip=Git-Hooks werden mit denselben Benutzer-Rechten ausgeführt, mit denen Forgejo läuft, und haben die gleiche Ebene von Host-Zugriff. Dadurch können Benutzer mit diesen speziellen Git-Hook-Rechten auf alle Forgejo-Repositorys sowie auf die von Forgejo verwendete Datenbank zugreifen und diese ändern. Auch das Erhalten von Administratorrechten für Forgejo ist möglich.
+users.allow_import_local=Darf lokale Repositorys importieren
users.allow_create_organization=Darf Organisationen erstellen
users.update_profile=Benutzerkonto aktualisieren
users.delete_account=Benutzerkonto löschen
@@ -2955,8 +2976,8 @@ orgs.members=Mitglieder
orgs.new_orga=Neue Organisation
repos.repo_manage_panel=Repositoryverwaltung
-repos.unadopted=Nicht übernommene Repositories
-repos.unadopted.no_more=Keine weiteren nicht übernommenen Repositories gefunden
+repos.unadopted=Nicht übernommene Repositorys
+repos.unadopted.no_more=Keine weiteren nicht übernommenen Repositorys gefunden
repos.owner=Besitzer
repos.name=Name
repos.private=Privat
@@ -2981,12 +3002,12 @@ packages.size=Größe
packages.published=Veröffentlicht
defaulthooks=Standard-Webhooks
-defaulthooks.desc=Webhooks senden automatisch eine HTTP-POST-Anfrage an einen Server, wenn bestimmte Forgejo-Events ausgelöst werden. Hier definierte Webhooks sind die Standardwerte, die in alle neuen Repositories kopiert werden. Mehr Infos findest du in der Webhooks-Anleitung (auf Englisch).
+defaulthooks.desc=Webhooks senden automatisch ein HTTP-POST-Anfragen an einen Server, wenn bestimmte Forgejo-Events ausgelöst werden. Hier definierte Webhooks sind die Standardwerte, die in alle neuen Repositorys kopiert werden. Mehr Infos findest du in der Webhooks-Anleitung (auf Englisch).
defaulthooks.add_webhook=Standard-Webhook hinzufügen
defaulthooks.update_webhook=Standard-Webhook aktualisieren
systemhooks=System-Webhooks
-systemhooks.desc=Webhooks senden automatisch HTTP-POST-Anfragen an einen Server, wenn bestimmte Forgejo-Events ausgelöst werden. Hier definierte Webhooks werden auf alle Repositories des Systems übertragen, beachte daher mögliche Performance-Einbrüche. Mehr Infos findest du in der Webhooks-Anleitung (auf Englisch).
+systemhooks.desc=Webhooks senden automatisch HTTP-POST-Anfragen an einen Server, wenn bestimmte Forgejo-Events ausgelöst werden. Hier definierte Webhooks werden auf alle Repositorys des Systems übertragen, beachte daher mögliche Performance-Einbrüche. Mehr Infos findest du in der Webhooks-Anleitung (auf Englisch).
systemhooks.add_webhook=System-Webhook hinzufügen
systemhooks.update_webhook=System-Webhook aktualisieren
@@ -3021,10 +3042,10 @@ auths.search_page_size=Seitengröße
auths.filter=Benutzerfilter
auths.admin_filter=Admin-Filter
auths.restricted_filter=Eingeschränkte Filter
-auths.restricted_filter_helper=Leer lassen, um keine Benutzer als eingeschränkt festzulegen. Verwende einen Stern ('*'), um alle Benutzer, die nicht dem Admin-Filter entsprechen, als eingeschränkt zu setzen.
+auths.restricted_filter_helper=Leer lassen, um keine Benutzer als eingeschränkt festzulegen. Verwende einen Asterisk („*“), um alle Benutzer, die nicht dem Admin-Filter entsprechen, als eingeschränkt zu setzen.
auths.verify_group_membership=Gruppenmitgliedschaft in LDAP verifizieren (zum Überspringen leer lassen)
auths.group_search_base=Gruppensuche Basisdomainname
-auths.group_attribute_list_users=Gruppenattribut, welches die die Benutzerliste enthält
+auths.group_attribute_list_users=Gruppenattribut, welches die Benutzerliste enthält
auths.user_attribute_in_group=Benutzerattribut in der Gruppenliste
auths.map_group_to_team=Ordne LDAP-Gruppen Organisationsteams zu (zum Überspringen leer lassen)
auths.map_group_to_team_removal=Benutzer aus synchronisierten Teams entfernen, wenn der Benutzer nicht zur entsprechenden LDAP-Gruppe gehört
@@ -3038,7 +3059,7 @@ auths.allowed_domains_helper=Leer lassen, um alle Domains zuzulassen. Trenne meh
auths.skip_tls_verify=TLS-Verifikation überspringen
auths.force_smtps=SMTPS erzwingen
auths.force_smtps_helper=SMTPS wird immer auf Port 465 verwendet. Setze dies, um SMTPS auf anderen Ports zu erzwingen. (Sonst wird STARTTLS auf anderen Ports verwendet, wenn es vom Host unterstützt wird.)
-auths.helo_hostname=HELO Hostname
+auths.helo_hostname=HELO-Hostname
auths.helo_hostname_helper=Mit HELO gesendeter Hostname. Leer lassen, um den aktuellen Hostnamen zu senden.
auths.disable_helo=HELO deaktivieren
auths.pam_service_name=PAM-Dienstname
@@ -3062,9 +3083,9 @@ auths.oauth2_required_claim_name_helper=Setze diesen Namen, damit Nutzer aus die
auths.oauth2_required_claim_value=Benötigter Claim-Wert
auths.oauth2_required_claim_value_helper=Setze diesen Wert, damit Nutzer aus dieser Quelle sich nur anmelden dürfen, wenn sie einen Claim mit diesem Namen und Wert besitzen
auths.oauth2_group_claim_name=Claim-Name, der Gruppennamen für diese Quelle angibt. (Optional)
-auths.oauth2_admin_group=Gruppen-Claim-Wert für Administratoren. (Optional - erfordert Claim-Namen oben)
-auths.oauth2_restricted_group=Gruppen-Claim-Wert für eingeschränkte User. (Optional - erfordert Claim-Namen oben)
-auths.oauth2_map_group_to_team=Gruppen aus OAuth-Claims den Organisationsteams zuordnen. (Optional - oben muss der Name des Claims angegeben werden)
+auths.oauth2_admin_group=Gruppen-Claim-Wert für Administratoren. (Optional – erfordert Claim-Namen oben)
+auths.oauth2_restricted_group=Gruppen-Claim-Wert für eingeschränkte User. (Optional – erfordert Claim-Namen oben)
+auths.oauth2_map_group_to_team=Gruppen aus OAuth-Claims den Organisationsteams zuordnen. (Optional – oben muss der Name des Claims angegeben werden)
auths.oauth2_map_group_to_team_removal=Benutzer aus synchronisierten Teams entfernen, wenn der Benutzer nicht zur entsprechenden Gruppe gehört.
auths.enable_auto_register=Automatische Registrierung aktivieren
auths.sspi_auto_create_users=Benutzer automatisch anlegen
@@ -3072,19 +3093,19 @@ auths.sspi_auto_create_users_helper=Erlaube der SSPI Authentifikationsmethode, a
auths.sspi_auto_activate_users=Benutzer automatisch aktivieren
auths.sspi_auto_activate_users_helper=Erlaube der SSPI Authentifikationsmethode, automatisch neue Benutzerkonten zu aktivieren
auths.sspi_strip_domain_names=Domain vom Nutzernamen entfernen
-auths.sspi_strip_domain_names_helper=Falls aktiviert werden Domainnamen bei Loginnamen entfernt (z.B. "DOMAIN\nutzer" und "nutzer@example.ort" werden beide nur "nutzer").
+auths.sspi_strip_domain_names_helper=Falls aktiviert, werden Domainnamen von Loginnamen entfernt (z.B. „DOMAIN\benutzer“ und „benutzer@example.org“ werden beide nur „nutzer“).
auths.sspi_separator_replacement=Trennzeichen als Ersatz für \, / und @
-auths.sspi_separator_replacement_helper=Das zu verwendende Trennzeichen um Logon-Namen (zB. \ in "DOMAIN\user") und die Hauptnamen von Benutzern (z. B. das @ in "user@example.org") zu separieren.
+auths.sspi_separator_replacement_helper=Das zu verwendende Trennzeichen, um Logon-Namen (z.B. das „\“ in „DOMAIN\benutzer“) und die Hauptnamen von Benutzern (z.B. das „@“ in „benutzer@example.org“) zu trennen.
auths.sspi_default_language=Standardsprache für Benutzer
-auths.sspi_default_language_helper=Standardsprache für Benutzer, die automatisch mit der SSPI Authentifizierungsmethode erstellt wurden. Leer lassen, wenn du es bevorzugst, dass eine Sprache automatisch erkannt wird.
+auths.sspi_default_language_helper=Standardsprache für Benutzer, die automatisch mit der SSPI-Authentifizierungsmethode erstellt wurden. Leer lassen, wenn du es bevorzugst, dass eine Sprache automatisch erkannt wird.
auths.tips=Tipps
auths.tips.oauth2.general=OAuth2-Authentifizierung
auths.tips.oauth2.general.tip=Beim Registrieren einer OAuth2-Anwendung sollte die Callback-URL folgendermaßen lauten:
auths.tip.oauth2_provider=OAuth2-Anbieter
-auths.tip.bitbucket=Registriere einen neuen OAuth-Consumer unter https://bitbucket.org/account/user//oauth-consumers/new und füge die Berechtigung „Account“ – „Read“ hinzu.
-auths.tip.nextcloud=Registriere über das "Settings -> Security -> OAuth 2.0 client"-Menü einen neuen "OAuth consumer" auf der Nextcloud-Instanz
-auths.tip.dropbox=Erstelle eine neue App auf https://www.dropbox.com/developers/apps.
-auths.tip.facebook=Erstelle eine neue Anwendung auf https://developers.facebook.com/apps und füge das Produkt „Facebook Login“ hinzu.
+auths.tip.bitbucket=Registriere einen neuen OAuth-Consumer unter https://bitbucket.org/account/user//oauth-consumers/new und füge die Berechtigung „Account“ – „Read“ hinzu
+auths.tip.nextcloud=Registriere einen neuen OAuth-Consumer auf deiner Instanz über das folgende Menü: „Settings -> Security -> OAuth 2.0 client“
+auths.tip.dropbox=Erstelle eine neue App auf https://www.dropbox.com/developers/apps
+auths.tip.facebook=Erstelle eine neue Anwendung auf https://developers.facebook.com/apps und füge das Produkt „Facebook Login“ hinzu
auths.tip.github=Erstelle unter https://github.com/settings/applications/new eine neue OAuth-Anwendung.
auths.tip.gitlab=Erstelle unter https://gitlab.com/profile/applications eine neue Anwendung.
auths.tip.google_plus=Du erhältst die OAuth2-Client-Zugangsdaten in der Google-API-Konsole unter https://console.developers.google.com/
@@ -3092,11 +3113,11 @@ auths.tip.openid_connect=Benutze die OpenID-Connect-Discovery-URL (/.wel
auths.tip.twitter=Gehe auf https://dev.twitter.com/apps, erstelle eine Anwendung und stelle sicher, dass die Option „Allow this application to be used to Sign in with Twitter“ aktiviert ist
auths.tip.discord=Erstelle unter https://discordapp.com/developers/applications/me eine neue Anwendung.
auths.tip.gitea=Registriere eine neue OAuth2-Anwendung. Eine Anleitung findest du unter https://docs.gitea.com/development/oauth2-provider/
-auths.tip.yandex=`Erstelle eine neue Anwendung auf https://oauth.yandex.com/client/new. Wähle folgende Berechtigungen aus dem "Yandex.Passport API" Bereich: "Zugriff auf E-Mail-Adresse", "Zugriff auf Benutzeravatar" und "Zugriff auf Benutzername, Vor- und Nachname, Geschlecht"`
-auths.tip.mastodon=Gebe eine benutzerdefinierte URL für die Mastodon-Instanz ein, mit der du dich authentifizieren möchtest (oder benutze die standardmäßige)
+auths.tip.yandex=`Erstelle eine neue Anwendung auf https://oauth.yandex.com/client/new. Wähle folgende Berechtigungen aus dem Abschnitt „Yandex.Passport API“: „Zugriff auf E-Mail-Adresse“, „Zugriff auf Benutzeravatar“ und „Zugriff auf Benutzername, Vor- und Nachname, Geschlecht“`
+auths.tip.mastodon=Gib eine benutzerdefinierte URL für die Mastodon-Instanz ein, mit der du dich authentifizieren möchtest (oder benutze die standardmäßige)
auths.edit=Authentifikationsquelle bearbeiten
auths.activated=Diese Authentifikationsquelle ist aktiviert
-auths.new_success=Die Authentifizierung "%s" wurde hinzugefügt.
+auths.new_success=Die Authentifizierung „%s“ wurde hinzugefügt.
auths.update_success=Diese Authentifizierungsquelle wurde aktualisiert.
auths.update=Authentifizierungsquelle aktualisieren
auths.delete=Authentifikationsquelle löschen
@@ -3104,16 +3125,16 @@ auths.delete_auth_title=Authentifizierungsquelle löschen
auths.delete_auth_desc=Das Löschen einer Authentifizierungsquelle verhindert, dass Benutzer sich darüber anmelden können. Fortfahren?
auths.still_in_used=Diese Authentifizierungsquelle wird noch verwendet. Bearbeite oder lösche zuerst alle Benutzer, die diese Authentifizierungsquelle benutzen.
auths.deletion_success=Die Authentifizierungsquelle „%s“ wurde gelöscht.
-auths.login_source_exist=Die Authentifizierungsquelle "%s" existiert bereits.
+auths.login_source_exist=Die Authentifizierungsquelle „%s“ existiert bereits.
auths.login_source_of_type_exist=Eine Authentifizierungart dieses Typs existiert bereits.
auths.unable_to_initialize_openid=OpenID Connect Provider konnte nicht initialisiert werden: %s
auths.invalid_openIdConnectAutoDiscoveryURL=Ungültige Auto-Discovery-URL (dies muss eine gültige URL sein, die mit http:// oder https:// beginnt)
config.server_config=Serverkonfiguration
-config.app_name=Seitentitel
+config.app_name=Instanztitel
config.app_ver=Forgejo-Version
-config.app_url=Forgejo-Basis-URL
-config.custom_conf=Konfigurations-Datei-Pfad
+config.app_url=Basis-URL
+config.custom_conf=Konfigurationsdatei-Pfad
config.custom_file_root_path=Benutzerdefinierter Root-Pfad
config.domain=Server-Domain
config.offline_mode=Lokaler Modus
@@ -3142,8 +3163,8 @@ config.ssh_minimum_key_sizes=Mindestschlüssellängen
config.lfs_config=LFS-Konfiguration
config.lfs_enabled=Aktiviert
-config.lfs_content_path=LFS Content-Pfad
-config.lfs_http_auth_expiry=Ablauf der LFS HTTP Authentifizierung
+config.lfs_content_path=LFS-Content-Pfad
+config.lfs_http_auth_expiry=Ablauf der LFS-HTTP-Authentifizierung
config.db_config=Datenbankkonfiguration
config.db_type=Typ
@@ -3166,7 +3187,7 @@ config.require_sign_in_view=Seiten nur für angemeldete Benutzer zugänglich
config.mail_notify=E-Mail-Benachrichtigungen aktivieren
config.enable_captcha=CAPTCHA aktivieren
config.active_code_lives=Aktivierungscode-Lebensdauer
-config.reset_password_code_lives=Kontowiederherstellungs-Code Ablaufzeit
+config.reset_password_code_lives=Kontowiederherstellungscode-Ablaufzeit
config.default_keep_email_private=E-Mail-Adressen standardmäßig verbergen
config.default_allow_create_organization=Erstellen von Organisationen standardmäßig erlauben
config.enable_timetracking=Zeiterfassung aktivieren
@@ -3192,13 +3213,13 @@ config.mailer_user=Benutzer
config.mailer_use_sendmail=Sendmail benutzen
config.mailer_sendmail_path=Sendmail-Pfad
config.mailer_sendmail_args=Zusätzliche Argumente für Sendmail
-config.mailer_sendmail_timeout=Sendmail Timeout
+config.mailer_sendmail_timeout=Sendmail-Timeout
config.mailer_use_dummy=Dummy
config.test_email_placeholder=E-Mail (z.B. test@example.com)
config.send_test_mail=Test-E-Mail senden
config.send_test_mail_submit=Senden
-config.test_mail_failed=Das Senden der Test-E-Mail an '%s' ist fehlgeschlagen: %v
-config.test_mail_sent=Eine Test-E-Mail wurde an "%s" gesendet.
+config.test_mail_failed=Das Senden der Test-E-Mail an „%s“ ist fehlgeschlagen: %v
+config.test_mail_sent=Eine Test-E-Mail wurde an „%s“ gesendet.
config.oauth_config=OAuth-Konfiguration
config.oauth_enabled=Aktiviert
@@ -3230,15 +3251,15 @@ config.git_max_diff_line_characters=Max. Diff-Zeichen (in einer Zeile)
config.git_max_diff_files=Max. Diff-Dateien (Angezeigte)
config.git_gc_args=GC-Argumente
config.git_migrate_timeout=Zeitlimit für Migration
-config.git_mirror_timeout=Zeitlimit für Mirror-Aktualisierung
-config.git_clone_timeout=Zeitlimit für Clone
+config.git_mirror_timeout=Zeitlimit für Spiegel-Aktualisierung
+config.git_clone_timeout=Zeitlimit für Klon
config.git_pull_timeout=Zeitlimit für Pull
config.git_gc_timeout=Zeitlimit für GC
config.log_config=Konfiguration des Loggings
config.logger_name_fmt=Logger: %s
config.disabled_logger=Deaktiviert
-config.access_log_mode=Access-Log-Modus
+config.access_log_mode=Zugriffslog-Modus
config.access_log_template=Zugriffslog-Vorlage
config.xorm_log_sql=SQL protokollieren
@@ -3326,7 +3347,7 @@ comment_issue=`hat das Issue %[3]s#%[2]s kommentiert`
comment_pull=`Pull-Request %[3]s#%[2]s wurde kommentiert`
merge_pull_request=`Pull-Request %[3]s#%[2]s wurde zusammengeführt`
auto_merge_pull_request=`Pull-Request %[3]s#%[2]s wurde automatisch zusammengeführt`
-transfer_repo=hat Repository %s
transferiert an %s
+transfer_repo=hat Repository %s
übertragen zu %s
push_tag=Tag %[3]s nach %[4]s wurde gepusht
delete_tag=hat Tag %[2]s in %[3]s gelöscht
delete_branch=hat Branch %[2]s in %[3]s gelöscht
@@ -3335,15 +3356,15 @@ compare_commits=Vergleiche %d Commits
compare_commits_general=Commits vergleichen
mirror_sync_push=Commits zu %[3]s bei %[4]s wurden von einem Spiegel synchronisiert
mirror_sync_create=neue Referenz %[3]s bei %[4]s wurde von einem Spiegel synchronisiert
-mirror_sync_delete=hat die Referenz des Mirrors %[2]s
in %[3]s synchronisiert und gelöscht
-approve_pull_request=`hat %[3]s#%[2]s approved`
+mirror_sync_delete=hat die Referenz des Spiegels %[2]s
in %[3]s synchronisiert und gelöscht
+approve_pull_request=`hat %[3]s#%[2]s genehmigt`
reject_pull_request=`schlug Änderungen für %[3]s#%[2]s vor`
-publish_release=`veröffentlichte Release "%[4]s" in %[3]s `
+publish_release=`veröffentlichte Release „%[4]s“ in %[3]s `
review_dismissed=`verwarf das Review von %[4]s in %[3]s#%[2]s `
review_dismissed_reason=Grund:
create_branch=legte den Branch %[3]s in %[4]s an
-starred_repo=markiert %[2]s
-watched_repo=beobachtet %[2]s
+starred_repo=favorisierte %[2]s
+watched_repo=beobachtet ab jetzt %[2]s
[tool]
now=jetzt
@@ -3358,7 +3379,7 @@ future=Zukunft
seconds=%d Sekunden
minutes=%d Minuten
hours=%d Stunden
-days=%d Tagen
+days=%d Tage
weeks=%d Wochen
months=%d Monaten
years=%d Jahren
@@ -3393,8 +3414,8 @@ error.no_committer_account=Es ist kein Account mit der E-Mail-Adresse des Commit
error.no_gpg_keys_found=Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
error.not_signed_commit=Kein signierter Commit
error.failed_retrieval_gpg_keys=Fehler beim Abrufen eines Keys des Commiter-Kontos
-error.probable_bad_signature=WARNHINWEIS! Obwohl es einen Schlüssel mit dieser ID in der Datenbank gibt, wird dieser Commit nicht verifiziert! Dieser Commit ist nicht vertrauenswürdig.
-error.probable_bad_default_signature=WARNHINWEIS! Obwohl der Standardschlüssel diese ID hat, wird der Commit nicht verifiziert! Dieser Commit ist NICHT vertrauenswürdig.
+error.probable_bad_signature=WARNHINWEIS! Obwohl es einen Schlüssel mit dieser ID in der Datenbank gibt, verifiziert er nicht diesen Commit! Dieser Commit ist VERDÄCHTIG.
+error.probable_bad_default_signature=WARNHINWEIS! Obwohl der Standardschlüssel diese ID hat, verifiziert er nicht diesen Commit! Dieser Commit ist VERDÄCHTIG.
[units]
unit=Einheit
@@ -3405,7 +3426,7 @@ error.unit_not_allowed=Du hast keine Berechtigung, um auf diesen Repository-Bere
title=Pakete
desc=Repository-Pakete verwalten.
empty=Noch keine Pakete vorhanden.
-empty.documentation=Weitere Informationen zur Paket-Registry findest Du in der Dokumentation .
+empty.documentation=Weitere Informationen zur Paket-Registry findest du in der Dokumentation .
empty.repo=Hast du ein Paket hochgeladen, das hier nicht angezeigt wird? Gehe zu den Paketeinstellungen und verlinke es mit diesem Repo.
registry.documentation=Für weitere Informationen zur %s-Registry, schaue in der Dokumentation nach.
filter.type=Typ
@@ -3437,20 +3458,20 @@ alpine.registry.info=Wähle $branch und $repository aus der Liste unten.
alpine.install=Nutze folgenden Befehl, um das Paket zu installieren:
alpine.repository=Repository-Informationen
alpine.repository.branches=Branches
-alpine.repository.repositories=Repositories
+alpine.repository.repositories=Repositorys
alpine.repository.architectures=Architekturen
cargo.registry=Richte diese Registry in der Cargo-Konfigurationsdatei ein (z.B. ~/.cargo/config.toml
):
cargo.install=Um das Paket mit Cargo zu installieren, führe den folgenden Befehl aus:
-chef.registry=Richte diese Registry in deiner ~/.chef/config.rb
Datei ein:
+chef.registry=Richte diese Registry in deiner ~/.chef/config.rb
-Datei ein:
chef.install=Nutze folgenden Befehl, um das Paket zu installieren:
-composer.registry=Setze diese Paketverwaltung in deiner ~/.composer/config.json
Datei auf:
+composer.registry=Setze diese Paketverwaltung in deiner ~/.composer/config.json
-Datei auf:
composer.install=Nutze folgenden Befehl, um das Paket mit Composer zu installieren:
composer.dependencies=Abhängigkeiten
composer.dependencies.development=Entwicklungsabhängigkeiten
conan.details.repository=Repository
conan.registry=Diese Registry über die Kommandozeile einrichten:
conan.install=Um das Paket mit Conan zu installieren, führe den folgenden Befehl aus:
-conda.registry=Richte diese Registry als Conda-Repository in deiner .condarc
Datei ein:
+conda.registry=Richte diese Registry als Conda-Repository in deiner .condarc
-Datei ein:
conda.install=Um das Paket mit Conda zu installieren, führe den folgenden Befehl aus:
container.details.type=Container-Image Typ
container.details.platform=Plattform
@@ -3461,7 +3482,7 @@ container.layers=Container-Image Ebenen
container.labels=Labels
container.labels.key=Schlüssel
container.labels.value=Wert
-cran.registry=Richte diese Registry in deiner Rprofile.site
Datei ein:
+cran.registry=Richte diese Registry in deiner Rprofile.site
-Datei ein:
cran.install=Nutze folgenden Befehl, um das Paket zu installieren:
debian.registry=Diese Registry über die Kommandozeile einrichten:
debian.registry.info=Wähle $distribution und $component aus der Liste unten.
@@ -3475,9 +3496,9 @@ go.install=Installiere das Paket über die Kommandozeile:
helm.registry=Diese Paketverwaltung über die Kommandozeile einrichten:
helm.install=Nutze folgenden Befehl, um das Paket zu installieren:
maven.registry=Setze diese Paketverwaltung in der pom.xml
deines Projektes auf:
-maven.install=Nimm Folgendes in den dependencies
deiner pom.xml
auf, um das Paket zu installieren:
+maven.install=Um das Paket zu verwenden, nimm folgendes in den dependencies
-Block in der pom.xml
-Datei auf:
maven.install2=Über die Kommandozeile ausführen:
-maven.download=Nutze folgendes Kommando, um die Dependency herunterzuladen:
+maven.download=Nutze folgendes Kommando, um die Abhängigkeit herunterzuladen:
nuget.registry=Diese Registry über die Kommandozeile einrichten:
nuget.install=Um das Paket mit NuGet zu installieren, führe den folgenden Befehl aus:
nuget.dependency.framework=Zielframework
@@ -3486,7 +3507,7 @@ npm.install=Um das Paket mit npm zu installieren, führe den folgenden Befehl au
npm.install2=oder füge es zur package.json-Datei hinzu:
npm.dependencies=Abhängigkeiten
npm.dependencies.development=Entwicklungsabhängigkeiten
-npm.dependencies.peer=Peer Abhängigkeiten
+npm.dependencies.peer=Peer-Abhängigkeiten
npm.dependencies.optional=Optionale Abhängigkeiten
npm.details.tag=Tag
pub.install=Um das Paket mit Dart zu installieren, führe den folgenden Befehl aus:
@@ -3509,7 +3530,7 @@ swift.install=Füge das Paket deiner Package.swift
Datei hinzu:
swift.install2=und führe den folgenden Befehl aus:
vagrant.install=Um eine Vagrant-Box hinzuzufügen, führe den folgenden Befehl aus:
settings.link=Dieses Paket einem Repository zuweisen
-settings.link.description=Wenn du ein Paket mit einem Repository verknüpfst, wird es in der Paketliste des Repositories angezeigt.
+settings.link.description=Wenn du ein Paket mit einem Repository verknüpfst, wird es in der Paketliste des Repositorys angezeigt.
settings.link.select=Repository auswählen
settings.link.button=Repository-Link aktualisieren
settings.link.success=Repository-Link wurde erfolgreich aktualisiert.
@@ -3562,7 +3583,7 @@ none=Noch keine Secrets vorhanden.
creation=Secret hinzufügen
creation.name_placeholder=Groß-/Kleinschreibung wird ignoriert, nur alphanumerische Zeichen oder Unterstriche, darf nicht mit GITEA_ oder GITHUB_ beginnen
creation.value_placeholder=Beliebigen Inhalt eingeben. Leerzeichen am Anfang und Ende werden weggelassen.
-creation.success=Das Secret "%s" wurde hinzugefügt.
+creation.success=Das Secret „%s“ wurde hinzugefügt.
creation.failed=Secret konnte nicht hinzugefügt werden.
deletion=Secret entfernen
deletion.description=Das Entfernen eines Secrets kann nicht rückgängig gemacht werden. Fortfahren?
@@ -3625,7 +3646,7 @@ runs.all_workflows=Alle Workflows
runs.commit=Commit
runs.scheduled=Geplant
runs.pushed_by=gepusht von
-runs.invalid_workflow_helper=Die Workflow-Konfigurationsdatei ist ungültig. Bitte überprüfe Deine Konfigurationsdatei: %s
+runs.invalid_workflow_helper=Die Workflow-Konfigurationsdatei ist ungültig. Bitte überprüfe deine Konfigurationsdatei: %s
runs.actor=Initiator
runs.status=Status
runs.actors_no_select=Alle Initiatoren
@@ -3634,9 +3655,9 @@ runs.no_results=Keine passenden Ergebnisse gefunden.
runs.no_runs=Der Workflow hat noch keine Ausführungen.
workflow.disable=Workflow deaktivieren
-workflow.disable_success=Workflow '%s' erfolgreich deaktiviert.
+workflow.disable_success=Workflow „%s“ erfolgreich deaktiviert.
workflow.enable=Workflow aktivieren
-workflow.enable_success=Workflow '%s' erfolgreich aktiviert.
+workflow.enable_success=Workflow „%s“ erfolgreich aktiviert.
workflow.disabled=Workflow ist deaktiviert.
need_approval_desc=Um Workflows für den Pull-Request eines Forks auszuführen, ist eine Genehmigung erforderlich.
@@ -3661,6 +3682,7 @@ runs.no_workflows = Es gibt noch keine Workflows.
runs.no_workflows.documentation = Für weitere Informationen über Forgejo Actions, siehe die Dokumentation .
runs.empty_commit_message = (leere Commit-Nachricht)
variables.id_not_exist = Variable mit ID %d existiert nicht.
+runs.workflow = Workflow
[projects]
type-1.display_name=Individuelles Projekt
@@ -3684,3 +3706,5 @@ component_loading_info = Dies könnte einen Moment dauern …
component_failed_to_load = Ein unerwarteter Fehler ist aufgetreten.
component_loading = Lade %s …
contributors.what = Beiträge
+recent_commits.what = neueste Commits
+code_frequency.what = Code-Frequenz
diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini
index ab5bcefc7c..eaa0a5d77d 100644
--- a/options/locale/locale_el-GR.ini
+++ b/options/locale/locale_el-GR.ini
@@ -7,44 +7,44 @@ logo=Λογότυπο
sign_in=Είσοδος
sign_in_with_provider=Είσοδος με %s
sign_in_or=ή
-sign_out=Έξοδος
+sign_out=Αποσύνδεση
sign_up=Εγγραφή
link_account=Σύνδεση λογαριασμού
register=Εγγραφή
version=Έκδοση
-powered_by=Με τη δύναμη του %s
+powered_by=Βασισμένο στο %s
page=Σελίδα
template=Πρότυπο
language=Γλώσσα
notifications=Ειδοποιήσεις
-active_stopwatch=Ενεργή Καταγραφή Χρόνου
-tracked_time_summary=Περίληψη του χρόνου παρακολούθησης με βάση τα φίλτρα της λίστας ζητημάτων
+active_stopwatch=Ενεργή καταγραφή χρόνου
+tracked_time_summary=Περίληψη του χρόνου παρακολούθησης βάσει των φίλτρων της λίστας ζητημάτων
create_new=Δημιουργία…
user_profile_and_more=Προφίλ και ρυθμίσεις…
-signed_in_as=Είσοδος ως
+signed_in_as=Συνδεδεμένος ως
enable_javascript=Απαιτείται JavaScript για να εμφανιστεί αυτή η ιστοσελίδα.
toc=Πίνακας Περιεχομένων
-licenses=Άδειες
+licenses=Άδειες Χρήσης
return_to_gitea=Επιστροφή στο Forgejo
username=Όνομα Χρήστη
-email=Διεύθυνση Email
+email=Διεύθυνση ηλεκτρονικού ταχυδρομείου (email)
password=Κωδικός πρόσβασης
-access_token=Διακριτικό Πρόσβασης
-re_type=Επιβεβαίωση Κωδικού Πρόσβασης
+access_token=Διακριτικό πρόσβασης
+re_type=Επιβεβαίωση κωδικού πρόσβασης
captcha=CAPTCHA
twofa=Έλεγχος Ταυτότητας Δύο Παραγόντων
twofa_scratch=Κωδικός Μίας Χρήσης Δύο Παραγόντων
passcode=Κωδικός
webauthn_insert_key=Εισάγετε το κλειδί ασφαλείας σας
-webauthn_sign_in=Πατήστε το κουμπί στο κλειδί ασφαλείας. Αν το κλειδί ασφαλείας σας δεν έχει κουμπί, τοποθετήστε το ξανά.
+webauthn_sign_in=Πατήστε το κουμπί στο κλειδί ασφαλείας σας. Αν το κλειδί ασφαλείας σας δεν έχει κουμπί, αποσυνδέστε το και συνδέστε το ξανά.
webauthn_press_button=Παρακαλώ πατήστε το κουμπί στο κλειδί ασφαλείας…
webauthn_use_twofa=Χρησιμοποιήστε έναν κωδικό δύο παραγόντων από το τηλέφωνό σας
-webauthn_error=Αδύνατη η ανάγνωση του κλειδιού ασφαλείας.
+webauthn_error=Δεν ήταν δυνατή η επικοινωνία με το κλειδί ασφαλείας σας.
webauthn_unsupported_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει επί του παρόντος WebAuthn.
webauthn_error_unknown=Παρουσιάστηκε ένα άγνωστο σφάλμα. Παρακαλώ προσπαθήστε ξανά.
-webauthn_error_insecure=`Το WebAuthn υποστηρίζει μόνο ασφαλείς συνδέσεις. Για δοκιμές πάνω από HTTP, μπορείτε να χρησιμοποιήσετε την προέλευση "localhost" ή "127.0.0.1"`
+webauthn_error_insecure=Το WebAuthn υποστηρίζει μόνο ασφαλείς συνδέσεις. Για δοκιμές πάνω από HTTP, μπορείτε να χρησιμοποιήσετε την προέλευση «localhost» ή «127.0.0.1»
webauthn_error_unable_to_process=Ο διακομιστής δεν μπόρεσε να επεξεργαστεί το αίτημά σας.
webauthn_error_duplicated=Το κλειδί ασφαλείας δεν επιτρέπεται για αυτό το αίτημα. Βεβαιωθείτε ότι το κλειδί δεν έχει ήδη καταχωρηθεί.
webauthn_error_empty=Πρέπει να ορίσετε ένα όνομα για αυτό το κλειδί.
@@ -80,17 +80,17 @@ pull_requests=Pull Requests
issues=Ζητήματα
milestones=Ορόσημα
-ok=OK
+ok=Εντάξει
cancel=Ακύρωση
-retry=Επανάληψη
+retry=Επαναπροσπάθεια
rerun=Επανεκτέλεση
rerun_all=Επανεκτέλεση όλων
save=Αποθήκευση
add=Προσθήκη
add_all=Προσθήκη Όλων
remove=Αφαίρεση
-remove_all=Αφαίρεση Όλων
-remove_label_str=`Αφαίρεση του αντικειμένου "%s"`
+remove_all=Αφαίρεση όλων
+remove_label_str=Αφαίρεση αντικειμένου «%s»
edit=Επεξεργασία
view=Προβολή
@@ -135,12 +135,21 @@ concept_user_organization=Οργανισμός
show_timestamps=Εμφάνιση χρονοσημάνσεων
show_log_seconds=Εμφάνιση δευτερολέπτων
show_full_screen=Εμφάνιση πλήρους οθόνης
-download_logs=Λήψη καταγραφών
+download_logs=Λήψη αρχείων καταγραφής
-confirm_delete_selected=Επιβεβαιώνετε τη διαγραφή όλων των επιλεγμένων στοιχείων;
+confirm_delete_selected=Είστε βέβαιοι πως θέλετε να διαγράψετε όλα τα επιλεγμένα στοιχεία;
name=Όνομα
value=Τιμή
+toggle_menu = Εναλλαγή μενού
+confirm_delete_artifact = Είστε βέβαιοι πως θέλετε να διαγράψετε το προϊόν «%s»;
+filter = Φίλτρο
+filter.is_archived = Αρχειοθετημένο
+filter.clear = Απενεργοποίηση φίλτρου
+filter.not_archived = Μη αρχειοθετημένο
+filter.is_template = Πρότυπο
+filter.public = Δημόσιο
+filter.private = Ιδιωτικό
[aria]
navbar=Γραμμή Πλοήγησης
@@ -181,29 +190,30 @@ missing_csrf=Bad Request: δεν υπάρχει διακριτικό CSRF
invalid_csrf=Λάθος Αίτημα: μη έγκυρο διακριτικό CSRF
not_found=Ο προορισμός δεν βρέθηκε.
network_error=Σφάλμα δικτύου
+server_internal = Σφάλμα Διακομιστή
[startpage]
app_desc=Μια ανώδυνη, αυτο-φιλοξενούμενη υπηρεσία Git
-install=Εύκολο στην εγκατάσταση
-install_desc=Απλά εκτελέστε το αρχείο προγράμματος για την πλατφόρμα σας, χρήσιμοποιήστε το με το Docker , ή εγκαταστήστε το πακέτο .
-platform=Πολυπλατφορμικό
-platform_desc=Ο Forgejo τρέχει οπουδήποτε Go μπορεί να γίνει compile για: Windows, macOS, Linux, ARM, κλπ. Επιλέξτε αυτό που αγαπάτε!
+install=Εύκολη εγκατάσταση
+install_desc=Απλά τρέξε το αρχείο που αντιστοιχεί στην πλατφόρμα σου, εγκατέστησέ το με το Docker , ή χρησιμοποίησε ένα πακέτο λογισμικού .
+platform=Τρέχει παντού
+platform_desc=Το Forgejo τρέχει σε όλα τα συστήματα που υποστηρίζει η γλώσσα Go , όπως: Windows, macOS, Linux, ARM, κλπ. Διάλεξε αυτό που αγαπάς!
lightweight=Ελαφρύ
-lightweight_desc=Forgejo έχει χαμηλές ελάχιστες απαιτήσεις και μπορεί να τρέξει σε ένα οικονομικό Raspberry Pi. Εξοικονομήστε ενέργεια!
+lightweight_desc=Το Forgejo έχει ελάχιστες απαιτήσεις, μπορείς και να το τρέξεις σε ένα φτηνό Raspberry Pi. Εξοικονόμησε ενέργεια!
license=Ανοικτού κώδικα
-license_desc=Κατεβάστε το Forgejo ! Ελάτε μαζί μας και συνεισφέρετε για να κάνετε αυτό το έργο ακόμα καλύτερο. Δεν είναι ντροπή να συνεισφέρετε!
+license_desc=Κατέβασε το Forgejo ! Ακόμα, μπορείς να μας βοηθήσεις να κάνουμε το έργο μας καλύτερο με τις συνεισφορές σου . Χωρίς ντροπές!
[install]
install=Εγκατάσταση
-title=Αρχικές Ρυθμίσεις
+title=Αρχικές ρυθμίσεις
docker_helper=Αν εκτελέσετε το Forgejo μέσα στο Docker, παρακαλώ διαβάστε την τεκμηρίωση πριν αλλάξετε τις ρυθμίσεις.
require_db_desc=Το Forgejo απαιτεί MySQL, PostgreSQL, MSSQL, SQLite3 ή TiDB (με πρωτόκολλο MySQL).
-db_title=Ρυθμίσεις Βάσης Δεδομένων
-db_type=Τύπος της Βάσης Δεδομένων
+db_title=Ρυθμίσεις βάσης δεδομένων
+db_type=Είδος βάσης δεδομένων
host=Διακομιστής
user=Όνομα Χρήστη
password=Συνθηματικό
-db_name=Όνομα Βάσης Δεδομένων
+db_name=Όνομα βάσης Δδδομένων
db_schema=Σχήμα
db_schema_helper=Αφήστε κενό για την προεπιλογή της βάσης δεδομένων ("public").
ssl_mode=SSL
@@ -222,91 +232,94 @@ err_admin_name_is_reserved=Το Όνομα χρήστη του Διαχειρι
err_admin_name_pattern_not_allowed=Το Όνομα χρήστη του Διαχειριστή δεν είναι έγκυρο, ταιριάζει σε μια δεσμευμένη μορφή
err_admin_name_is_invalid=Το Όνομα Χρήστη του Διαχειριστή δεν είναι έγκυρο
-general_title=Γενικές Ρυθμίσεις
-app_name=Τίτλος Ιστοτόπου
+general_title=Γενικές ρυθμίσεις
+app_name=Τίτλος ιστοτόπου
app_name_helper=Μπορείτε να εισάγετε το όνομα της εταιρείας σας εδώ.
-repo_path=Ριζική Διαδρομή Αποθετηρίου
+repo_path=Τοποθεσία αρχείων αποθετηρίων
repo_path_helper=Τα απομακρυσμένα αποθετήρια Git θα αποθηκεύονται σε αυτόν τον κατάλογο.
-lfs_path=Ριζική Διαδρομή Git LFS
+lfs_path=Τοποθεσία αρχείων Git LFS
lfs_path_helper=Τα αρχεία που παρακολουθούνται από το Git LFS θα αποθηκεύονται σε αυτόν τον φάκελο. Αφήστε κενό για να το απενεργοποιήσετε.
-run_user=Εκτέλεση Σαν Χρήστη
-run_user_helper=Το όνομα του χρήστη του λειτουργικού συστήματος ο οποίος εκτελεί το Gitea. Επισημαίνεται ότι αυτός ο χρήστης πρέπει να έχει πρόσβαση στο ριζικό φάκελο του αποθετηρίου.
-domain=Domain Διακομιστή
+run_user=Εκτέλεση ως
+run_user_helper=Το όνομα του χρήστη του λειτουργικού συστήματος ο οποίος εκτελεί το Forgejo. Επισημαίνεται ότι αυτός ο χρήστης πρέπει να έχει πρόσβαση στον φάκελο που περιέχει όλα τα αποθετηρία.
+domain=Domain διακομιστή
domain_helper=Όνομα domain διακομιστή ή η διεύθυνση του.
-ssh_port=Θύρα της υπηρεσίας SSH
-ssh_port_helper=Αριθμός θύρας που ακούει η υπηρεσία SSH. Αφήστε κενό για να το απενεργοποιήσετε.
-http_port=Η HTTP θύρα που ακούει το Forgejo
-http_port_helper=Αριθμός θύρας που θα ακούει η υπηρεσία web του Forgejo.
-app_url=Βασικό URL του Forgejo
+ssh_port=Θύρα υπηρεσίας SSH
+ssh_port_helper=Αριθμός θύρας στην οποία θα «ακούει» η υπηρεσία SSH. Αφήστε κενό για να την απενεργοποιήσετε.
+http_port=Θύρα υπηρεσίας HTTP
+http_port_helper=Αριθμός θύρας στην οποία θα «ακούει» η web υπηρεσία του Forgejo. Αφήστε κενό για να την απενεργοποιήσετε.
+app_url=Βασικό URL
app_url_helper=Βασική Διεύθυνση για τα URL κλωνοποίησης μέσω HTTP(S) και για τις ειδοποιήσεις μέσω email.
-log_root_path=Διαδρομή Αρχείων Καταγραφής
+log_root_path=Τοποθεσία αρχείων καταγραφής
log_root_path_helper=Τα αρχεία καταγραφής θα γράφονται σε αυτόν τον κατάλογο.
-optional_title=Προαιρετικές Ρυθμίσεις
-email_title=Ρυθμίσεις Email
+optional_title=Προαιρετικές ρυθμίσεις
+email_title=Ρυθμίσεις email
smtp_addr=Διακομιστής SMTP
smtp_port=Θύρα SMTP
-smtp_from=Αποστολή Email Ως
+smtp_from=Αποστολή email ως
smtp_from_helper=Η διεύθυνση email που θα χρησιμοποιεί το Forgejo. Εισάγετε μια απλή διεύθυνση ηλεκτρονικού ταχυδρομείου ή χρησιμοποιήστε τη μορφή "Όνομα" .
-mailer_user=Όνομα Χρήστη SMTP
+mailer_user=Όνομα χρήστη SMTP
mailer_password=Κωδικός SMTP
-register_confirm=Απαιτείται Επιβεβαίωση της Διεύθυνσης Εmail για Εγγραφή
-mail_notify=Ενεργοποίηση Ειδοποιήσεων με Email
-server_service_title=Ρυθμίσεις Διακομιστή και Υπηρεσιών Τρίτων
-offline_mode=Ενεργοποίηση Τοπικής Λειτουργίας
+register_confirm=Να απαιτείται η επιβεβαίωση της διεύθυνσης email για την δημιουργία λογαριασμού
+mail_notify=Ενεργοποίηση ειδοποιήσεων email
+server_service_title=Ρυθμίσεις διακομιστή και υπηρεσιών τρίτων
+offline_mode=Ενεργοποίηση τοπικής λειτουργίας
offline_mode_popup=Απενεργοποιήση των δικτύων διανομής περιεχομένου τρίτων και σερβίρετε όλων των πόρων τοπικά.
disable_gravatar=Απενεργοποίηση Gravatar
disable_gravatar_popup=Απενεργοποιήση του Gravatar και των εξωτερικών πηγών avatar. Θα χρησιμοποιηθεί ένα προεπιλεγμένο avatar εκτός αν ένας χρήστης ανεβάσει τοπικά ένα avatar.
-federated_avatar_lookup=Ενεργοποίηση Ομόσπονδων Avatars
+federated_avatar_lookup=Ενεργοποίηση αποκεντρωμένων avatars
federated_avatar_lookup_popup=Ενεργοποίηση ομόσπονδης αναζήτησης avatar χρησιμοποιώντας το Libravatar.
-disable_registration=Απενεργοποίηση Αυτοεγγραφής
+disable_registration=Απενεργοποίηση αυτοεγγραφής
disable_registration_popup=Απενεργοποίηση αυτοεγγραφής χρήστη. Μόνο οι διαχειριστές θα μπορούν να δημιουργήσουν νέους λογαριασμούς χρηστών.
-allow_only_external_registration_popup=Να Επιτρέπεται Η Εγγραφή Μόνο Μέσω Εξωτερικών Υπηρεσιών
-openid_signin=Ενεργοποίηση Σύνδεσης μέσω OpenID
+allow_only_external_registration_popup=Να επιτρέπεται η εγγραφή μόνο μέσω εξωτερικών υπηρεσιών
+openid_signin=Ενεργοποίηση σύνδεσης μέσω OpenID
openid_signin_popup=Ενεργοποίηση σύνδεσης χρήστη μέσω OpenID.
-openid_signup=Ενεργοποίηση Ιδιοεγγραφής μέσω OpenID
+openid_signup=Ενεργοποίηση εγγραφών μέσω OpenID
openid_signup_popup=Ενεργοποίηση ιδιοεγγραφής χρηστών με βάση το OpenID.
enable_captcha=Ενεργοποίηση CAPTCHA στην εγγραφή
-enable_captcha_popup=Απαιτείται ένα CAPTCHA για τη ιδιοεγγραφή του χρήστη.
-require_sign_in_view=Απαιτείται Είσοδος για τη Προβολή Σελίδων
+enable_captcha_popup=Να απαιτείται ένα CAPTCHA για τη εγγραφή του χρήστη.
+require_sign_in_view=Να απαιτείται είσοδος για την προβολή σελίδων
require_sign_in_view_popup=Περιορισμός της πρόσβασης σε συνδεδεμένους χρήστες. Οι επισκέπτες θα μπορούν μόνο να δουν τις σελίδες εισόδου και εγγραφής.
admin_setting_desc=Η δημιουργία ενός λογαριασμού διαχειριστή είναι προαιρετική. Ο πρώτος εγγεγραμμένος χρήστης θα γίνει αυτόματα διαχειριστής.
-admin_title=Ρυθμίσεις Λογαριασμού Διαχειριστή
-admin_name=Όνομα Χρήστη Διαχειριστή
+admin_title=Ρυθμίσεις λογαριασμού διαχειριστή
+admin_name=Όνομα χρήστη διαχειριστή
admin_password=Κωδικός Πρόσβασης
-confirm_password=Επιβεβαίωση Κωδικού Πρόσβασης
-admin_email=Διεύθυνση Email
+confirm_password=Επιβεβαίωση κωδικού πρόσβασης
+admin_email=Διεύθυνση email
install_btn_confirm=Εγκατάσταση Forgejo
-test_git_failed=Αδυναμία δοκιμής της εντολής 'git': %v
-sqlite3_not_available=Αυτή η έκδοση Forgejo δεν υποστηρίζει την SQLite3. Παρακαλώ κατεβάστε την επίσημη δυαδική έκδοση από το %s (όχι την έκδοση 'gobuild').
+test_git_failed=Αδυναμία δοκιμής της εντολής «git»: %v
+sqlite3_not_available=Αυτή η έκδοση του Forgejo δεν υποστηρίζει την SQLite3. Παρακαλώ κατεβάστε την επίσημη έκδοση από το %s (όχι την έκδοση «gobuild»).
invalid_db_setting=Οι ρυθμίσεις της βάσης δεδομένων δεν είναι έγκυρες: %v
-invalid_db_table=Ο πίνακας βάσης δεδομένων "%s" δεν είναι έγκυρος: %v
+invalid_db_table=Ο πίνακας βάσης δεδομένων «%s» δεν είναι έγκυρος: %v
invalid_repo_path=Η αρχική διαδρομή των αποθετηρίων δεν είναι έγκυρη: %v
invalid_app_data_path=Η διαδρομή δεδομένων εφαρμογής (app data) δεν είναι έγκυρη: %v
-run_user_not_match=Το όνομα χρήστη 'εκτέλεση ως' δεν είναι το τρέχον όνομα χρήστη: %s -> %s
+run_user_not_match=Το όνομα χρήστη «Εκτέλεση ως» δεν είναι το τρέχον όνομα χρήστη: %s -> %s
internal_token_failed=Αποτυχία δημιουργίας εσωτερικού διακριτικού: %v
secret_key_failed=Αποτυχία δημιουργίας μυστικού κλειδιού: %v
save_config_failed=Αποτυχία αποθήκευσης ρυθμίσεων: %v
invalid_admin_setting=Η ρύθμιση λογαριασμού διαχειριστή δεν είναι έγκυρη: %v
-invalid_log_root_path=Η διαδρομή της καταγραφής δεν είναι έγκυρη: %v
+invalid_log_root_path=Η τοποθεσία αρχείων καταγραφής δεν είναι έγκυρη: %v
default_keep_email_private=Απόκρυψη διευθύνσεων email από προεπιλογή
default_keep_email_private_popup=Απόκρυψη διευθύνσεων email των νέων λογαριασμών χρήστη σαν προεπιλογή.
-default_allow_create_organization=Να επιτρέπεται η δημιουργία οργανισμών σαν προεπιλογή
+default_allow_create_organization=Να επιτρέπεται η δημιουργία οργανισμών από προεπιλογή
default_allow_create_organization_popup=Επιτρέψτε σε νέους λογαριασμούς χρηστών να δημιουργούν οργανισμούς σαν προεπιλογή.
-default_enable_timetracking=Ενεργοποίηση Καταγραφής Χρόνου σαν Προεπιλογή
+default_enable_timetracking=Ενεργοποίηση καταγραφής χρόνου από προεπιλογή
default_enable_timetracking_popup=Ενεργοποίηση καταγραφής χρόνου για νέα αποθετήρια σαν προεπιλογή.
-no_reply_address=Κρυφό Όνομα Τομέα Email
-no_reply_address_helper=Όνομα τομέα για χρήστες με μια κρυφή διεύθυνση email. Για παράδειγμα, το όνομα χρήστη 'nikos' θα συνδεθεί στο Git ως 'nikos@noreply.example.org' αν ο κρυφός τομέας email έχει οριστεί ως 'noreply.example.org'.
-password_algorithm=Αλγόριθμος Hash Κωδικού Πρόσβασης
+no_reply_address=Domain κρυφών email
+no_reply_address_helper=Όνομα τομέα (domain) για χρήστες με μια κρυφή διεύθυνση email. Για παράδειγμα, το όνομα χρήστη 'nikos' θα συνδεθεί στο Git ως 'nikos@noreply.example.org' αν ο κρυφός τομέας email έχει οριστεί ως 'noreply.example.org'.
+password_algorithm=Αλγόριθμος hash κωδικών πρόσβασης
invalid_password_algorithm=Μη έγκυρος αλγόριθμος κωδικού πρόσβασης
password_algorithm_helper=Ορίστε τον αλγόριθμο κατακερματισμού για το κωδικό πρόσβασης. Οι αλγόριθμοι διαφέρουν σε απαιτήσεις και αντοχή. Ο αλγόριθμος argon2 είναι αρκετά ασφαλής, αλλά χρησιμοποιεί πολλή μνήμη και μπορεί να είναι ακατάλληλος για μικρά συστήματα.
-enable_update_checker=Ενεργοποίηση Ελεγκτή Ενημερώσεων
+enable_update_checker=Ενεργοποίηση ελέγχου ενημερώσεων
enable_update_checker_helper=Ελέγχει περιοδικά για νέες εκδόσεις κάνοντας σύνδεση στο gitea.io.
env_config_keys=Ρυθμίσεις Περιβάλλοντος
env_config_keys_prompt=Οι ακόλουθες μεταβλητές περιβάλλοντος θα εφαρμοστούν επίσης στο αρχείο ρυθμίσεων σας:
+allow_dots_in_usernames = Επιτρέπει την χρήση τελείων σε ονόματα χρηστών. Δεν θα επηρεαστούν λογαριασμοί που ήδη υπάρχουν.
+enable_update_checker_helper_forgejo = Θα γίνεται τακτικά έλεγχος για νέες εκδόσεις του Forgejo ελέγχοντας μία εγγραφή DNS TXT στο release.forgejo.org.
+smtp_from_invalid = Η διεύθυνση του πεδίου «Αποστολή email ως» δεν είναι έγκυρη.
[home]
-uname_holder=Όνομα Χρήστη ή Διεύθυνση Email
+uname_holder=Όνομα χρήστη ή διεύθυνση email
password_holder=Κωδικός Πρόσβασης
switch_dashboard_context=Εναλλαγή Περιεχομένων Αρχικού Πίνακα
my_repos=Αποθετήρια
@@ -318,7 +331,7 @@ view_home=Προβολή %s
search_repos=Βρείτε ένα αποθετήριο…
filter=Άλλα Φίλτρα
filter_by_team_repositories=Φιλτράρισμα ανά αποθετήρια ομάδας
-feed_of=`Τροφοδοσία του "%s"`
+feed_of=Ροή (feed) του «%s»
show_archived=Αρχειοθετήθηκε
show_both_archived_unarchived=Εμφάνιση και αρχειοθετημένων και μη αρχειοθετημένων
@@ -340,52 +353,52 @@ search=Αναζήτηση
go_to=Μετάβαση σε
code=Κώδικας
search.type.tooltip=Τύπος αναζήτησης
-search.fuzzy=Fuzzy
+search.fuzzy=Όμοιο
search.fuzzy.tooltip=Συμπερίληψη και των αποτελεσμάτων που είναι πλησιέστερα με τον όρο αναζήτησης
search.match=Ταίριασμα
search.match.tooltip=Συμπερίληψη μόνο των αποτελεσμάτων που ταιριάζουν ακριβώς με τον όρο αναζήτησης
code_search_unavailable=Η αναζήτηση κώδικα δεν είναι διαθέσιμη αυτή τη στιγμή. Παρακαλώ επικοινωνήστε με το διαχειριστή.
-repo_no_results=Δεν βρέθηκαν αποθετήρια που να ταιρίαζουν με τα κριτήρια.
+repo_no_results=Δεν βρέθηκαν σχετικά αποθετήρια.
user_no_results=Δεν βρέθηκαν χρήστες που να ταιριάζουν με τα κριτήρια.
org_no_results=Δεν βρέθηκαν οργανισμοί που να ταιριάζουν με τα κριτήρια.
code_no_results=Δεν βρέθηκε πηγαίος κώδικας που να ταιριάζει με τον όρο αναζήτησης.
-code_search_results=`Αποτελέσματα αναζήτησης για "%s"`
-code_last_indexed_at=Τελευταίο δημιουργία ευρετηρίου στις %s
+code_search_results=`Αποτελέσματα για «%s»`
+code_last_indexed_at=Τελευταία δημιουργία ευρετηρίου: %s
relevant_repositories_tooltip=Τα αποθετήρια που είναι forks ή που δεν έχουν θέμα, εικονίδιο και περιγραφή είναι κρυμμένα.
-relevant_repositories=Εμφανίζονται μόνο τα σχετικά αποθετήρια, εμφάνιση χωρίς φίλτρο .
+relevant_repositories=Εμφανίζονται μόνο αποθετήρια που θεωρούμε «σχετικά» για εσάς. Εμφάνιση αποτελεσμάτων χωρίς φίλτρο .
[auth]
-create_new_account=Εγγραφή Λογαριασμού
+create_new_account=Δημιουργία Λογαριασμού
register_helper_msg=Έχετε ήδη λογαριασμό; Συνδεθείτε τώρα!
social_register_helper_msg=Έχετε ήδη λογαριασμό; Συνδέστε το τώρα!
-disable_register_prompt=Η εγγραφή είναι απενεργοποιημένη. Παρακαλούμε επικοινωνήστε με το διαχειριστή του ιστοτόπου.
-disable_register_mail=Η Επιβεβαίωση email για την εγγραφή είναι απενεργοποιημένη.
+disable_register_prompt=Οι εγγραφές είναι απενεργοποιημένες. Παρακαλούμε να επικοινωνήσετε με το διαχειριστή του ιστοτόπου.
+disable_register_mail=Η επιβεβαίωση email κατά την εγγραφή είναι απενεργοποιημένη.
manual_activation_only=Επικοινωνήστε με το διαχειριστή της υπηρεσίας για να ολοκληρώσετε την ενεργοποίηση.
-remember_me=Απομνημόνευση αυτής της συσκευής
-remember_me.compromised=Το διακριτικό σύνδεσης δεν είναι πλέον έγκυρο, αυτό ίσως υποδεικνύει έναν κλεμμένο λογαριασμό. Παρακαλώ ελέγξτε το λογαριασμό σας για ασυνήθιστες δραστηριότητες.
+remember_me=Θέλω να παραμείνω συνδεδεμένος
+remember_me.compromised=Το διακριτικό σύνδεσης δεν είναι πλέον έγκυρο, αυτό ίσως υποδεικνύει έναν κλεμμένο λογαριασμό. Παρακαλώ ελέγξτε τον λογαριασμό σας για ασυνήθιστες δραστηριότητες.
forgot_password_title=Ξέχασα Τον Κωδικό Πρόσβασης
forgot_password=Ξεχάσατε τον κωδικό πρόσβασης;
sign_up_now=Χρειάζεστε λογαριασμό; Εγγραφείτε τώρα.
sign_up_successful=Ο λογαριασμός δημιουργήθηκε επιτυχώς. Καλώς ορίσατε!
-confirmation_mail_sent_prompt=Ένα νέο email επιβεβαίωσης έχει σταλεί στο %s . Παρακαλώ ελέγξτε τα εισερχόμενα σας μέσα στις επόμενες %s για να ολοκληρώσετε τη διαδικασία εγγραφής.
+confirmation_mail_sent_prompt=Ένα νέο email επιβεβαίωσης έχει σταλεί στην διεύθυνση %s . Για την ολοκλήρωση της εγγραφής σας, παρακαλώ ελέγξτε τα εισερχόμενα σας μέσα στις επόμενες %s.
must_change_password=Ενημερώστε τον κωδικό πρόσβασης σας
allow_password_change=Απαιτείται από το χρήστη να αλλάξει τον κωδικό πρόσβασης (συνιστόμενο)
-reset_password_mail_sent_prompt=Ένα email επιβεβαίωσης έχει σταλεί στο %s . Παρακαλώ ελέγξτε τα εισερχόμενα σας στις επόμενες %s για να ολοκληρώσετε τη διαδικασία ανάκτησης λογαριασμού.
+reset_password_mail_sent_prompt=Ένα email επιβεβαίωσης έχει σταλεί στο %s . Για να την ολοκλήρωση της ανάκτησης του λογαριασμού σας, παρακαλώ ελέγξτε τα εισερχόμενα σας στις επόμενες %s.
active_your_account=Ενεργοποιήστε Το Λογαριασμό Σας
account_activated=Ο λογαριασμός έχει ενεργοποιηθεί
prohibit_login=Απαγορεύεται η Σύνδεση
-prohibit_login_desc=Ο λογαριασμός σας δεν επιτρέπεται να συνδεθεί, παρακαλούμε επικοινωνήστε με το διαχειριστή σας.
+prohibit_login_desc=Δεν επιτρέπεται η σύνδεση στον λογαριασμό σας, παρακαλούμε επικοινωνήστε με το διαχειριστή σας.
resent_limit_prompt=Έχετε ήδη ζητήσει ένα email ενεργοποίησης πρόσφατα. Παρακαλώ περιμένετε 3 λεπτά και προσπαθήστε ξανά.
-has_unconfirmed_mail=Γεια σας %s, έχετε μια ανεπιβεβαίωτη διεύθυνση ηλεκτρονικού ταχυδρομείου (%s ). Εάν δεν έχετε λάβει email επιβεβαίωσης ή χρειάζεται να αποστείλετε εκ νέου ένα νέο, παρακαλώ κάντε κλικ στο παρακάτω κουμπί.
+has_unconfirmed_mail=Γειά %s, η διεύθυνση ηλεκτρονικού ταχυδρομείου σας (%s ) δεν έχει επιβεβαιωθεί ακόμα. Εάν δεν έχετε λάβει κάποιο email επιβεβαίωσης ή αν χρειάζεστε ένα νέο email επιβεβαίωσης, παρακαλώ πατήστε το παρακάτω κουμπί.
resend_mail=Κάντε κλικ εδώ για να στείλετε ξανά το email ενεργοποίησης
email_not_associate=Η διεύθυνση ηλεκτρονικού ταχυδρομείου δεν είναι συσχετισμένη με κάποιο λογαριασμό.
send_reset_mail=Αποστολή Email Ανάκτησης Λογαριασμού
reset_password=Ανάκτηση Λογαριασμού
invalid_code=Ο κωδικός επιβεβαίωσης δεν είναι έγκυρος ή έχει λήξει.
-invalid_code_forgot_password=Ο κωδικός επιβεβαίωσης δεν είναι έγκυρος ή έληξε. Πατήστε εδώ για να ξεκινήσετε νέα συνεδρία.
+invalid_code_forgot_password=Ο κωδικός επιβεβαίωσης έχει λήξει ή δεν είναι έγκυρος. Πατήστε εδώ για να ξεκινήσετε νέα συνεδρία.
invalid_password=Ο κωδικός πρόσβασης σας δεν ταιριάζει με τον κωδικό που χρησιμοποιήθηκε για τη δημιουργία του λογαριασμού.
reset_password_helper=Ανάκτηση Λογαριασμού
-reset_password_wrong_user=Έχετε συνδεθεί ως %s, αλλά ο σύνδεσμος ανάκτησης λογαριασμού προορίζεται για το %s
+reset_password_wrong_user=Έχετε συνδεθεί με τον λογαριασμό %s, αλλά ο σύνδεσμος ανάκτησης λογαριασμού προορίζεται για τον λογαριασμό %s
password_too_short=Το μήκος του κωδικού πρόσβασης δεν μπορεί να είναι μικρότερο από %d χαρακτήρες.
non_local_account=Οι μη τοπικοί χρήστες δεν μπορούν να ενημερώσουν τον κωδικό πρόσβασής τους μέσω του διεπαφής web του Forgejo.
verify=Επαλήθευση
@@ -396,7 +409,7 @@ twofa_passcode_incorrect=Ο κωδικός σας είναι εσφαλμένο
twofa_scratch_token_incorrect=Ο κωδικός μιας χρήσης είναι εσφαλμένος.
login_userpass=Είσοδος
login_openid=OpenID
-oauth_signup_tab=Εγγραφή Νέου Λογαριασμού
+oauth_signup_tab=Δημιουργία νέου λογαριασμού
oauth_signup_title=Ολοκλήρωση Νέου Λογαριασμού
oauth_signup_submit=Ολοκληρωμένος Λογαριασμός
oauth_signin_tab=Σύνδεση με υπάρχων λογαριασμό
@@ -410,7 +423,7 @@ openid_connect_title=Σύνδεση σε υπάρχων λογαριασμό
openid_connect_desc=Το επιλεγμένο OpenID URI είναι άγνωστο. Συνδέστε το με ένα νέο λογαριασμό εδώ.
openid_register_title=Δημιουργία νέου λογαριασμού
openid_register_desc=Το επιλεγμένο OpenID URI είναι άγνωστο. Συνδέστε το με ένα νέο λογαριασμό εδώ.
-openid_signin_desc=Εισάγετε το OpenID URI σας. Για παράδειγμα: alice.openid.example.org ή https://openid.example.org/alice.
+openid_signin_desc=Εισάγετε το OpenID URI σας. Για παράδειγμα: ioanna.openid.example.org ή https://openid.example.org/grigoris.
disable_forgot_password_mail=Η ανάκτηση λογαριασμού είναι απενεργοποιημένη επειδή δεν έχει οριστεί email. Παρακαλούμε επικοινωνήστε με το διαχειριστή.
disable_forgot_password_mail_admin=Η ανάκτηση λογαριασμού είναι διαθέσιμη μόνο όταν έχει οριστεί το email. Παρακαλούμε ορίστει το email σας για να ενεργοποιήσετε την ανάκτηση λογαριασμού.
email_domain_blacklisted=Δεν μπορείτε να εγγραφείτε με τη διεύθυνση email σας.
@@ -418,39 +431,43 @@ authorize_application=Εξουσιοδότηση Εφαρμογής
authorize_redirect_notice=Θα μεταφερθείτε στο %s εάν εξουσιοδοτήσετε αυτήν την εφαρμογή.
authorize_application_created_by=Αυτή η εφαρμογή δημιουργήθηκε από %s.
authorize_application_description=Εάν παραχωρήσετε την πρόσβαση, θα μπορεί να έχει πρόσβαση και να γράφει σε όλες τις πληροφορίες του λογαριασμού σας, συμπεριλαμβανομένων των ιδιωτικών αποθετηρίων και οργανισμών.
-authorize_title=Εξουσιοδότηση του "%s" για έχει πρόσβαση στο λογαριασμό σας;
+authorize_title=Είστε βέβαιοι πως θέλετε να δώσετε πρόσβαση στον λογαριασμό σας στην εφαρμογή «%s»;
authorization_failed=Αποτυχία εξουσιοδότησης
authorization_failed_desc=Η εξουσιοδότηση απέτυχε επειδή εντοπίστηκε μια μη έγκυρη αίτηση. Παρακαλούμε επικοινωνήστε με το συντηρητή της εφαρμογής που προσπαθήσατε να εξουσιοδοτήσετε.
sspi_auth_failed=Αποτυχία ταυτοποίησης SSPI
-password_pwned=Ο κωδικός πρόσβασης που επιλέξατε είναι σε μια λίστα κλεμμένων κωδικών πρόσβασης που προηγουμένως εκτέθηκαν σε παραβίαση δημόσιων δεδομένων. Παρακαλώ δοκιμάστε ξανά με διαφορετικό κωδικό πρόσβασης και σκεφτείτε να αλλάξετε αυτόν τον κωδικό πρόσβασης όπου αλλού χρησιμοποιείται.
+password_pwned=Ο κωδικός πρόσβασης που επιλέξατε βρίσκεται σε μια λίστα κλεμμένων κωδικών πρόσβασης που προηγουμένως εκτέθηκαν σε παραβίαση δημόσιων δεδομένων. Παρακαλώ δοκιμάστε ξανά με διαφορετικό κωδικό πρόσβασης και σκεφτείτε να αλλάξετε αυτόν τον κωδικό πρόσβασης όπου αλλού χρησιμοποιείται.
password_pwned_err=Δεν ήταν δυνατή η ολοκλήρωση του αιτήματος προς το HaveIBeenPwned
+change_unconfirmed_email_error = Δεν ήταν δυνατή η αλλαγή της διεύθυνσης email: %v
+last_admin = Δεν μπορείτε να αφαιρέσετε τον μοναδικό διαχειριστή. Πρέπει να υπάρχει τουλάχιστον ένας διαχειριστής.
+change_unconfirmed_email = Αν έχετε εισάγει μία λανθασμένη διεύθυνση email κατά την εγγραφή σας, μπορείτε να την αλλάξετε παρακάτω, έτσι ώστε να σας σταλεί εκ νέου ένα νέο email επιβεβαίωσης στην νέα σας διεύθυνση.
+change_unconfirmed_email_summary = Αλλαγή της διεύθυνσης email στην οποία θα σταλεί το email επιβεβαίωσης.
[mail]
view_it_on=Δείτε το στο %s
reply=ή απαντήστε απευθείας σε αυτό το email
-link_not_working_do_paste=Δε λειτουργεί; Δοκιμάστε να το αντιγράψετε στο browser σας.
-hi_user_x=Γειά σου %s ,
+link_not_working_do_paste=Δεν δουλεύει το link; Δοκιμάστε να το αντιγράψετε στην μπάρα διεύθυνσης (URL bar) του browser σας.
+hi_user_x=Γειά %s ,
activate_account=Παρακαλώ ενεργοποιήστε το λογαριασμό σας
-activate_account.title=%s, παρακαλώ ενεργοποιήστε το λογαριασμό σας
+activate_account.title=%s, παρακαλώ ενεργοποιήστε τον λογαριασμό σας
activate_account.text_1=Γεια σας %[1]s , ευχαριστούμε για την εγγραφή στο %[2]s!
-activate_account.text_2=Παρακαλούμε κάντε κλικ στον παρακάτω σύνδεσμο για να ενεργοποιήσετε το λογαριασμό σας μέσα σε %s :
+activate_account.text_2=Για να ενεργοποιήσετε τον λογαριασμό σας, παρακαλώ πατήστε τον σύνδεσμο που ακολουθεί μέσα σε %s :
activate_email=Επιβεβαιώστε τη διεύθυνση email σας
-activate_email.title=%s, παρακαλώ επαληθεύστε τη διεύθυνση email σας
-activate_email.text=Παρακαλώ κάντε κλικ στον παρακάτω σύνδεσμο για να επαληθεύσετε τη διεύθυνση email σας στο %s :
+activate_email.title=%s, επαληθεύστε τη διεύθυνση email σας
+activate_email.text=Για να επαληθεύσετε τη διεύθυνση email σας, παρακαλώ πατήστε τον ακόλουθο σύνδεσμο μέσα σε %s :
register_notify=Καλώς ήλθατε στο Forgejo
register_notify.title=%[1]s, καλώς ήρθατε στο %[2]s
-register_notify.text_1=αυτό είναι το email επιβεβαίωσης εγγραφής για το %s!
-register_notify.text_2=Τώρα μπορείτε να συνδεθείτε μέσω του ονόματος χρήστη: %s.
-register_notify.text_3=Εάν αυτός ο λογαριασμός έχει δημιουργηθεί για εσάς, παρακαλώ ορίστε πρώτα τον κωδικό πρόσβασής σας .
+register_notify.text_1=αυτό είναι το email επιβεβαίωσης εγγραφής σας για το %s!
+register_notify.text_2=Μπορείτε να συνδεθείτε χρησιμοποιώντας το όνομα χρήστη σας: %s
+register_notify.text_3=Εάν κάποιος άλλος δημιούργησε τον λογαριασμό για εσάς, παρακαλώ ορίστε πρώτα τον κωδικό πρόσβασής σας .
reset_password=Ανάκτηση του λογαριασμού σας
-reset_password.title=%s, ζητήσατε να ανακτήσετε το λογαριασμό σας
-reset_password.text=Κάντε κλικ στον παρακάτω σύνδεσμο για να ανακτήσετε το λογαριασμό σας εντός %s :
+reset_password.title=%s, λάβαμε ένα αίτημα ανάκτησης για τον λογαριασμό σας
+reset_password.text=Εφόσον το αίτημα δημιουργήθηκε από εσάς, πατήστε τον ακόλουθο σύνδεσμο για να ανακτήσετε το λογαριασμό σας μέσα σε %s :
-register_success=Επιτυχής εγγραφή
+register_success=Η εγγραφή ολοκληρώθηκε επιτυχώς
issue_assigned.pull=@%[1]s σας έχει αναθέσει στο pull request %[2]s στο αποθετήριο %[3]s.
issue_assigned.issue=@%[1]s σας ανέθεσε το ζήτημα %[2]s στο αποθετήριο %[3]s.
@@ -478,18 +495,21 @@ release.downloads=Λήψεις:
release.download.zip=Πηγαίος Κώδικας (Zip)
release.download.targz=Πηγαίος Κώδικας (TAR.GZ)
-repo.transfer.subject_to=%s θα ήθελε να μεταφέρει το "%s" σε %s
-repo.transfer.subject_to_you=`%s θα ήθελε να σας μεταφέρει το "%s"`
+repo.transfer.subject_to=%s θα ήθελε να μεταφέρει το «%s» σε %s
+repo.transfer.subject_to_you=Ο χρήστης %s θέλει να σας μεταφέρει το αποθετήριο «%s»
repo.transfer.to_you=εσάς
-repo.transfer.body=Για να το αποδεχτείτε ή να το απορρίψετε, επισκεφθείτε το %s ή απλά αγνοήστε το.
+repo.transfer.body=Για να αποδεχτείτε ή να απορρίψετε το αίτημα αυτό, επισκεφθείτε το %s ή απλά αγνοήστε το.
-repo.collaborator.added.subject=%s σας πρόσθεσε στο %s
-repo.collaborator.added.text=Έχετε προστεθεί ως συνεργάτης του αποθετηρίου:
+repo.collaborator.added.subject=Ο χρήστης %s σας πρόσθεσε στο %s ως συντελεστή
+repo.collaborator.added.text=Είστε πλέον συντελεστής του ακόλουθου αποθετηρίου:
team_invite.subject=%[1]s σας προσκάλεσε να συμμετέχετε στον οργανισμό %[2]s
team_invite.text_1=%[1]s σας προσκάλεσε να συμμετέχετε στην ομάδα %[2]s στον οργανισμός %[3]s.
team_invite.text_2=Παρακαλώ κάντε κλικ στον παρακάτω σύνδεσμο για να συμμετάσχετε στην ομάδα:
team_invite.text_3=Σημείωση: Αυτή η πρόσκληση προοριζόταν για %[1]s. Αν δεν περιμένατε αυτή την πρόσκληση, μπορείτε να αγνοήσετε αυτό το email.
+admin.new_user.text = Παρακαλώ πατήστε εδώ για να διαχειριστείτε τον χρήστη μέσω του πίνακα διαχειριστών.
+admin.new_user.subject = Εγγραφή νέου χρήστη %s
+admin.new_user.user_info = Πληροφορίες Χρήστη
[modal]
yes=Ναι
@@ -522,18 +542,18 @@ SSPISeparatorReplacement=Διαχωριστικό
SSPIDefaultLanguage=Προεπιλεγμένη Γλώσσα
require_error=` δεν μπορεί να είναι κενό.`
-alpha_dash_error=` πρέπει να περιέχει μόνο αλφαριθμητικά, παύλες ('-') και κάτω παύλες ('_').`
-alpha_dash_dot_error=` πρέπει να περιέχει μόνο αλφαριθμητικά, παύλα ('-'), κάτω παύλα ('_') και τελείες ('.').`
+alpha_dash_error=`: Πρέπει να περιέχει μόνο αλφαριθμητικά, παύλες ('-') και κάτω παύλες ('_').`
+alpha_dash_dot_error=`: Πρέπει να περιέχει μόνο αλφαριθμητικά, παύλα ('-'), κάτω παύλα ('_') και τελείες ('.').`
git_ref_name_error=` πρέπει να είναι ένα καλά διαμορφωμένο όνομα αναφοράς Git.`
size_error=`πρέπει να έχει μέγεθος %s.`
min_size_error=` πρέπει να περιέχει τουλάχιστον %s χαρακτήρες.`
max_size_error=` πρέπει να περιέχει το πολύ %s χαρακτήρες.`
email_error=` δεν είναι έγκυρη διεύθυνση email.`
-url_error=`Το "%s" δεν είναι ένα έγκυρο URL.`
-include_error=` πρέπει να περιέχει το μέρος "%s".`
+url_error=`Το «%s» δεν είναι ένα έγκυρο URL.`
+include_error=` πρέπει να περιέχει το μέρος «%s».`
glob_pattern_error=` το μοτίβο ταιριάσματος (glob) δεν είναι έγκυρο: %s.`
regex_pattern_error=` το μοτίβο regex δεν είναι έγκυρο: %s.`
-username_error=` μπορεί να περιέχει μόνο αλφαριθμητικούς χαρακτήρες ('0-9','a-z','A-Z'), παύλα ('-'), κάτω παύλα ('_') και τελεία ('.'). Δεν μπορεί να ξεκινά ή να τελειώνει με μη αλφαριθμητικούς χαρακτήρες, επίσης απαγορεύονται οι διαδοχικοί μη αλφαριθμητικοί χαρακτήρες.`
+username_error=`: Μπορεί να περιέχει μόνο αλφαριθμητικούς χαρακτήρες ('0-9','a-z','A-Z'), παύλα ('-'), κάτω παύλα ('_') και τελεία ('.'). Δεν μπορεί να αρχίζει ή να τελειώνει με μη αλφαριθμητικούς χαρακτήρες. Επίσης δεν επιτρέπεται η χρήση διαδοχικών μη αλφαριθμητικών χαρακτήρων.`
invalid_group_team_map_error=` η αντιστοίχιση δεν είναι έγκυρη: %s`
unknown_error=Άγνωστο σφάλμα:
captcha_incorrect=Ο κωδικός CAPTCHA είναι λάθος.
@@ -556,7 +576,7 @@ team_name_been_taken=Το όνομα της ομάδας χρησιμοποιε
team_no_units_error=Να επιτρέπεται η πρόσβαση σε τουλάχιστον μία ενότητα αποθετηρίου.
email_been_used=Η διεύθυνση email χρησιμοποιείται ήδη.
email_invalid=Η διεύθυνση email δεν είναι έγκυρη.
-openid_been_used=Η διεύθυνση OpenID "%s" χρησιμοποιείται ήδη.
+openid_been_used=Η διεύθυνση OpenID «%s» χρησιμοποιείται ήδη.
username_password_incorrect=Το όνομα χρήστη ή ο κωδικός πρόσβασης δεν είναι σωστά.
password_complexity=Ο κωδικός πρόσβασης δεν περνά τις απαιτήσεις πολυπλοκότητας:
password_lowercase_one=Τουλάχιστον ένα πεζό γράμμα
@@ -569,30 +589,32 @@ enterred_invalid_owner_name=Το όνομα νέου ιδιοκτήτη δεν
enterred_invalid_password=Ο κωδικός πρόσβασης που εισάγατε είναι λάθος.
user_not_exist=Δεν υπάρχει ο χρήστης.
team_not_exist=Δεν υπάρχει η ομάδα.
-last_org_owner=Δεν μπορείτε να καταργήσετε τον τελευταίο χρήστη από την ομάδα 'ιδιοκτήτών'. Πρέπει να υπάρχει τουλάχιστον ένας ιδιοκτήτης για έναν οργανισμό.
+last_org_owner=Δεν μπορείτε να καταργήσετε τον τελευταίο χρήστη από την ομάδα ιδιοκτητών. Πρέπει να υπάρχει τουλάχιστον ένας ιδιοκτήτης στον οργανισμό.
cannot_add_org_to_team=Ένας οργανισμός δεν μπορεί να προστεθεί ως μέλος ομάδας.
duplicate_invite_to_team=Ο χρήστης είχε ήδη προσκληθεί ως μέλος της ομάδας.
-organization_leave_success=Έχετε εγκαταλείψει με επιτυχία τον οργανισμό %s.
+organization_leave_success=Η αποχώρησή σας από τον οργανισμό %s ήταν επιτυχής.
-invalid_ssh_key=Δεν είναι δυνατή η επαλήθευση του SSH κλειδιού σας: %s
-invalid_gpg_key=Δεν είναι δυνατή η επαλήθευση του GPG κλειδιού σας: %s
+invalid_ssh_key=Δεν είναι δυνατή η επαλήθευση του κλειδιού SSH σας: %s
+invalid_gpg_key=Δεν είναι δυνατή η επαλήθευση του κλειδιού GPG σας: %s
invalid_ssh_principal=Μη έγκυρη αρχή: %s
must_use_public_key=Το κλειδί που δώσατε είναι ένα ιδιωτικό κλειδί. Παρακαλώ μην ανεβάζετε το ιδιωτικό σας κλειδί οπουδήποτε. Χρησιμοποιήστε το δημόσιο κλειδί αντί αυτού.
-unable_verify_ssh_key=Αδυναμία επαλήθευσης του κλειδιού SSH, ελέγξτε το ξανά για λάθη.
+unable_verify_ssh_key=Δεν είναι δυνατή η επαλήθευση του κλειδιού SSH, ελέγξτε το ξανά για λάθη.
auth_failed=Αποτυχία ταυτοποίησης: %v
-still_own_repo=Ο λογαριασμός σας κατέχει ένα ή περισσότερα αποθετήρια, διαγράψτε ή μεταφέρετε τα πρώτα.
-still_has_org=Ο λογαριασμός σας είναι μέλος σε ένα ή περισσότερους οργανισμούς, φύγετε από αυτούς πρώτα.
-still_own_packages=Ο λογαριασμός σας κατέχει ένα ή περισσότερα πακέτα, διαγράψτε τα πρώτα.
-org_still_own_repo=Αυτός ο οργανισμός κατέχει ακόμα ένα ή περισσότερα αποθετήρια, διαγράψτε τα ή μεταφέρετε τα πρώτα.
-org_still_own_packages=Αυτός ο οργανισμός κατέχει ακόμα ένα ή περισσότερα πακέτα, διαγράψτε τα πρώτα.
+still_own_repo=Ο λογαριασμός σας κατέχει ένα ή περισσότερα αποθετήρια, πρέπει πρώτα να τα διαγράψετε ή να τα μεταφέρετε.
+still_has_org=Ο λογαριασμός σας είναι μέλος σε έναν ή και περισσότερους οργανισμούς, πρέπει πρώτα να αποχωρήσετε από αυτούς.
+still_own_packages=Ο λογαριασμός σας κατέχει ένα ή περισσότερα πακέτα, πρέπει πρώτα να τα διαγράψετε.
+org_still_own_repo=Αυτός ο οργανισμός κατέχει ακόμα ένα ή περισσότερα αποθετήρια, πρέπει πρώτα να τα διαγράψετε ή να τα μεταφέρετε.
+org_still_own_packages=Αυτός ο οργανισμός κατέχει ακόμα ένα ή περισσότερα πακέτα, πρέπει πρώτα να τα διαγράψετε.
target_branch_not_exist=Ο κλάδος προορισμού δεν υπάρχει.
+username_error_no_dots = `: Μπορεί να περιέχει μόνο αλφαριθμητικούς χαρακτήρες ('0-9','a-z','A-Z'), παύλα ('-') και κάτω παύλα ('_'). Δεν μπορεί να αρχίζει ή να τελειώνει με μη αλφαριθμητικούς χαρακτήρες. Επίσης δεν επιτρέπεται η χρήση διαδοχικών μη αλφαριθμητικών χαρακτήρων.`
+admin_cannot_delete_self = Δεν μπορείτε να διαγράψετε τον εαυτό σας όσο είστε διαχειριστής. Πρέπει πρώτα να αφαιρέσετε τα δικαιώματα διαχειριστή σας.
[user]
change_avatar=Αλλαγή του avatar σας…
-joined_on=Εγγράφηκε την %s
+joined_on=Εγγράφηκε στις %s
repositories=Αποθετήρια
activity=Δημόσια Δραστηριότητα
followers=Ακόλουθοι
@@ -608,12 +630,20 @@ user_bio=Βιογραφικό
disabled_public_activity=Αυτός ο χρήστης έχει απενεργοποιήσει τη δημόσια προβολή της δραστηριότητας.
email_visibility.limited=Η διεύθυνση email σας είναι ορατή σε όλους τους ταυτοποιημένους χρήστες
email_visibility.private=Η διεύθυνση email σας είναι ορατή μόνο σε εσάς και στους διαχειριστές
-show_on_map=Εμφάνιση της τοποθεσίας στο χάρτη
+show_on_map=Εμφάνιση τοποθεσίας στον χάρτη
settings=Ρυθμίσεις Χρήστη
-form.name_reserved=Το όνομα χρήστη "%s" είναι δεσμευμένο.
-form.name_pattern_not_allowed=Το μοτίβο "%s" δεν επιτρέπεται μέσα σε ένα όνομα χρήστη.
-form.name_chars_not_allowed=Το όνομα χρήστη "%s" περιέχει μη έγκυρους χαρακτήρες.
+form.name_reserved=Το όνομα χρήστη «%s» είναι δεσμευμένο.
+form.name_pattern_not_allowed=Το μοτίβο «%s» δεν επιτρέπεται μέσα σε ένα όνομα χρήστη.
+form.name_chars_not_allowed=Το όνομα χρήστη «%s» περιέχει μη έγκυρους χαρακτήρες.
+follow_blocked_user = Δεν μπορείτε να ακολουθήσετε τον χρήστη, επειδή τον έχετε αποκλείσει ή επειδή ο χρήστης σας έχει αποκλείσει.
+unblock = Άρση αποκλεισμού
+block = Αποκλεισμός
+block_user = Αποκλεισμός χρήστη
+block_user.detail_1 = Ο χρήστης δεν θα ακολουθεί πια τον λογαριασμό σας.
+block_user.detail_2 = Ο χρήστης δεν θα μπορεί να αλληλεπιδράσει με τα αποθετήριά σας, να δημιουργήσει ζητήματα ή να αφήσει σχόλια.
+block_user.detail_3 = Ο χρήστης δεν θα μπορέσει να σας προσθέσει στα αποθετήριά του, ενώ εσείς δεν θα μπορείτε να τον προσθέσετε στα δικά σας αποθετήρια.
+block_user.detail = Επισημαίνεται πως αν αποκλείσετε αυτόν τον χρήστη, θα προκύψουν ταυτόχρονα και άλλες ενέργειες. Μερικές από αυτές:
[settings]
profile=Προφίλ
@@ -625,7 +655,7 @@ avatar=Εικόνα
ssh_gpg_keys=Κλειδιά SSH / GPG
social=Λογαριασμοί Κοινωνικών Δικτύων
applications=Εφαρμογές
-orgs=Διαχείριση Οργανισμών
+orgs=Διαχείριση οργανισμών
repos=Αποθετήρια
delete=Διαγραφή Λογαριασμού
twofa=Έλεγχος Ταυτότητας Δύο Παραγόντων
@@ -634,7 +664,7 @@ organization=Οργανισμοί
uid=UID
webauthn=Κλειδιά Ασφαλείας
-public_profile=Δημόσιο Προφίλ
+public_profile=Δημόσιο προφίλ
biography_placeholder=Πείτε μας λίγο για τον εαυτό σας! (Μπορείτε να γράψετε με Markdown)
location_placeholder=Μοιραστείτε την κατά προσέγγιση τοποθεσία σας με άλλους
profile_desc=Ελέγξτε πώς εμφανίζεται το προφίλ σας σε άλλους χρήστες. Η κύρια διεύθυνση email σας θα χρησιμοποιηθεί για ειδοποιήσεις, ανάκτηση κωδικού πρόσβασης και λειτουργίες Git που βασίζονται στο web.
@@ -645,12 +675,12 @@ location=Τοποθεσία
update_theme=Ενημέρωση Θέματος Διεπαφής
update_profile=Ενημέρωση Προφίλ
update_language=Ενημέρωση Γλώσσας
-update_language_not_found=Η γλώσσα "%s" δεν είναι διαθέσιμη.
+update_language_not_found=Η γλώσσα «%s» δεν είναι διαθέσιμη.
update_language_success=Η γλώσσα ενημερώθηκε.
update_profile_success=Το προφίλ σας έχει ενημερωθεί.
change_username=Το όνομα χρήστη σας έχει αλλάξει.
-change_username_prompt=Σημείωση: Αλλάζοντας το όνομα χρήστη σας αλλάζει επίσης το URL του λογαριασμού σας.
-change_username_redirect_prompt=Το παλιό όνομα χρήστη θα ανακατευθύνει μέχρι να ζητηθεί ξανά.
+change_username_prompt=Επισήμανση: Η αλλαγή του ονόματος χρήστη σας θα αλλάξει και το URL του λογαριασμού σας.
+change_username_redirect_prompt=Το παλιό όνομα χρήστη θα ανακατευθύνει έως ότου χρησιμοποιηθεί ξανά.
continue=Συνέχεια
cancel=Ακύρωση
language=Γλώσσα
@@ -675,30 +705,30 @@ comment_type_group_project=Έργο
comment_type_group_issue_ref=Αναφορά ζητήματος
saved_successfully=Οι ρυθμίσεις σας αποθηκεύτηκαν επιτυχώς.
privacy=Απόρρητο
-keep_activity_private=Απόκρυψη Δραστηριότητας από τη σελίδα προφίλ
+keep_activity_private=Απόκρυψη δραστηριότητας από τη σελίδα προφίλ
keep_activity_private_popup=Με αυτή την επιλογή η δραστηριότητα σας είναι ορατή μόνο σε εσάς και τους διαχειριστές
lookup_avatar_by_mail=Αναζήτηση ενός Avatar με διεύθυνση email
federated_avatar_lookup=Συνενωμένη Αναζήτηση Avatar
-enable_custom_avatar=Χρήση Προσαρμοσμένης Εικόνας
+enable_custom_avatar=Χρήση προσαρμοσμένης εικόνας προφίλ
choose_new_avatar=Επιλέξτε νέα εικόνα
update_avatar=Ενημέρωση Εικόνας
delete_current_avatar=Διαγραφή Τρέχουσας Εικόνας
uploaded_avatar_not_a_image=Το αρχείο που ανεβάσατε δεν είναι εικόνα.
-uploaded_avatar_is_too_big=Το μέγεθος αρχείου που ανέβηκε (%d KiB) υπερβαίνει το μέγιστο μέγεθος (%d KiB).
+uploaded_avatar_is_too_big=Το μέγεθος του ανεβασμένου αρχείου (%d KiB) υπερβαίνει το μέγιστο μέγεθος (%d KiB).
update_avatar_success=Η εικόνα σας έχει ενημερωθεί.
update_user_avatar_success=Το avatar του χρήστη ενημερώθηκε.
-update_password=Ενημέρωση Κωδικού Πρόσβασης
-old_password=Τρέχων Κωδικός Πρόσβασης
-new_password=Νέος Κωδικός Πρόσβασης
-retype_new_password=Επιβεβαίωση Νέου Κωδικού Πρόσβασης
+update_password=Ενημέρωση κωδικού πρόσβασης
+old_password=Τρέχων κωδικός πρόσβασης
+new_password=Νέος κωδικός πρόσβασης
+retype_new_password=Επιβεβαίωση νέου κωδικού
password_incorrect=Ο τρέχων κωδικός πρόσβασης είναι λάθος.
change_password_success=Ο κωδικός πρόσβασής σας έχει ενημερωθεί. Από εδώ και τώρα συνδέεστε χρησιμοποιώντας τον νέο κωδικό πρόσβασής σας.
password_change_disabled=Οι μη τοπικοί χρήστες δεν μπορούν να ενημερώσουν τον κωδικό πρόσβασής τους μέσω του διεπαφής web του Forgejo.
emails=Διευθύνσεις Email
-manage_emails=Διαχείριση Διευθύνσεων Email
+manage_emails=Διαχείριση διευθύνσεων email
manage_themes=Επιλέξτε προεπιλεγμένο θέμα διεπαφής
manage_openid=Διαχείριση Διευθύνσεων OpenID
email_desc=Η κύρια διεύθυνση ηλεκτρονικού ταχυδρομείου σας θα χρησιμοποιηθεί για ειδοποιήσεις, ανάκτηση του κωδικού πρόσβασης και, εφόσον δεν είναι κρυμμένη, λειτουργίες Git στον ιστότοπο.
@@ -719,31 +749,31 @@ theme_update_error=Το επιλεγμένο θέμα διεπαφής δεν υ
openid_deletion=Αφαίρεση Διεύθυνσης OpenID
openid_deletion_desc=Η κατάργηση αυτής της διεύθυνσης OpenID από τον λογαριασμό σας θα σας εμποδίσει να συνδέεστε με αυτό. Συνέχεια;
openid_deletion_success=Η διεύθυνση OpenID αφαιρέθηκε.
-add_new_email=Προσθήκη Νέας Διεύθυνσης Email
+add_new_email=Προσθήκη νέας διεύθυνσης email
add_new_openid=Προσθήκη Νέου OpenID URI
add_email=Προσθήκη Διεύθυνσης Email
add_openid=Προσθήκη OpenID URI
-add_email_confirmation_sent=Μια επιβεβαίωση στάλθηκε στο email "%s". Ελέγξτε τα εισερχόμενα σας μέσα στα επόμενα %s για να επιβεβαιώσετε τη διεύθυνση email σας.
+add_email_confirmation_sent=Ένα email επιβεβαίωσης έχει σταλεί στην διεύθυνση «%s». Για να επιβεβαιώσετε τη διεύθυνση email σας, παρακαλώ ελέγξτε τα εισερχόμενα σας μέσα σε %s.
add_email_success=Η νέα διεύθυνση email έχει προστεθεί.
email_preference_set_success=Οι προτιμήσεις email έχουν οριστεί επιτυχώς.
add_openid_success=Προστέθηκε η νέα διεύθυνση OpenID.
-keep_email_private=Απόκρυψη Διεύθυνσης Email
+keep_email_private=Απόκρυψη διεύθυνσης email
keep_email_private_popup=Αυτό θα κρύψει τη διεύθυνση ηλεκτρονικού ταχυδρομείου σας από το προφίλ σας, καθώς και όταν κάνετε ένα pull request ή επεξεργαστείτε ένα αρχείο χρησιμοποιώντας τη διεπαφή ιστού. Οι ωθούμενες υποβολές δεν θα τροποποιηθούν. Χρησιμοποιήστε το %s στις υποβολές για να τις συσχετίσετε με το λογαριασμό σας.
openid_desc=Το OpenID σας επιτρέπει να αναθέσετε τον έλεγχο ταυτότητας σε έναν εξωτερικό πάροχο.
-manage_ssh_keys=Διαχείριση SSH Κλειδιών
+manage_ssh_keys=Διαχείριση κλειδιών SSH
manage_ssh_principals=Διαχείριση Των Αρχών Πιστοποιητικού SSH
-manage_gpg_keys=Διαχείριση Κλειδιών GPG
+manage_gpg_keys=Διαχείριση κλειδιών GPG
add_key=Προσθήκη Κλειδιού
-ssh_desc=Αυτά τα δημόσια SSH κλειδιά συνδέονται με το λογαριασμό σας. Τα αντίστοιχα ιδιωτικά κλειδιά επιτρέπουν πλήρη πρόσβαση στα αποθετήριά σας.
+ssh_desc=Αυτά τα δημόσια κλειδιά SSH θα συσχετιθούν με το λογαριασμό σας. Τα ιδιωτικά κλειδιά που τους αντιστοιχούν θα επιτρέπουν πλήρη πρόσβαση στα αποθετήριά σας. Ένα επιβεβαιωμένο κλειδί SSH μπορεί να χρησιμοποιηθεί για την υπογραφή των υποβολών (commits) σας.
principal_desc=Αυτές οι αρχές πιστοποιητικών SSH συνδέονται με το λογαριασμό σας και επιτρέπουν την πλήρη πρόσβαση στα αποθετήριά σας.
-gpg_desc=Αυτά τα δημόσια κλειδιά GPG συνδέονται με το λογαριασμό σας. Κρατήστε τα ιδιωτικά κλειδιά σας ασφαλή καθώς επιτρέπουν την επαλήθευση των υποβολών.
-ssh_helper=Χρειάζεστε βοήθεια; Συμβουλευτείτε τον οδηγό του GitHub για να δημιουργήσετε τα δικά σας SSH κλειδιά ή να επιλύσετε κοινά προβλήματα που ίσως αντιμετωπίσετε με τη χρήση του SSH.
+gpg_desc=Αυτά τα δημόσια κλειδιά GPG συσχετίζονται με το λογαριασμό σας και επιτρέπουν την επικύρωση των υποβολών (commits) σας. Κρατήστε τα ιδιωτικά κλειδιά σας ασφαλή, καθώς επιτρέπουν την υπογραφή των υποβολών (commits) εκ μέρους σας.
+ssh_helper=Χρειάζεστε βοήθεια; Συμβουλευτείτε τον οδηγό του GitHub για να δημιουργήσετε τα δικά σας κλειδιά SSH ή να επιλύσετε κοινά προβλήματα που ίσως αντιμετωπίσετε με τη χρήση του SSH.
gpg_helper=Χρειάζεστε βοήθεια; Συμβουλευτείτε τον οδηγό του GitHub για το GPG .
add_new_key=Προσθήκη SSH Κλειδιού
add_new_gpg_key=Προσθήκη GPG Κλειδιού
-key_content_ssh_placeholder=Ξεκινάει με 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'sk-ecdsa-sha2-nistp256@openssh.com', ή 'sk-ssh-ed25519@openssh.com'
-key_content_gpg_placeholder=Ξεκινά με '-----BEGIN PGP PUBLIC KEY BLOCK-----'
+key_content_ssh_placeholder=Αρχίζει με «ssh-ed25519», «ssh-rsa», «ecdsa-sha2-nistp256», «ecdsa-sha2-nistp384», «ecdsa-sha2-nistp521», «sk-ecdsa-sha2-nistp256@openssh.com», ή «sk-ssh-ed25519@openssh.com»
+key_content_gpg_placeholder=Αρχίζει με «-----BEGIN PGP PUBLIC KEY BLOCK-----»
add_new_principal=Προσθήκη Αρχής (Principal)
ssh_key_been_used=Αυτό το κλειδί SSH έχει ήδη προστεθεί στο διακομιστή.
ssh_key_name_used=Υπάρχει ήδη ένα SSH κλειδί με το ίδιο όνομα στο λογαριασμό σας.
@@ -761,8 +791,8 @@ gpg_token=Διακριτικό
gpg_token_help=Μπορείτε να δημιουργήσετε μια υπογραφή χρησιμοποιώντας:
gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
gpg_token_signature=Θωρακισμένη υπογραφή GPG
-key_signature_gpg_placeholder=Ξεκινά με '-----BEGIN PGP SIGNATURE-----'
-verify_gpg_key_success=Το κλειδί GPG "%s" επαληθεύτηκε.
+key_signature_gpg_placeholder=Αρχίζει με «-----BEGIN PGP SIGNATURE-----»
+verify_gpg_key_success=Το κλειδί GPG «%s» επαληθεύτηκε.
ssh_key_verified=Επαληθευμένο Κλειδί
ssh_key_verified_long=Το κλειδί έχει επαληθευτεί με ένα διακριτικό και μπορεί να χρησιμοποιηθεί για να επαληθεύσει τα commits που ταιριάζουν με οποιεσδήποτε ενεργοποιημένες διευθύνσεις ηλεκτρονικού ταχυδρομείου για αυτόν το χρήστη.
ssh_key_verify=Επαλήθευση
@@ -771,16 +801,16 @@ ssh_token_required=Πρέπει να δώσετε μια υπογραφή για
ssh_token=Διακριτικό
ssh_token_help=Μπορείτε να δημιουργήσετε μια υπογραφή χρησιμοποιώντας:
ssh_token_signature=Θωρακισμένη υπογραφή SSH
-key_signature_ssh_placeholder=Ξεκινά με '-----BEGIN SSH SIGNATURE-----'
-verify_ssh_key_success=Το κλειδί SSH "%s" επαληθεύτηκε.
+key_signature_ssh_placeholder=Αρχίζει με «-----BEGIN SSH SIGNATURE-----»
+verify_ssh_key_success=Το κλειδί SSH «%s» επαληθεύτηκε.
subkeys=Υποκλειδιά
key_id=ID Κλειδιού
-key_name=Όνομα Κλειδιού
+key_name=Όνομα κλειδιού
key_content=Περιεχόμενο
principal_content=Περιεχόμενο
-add_key_success=Το κλειδί SSH "%s" προστέθηκε.
-add_gpg_key_success=Το κλειδί GPG "%s" προστέθηκε.
-add_principal_success=Προστέθηκε η αρχή πιστοποίησης SSH "%s".
+add_key_success=Το κλειδί SSH «%s» προστέθηκε.
+add_gpg_key_success=Το κλειδί GPG «%s» προστέθηκε.
+add_principal_success=Προστέθηκε η αρχή πιστοποίησης SSH «%s».
delete_key=Διαγραφή
ssh_key_deletion=Διαγραφή Κλειδιού SSH
gpg_key_deletion=Διαγραφή Κλειδιού GPG
@@ -807,17 +837,17 @@ ssh_disabled=SSH Απενεργοποιημένο
ssh_signonly=Το SSH είναι απενεργοποιημένο αυτή τη στιγμή, έτσι αυτά τα κλειδιά είναι μόνο για την επαλήθευση υπογραφής των υποβολών.
ssh_externally_managed=Αυτό το κλειδί SSH διαχειρίζεται εξωτερικά για αυτόν το χρήστη
manage_social=Διαχείριση Συσχετιζόμενων Λογαριασμών Κοινωνικών Δικτύων
-social_desc=Αυτοί οι κοινωνικοί λογαριασμοί μπορούν να χρησιμοποιηθούν για να συνδεθείτε στο λογαριασμό σας. Βεβαιωθείτε ότι τους αναγνωρίζετε όλους.
+social_desc=Αυτοί οι λογαριασμοί κοινωνικών δικτύων μπορούν να χρησιμοποιηθούν για να συνδεθείτε στο λογαριασμό σας. Βεβαιωθείτε ότι τους αναγνωρίζετε όλους.
unbind=Αποσύνδεση
-unbind_success=Ο κοινωνικός λογαριασμός έχει διαγραφεί επιτυχώς.
+unbind_success=Ο λογαριασμός κοινωνικού δικτύου έχει διαγραφεί επιτυχώς.
-manage_access_token=Διαχείριση Διακριτικών Πρόσβασης
-generate_new_token=Δημιουργία Νέου Διακριτικού
+manage_access_token=Διαχείριση διακριτικών πρόσβασης (tokens)
+generate_new_token=Δημιουργία νέου διακριτικού (token)
tokens_desc=Αυτά τα διακριτικά (tokens) παρέχουν πρόσβαση στο λογαριασμό σας μέσω του API του Forgejo.
-token_name=Όνομα Διακριτικού
+token_name=Όνομα διακριτικού
generate_token=Δημιουργία Διακριτικού
generate_token_success=Το νέο διακριτικό σας έχει δημιουργηθεί. Αντιγράψτε το τώρα καθώς δεν θα εμφανιστεί ξανά.
-generate_token_name_duplicate=Το %s έχει ήδη χρησιμοποιηθεί ως όνομα εφαρμογής. Παρακαλούμε χρησιμοποιήστε ένα νέο.
+generate_token_name_duplicate=Το %s χρησιμοποιείται ήδη ως όνομα εφαρμογής. Παρακαλούμε χρησιμοποιήστε ένα νέο.
delete_token=Διαγραφή
access_token_deletion=Διαγραφή Διακριτικού Πρόσβασης
access_token_deletion_cancel_action=Άκυρο
@@ -835,7 +865,7 @@ access_token_desc=Τα επιλεγμένα δικαιώματα διακριτ
at_least_one_permission=Πρέπει να επιλέξετε τουλάχιστον ένα δικαίωμα για να δημιουργήσετε ένα διακριτικό
permissions_list=Δικαιώματα:
-manage_oauth2_applications=Διαχείριση Εφαρμογών Oauth2
+manage_oauth2_applications=Διαχείριση εφαρμογών OAuth2
edit_oauth2_application=Επεξεργασία Εφαρμογής Oauth2
oauth2_applications_desc=Οι εφαρμογές OAuth2 επιτρέπουν στην εξωτερική εφαρμογή σας την ασφαλή ταυτοποίηση των χρηστών σε αυτό το Forgejo.
remove_oauth2_application=Αφαίρεση Εφαρμογής Oauth2
@@ -843,9 +873,9 @@ remove_oauth2_application_desc=Η αφαίρεση μιας εφαρμογής O
remove_oauth2_application_success=Η εφαρμογή έχει διαγραφεί.
create_oauth2_application=Δημιουργία νέας εφαρμογής OAuth2
create_oauth2_application_button=Δημιουργία Εφαρμογής
-create_oauth2_application_success=Έχετε δημιουργήσει με επιτυχία μια νέα εφαρμογή OAuth2.
-update_oauth2_application_success=Έχετε ενημερώσει με επιτυχία την εφαρμογή OAuth2.
-oauth2_application_name=Όνομα Εφαρμογής
+create_oauth2_application_success=Δημιουργήσατε επιτυχώς μια νέα εφαρμογή OAuth2.
+update_oauth2_application_success=Ενημερώσατε την εφαρμογή OAuth2 επιτυχώς.
+oauth2_application_name=Όνομα εφαρμογής
oauth2_confidential_client=Εμπιστευτικός Πελάτης. Επιλέξτε το για εφαρμογές που διατηρούν το μυστικό κωδικό κρυφό, όπως πχ οι εφαρμογές ιστού. Μην επιλέγετε για εγγενείς εφαρμογές, συμπεριλαμβανομένων εφαρμογών επιφάνειας εργασίας και εφαρμογών για κινητά.
oauth2_redirect_uris=URI Ανακατεύθυνσης. Χρησιμοποιήστε μια νέα γραμμή για κάθε URI.
save_application=Αποθήκευση
@@ -857,13 +887,13 @@ oauth2_client_secret_hint=Το μυστικό δε θα εμφανιστεί ξ
oauth2_application_edit=Επεξεργασία
oauth2_application_create_description=Οι εφαρμογές OAuth2 δίνει πρόσβαση στην εξωτερική εφαρμογή σας σε λογαριασμούς χρηστών σε αυτή την υπηρεσία.
oauth2_application_remove_description=Αφαιρώντας μια εφαρμογή OAuth2 θα αποτραπεί η πρόσβαση αυτής, σε εξουσιοδοτημένους λογαριασμούς χρηστών σε αυτή την υπηρεσία. Συνέχεια;
-oauth2_application_locked=Το Gitea κάνει προεγγραφή σε μερικές εφαρμογές OAuth2 κατά την εκκίνηση αν είναι ενεργοποιημένες στις ρυθμίσεις. Για την αποφυγή απροσδόκητης συμπεριφοράς, αυτές δεν μπορούν ούτε να επεξεργαστούν ούτε να καταργηθούν. Παρακαλούμε ανατρέξτε στην τεκμηρίωση OAuth2 για περισσότερες πληροφορίες.
+oauth2_application_locked=Το Forgejo κάνει προεγγραφή σε μερικές εφαρμογές OAuth2 κατά την εκκίνηση αν είναι ενεργοποιημένες στις ρυθμίσεις. Για την αποφυγή απροσδόκητης συμπεριφοράς, αυτές δεν μπορούν ούτε να επεξεργαστούν ούτε να καταργηθούν. Παρακαλούμε ανατρέξτε στην τεκμηρίωση OAuth2 για περισσότερες πληροφορίες.
-authorized_oauth2_applications=Εξουσιοδοτημένες Εφαρμογές OAuth2
-authorized_oauth2_applications_description=Έχετε χορηγήσει πρόσβαση στον προσωπικό σας λογαριασμό σε αυτές τις εφαρμογές τρίτων. Ανακαλέστε την πρόσβαση για εφαρμογές που δεν χρειάζεστε πλέον.
+authorized_oauth2_applications=Εξουσιοδοτημένες εφαρμογές OAuth2
+authorized_oauth2_applications_description=Έχετε χορηγήσει πρόσβαση στον προσωπικό σας λογαριασμό σε αυτές τις εφαρμογές τρίτων. Παρακαλείσθε να ανακαλέσετε την πρόσβαση για εφαρμογές που δεν χρειάζεστε πλέον.
revoke_key=Ανάκληση
revoke_oauth2_grant=Ανάκληση Πρόσβασης
-revoke_oauth2_grant_description=Η ανάκληση πρόσβασης για αυτή την εξωτερική εφαρμογή θα αποτρέψει αυτή την εφαρμογή από την πρόσβαση στα δεδομένα σας. Σίγουρα;
+revoke_oauth2_grant_description=Αν ανακαλέσετε την πρόσβαση αυτής της ανεξάρτητης («third-party») εφαρμογής, θα ανακληθεί ταυτόχρονα και η πρόσβασή της στα δεδομένα σας. Είστε βέβαιοι;
revoke_oauth2_grant_success=Η πρόσβαση ανακλήθηκε επιτυχώς.
twofa_desc=Ο έλεγχος ταυτότητας δύο παραγόντων ενισχύει την ασφάλεια του λογαριασμού σας.
@@ -882,11 +912,11 @@ scan_this_image=Σαρώστε αυτή την εικόνα με την εφαρ
or_enter_secret=Ή εισάγετε το μυστικό: %s
then_enter_passcode=Και εισάγετε τον κωδικό που εμφανίζεται στην εφαρμογή:
passcode_invalid=Ο κωδικός είναι λάθος. Δοκιμάστε ξανά.
-twofa_enrolled=Ο λογαριασμός σας έχει εγγραφεί σε ταυτοποίηση δύο παραγόντων. Αποθηκεύστε το διακριτικό μιας χρήσης (%s) σε ασφαλές μέρος καθώς εμφανίζεται μόνο μία φορά!
+twofa_enrolled=Ο λογαριασμός σας έχει εγγραφεί σε ταυτοποίηση δύο παραγόντων. Αποθηκεύστε το διακριτικό μιας χρήσης (%s) σε ένα ασφαλές μέρος, επειδή δεν θα ξαναεμφανιστεί.
twofa_failed_get_secret=Αποτυχία λήψης μυστικού.
webauthn_desc=Τα κλειδιά ασφαλείας είναι συσκευές που περιέχουν κρυπτογραφικά κλειδιά. Μπορούν να χρησιμοποιηθούν για έλεγχο ταυτότητας δύο παραγόντων. Τα κλειδιά ασφαλείας πρέπει να υποστηρίζουν το πρότυπο WebAuthn Authn Authenticator .
-webauthn_register_key=Προσθήκη Κλειδιού Ασφαλείας
+webauthn_register_key=Προσθήκη κλειδιού ασφαλείας
webauthn_nickname=Ψευδώνυμο
webauthn_delete_key=Αφαίρεση Κλειδιού Ασφαλείας
webauthn_delete_key_desc=Αν αφαιρέσετε ένα κλειδί ασφαλείας δεν μπορείτε πλέον να συνδεθείτε με αυτό. Συνέχεια;
@@ -897,21 +927,21 @@ manage_account_links=Διαχείριση Συνδεδεμένων Λογαρι
manage_account_links_desc=Αυτοί οι εξωτερικοί λογαριασμοί είναι συνδεδεμένοι στον Forgejo λογαριασμό σας.
account_links_not_available=Προς το παρόν δεν υπάρχουν εξωτερικοί λογαριασμοί συνδεδεμένοι με τον λογαριασμό σας στο Forgejo.
link_account=Σύνδεση Λογαριασμού
-remove_account_link=Αφαίρεση Συνδεδεμένου Λογαριασμού
+remove_account_link=Αφαίρεση συνδεδεμένου λογαριασμού
remove_account_link_desc=Η κατάργηση ενός συνδεδεμένου λογαριασμού θα ανακαλέσει την πρόσβασή του στο λογαριασμό σας στο Forgejo. Συνέχεια;
remove_account_link_success=Ο συνδεδεμένος λογαριασμός έχει αφαιρεθεί.
hooks.desc=Προσθήκη webhooks που θα ενεργοποιούνται για όλα τα αποθετήρια που σας ανήκουν.
orgs_none=Δεν είστε μέλος σε κάποιο οργανισμό.
-repos_none=Δεν κατέχετε κάποιο αποθετήριο.
+repos_none=Δεν σας ανήκει κανένα κάποιο αποθετήριο.
delete_account=Διαγραφή Του Λογαριασμού Σας
delete_prompt=Αυτή η ενέργεια θα διαγράψει μόνιμα το λογαριασμό σας. ΔΕΝ ΘΑ ΜΠΟΡΕΙ να επανέλθει.
delete_with_all_comments=Ο λογαριασμός σας είναι νεότερος από %s. Για να αποφύγετε τα σχόλια φαντάσματα, όλα τα σχόλια σε ζητήματα/PR θα διαγραφούν από αυτόν.
confirm_delete_account=Επιβεβαίωση Διαγραφής
delete_account_title=Διαγραφή Λογαριασμού Χρήστη
-delete_account_desc=Είστε βέβαιοι ότι θέλετε να διαγράψετε μόνιμα αυτό τον λογαριασμό χρήστη;
+delete_account_desc=Είστε βέβαιοι ότι θέλετε να διαγράψετε μόνιμα αυτό τον λογαριασμό;
email_notifications.enable=Ενεργοποίηση Ειδοποιήσεων Μέσω Email
email_notifications.onmention=Email Μόνο κατά την Αναφορά
@@ -923,9 +953,15 @@ visibility=Ορατότητα χρήστη
visibility.public=Δημόσια
visibility.public_tooltip=Ορατό σε όλους
visibility.limited=Περιορισμένη
-visibility.limited_tooltip=Ορατό μόνο στους ταυτοποιημένους χρήστες
+visibility.limited_tooltip=Ορατό μόνο σε ταυτοποιημένους χρήστες
visibility.private=Ιδιωτική
visibility.private_tooltip=Ορατό μόνο στα μέλη των οργανισμών που συμμετέχετε
+blocked_users_none = Δεν έχετε αποκλείσει κανέναν χρήστη.
+blocked_since = Αποκλεισμένος από %s
+user_unblock_success = Η άρση αποκλεισμού του χρήστη ήταν επιτυχής.
+change_password = Αλλαγή κωδικού πρόσβασης
+blocked_users = Αποκλεισμένοι χρήστες
+user_block_success = Ο αποκλεισμός του χρήστη ήταν επιτυχής.
[repo]
new_repo_helper=Ένα αποθετήριο περιέχει όλα τα αρχεία έργου, συμπεριλαμβανομένου του ιστορικού εκδόσεων. Ήδη φιλοξενείται αλλού; Μετεγκατάσταση αποθετηρίου.
@@ -940,7 +976,7 @@ template_helper=Μετατροπή σε πρότυπο αποθετήριο
template_description=Τα πρότυπα αποθετήρια επιτρέπουν στους χρήστες να δημιουργήσουν νέα αποθετήρια με την ίδια δομή, αρχεία και προαιρετικές ρυθμίσεις.
visibility=Ορατότητα
visibility_description=Μόνο ο ιδιοκτήτης ή τα μέλη του οργανισμού εάν έχουν δικαιώματα, θα είναι σε θέση να το δουν.
-visibility_helper=Αλλάξτε το αποθετήριο σε ιδιωτικό
+visibility_helper=Αλλαγή ορατότητας σε «Ιδιωτικό»
visibility_helper_forced=Ο διαχειριστής σας αναγκάζει τα νέα αποθετήρια να είναι ιδιωτικά.
visibility_fork_helper=(Αλλάζοντας αυτό θα επηρεάσει όλα τα forks.)
clone_helper=Χρειάζεστε βοήθεια για τη κλωνοποίηση; Επισκεφθείτε τη Βοήθεια .
@@ -977,19 +1013,19 @@ trust_model_helper=Επιλέξτε ένα μοντέλο εμπιστοσύνη
trust_model_helper_collaborator=Συνεργάτης: Εμπιστοσύνη υπογραφών από συνεργάτες
trust_model_helper_committer=Υποβολέας: Εμπιστοσύνη των υπογραφών που ταιριάζουν με τους υποβολείς
trust_model_helper_collaborator_committer=Συνεργάτης+Υποβολέας: Εμπιστοσύνη των υπογραφών από συνεργάτες που ταιριάζουν με τον υποβολέα
-trust_model_helper_default=Προεπιλογή: Χρησιμοποιήστε το προεπιλεγμένο μοντέλο εμπιστοσύνης για αυτήν την εγκατάσταση
+trust_model_helper_default=Προεπιλογή: Χρήση προεπιλεγμένου μοντέλου εμπιστοσύνης για αυτήν την εγκατάσταση
create_repo=Δημιουργία Αποθετηρίου
default_branch=Προεπιλεγμένος Κλάδος
default_branch_label=προεπιλογή
default_branch_helper=Ο προεπιλεγμένος κλάδος είναι ο βασικός κλάδος για pull requests και υποβολές κώδικα.
mirror_prune=Καθαρισμός
mirror_prune_desc=Αφαίρεση παρωχημένων αναφορών απομακρυσμένης-παρακολούθησης
-mirror_interval=Διάστημα ανανέωσης ειδώλου (έγκυρες μονάδες ώρας είναι 'h', 'm', 's'). 0 για απενεργοποίηση του αυτόματου συγχρονισμού. (Ελάχιστο διάστημα: %s)
+mirror_interval=Διάστημα ανανέωσης ειδώλου (έγκυρες μονάδες ώρας είναι "h", "m", "s"). 0 για απενεργοποίηση του αυτόματου συγχρονισμού. (Ελάχιστο διάστημα: %s)
mirror_interval_invalid=Το χρονικό διάστημα του ειδώλου δεν είναι έγκυρο.
mirror_sync_on_commit=Συγχρονισμός κατά την ώθηση
mirror_address=Κλωνοποίηση Από Το URL
mirror_address_desc=Τοποθετήστε όλα τα απαιτούμενα διαπιστευτήρια στην ενότητα Εξουσιοδότηση.
-mirror_address_url_invalid=Η διεύθυνση URL που δόθηκε δεν είναι έγκυρη. Πρέπει να κάνετε escape όλα τα στοιχεία του url σωστά.
+mirror_address_url_invalid=Η παρεχόμενη διεύθυνση URL δεν είναι έγκυρη. Πρέπει να κάνετε escape όλα τα στοιχεία του url σωστά.
mirror_address_protocol_invalid=Η παρεχόμενη διεύθυνση URL δεν είναι έγκυρη. Μόνο οι τοποθεσίες http(s):// ή git:// μπορούν να χρησιμοποιηθούν για τη δημιουργία ειδώλου.
mirror_lfs=Large File Storage (LFS)
mirror_lfs_desc=Ενεργοποίηση αντικατοπτρισμού δεδομένων LFS.
@@ -1025,9 +1061,9 @@ tree_path_not_found_branch=Η διαδρομή %[1]s δεν υπάρχει στ
tree_path_not_found_tag=Η διαδρομή %[1]s δεν υπάρχει στην ετικέτα %[2]s
transfer.accept=Αποδοχή Μεταφοράς
-transfer.accept_desc=`Μεταφορά στο "%s"`
+transfer.accept_desc=`Μεταφορά στο «%s»`
transfer.reject=Απόρριψη Μεταφοράς
-transfer.reject_desc=`Ακύρωση μεταφοράς σε "%s"`
+transfer.reject_desc=`Ακύρωση μεταφοράς στο «%s»`
transfer.no_permission_to_accept=Δεν έχετε άδεια να αποδεχτείτε αυτή τη μεταφορά.
transfer.no_permission_to_reject=Δεν έχετε άδεια να απορρίψετε αυτή τη μεταφορά.
@@ -1055,11 +1091,11 @@ archive.pull.nocomment=Αυτό το repo αρχειοθετήθηκε. Δεν
form.reach_limit_of_creation_1=Έχετε ήδη συμπληρώσει το όριο του %d αποθετηρίου.
form.reach_limit_of_creation_n=Έχετε ήδη συμπληρώσει το όριο των %d αποθετηρίων.
-form.name_reserved=Το όνομα αποθετηρίου "%s" είναι δεσμευμένο.
-form.name_pattern_not_allowed=Το μοτίβο "%s" δεν επιτρέπεται στο όνομα του αποθετηρίου.
+form.name_reserved=Το όνομα αποθετηρίου «%s» είναι δεσμευμένο.
+form.name_pattern_not_allowed=Το μοτίβο «%s» δεν επιτρέπεται στο όνομα του αποθετηρίου.
need_auth=Εξουσιοδότηση
-migrate_options=Επιλογές Μεταφοράς
+migrate_options=Ρυθμίσεις μεταφοράς
migrate_service=Υπηρεσία Μεταφοράς
migrate_options_mirror_helper=Αυτό το αποθετήριο θα είναι είδωλο
migrate_options_lfs=Μεταφορά αρχείων LFS
@@ -1067,7 +1103,7 @@ migrate_options_lfs_endpoint.label=Άκρο LFS
migrate_options_lfs_endpoint.description=Η μεταφορά θα προσπαθήσει να χρησιμοποιήσει το Git remote για να καθορίσει τον διακομιστή LFS . Μπορείτε επίσης να καθορίσετε ένα δικό σας endpoint αν τα δεδομένα LFS του αποθετηρίου αποθηκεύονται κάπου αλλού.
migrate_options_lfs_endpoint.description.local=Μια διαδρομή στο τοπικό διακομιστή επίσης υποστηρίζεται.
migrate_options_lfs_endpoint.placeholder=Αν αφεθεί κενό, το άκρο θα προκύψει από το URL του κλώνου
-migrate_items=Στοιχεία Μεταφοράς
+migrate_items=Αντικείμενα μεταφοράς
migrate_items_wiki=Wiki
migrate_items_milestones=Ορόσημα
migrate_items_labels=Σήματα
@@ -1076,21 +1112,21 @@ migrate_items_pullrequests=Pull Requests
migrate_items_merge_requests=Merge Requests
migrate_items_releases=Κυκλοφορίες
migrate_repo=Μεταφορά Αποθετηρίου
-migrate.clone_address=Μεταφορά / Κλωνοποίηση Από Το URL
-migrate.clone_address_desc=Το HTTP(S) ή Git URL 'κλωνοποίησης' ενός υπάρχοντος αποθετηρίου
+migrate.clone_address=Μεταφορά / Κλωνοποίηση από το URL
+migrate.clone_address_desc=Το HTTP(S) ή το Git URL «κλωνοποίησης» ενός υπάρχοντος αποθετηρίου
migrate.github_token_desc=Μπορείτε να βάλετε ένα ή περισσότερα διακριτικά εδώ, χωρισμένα με κόμμα, για να κάνετε τη μετεγκατάσταση πιο γρήγορα, λόγω του ορίου ρυθμού του GitHub API. ΠΡΟΣΟΧΗ: Η κατάχρηση αυτής της δυνατότητας μπορεί να παραβιάσει την πολιτική του παρόχου υπηρεσιών και να οδηγήσει σε αποκλεισμό του λογαριασμού σας.
migrate.clone_local_path=ή μια διαδρομή τοπικού διακομιστή
migrate.permission_denied=Δεν επιτρέπεται η εισαγωγή τοπικών αποθετηρίων.
migrate.permission_denied_blocked=Δεν μπορείτε να εισαγάγετε από μη επιτρεπόμενους υπολογιστές, παρακαλούμε ζητήστε από τον διαχειριστή να ελέγξει τις ρυθμίσεις ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS.
-migrate.invalid_local_path=Η τοπική διαδρομή δεν είναι έγκυρη. Δεν υπάρχει ή δεν είναι φάκελος.
+migrate.invalid_local_path=Η τοποθεσία αρχείου δεν είναι έγκυρη. Το αρχείο δεν υπάρχει ή δεν είναι φάκελος.
migrate.invalid_lfs_endpoint=Η διεύθυνση LFS δεν είναι έγκυρο.
migrate.failed=Η μεταφορά απέτυχε: %v
migrate.migrate_items_options=Το Διακριτικό Πρόσβασης απαιτείται για τη μεταφορά πρόσθετων στοιχείων
migrated_from=Μεταφέρθηκε από %[2]s
migrated_from_fake=Μεταφέρθηκε από %[1]s
-migrate.migrate=Μεταφορά Από %s
-migrate.migrating=Γίνεται μεταφορά από %s ...
-migrate.migrating_failed=Η μετεγρατάσταση από %s απέτυχε.
+migrate.migrate=Μεταφορά από το %s
+migrate.migrating=Γίνεται μεταφορά από το %s ...
+migrate.migrating_failed=Η μεταφορά από το %s απέτυχε.
migrate.migrating_failed.error=Αποτυχία μεταφοράς: %s
migrate.migrating_failed_no_addr=Η μεταφορά απέτυχε.
migrate.github.description=Μεταφορά δεδομένων από το github.com ή άλλους διακομιστές GitHub.
@@ -1132,7 +1168,7 @@ clone_this_repo=Κλωνοποίηση αυτού του αποθετηρίου
cite_this_repo=Αναφορά σε αυτό το αποθετήριο
create_new_repo_command=Δημιουργία νέου αποθετηρίου στη γραμμή εντολών
push_exist_repo=Προώθηση ενός υπάρχοντος αποθετηρίου από τη γραμμή εντολών
-empty_message=Αυτό το αποθετήριο δεν περιέχει τίποτα.
+empty_message=Αυτό το αποθετήριο δεν έχει περιεχόμενο.
broken_message=Τα δεδομένα Git που διέπουν αυτό το αποθετήριο δεν μπορούν να διαβαστούν. Επικοινωνήστε με το διαχειριστή ή διαγράψτε αυτό το αποθετήριο.
code=Κώδικας
@@ -1148,7 +1184,7 @@ issues=Ζητήματα
pulls=Pull Requests
project_board=Έργα
packages=Πακέτα
-actions=Δράσεις
+actions=Actions
labels=Σήματα
org_labels_desc=Τα σήματα στο επίπεδο οργανισμού, που μπορούν να χρησιμοποιηθούν με όλα τα αποθετήρια κάτω από αυτόν τον οργανισμό
org_labels_desc_manage=διαχείριση
@@ -1181,8 +1217,8 @@ escape_control_characters=Escape
unescape_control_characters=Unescape
file_copy_permalink=Αντιγραφή Permalink
view_git_blame=Προβολή Git Blame
-video_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 'video'.
-audio_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 'audio'.
+video_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 «video».
+audio_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 «audio».
stored_lfs=Αποθηκεύτηκε με το Git LFS
symbolic_link=Symbolic link
executable_file=Εκτελέσιμο Αρχείο
@@ -1214,14 +1250,14 @@ editor.must_be_on_a_branch=Πρέπει να βρίσκεστε σε έναν κ
editor.fork_before_edit=Πρέπει να κάνετε fork αυτό το αποθετήριο για να κάνετε ή να προτείνετε αλλαγές σε αυτό το αρχείο.
editor.delete_this_file=Διαγραφή Αρχείου
editor.must_have_write_access=Πρέπει να έχετε πρόσβαση εγγραφής για να κάνετε ή να προτείνετε αλλαγές σε αυτό το αρχείο.
-editor.file_delete_success=Το αρχείο "%s" έχει διαγραφεί.
+editor.file_delete_success=Το αρχείο «%s» έχει διαγραφεί.
editor.name_your_file=Ονομάστε το αρχείο σας…
-editor.filename_help=Προσθέστε έναν φάκελο πληκτρολογώντας το όνομά του, ακολουθούμενο από μια κάθετο ('/'). Αφαιρέστε ένα φάκελο πληκτρολογώντας ένα backspace στην αρχή του πεδίου.
+editor.filename_help=Προσθέστε έναν φάκελο πληκτρολογώντας μια κάθετο ('/') και το όνομα του φακέλου. Αφαιρέστε έναν φάκελο πληκτρολογώντας ένα backspace στην αρχή του πεδίου.
editor.or=ή
editor.cancel_lower=Ακύρωση
editor.commit_signed_changes=Υποβολή Υπογεγραμμένων Αλλαγών
editor.commit_changes=Υποβολή Αλλαγών
-editor.add_tmpl=Προσθήκη ''
+editor.add_tmpl=Προσθήκη «»
editor.add=Προσθήκη %s
editor.update=Ενημέρωση %s
editor.delete=Διαγραφή %s
@@ -1241,36 +1277,36 @@ editor.cancel=Ακύρωση
editor.filename_cannot_be_empty=Το όνομα αρχείου δεν μπορεί να είναι κενό.
editor.filename_is_invalid=Το όνομα αρχείου δεν είναι έγκυρο: "%s".
editor.branch_does_not_exist=Ο κλάδος "%s" δεν υπάρχει σε αυτό το αποθετήριο.
-editor.branch_already_exists=Ο κλάδος "%s" υπάρχει ήδη σε αυτό το αποθετήριο.
-editor.directory_is_a_file=Το όνομα φακέλου "%s" χρησιμοποιείται ήδη ως όνομα αρχείου σε αυτό το αποθετήριο.
-editor.file_is_a_symlink=`Το "%s" είναι συμβολικός σύνδεσμος. Οι συμβολικοί σύνδεσμοι δεν μπορούν να επεξεργαστούν στην ενσωματωμένη εφαρμογή`
-editor.filename_is_a_directory=Το όνομα αρχείου "%s" χρησιμοποιείται ήδη ως όνομα φακέλου σε αυτό το αποθετήριο.
-editor.file_editing_no_longer_exists=Το αρχείο "%s" που επεξεργάζεται, δεν υπάρχει πλέον σε αυτό το αποθετήριο.
-editor.file_deleting_no_longer_exists=Το αρχείο "%s" που διαγράφεται, δεν υπάρχει πλέον σε αυτό το αποθετήριο.
+editor.branch_already_exists=Ο κλάδος «%s» υπάρχει ήδη σε αυτό το αποθετήριο.
+editor.directory_is_a_file=Το όνομα φακέλου «%s» χρησιμοποιείται ήδη ως όνομα αρχείου σε αυτό το αποθετήριο.
+editor.file_is_a_symlink=`Το «%s» είναι συμβολικός σύνδεσμος. Οι συμβολικοί σύνδεσμοι δεν μπορούν να επεξεργαστούν στην ενσωματωμένη εφαρμογή`
+editor.filename_is_a_directory=Το όνομα αρχείου «%s» χρησιμοποιείται ήδη ως όνομα φακέλου σε αυτό το αποθετήριο.
+editor.file_editing_no_longer_exists=Το αρχείο «%s», το οποίο επεξεργάζεστε, δεν υπάρχει πλέον σε αυτό το αποθετήριο.
+editor.file_deleting_no_longer_exists=Το αρχείο «%s», το οποίο διαγράφεται, δεν υπάρχει πλέον σε αυτό το αποθετήριο.
editor.file_changed_while_editing=Τα περιεχόμενα του αρχείου άλλαξαν από τότε που ξεκίνησε η επεξεργασία. Κάντε κλικ εδώ για να τα δείτε ή Υποβολή Αλλαγών ξανά για να τα αντικαταστήσετε.
-editor.file_already_exists=Ένα αρχείο με το όνομα "%s" υπάρχει ήδη σε αυτό το αποθετήριο.
+editor.file_already_exists=Υπάρχει ήδη ένα αρχείο με το όνομα «%s» σε αυτό το αποθετήριο.
editor.commit_empty_file_header=Υποβολή ενός κενού αρχείου
editor.commit_empty_file_text=Το αρχείο που πρόκειται να υποβληθεί είναι κενό. Συνέχεια;
editor.no_changes_to_show=Δεν υπάρχουν αλλαγές για εμφάνιση.
-editor.fail_to_update_file=Αποτυχία ενημέρωσης/δημιουργίας του αρχείου "%s".
+editor.fail_to_update_file=Αποτυχία ενημέρωσης/δημιουργίας του αρχείου «%s».
editor.fail_to_update_file_summary=Μήνυμα Σφάλματος:
editor.push_rejected_no_message=Η αλλαγή απορρίφθηκε από το διακομιστή χωρίς κάποιο μήνυμα. Παρακαλώ ελέγξτε τα Άγκιστρα Git.
editor.push_rejected=Η αλλαγή απορρίφθηκε από τον διακομιστή. Παρακαλώ ελέγξτε τα Άγκιστρα Git.
editor.push_rejected_summary=Μήνυμα Πλήρους Απόρριψης:
editor.add_subdir=Προσθήκη φακέλου…
-editor.unable_to_upload_files=Αποτυχία αποστολής αρχείων στο "%s" με το σφάλμα: %v
-editor.upload_file_is_locked=Το αρχείο "%s" είναι κλειδωμένο από %s.
-editor.upload_files_to_dir=`Μεταφόρτωση αρχείων στο "%s"`
-editor.cannot_commit_to_protected_branch=Δεν είναι δυνατή η υποβολή στον προστατευόμενο κλάδο "%s".
+editor.unable_to_upload_files=Αποτυχία αποστολής αρχείων στο «%s» με το σφάλμα: %v
+editor.upload_file_is_locked=Το αρχείο «%s» είναι κλειδωμένο από %s.
+editor.upload_files_to_dir=Μεταφόρτωση αρχείων στο «%s»
+editor.cannot_commit_to_protected_branch=Δεν είναι δυνατή η υποβολή στον προστατευόμενο κλάδο «%s».
editor.no_commit_to_branch=Δεν είναι δυνατή η απευθείας υποβολή στο κλάδο επειδή:
editor.user_no_push_to_branch=Ο χρήστης δεν μπορεί να κάνει push στο κλάδο
editor.require_signed_commit=Ο κλάδος απαιτεί υπογεγραμμένη υποβολή
-editor.cherry_pick=Ανθολόγηση (cherry-pic) του %s στο:
+editor.cherry_pick=Ανθολόγηση (cherry-pick) του %s σε:
editor.revert=Απόσυρση του %s στο:
commits.desc=Δείτε το ιστορικό αλλαγών του πηγαίου κώδικα.
commits.commits=Υποβολές
-commits.no_commits=Δεν υπάρχουν κοινές υποβολές. Τα "%s" και "%s" έχουν εντελώς διαφορετικές ιστορίες.
+commits.no_commits=Δεν υπάρχουν κοινές υποβολές. Τα «%s» και «%s» έχουν εντελώς διαφορετικές ιστορίες.
commits.nothing_to_compare=Αυτοί οι κλάδοι είναι όμοιοι.
commits.search=Αναζήτηση υποβολών…
commits.search.tooltip=Μπορείτε να προθέτετε τις λέξεις-κλειδιά με "author:", "committer:", "after:", ή "before:", π.χ. "επαναφορά author:Alice before:2019-01-13".
@@ -1293,8 +1329,8 @@ commit.revert=Απόσυρση
commit.revert-header=Απόσυρση: %s
commit.revert-content=Επιλέξτε κλάδο για απόσυρση σε αυτό:
commit.cherry-pick=Cherry-pick
-commit.cherry-pick-header=Ανθολόγηση: %s
-commit.cherry-pick-content=Επιλέξτε κλάδο για να κάνετε ανθολόγηση σε αυτό:
+commit.cherry-pick-header=Cherry-pick: %s
+commit.cherry-pick-content=Επιλέξτε τον κλάδο που θέλετε να ανθολογήσετε (cherry-pick):
commitstatus.error=Σφάλμα
commitstatus.failure=Αποτυχία
@@ -1312,38 +1348,38 @@ projects.create=Δημιουργία Έργου
projects.title=Τίτλος
projects.new=Νέο έργο
projects.new_subheader=Συντονισμός, παρακολούθηση και ενημέρωση της δουλειάς σας σε ένα μέρος, έτσι ώστε τα έργα να παραμένουν διαφανή και μέσα στο χρονοδιάγραμμα.
-projects.create_success=Το έργο "%s" δημιουργήθηκε.
+projects.create_success=Το έργο «%s» δημιουργήθηκε.
projects.deletion=Διαγραφή Έργου
projects.deletion_desc=Η διαγραφή ενός έργου το αφαιρεί από όλα τα σχετιζόμενα ζητήματα. Συνέχεια;
projects.deletion_success=Το έργο έχει διαγραφεί.
projects.edit=Επεξεργασία Έργων
projects.edit_subheader=Τα Έργα οργανώνουν τα ζητήματα και παρακολουθούν τη πρόοδο τους.
projects.modify=Ενημέρωση Έργου
-projects.edit_success=Το έργο "%s" ενημερώθηκε.
+projects.edit_success=Το έργο «%s» ενημερώθηκε.
projects.type.none=Κανένα
projects.type.basic_kanban=Βασικό Kanban
-projects.type.bug_triage=Διαλογή Σφαλμάτων
+projects.type.bug_triage=Διαλογή σφαλμάτων
projects.template.desc=Πρότυπο έργου
projects.template.desc_helper=Επιλέξτε ένα πρότυπο έργου για να ξεκινήσετε
projects.type.uncategorized=Χωρίς Κατηγορία
-projects.column.edit=Επεξεργασία Στήλης
+projects.column.edit=Επεξεργασία στήλης
projects.column.edit_title=Όνομα
projects.column.new_title=Όνομα
-projects.column.new_submit=Δημιουργία Στήλης
-projects.column.new=Νέα Στήλη
-projects.column.set_default=Ορισμός Προεπιλογής
-projects.column.set_default_desc=Ορίστε αυτή τη στήλη ως προεπιλογή για ζητήματα και pull requests χωρίς κατηγορία
-projects.column.unset_default=Αφαίρεση Προεπιλογής
+projects.column.new_submit=Δημιουργία στήλης
+projects.column.new=Νέα στήλη
+projects.column.set_default=Ορισμός προεπιλογής
+projects.column.set_default_desc=Ορίστε αυτή τη στήλη ως προεπιλεγμένη για ζητήματα και pull requests που δεν ανήκουν σε κάποια κατηγορία
+projects.column.unset_default=Αφαίρεση προεπιλογής
projects.column.unset_default_desc=Αφαίρεση της προεπιλογής αυτής της στήλης
-projects.column.delete=Διαγραφή Στήλης
-projects.column.deletion_desc=Η διαγραφή μιας στήλης έργου μετακινεί όλα τα συναφή ζητήματα σε 'Χωρίς Κατηγορία'. Συνέχεια;
+projects.column.delete=Διαγραφή στήλης
+projects.column.deletion_desc=Όταν διαγράφεται μία στήλη έργου, όλα τα ζητήματα που ανήκουν σε αυτή θα μείνουν «Χωρίς Κατηγορία». Συνέχεια;
projects.column.color=Έγχρωμο
projects.open=Άνοιγμα
projects.close=Κλείσιμο
projects.column.assigned_to=Ανατέθηκε σε
-projects.card_type.desc=Προεπισκοπήσεις Καρτών
-projects.card_type.images_and_text=Εικόνες και Κείμενο
-projects.card_type.text_only=Μόνο Κείμενο
+projects.card_type.desc=Προεπισκοπήσεις καρτών
+projects.card_type.images_and_text=Εικόνες και κείμενο
+projects.card_type.text_only=Μόνο κείμενο
issues.desc=Οργανώστε αναφορές σφαλμάτων, εργασίες και ορόσημα.
issues.filter_assignees=Φίλτρο Αποδέκτη
@@ -1385,10 +1421,10 @@ issues.new_label_placeholder=Όνομα σήματος
issues.new_label_desc_placeholder=Περιγραφή
issues.create_label=Δημιουργία Σήματος
issues.label_templates.title=Χρήση ενός προκαθορισμένου συνόλου σημάτων
-issues.label_templates.info=Δεν υπάρχουν σήματα ακόμα. Δημιουργήστε ένα σήμα με το 'Νέο Σήμα' ή χρησιμοποιήστε ένα σύνολο προκαθορισμένων σημάτων:
+issues.label_templates.info=Δεν υπάρχουν σήματα ακόμα. Δημιουργήστε ένα σήμα με το κουμπί «Νέο Σήμα» ή χρησιμοποιήστε ένα σετ προκαθορισμένων σημάτων:
issues.label_templates.helper=Επιλέξτε ένα σύνολο σημάτων
issues.label_templates.use=Χρήση Συνόλου Σημάτων
-issues.label_templates.fail_to_load_file=Αποτυχία φόρτωσης των προτύπων σημάτων από το αρχείο "%s": %v
+issues.label_templates.fail_to_load_file=Αποτυχία φόρτωσης των προτύπων σημάτων από το αρχείο «%s»: %v
issues.add_label=πρόσθεσε τη σήμανση %s %s
issues.add_labels=πρόσθεσε τα σήματα %s %s
issues.remove_label=αφαίρεσε το σήμα %s %s
@@ -1472,7 +1508,7 @@ issues.draft_title=Προσχέδιο
issues.num_comments_1=%d σχόλιο
issues.num_comments=%d σχόλια
issues.commented_at=`σχολίασε %s `
-issues.delete_comment_confirm=Θέλετε σίγουρα να διαγράψετε αυτό το σχόλιο;
+issues.delete_comment_confirm=Είστε βέβαιοι πως θέλετε να διαγράψετε αυτό το σχόλιο;
issues.context.copy_link=Αντιγραφή Συνδέσμου
issues.context.quote_reply=Παράθεση Απάντησης
issues.context.reference_issue=Αναφορά σε νέο ζήτημα
@@ -1533,32 +1569,32 @@ issues.label_edit=Επεξεργασία
issues.label_delete=Διαγραφή
issues.label_modify=Επεξεργασία Σήματος
issues.label_deletion=Διαγραφή Σήματος
-issues.label_deletion_desc=Η διαγραφή ενός σήματος την αφαιρεί από όλα τα ζητήματα. Συνέχεια
+issues.label_deletion_desc=Η διαγραφή ενός σήματος θα το αφαιρέσει από όλα τα ζητήματα. Να γίνει συνέχεια;
issues.label_deletion_success=Το σήμα έχει διαγραφεί.
issues.label.filter_sort.alphabetically=Αλφαβητικά
issues.label.filter_sort.reverse_alphabetically=Αντίστροφα αλφαβητικά
issues.label.filter_sort.by_size=Μικρότερο μέγεθος
issues.label.filter_sort.reverse_by_size=Μεγαλύτερο μέγεθος
issues.num_participants=%d Συμμετέχοντες
-issues.attachment.open_tab=`Κάντε κλικ για να δείτε το "%s" σε μια νέα καρτέλα`
-issues.attachment.download=`Κάντε κλικ για να λάβετε το "%s"`
+issues.attachment.open_tab=`Πατήστε εδώ για να ανοίξετε το «%s» σε μια νέα καρτέλα`
+issues.attachment.download=`Πατήστε εδώ για να κατεβάσετε το «%s»`
issues.subscribe=Εγγραφή
issues.unsubscribe=Διαγραφή
issues.unpin_issue=Άφεση Ζητήματος
-issues.max_pinned=Δεν μπορείτε να διατηρήσετε περισσότερα ζητήματα
-issues.pin_comment=διατήρησε αυτό %s
-issues.unpin_comment=άφησε αυτό %s
+issues.max_pinned=Δεν μπορείτε να καρφιτσώσετε περισσότερα ζητήματα
+issues.pin_comment=καρφίτσωσε το %s
+issues.unpin_comment=ξεκαρφίτσωσε το %s
issues.lock=Κλείδωμα συνομιλίας
issues.unlock=Ξεκλείδωμα συνομιλίας
issues.lock.unknown_reason=Αδυναμία κλειδώματος ενός ζητήματος με άγνωστο λόγο.
issues.lock_duplicate=Ένα ζήτημα δεν μπορεί να κλειδωθεί δύο φορές.
issues.unlock_error=Δεν είναι δυνατό να ξεκλειδώσετε ένα ζήτημα που δεν είναι κλειδωμένο.
-issues.lock_with_reason=κλειδωμένο ως %s και περιορισμένη συνομιλία με συνεργάτες %s
-issues.lock_no_reason=κλειδωμένη και περιορισμένη συνομιλία με συνεργάτες %s
+issues.lock_with_reason=κλείδωσε το ζήτημα επειδή είναι %s και περιόρισε την συνομιλία σε συνεργάτες %s
+issues.lock_no_reason=κλείδωσε το ζήτημα και περιόρισε την σε συνεργάτες %s
issues.unlock_comment=ξεκλείδωσε αυτή τη συνομιλία %s
issues.lock_confirm=Κλείδωμα
issues.unlock_confirm=Ξεκλείδωμα
-issues.lock.notice_1=- Άλλοι χρήστες δεν μπορούν να προσθέσουν νέα σχόλια σε αυτό το ζήτημα.
+issues.lock.notice_1=- Άλλοι χρήστες δεν μπορούν να αφήσουν νέα σχόλια σε αυτό το ζήτημα.
issues.lock.notice_2=- Εσείς και άλλοι συνεργάτες με πρόσβαση σε αυτό το αποθετήριο μπορούν ακόμα να αφήσουν σχόλια που μπορούν να δουν άλλοι.
issues.lock.notice_3=- Μπορείτε πάντα να ξεκλειδώσετε αυτό το ζήτημα ξανά στο μέλλον.
issues.unlock.notice_1=- Όλοι θα ήταν σε θέση να σχολιάσουν αυτό το ζήτημα για άλλη μια φορά.
@@ -1579,7 +1615,7 @@ issues.tracking_already_started=`Έχετε ήδη ξεκινήσει την κ
issues.stop_tracking=Διακοπή Χρονομέτρου
issues.stop_tracking_history=`σταμάτησε να εργάζεται %s`
issues.cancel_tracking=Απόρριψη
-issues.cancel_tracking_history=`ακύρωσε τη παρακολούθηση χρόνου %s`
+issues.cancel_tracking_history=`ακύρωσε τη καταγραφή χρόνου %s`
issues.add_time=Χειροκίνητη Προσθήκη Ώρας
issues.del_time=Διαγραφή αυτού του αρχείου χρόνου
issues.add_time_short=Προσθήκη Χρόνου
@@ -1592,9 +1628,9 @@ issues.add_time_sum_to_small=Δεν εισήχθη χρόνος.
issues.time_spent_total=Συνολική Δαπάνη Χρόνου
issues.time_spent_from_all_authors=`Συνολική Δαπάνη Χρόνου: %s`
issues.due_date=Ημερομηνία Παράδοσης
-issues.invalid_due_date_format=Η μορφή της ημερομηνίας παράδοσης πρέπει να είναι 'yyyy-mm-dd'.
+issues.invalid_due_date_format=Η ημερομηνίας παράδοσης πρέπει να έχει την μορφή «εεεε-μμ-ηη».
issues.error_modifying_due_date=Αποτυχία τροποποίησης της ημερομηνίας παράδοσης.
-issues.error_removing_due_date=Αποτυχία κατάργησης της ημερομηνίας παράδοσης.
+issues.error_removing_due_date=Δεν ήταν δυνατή η κατάργηση της ημερομηνίας παράδοσης.
issues.push_commit_1=πρόσθεσε %d υποβολή %s
issues.push_commits_n=πρόσθεσε %d υποβολές %s
issues.force_push_codes=`force-pushed %[1]s από το %[2]s
στο %[4]s
%[6]s`
@@ -1602,10 +1638,10 @@ issues.force_push_compare=Σύγκριση
issues.due_date_form=εεεε-μμ-ηη
issues.due_date_form_add=Προσθήκη ημερομηνίας παράδοσης
issues.due_date_form_edit=Επεξεργασία
-issues.due_date_form_remove=Διαγραφή
+issues.due_date_form_remove=Αφαίρεση
issues.due_date_not_writer=Χρειάζεστε πρόσβαση εγγραφής στο αποθετήριο για να ενημερώσετε την ημερομηνία λήξης ενός προβλήματος.
issues.due_date_not_set=Δεν ορίστηκε ημερομηνία παράδοσης.
-issues.due_date_added=πρόσθεσε την ημερομηνία παράδοσης %s %s
+issues.due_date_added=όρισε την ημερομηνία παράδοσης %s %s
issues.due_date_modified=τροποποίησε την ημερομηνία παράδοσης από %[2]s σε %[1]s %[3]s
issues.due_date_remove=αφαίρεσε την ημερομηνία παράδοσης %s %s
issues.due_date_overdue=Εκπρόθεσμο
@@ -1613,8 +1649,8 @@ issues.due_date_invalid=Η ημερομηνία παράδοσης δεν είν
issues.dependency.title=Εξαρτήσεις
issues.dependency.issue_no_dependencies=Δεν έχουν οριστεί εξαρτήσεις.
issues.dependency.pr_no_dependencies=Δεν έχουν οριστεί εξαρτήσεις.
-issues.dependency.no_permission_1=Δεν έχετε άδεια για ανάγνωση %d εξάρτηση
-issues.dependency.no_permission_n=Δεν έχετε άδεια να ανάγνωση %d εξαρτήσεων
+issues.dependency.no_permission_1=Δεν έχετε άδεια για την ανάγνωση %d εξάρτησης
+issues.dependency.no_permission_n=Δεν έχετε άδεια για την ανάγνωση %d εξαρτήσεων
issues.dependency.no_permission.can_remove=Δεν έχετε άδεια για ανάγνωση αυτής της εξάρτησης, αλλά μπορείτε να τη καταργήσετε
issues.dependency.add=Προσθήκη εξάρτησης…
issues.dependency.cancel=Ακύρωση
@@ -1627,7 +1663,7 @@ issues.dependency.issue_closing_blockedby=Το κλείσιμο αυτού το
issues.dependency.issue_close_blocks=Αυτό το ζήτημα εμποδίζει το κλείσιμο των ακόλουθων ζητημάτων
issues.dependency.pr_close_blocks=Αυτό το pull request εμποδίζει το κλείσιμο των ακόλουθων ζητημάτων
issues.dependency.issue_close_blocked=Πρέπει να κλείσετε όλα τα ζητήματα που εμποδίζουν αυτό το ζήτημα πριν το κλείσετε.
-issues.dependency.issue_batch_close_blocked=Δεν είναι δυνατό το ομαδικό κλείσιμο ζητημάτων που επιλέξατε, επειδή το ζήτημα #%d ακόμα έχει ανοιχτές εξαρτήσεις
+issues.dependency.issue_batch_close_blocked=Δεν είναι δυνατό το κλείσιμο όλων των ζητημάτων που επιλέξατε, επειδή το ζήτημα #%d ακόμα έχει ανοιχτές εξαρτήσεις
issues.dependency.pr_close_blocked=Πρέπει να κλείσετε όλα τα ζητήματα που εμποδίζουν αυτό το pull request για να μπορέσετε να το συγχωνεύσετε.
issues.dependency.blocks_short=Μπλοκάρει
issues.dependency.blocked_by_short=Εξαρτάται από
@@ -1645,17 +1681,17 @@ issues.review.self.approval=Δεν μπορείτε να εγκρίνετε το
issues.review.self.rejection=Δεν μπορείτε να ζητήσετε αλλαγές στο δικό σας pull request.
issues.review.approve=ενέκρινε αυτές τις αλλαγές %s
issues.review.comment=αξιολόγησε %s
-issues.review.dismissed=απέρριψε την αξιολόγηση %s %s
+issues.review.dismissed=απέρριψε την αξιολόγηση του/της %s %s
issues.review.dismissed_label=Απορρίφθηκε
issues.review.left_comment=άφησε ένα σχόλιο
issues.review.content.empty=Θα πρέπει να αφήσετε ένα σχόλιο υποδεικνύοντας την ζητούμενη αλλαγή(ές).
issues.review.reject=ζήτησε αλλαγές %s
-issues.review.wait=ζητήθηκε για αναθεώρηση %s
-issues.review.add_review_request=ζητήθηκε αναθεώρηση από %s %s
-issues.review.remove_review_request=αφαιρέθηκε αίτηση αναθεώρησης για %s %s
-issues.review.remove_review_request_self=αρνήθηκε να αναθεωρήσει %s
+issues.review.wait=ζητήθηκε για έλεγχο %s
+issues.review.add_review_request=ζητήθηκε έλεγχος από %s %s
+issues.review.remove_review_request=αφαιρέθηκε αίτημα ελέγχου για %s %s
+issues.review.remove_review_request_self=αρνήθηκε να ελέγξει %s
issues.review.pending=Εκκρεμεί
-issues.review.pending.tooltip=Αυτό το σχόλιο προς το παρόν δεν είναι ορατό από άλλους χρήστες. Για να υποβάλετε τα σχόλιά σας, επιλέξτε "%s" -> "%s/%s/%s" στη κορυφή της σελίδας.
+issues.review.pending.tooltip=Αυτό το σχόλιο προς το παρόν δεν είναι ορατό σε άλλους χρήστες. Για να υποβάλετε τα σχόλιά σας, επιλέξτε "%s" -> "%s/%s/%s" στη κορυφή της σελίδας.
issues.review.review=Αξιολόγηση
issues.review.reviewers=Εξεταστές
issues.review.outdated=Παρωχημένο
@@ -1714,7 +1750,7 @@ pulls.has_pull_request=`Υπάρχει ήδη pull request μεταξύ αυτώ
pulls.create=Δημιουργία Pull Request
pulls.title_desc=θέλει να συγχωνεύσει %[1]d υποβολές από %[2]s
σε %[3]s
pulls.merged_title_desc=συγχώνευσε %[1]d υποβολές από %[2]s
σε %[3]s
%[4]s
-pulls.change_target_branch_at=`άλλαξε τον κλάδο στόχο από %s σε %s %s`
+pulls.change_target_branch_at=`άλλαξε τον κλάδο προορισμού από %s σε %s %s`
pulls.tab_conversation=Συζήτηση
pulls.tab_commits=Υποβολές
pulls.tab_files=Αρχεία Με Αλλαγές
@@ -1733,15 +1769,15 @@ pulls.add_prefix=Προσθήκη %s προθέματος
pulls.remove_prefix=Αφαίρεση %s προθέματος
pulls.data_broken=Αυτό το pull request είναι κατεστραμμένο λόγω των πληροφοριών του fork που λείπουν.
pulls.files_conflicted=Αυτό το pull request περιέχει αλλαγές που συγκρούονται με το κλάδο προορισμού.
-pulls.is_checking=Ο έλεγχος συγκρούσεων κατά την συγχώνευση είναι σε εξέλιξη. Δοκιμάστε ξανά σε λίγα λεπτά.
+pulls.is_checking=Ο έλεγχος συγκρούσεων κατά την συγχώνευση βρίσκεται σε εξέλιξη. Δοκιμάστε ξανά σε λίγα λεπτά.
pulls.is_ancestor=Αυτός ο κλάδος περιλαμβάνεται ήδη στον κλάδο προορισμού. Δεν υπάρχει τίποτα για συγχώνευση.
-pulls.is_empty=Οι αλλαγές σε αυτόν τον κλάδο είναι ήδη στον κλάδο προορισμού. Θα είναι μια κενή υποβολή.
+pulls.is_empty=Οι αλλαγές σε αυτόν τον κλάδο βρίσκονται ήδη στον κλάδο προορισμού. Θα είναι μια κενή υποβολή.
pulls.required_status_check_failed=Ορισμένοι απαιτούμενοι έλεγχοι δεν ήταν επιτυχείς.
pulls.required_status_check_missing=Λείπουν ορισμένοι απαιτούμενοι έλεγχοι.
pulls.required_status_check_administrator=Ως διαχειριστής, μπορείτε ακόμα να συγχωνεύσετε αυτό το pull request.
pulls.blocked_by_approvals=Το pull request δεν έχει ακόμα αρκετές εγκρίσεις. Δόθηκαν %d από %d εγκρίσεις.
-pulls.blocked_by_rejection=Αυτό το Pull Request έχει αλλαγές που ζητούνται από έναν επίσημο εξεταστή.
-pulls.blocked_by_official_review_requests=Αυτό το Pull Request έχει επίσημες αιτήσεις αξιολόγησης.
+pulls.blocked_by_rejection=Αυτό το pull request έχει αλλαγές που ζητούνται από έναν επίσημο εξεταστή.
+pulls.blocked_by_official_review_requests=Αυτό το pull request έχει επίσημες αιτήσεις αξιολόγησης.
pulls.blocked_by_outdated_branch=Αυτό το pull request έχει αποκλειστεί επειδή είναι παρωχημένο.
pulls.blocked_by_changed_protected_files_1=Αυτό το pull request έχει αποκλειστεί επειδή αλλάζει ένα προστατευμένο αρχείο:
pulls.blocked_by_changed_protected_files_n=Αυτό το pull request έχει αποκλειστεί επειδή αλλάζει προστατευμένα αρχεία:
@@ -1754,8 +1790,8 @@ pulls.approve_count_1=%d έγκριση
pulls.approve_count_n=%d εγκρίσεις
pulls.reject_count_1=%d αίτημα αλλαγής
pulls.reject_count_n=%d αιτήματα αλλαγής
-pulls.waiting_count_1=%d αναμονή αναθεώρησης
-pulls.waiting_count_n=%d αναμονή αναθεωρήσεων
+pulls.waiting_count_1=%d αναμονή αξιολόγησης
+pulls.waiting_count_n=%d αναμονές αξιολόγησης
pulls.wrong_commit_id=Το id υποβολής πρέπει να είναι ένα id υποβολής στον κλάδο προορισμού
pulls.no_merge_desc=Αυτό το pull request δεν μπορεί να συγχωνευθεί επειδή όλες οι επιλογές συγχώνευσης αποθετηρίων είναι απενεργοποιημένες.
@@ -1780,9 +1816,9 @@ pulls.unrelated_histories=H Συγχώνευση Απέτυχε: Η κεφαλή
pulls.merge_out_of_date=Η συγχώνευση απέτυχε: Κατά τη δημιουργία της συγχώνευσης, η βάση ενημερώθηκε. Συμβουλή: Δοκιμάστε ξανά.
pulls.head_out_of_date=Η συγχώνευση απέτυχε: Κατά τη δημιουργία της συγχώνευσης, το HEAD ενημερώθηκε. Συμβουλή: Δοκιμάστε ξανά.
pulls.has_merged=Αποτυχία: Το pull request έχει συγχωνευθεί, δεν είναι δυνατή η συγχώνευση ξανά ή να αλλάξει ο κλάδος προορισμού.
-pulls.push_rejected=Η συγχώνευση απέτυχε: Η ώθηση απορρίφθηκε. Ελέγξτε τα Άγκιστρα Git για αυτό το αποθετήριο.
+pulls.push_rejected=Αποτυχία ώθησης: Η ώθηση απορρίφθηκε. Ελέγξτε τα Άγκιστρα Git για αυτό το αποθετήριο.
pulls.push_rejected_summary=Μήνυμα Πλήρους Απόρριψης
-pulls.push_rejected_no_message=H Συγχώνευση Aπέτυχε: Η ώθηση απορρίφθηκε, αλλά δεν υπήρχε απομακρυσμένο μήνυμα. Ελέγξτε τα Άγκιστρα Git για αυτό το αποθετήριο
+pulls.push_rejected_no_message=H συγχώνευση απέτυχε: Η ώθηση απορρίφθηκε, αλλά δεν υπήρχε απομακρυσμένο μήνυμα. Ελέγξτε τα Git Hooks για αυτό το αποθετήριο
pulls.open_unmerged_pull_exists=`Δεν μπορείτε να ανοίξετε εκ νέου, επειδή υπάρχει ένα εκκρεμές pull request (#%d) με πανομοιότυπες ιδιότητες.`
pulls.status_checking=Μερικοί έλεγχοι εκκρεμούν
pulls.status_checks_success=Όλοι οι έλεγχοι ήταν επιτυχείς
@@ -1835,19 +1871,19 @@ milestones.no_due_date=Δεν υπάρχει ημερομηνία παράδοσ
milestones.open=Άνοιγμα
milestones.close=Κλείσιμο
milestones.new_subheader=Τα ορόσημα μπορούν να σας βοηθήσουν να οργανώσετε τα ζητήματα και να παρακολουθείτε την πρόοδό τους.
-milestones.completeness=%d%% Ολοκληρώθηκε
+milestones.completeness=%d%% ολοκληρωμένο
milestones.create=Δημιουργία Ορόσημου
milestones.title=Τίτλος
milestones.desc=Περιγραφή
milestones.due_date=Ημερομηνία Απαίτησης (Προαιρετικό)
milestones.clear=Εκκαθάριση
-milestones.invalid_due_date_format=Η μορφή ημερομηνίας απαίτησης πρέπει να είναι 'yyyy-mm-dd'.
-milestones.create_success=Το ορόσημο "%s" δημιουργήθηκε.
+milestones.invalid_due_date_format=Η ημερομηνίας απαίτησης πρέπει να έχει την μορφή «εεεε-μμ-ηη».
+milestones.create_success=Το ορόσημο «%s» δημιουργήθηκε.
milestones.edit=Επεξεργασία Ορόσημου
milestones.edit_subheader=Τα Ορόσημα οργανώνουν ζητήματα και καταγράφουν την πρόοδο.
milestones.cancel=Ακύρωση
milestones.modify=Ενημέρωση Ορόσημου
-milestones.edit_success=Το ορόσημο "%s" ενημερώθηκε.
+milestones.edit_success=Το ορόσημο «%s» ενημερώθηκε.
milestones.deletion=Διαγραφή Ορόσημου
milestones.deletion_desc=Η διαγραφή ενός ορόσημου το αφαιρεί από όλα τα συναφή ζητήματα. Συνέχεια;
milestones.deletion_success=Το ορόσημο έχει διαγραφεί.
@@ -1858,7 +1894,7 @@ milestones.filter_sort.most_complete=Περισσότερο πλήρη
milestones.filter_sort.most_issues=Περισσότερα ζητήματα
milestones.filter_sort.least_issues=Λιγότερα ζητήματα
-signing.will_sign=Αυτή η υποβολή θα υπογραφεί με το κλειδί "%s".
+signing.will_sign=Αυτή η υποβολή θα υπογραφεί με το κλειδί «%s».
signing.wont_sign.error=Παρουσιάστηκε σφάλμα κατά τον έλεγχο για το αν η υποβολή μπορεί να υπογραφεί.
signing.wont_sign.nokey=Δεν υπάρχει διαθέσιμο κλειδί για να υπογραφεί αυτή η υποβολή.
signing.wont_sign.never=Οι υποβολές δεν υπογράφονται ποτέ.
@@ -1894,12 +1930,12 @@ wiki.file_revision=Αναθεώρηση Σελίδας
wiki.wiki_page_revisions=Αναθεωρήσεις Σελίδας Wiki
wiki.back_to_wiki=Πίσω στη σελίδα wiki
wiki.delete_page_button=Διαγραφή Σελίδας
-wiki.delete_page_notice_1=Η διαγραφή της σελίδας wiki "%s" δεν μπορεί να αναιρεθεί. Συνέχεια;
+wiki.delete_page_notice_1=Η διαγραφή της σελίδας wiki «%s» δεν μπορεί να αναιρεθεί. Συνέχεια;
wiki.page_already_exists=Υπάρχει ήδη μια σελίδα wiki με το ίδιο όνομα.
-wiki.reserved_page=Το όνομα σελίδας wiki "%s" είναι δεσμευμένο.
+wiki.reserved_page=Το όνομα σελίδας wiki «%s» είναι δεσμευμένο.
wiki.pages=Σελίδες
wiki.last_updated=Τελευταία ενημέρωση %s
-wiki.page_name_desc=Εισάγετε ένα όνομα για αυτή τη σελίδα Wiki. Μερικά ειδικά ονόματα είναι: 'Home', '_Sidebar' και '_Footer'.
+wiki.page_name_desc=Εισάγετε ένα όνομα για αυτή τη σελίδα wiki. Μερικά ειδικά ονόματα είναι: «Home», «_Sidebar» και «_Footer».
wiki.original_git_entry_tooltip=Προβολή του αρχικού αρχείου Git αντί ενός εμφανίσιμου συνδέσμου.
activity=Δραστηριότητα
@@ -1973,11 +2009,11 @@ contributors.contribution_type.commits=Υποβολές
search=Αναζήτηση
search.search_repo=Αναζήτηση αποθετηρίου
search.type.tooltip=Τύπος αναζήτησης
-search.fuzzy=Fuzzy
+search.fuzzy=Όμοιο
search.fuzzy.tooltip=Συμπερίληψη και των αποτελεσμάτων που είναι πλησιέστερα με τον όρο αναζήτησης
search.match=Ταίριασμα
search.match.tooltip=Συμπερίληψη μόνο των αποτελεσμάτων που ταιριάζουν ακριβώς με τον όρο αναζήτησης
-search.results=Αποτελέσματα αναζήτησης για "%s" σε %s
+search.results=Αποτελέσματα αναζήτησης για «%s» σε %s
search.code_no_results=Δεν βρέθηκε πηγαίος κώδικας που να ταιριάζει με τον όρο αναζήτησης.
search.code_search_unavailable=Η αναζήτηση κώδικα δεν είναι διαθέσιμη αυτή τη στιγμή. Παρακαλώ επικοινωνήστε με το διαχειριστή.
@@ -2017,7 +2053,7 @@ settings.mirror_settings.push_mirror.add=Προσθήκη Είδωλου Push
settings.mirror_settings.push_mirror.edit_sync_time=Επεξεργασία διαστήματος συγχρονισμού ειδώλου
settings.sync_mirror=Συγχρονισμός Τώρα
-settings.pull_mirror_sync_in_progress=Έλκονται αλλαγές από το απομακρυσμένο %s αυτή τη στιγμή.
+settings.pull_mirror_sync_in_progress=Έλκονται αλλαγές από την απομακρυσμένη τοποθεσία %s αυτή τη στιγμή.
settings.push_mirror_sync_in_progress=Ώθηση αλλαγών στο απομακρυσμένο %s αυτή τη στιγμή.
settings.site=Ιστοσελίδα
settings.update_settings=Ενημέρωση Ρυθμίσεων
@@ -2047,8 +2083,8 @@ settings.tracker_issue_style.regexp=Κανονική Έκφραση
settings.tracker_issue_style.regexp_pattern=Μοτίβο Κανονικής Έκφρασης
settings.tracker_issue_style.regexp_pattern_desc=Η πρώτη ομάδα θα χρησιμοποιηθεί στη θέση του {index}
.
settings.tracker_url_format_desc=Χρησιμοποιήστε τα {user}
, {repo}
και {index}
για το όνομα χρήστη, το όνομα αποθετηρίου και το ευρετήριο ζητημάτων.
-settings.enable_timetracker=Ενεργοποίηση Καταγραφής Χρόνου
-settings.allow_only_contributors_to_track_time=Μόνο οι Συμμετέχοντες να Καταγράφουν Χρόνο
+settings.enable_timetracker=Ενεργοποίηση καταγραφής χρόνου
+settings.allow_only_contributors_to_track_time=Να επιτρέπεται η καταγραφή χρόνου μόνο από συνεισφέροντες
settings.pulls_desc=Ενεργοποίηση Pull Requests στο Αποθετήριο
settings.pulls.ignore_whitespace=Αγνόηση των Κενών Χαρακτήρων στις Συγκρούσεις
settings.pulls.enable_autodetect_manual_merge=Ενεργοποίηση αυτόματης ανίχνευσης συγχώνευσης (Σημείωση: σε ορισμένες ειδικές περιπτώσεις, μπορεί να προκύψουν εσφαλμένες κρίσεις)
@@ -2111,13 +2147,13 @@ settings.trust_model.collaboratorcommitter.long=Συνεργάτης+Υποβο
settings.trust_model.collaboratorcommitter.desc=Έγκυρες υπογραφές από συνεργάτες αυτού του αποθετηρίου θα επισημανθούν ως "αξιόπιστες" αν ταιριάζουν με τον υποβολέα. Διαφορετικά, οι έγκυρες υπογραφές θα φέρουν την ένδειξη "μη αξιόπιστη" αν η υπογραφή ταιριάζει με τον υποβολέα και "δεν ταιριάζει" διαφορετικά. Αυτό θα αναγκάσει το Forgejo να επισημανθεί ως ο υποβολέας στις υπογεγραμμένες υποβολές με τον πραγματικό υποβολέα να σημειώνεται ως Co-Authored-By: και Co-Committed-By: στο τέλος της υποβολής. Το προεπιλεγμένο κλειδί Forgejo πρέπει να ταιριάζει με έναν χρήστη στη βάση δεδομένων.
settings.wiki_delete=Διαγραφή Δεδομένων Wiki
settings.wiki_delete_desc=Η διαγραφή των δεδομένων του wiki του αποθετηρίου είναι μόνιμη και δεν μπορεί να αναιρεθεί.
-settings.wiki_delete_notices_1=- Αυτό θα διαγράψει μόνιμα και θα απενεργοποιήσει το wiki του αποθετηρίου για %s.
+settings.wiki_delete_notices_1=- Αυτή η ρύθμιση θα απενεργοποιήσει το wiki του αποθετηρίου για %s και θα το διαγράψει μόνιμα.
settings.confirm_wiki_delete=Διαγραφή Δεδομένων Wiki
settings.wiki_deletion_success=Τα δεδομένα wiki του αποθετηρίου έχουν διαγραφεί.
settings.delete=Διαγραφή Αυτού Του Αποθετηρίου
settings.delete_desc=Η διαγραφή ενός αποθετηρίου είναι μόνιμη και δεν μπορεί να αναιρεθεί.
settings.delete_notices_1=- Αυτή η ενέργεια ΔΕΝ ΜΠΟΡΕΙ να αναιρεθεί.
-settings.delete_notices_2=- Αυτή η ενέργεια θα διαγράψει μόνιμα το αποθετήριο %s συμπεριλαμβανομένου του κώδικα, των προβλημάτων, σχολίων, δεδομένων wiki και των ρυθμίσεων συνεργατών.
+settings.delete_notices_2=- Αυτή η ενέργεια θα διαγράψει μόνιμα το αποθετήριο %s συμπεριλαμβανομένου του κώδικα, των ζητημάτων, σχολίων, δεδομένων wiki και των ρυθμίσεων συνεργατών.
settings.delete_notices_fork_1=- Τα Forks αυτού του αποθετηρίου θα γίνουν ανεξάρτητα μετά τη διαγραφή.
settings.deletion_success=Το αποθετήριο έχει διαγραφεί.
settings.update_settings_success=Οι ρυθμίσεις του αποθετηρίου έχουν ενημερωθεί.
@@ -2270,7 +2306,7 @@ settings.title=Τίτλος
settings.deploy_key_content=Περιεχόμενο
settings.key_been_used=Ένα κλειδί διάθεσης με το ίδιο περιεχόμενο χρησιμοποιείται ήδη.
settings.key_name_used=Ένα κλειδί διάθεσης με το ίδιο όνομα υπάρχει ήδη.
-settings.add_key_success=Το κλειδί διάθεσης '%s' προστέθηκε.
+settings.add_key_success=Το κλειδί διάθεσης «%s» προστέθηκε.
settings.deploy_key_deletion=Αφαίρεση Κλειδιού Διάθεσης
settings.deploy_key_deletion_desc=Η κατάργηση ενός κλειδί διάθεσης θα ανακαλέσει την πρόσβασή του σε αυτό το αποθετήριο. Συνέχεια;
settings.deploy_key_deletion_success=Το κλειδί διάθεσης έχει αφαιρεθεί.
@@ -2281,7 +2317,7 @@ settings.protected_branch.delete_rule=Διαγραφή Κανόνα
settings.protected_branch_can_push=Επιτρέψτε ώθηση;
settings.protected_branch_can_push_yes=Μπορείτε να ωθήσετε
settings.protected_branch_can_push_no=Δεν μπορείτε να ωθήσετε
-settings.branch_protection=Προστασία Κλάδου για το Κλάδο '%s '
+settings.branch_protection=Κανόνες προστασίας για τον κλάδο «%s »
settings.protect_this_branch=Ενεργοποίηση Προστασίας Κλάδου
settings.protect_this_branch_desc=Αποτρέπει τη διαγραφή και περιορίζει το Git push και συγχώνευση στον κλάδο.
settings.protect_disable_push=Απενεργοποίηση Ώθησης
@@ -2328,9 +2364,9 @@ settings.protect_unprotected_file_patterns=Μοτίβα μη προστατευ
settings.protect_unprotected_file_patterns_desc=Μη προστατευμένα αρχεία που επιτρέπεται να αλλάξουν απευθείας εάν ο χρήστης έχει πρόσβαση εγγραφής, παρακάμπτοντας τον περιορισμό ώθησης. Επιπλέων μοτίβα μπορούν να διαχωριστούν με ερωτηματικό (';'). Δείτε την τεκμηρίωση github.com/gobwas/glob για τη σύνταξη του μοτίβου. Πχ: .drone.yml
, /docs/**/*.txt
.
settings.add_protected_branch=Ενεργοποίηση προστασίας
settings.delete_protected_branch=Απενεργοποίηση προστασίας
-settings.update_protect_branch_success=Η προστασία κλάδου για τον κανόνα "%s" ενημερώθηκε.
+settings.update_protect_branch_success=Η προστασία κλάδου για τον κανόνα «%s» ενημερώθηκε.
settings.remove_protected_branch_success=Η προστασία κλάδου για τον κανόνα "%s" αφαιρέθηκε.
-settings.remove_protected_branch_failed=Η αφαίρεση του κανόνα προστασίας κλάδου "%s" απέτυχε.
+settings.remove_protected_branch_failed=Η αφαίρεση του κανόνα προστασίας κλάδου «%s» απέτυχε.
settings.protected_branch_deletion=Απενεργοποίηση Προστασίας Κλάδου
settings.protected_branch_deletion_desc=Η απενεργοποίηση της προστασίας του κλάδου επιτρέπει στους χρήστες με άδεια εγγραφής να κάνουν push στον κλάδο. Συνέχεια;
settings.block_rejected_reviews=Φραγή συγχώνευσης αν υπάρχουν απορριπτικές αξιολογήσεις
@@ -2346,7 +2382,7 @@ settings.choose_branch=Επιλέξτε έναν κλάδο…
settings.no_protected_branch=Δεν υπάρχουν προστατευμένοι κλάδοι.
settings.edit_protected_branch=Επεξεργασία
settings.protected_branch_required_rule_name=Απαιτούμενο όνομα κανόνα
-settings.protected_branch_duplicate_rule_name=Διπλότυπο όνομα κανόνα
+settings.protected_branch_duplicate_rule_name=Υπάρχει ήδη ένας κανόνας για αυτούς τους κλάδους
settings.protected_branch_required_approvals_min=Οι απαιτούμενες εγκρίσεις δεν μπορούν να είναι αρνητικές.
settings.tags=Ετικέτες
settings.tags.protection=Προστασία Ετικετών
@@ -2365,14 +2401,14 @@ settings.matrix.homeserver_url=Homeserver URL
settings.matrix.room_id=ID Δωματίου
settings.matrix.message_type=Τύπος Μηνύματος
settings.archive.button=Αρχειοθέτηση Αποθετηρίου
-settings.archive.header=Αρχειοθέτηση Αυτού του Αποθετηρίου
+settings.archive.header=Αρχειοθέτηση αποθετηρίου
settings.archive.text=Η αρχειοθέτηση του αποθετηρίου θα το αλλάξει σε μόνο για ανάγνωση. Δε θα φαίνεται στον αρχικό πίνακα. Κανείς (ακόμα και εσείς!) δε θα μπορεί να κάνει νέες υποβολές, ή να ανοίξει ζητήματα ή pull request.
settings.archive.success=Το αποθετήριο αρχειοθετήθηκε με επιτυχία.
settings.archive.error=Παρουσιάστηκε σφάλμα κατά την προσπάθεια αρχειοθέτησης του αποθετηρίου. Δείτε το αρχείο καταγραφής για περισσότερες λεπτομέρειες.
settings.archive.error_ismirror=Δε μπορείτε να αρχειοθετήσετε ένα είδωλο αποθετηρίου.
settings.archive.branchsettings_unavailable=Οι ρυθμίσεις του κλάδου δεν είναι διαθέσιμες αν το αποθετήριο είναι αρχειοθετημένο.
settings.archive.tagsettings_unavailable=Οι ρυθμίσεις της ετικέτας δεν είναι διαθέσιμες αν το αποθετήριο είναι αρχειοθετημένο.
-settings.unarchive.button=Απο-Αρχειοθέτηση αποθετηρίου
+settings.unarchive.button=Αναίρεση αρχειοθέτησης αποθετηρίου
settings.unarchive.header=Απο-Αρχειοθέτηση του αποθετηρίου
settings.unarchive.text=Η απο-αρχειοθέτηση του αποθετηρίου θα αποκαταστήσει την ικανότητά του να λαμβάνει υποβολές και ωθήσεις, καθώς και νέα ζητήματα και pull-requests.
settings.unarchive.success=Το αποθετήριο απο-αρχειοθετήθηκε με επιτυχία.
@@ -2516,28 +2552,28 @@ release.releases_for=Κυκλοφορίες για %s
release.tags_for=Ετικέτες για %s
branch.name=Όνομα Κλάδου
-branch.already_exists=Ήδη υπάρχει ένας κλάδος με το όνομα "%s".
+branch.already_exists=Ήδη υπάρχει ένας κλάδος με το όνομα «%s».
branch.delete_head=Διαγραφή
-branch.delete=`Διαγραφή του Κλάδου "%s"`
+branch.delete=`Διαγραφή του Κλάδου «%s»`
branch.delete_html=Διαγραφή Κλάδου
branch.delete_desc=Η διαγραφή ενός κλάδου είναι μόνιμη. Αν και ο διαγραμμένος κλάδος μπορεί να συνεχίσει να υπάρχει για σύντομο χρονικό διάστημα πριν να αφαιρεθεί, ΔΕΝ ΜΠΟΡΕΙ να αναιρεθεί στις περισσότερες περιπτώσεις. Συνέχεια;
-branch.deletion_success=Ο κλάδος "%s" διαγράφηκε.
-branch.deletion_failed=Αποτυχία διαγραφής του κλάδου "%s".
-branch.delete_branch_has_new_commits=Ο κλάδος "%s" δεν μπορεί να διαγραφεί επειδή προστέθηκαν νέες υποβολές μετά τη συγχώνευση.
+branch.deletion_success=Ο κλάδος «%s» διαγράφηκε.
+branch.deletion_failed=Η διαγραφή του κλάδου «%s» απέτυχε.
+branch.delete_branch_has_new_commits=Ο κλάδος «%s» δεν μπορεί να διαγραφεί επειδή προστέθηκαν νέες υποβολές μετά τη συγχώνευση.
branch.create_branch=Δημιουργία κλάδου %s
-branch.create_from=`από το "%s"`
-branch.create_success=Ο κλάδος "%s" δημιουργήθηκε.
-branch.branch_already_exists=Ο κλάδος "%s" υπάρχει ήδη σε αυτό το αποθετήριο.
-branch.branch_name_conflict=Το όνομα του κλάδου "%s" συγκρούεται με το ήδη υπάρχον κλάδο "%s".
-branch.tag_collision=Ο κλάδος "%s" δεν μπορεί να δημιουργηθεί επειδή μια ετικέτα με το ίδιο όνομα υπάρχει ήδη στο αποθετήριο.
+branch.create_from=`από το «%s»`
+branch.create_success=Ο κλάδος «%s» δημιουργήθηκε.
+branch.branch_already_exists=Ο κλάδος «%s» υπάρχει ήδη σε αυτό το αποθετήριο.
+branch.branch_name_conflict=Το όνομα του κλάδου «%s» συγκρούεται με το ήδη υπάρχον κλάδο «%s».
+branch.tag_collision=Ο κλάδος «%s» δεν μπορεί να δημιουργηθεί επειδή μια ετικέτα με το ίδιο όνομα υπάρχει ήδη στο αποθετήριο.
branch.deleted_by=Διαγράφηκε από %s
-branch.restore_success=Ο κλάδος "%s" επαναφέρθηκε.
-branch.restore_failed=Αποτυχία επαναφοράς του κλάδου "%s".
-branch.protected_deletion_failed=Ο κλάδος "%s" προστατεύεται. Δεν μπορεί να διαγραφεί.
-branch.default_deletion_failed=Ο κλάδος "%s" είναι ο προεπιλεγμένος κλάδος. Δεν μπορεί να διαγραφεί.
-branch.restore=`Επαναφορά του Κλάδου "%s"`
-branch.download=`Λήψη του Κλάδου "%s"`
-branch.rename=`Μετονομασία Κλάδου "%s"`
+branch.restore_success=Ο κλάδος «%s» επαναφέρθηκε.
+branch.restore_failed=Αποτυχία επαναφοράς του κλάδου «%s».
+branch.protected_deletion_failed=Ο κλάδος «%s» είναι προστατευόμενος. Δεν μπορεί να διαγραφεί.
+branch.default_deletion_failed=Ο κλάδος «%s» είναι προεπιλεγμένος κλάδος. Δεν μπορεί να διαγραφεί.
+branch.restore=`Επαναφορά του κλάδου «%s»`
+branch.download=`Λήψη του κλάδου «%s»`
+branch.rename=`Μετονομασία κλάδου «%s»`
branch.search=Αναζήτηση Κλάδου
branch.included_desc=Αυτός ο κλάδος είναι μέρος του προεπιλεγμένου κλάδου
branch.included=Περιλαμβάνεται
@@ -2548,15 +2584,15 @@ branch.rename_branch_to=Μετονομασία του "%s" σε:
branch.confirm_rename_branch=Μετονομασία κλάδου
branch.create_branch_operation=Δημιουργία κλάδου
branch.new_branch=Δημιουργία νέου κλάδου
-branch.new_branch_from=`Δημιουργία νέου κλάδου από το "%s"`
+branch.new_branch_from=`Δημιουργία νέου κλάδου από το «%s»`
branch.renamed=Ο κλάδος %s μετονομάστηκε σε %s.
tag.create_tag=Δημιουργία ετικέτας %s
tag.create_tag_operation=Δημιουργία ετικέτας
tag.confirm_create_tag=Δημιουργία ετικέτας
-tag.create_tag_from=`Δημιουργία νέας ετικέτας από το "%s"`
+tag.create_tag_from=`Δημιουργία νέας ετικέτας από το «%s»`
-tag.create_success=Η ετικέτα "%s" δημιουργήθηκε.
+tag.create_success=Η ετικέτα «%s» δημιουργήθηκε.
topic.manage_topics=Διαχείριση Θεμάτων
topic.done=Ολοκληρώθηκε
@@ -2569,8 +2605,40 @@ find_file.no_matching=Δεν ταιριάζει κανένα αρχείο
error.csv.too_large=Δεν είναι δυνατή η απόδοση αυτού του αρχείου επειδή είναι πολύ μεγάλο.
error.csv.unexpected=Δεν είναι δυνατή η απόδοση αυτού του αρχείου, επειδή περιέχει έναν μη αναμενόμενο χαρακτήρα στη γραμμή %d και στη στήλη %d.
error.csv.invalid_field_count=Δεν είναι δυνατή η απόδοση αυτού του αρχείου, επειδή έχει λάθος αριθμό πεδίων στη γραμμή %d.
+commits.renamed_from = Μετονομάστηκε από %σ
+settings.wiki_rename_branch_main_desc = Ο κλάδος, ο οποίος χρησιμοποιείται εσωτερικά από το wiki, θα μετονομαστεί σε «%s». Αυτή η αλλαγή είναι μόνιμη και μη αναστρέψιμη.
+issues.comment.blocked_by_user = Δεν μπορείτε να αφήσετε σχόλιο σε αυτό το ζήτημα, επειδή ο κάτοχος του αποθετηρίου ή το άτομο που δημιούργησε το ζήτημα σας έχει αποκλείσει.
+pulls.blocked_by_user = Δεν μπορείτε να δημιουργήσετε pull request σε αυτό το αποθετήριο, επειδή ο κάτοχος του αποθετηρίου σας έχει αποκλείσει.
+pulls.made_using_agit = AGit
+wiki.cancel = Ακύρωση
+settings.units.add_more = Προσθήκη περισσότερων...
+settings.new_owner_blocked_doer = Ο νέος κάτοχος του αποθετηρίου σας έχει αποκλείσει.
+settings.enter_repo_name = Γράψτε το όνομα του κατόχου και του αποθετηρίου ακριβώς όπως το βλέπετε:
+settings.confirmation_string = Κείμενο επιβεβαίωσης
+settings.units.overview = Επισκόπηση
+pulls.commit_ref_at = `ανέφερε το pull request στην υποβολή %[2]s `
+contributors.contribution_type.filter_label = Είδος συνεισφοράς:
+settings.wiki_rename_branch_main_notices_1 = Αυτή η ενέργεια ΔΕΝ αναιρείται.
+activity.navbar.contributors = Συνεισφέροντες
+contributors.contribution_type.additions = Προσθήκες
+contributors.contribution_type.deletions = Διαγραφές
+migrate.forgejo.description = Μεταφορά δεδομένων από το codeberg.org ή άλλων υπηρεσιών Forgejo.
+rss.must_be_on_branch = Για να αποκτήσετε ένα RSS feed, πρέπει να βρίσκεστε σε έναν κλάδο.
+clone_in_vscodium = Κλωνοποίηση στο VSCodium
+editor.invalid_commit_mail = Αυτή η διεύθυνση email δεν είναι έγκυρη για την δημιουργία μίας υποβολής.
+pulls.nothing_to_compare_have_tag = Ο επιλεγμένος κλάδος/tag είναι παρόμοιος.
+issues.blocked_by_user = Δεν μπορείτε να δημιουργήσετε ζητήματα σε αυτό το αποθετήριο, επειδή ο κάτοχος του αποθετηρίου σας έχει αποκλείσει.
+pulls.agit_explanation = Δημιουργημένο μέσω του AGit. Το AGit επιτρέπει σε συνεισφέροντες να προτείνουν αλλαγές χρησιμοποιώντας την εντολή «git push», χωρίς την δημιουργία fork ή έναν νέο κλάδο.
+activity.navbar.recent_commits = Πρόσφατες υποβολές
+settings.wiki_globally_editable = Να επιτρέπεται η επεξεργασία του wiki σε όλους
[graphs]
+component_loading_failed = Δεν ήταν δυνατή η φόρτωση του %s
+component_loading = Γίνεται φόρτωση του %s...
+component_loading_info = Αυτό μπορεί να πάρει λίγη ώρα…
+component_failed_to_load = Προέκυψε ένα απρόσμενο σφάλμα.
+contributors.what = συνεισφορές
+recent_commits.what = πρόσφατες υποβολές
[org]
org_name_holder=Όνομα Οργανισμού
@@ -2595,8 +2663,8 @@ team_permission_desc=Δικαίωμα
team_unit_desc=Να επιτρέπεται η Πρόσβαση σε Τμήματα του Αποθετηρίου
team_unit_disabled=(Απενεργοποιημένο)
-form.name_reserved=Το όνομα οργανισμού "%s" είναι δεσμευμένο.
-form.name_pattern_not_allowed=Το μοτίβο "%s" δεν επιτρέπεται μέσα σε ένα όνομα οργανισμού.
+form.name_reserved=Το όνομα οργανισμού «%s» είναι δεσμευμένο.
+form.name_pattern_not_allowed=Το μοτίβο «%s» δεν επιτρέπεται μέσα σε ένα όνομα οργανισμού.
form.create_org_not_allowed=Δεν επιτρέπεται να δημιουργήσετε έναν οργανισμό.
settings=Ρυθμίσεις
@@ -2646,7 +2714,7 @@ members.invite_now=Πρόσκληση Τώρα
teams.join=Συμμετοχή
teams.leave=Αποχώρηση
-teams.leave.detail=Αποχώρηση από %s;
+teams.leave.detail=Αποχώρηση από την ομάδα %s;
teams.can_create_org_repo=Δημιουργία αποθετηρίων
teams.can_create_org_repo_helper=Τα μέλη μπορούν να δημιουργήσουν νέα αποθετήρια στον οργανισμό. Ο δημιουργός θα αποκτήσει πρόσβαση διαχειριστή στο νέο αποθετήριο.
teams.none_access=Καμία Πρόσβαση
@@ -2666,7 +2734,7 @@ teams.members=Μέλη Ομάδας
teams.update_settings=Ενημέρωση Ρυθμίσεων
teams.delete_team=Διαγραφή Ομάδας
teams.add_team_member=Προσθήκη Μέλους Ομάδας
-teams.invite_team_member=Πρόσκληση στο %s
+teams.invite_team_member=Πρόσκληση στην ομάδα %s
teams.invite_team_member.list=Εκκρεμείς Προσκλήσεις
teams.delete_team_title=Διαγραφή Ομάδας
teams.delete_team_desc=Η διαγραφή μιας ομάδας ανακαλεί τη πρόσβαση στο αποθετήριο από τα μέλη της. Συνέχεια;
@@ -2683,7 +2751,7 @@ teams.add_all_repos_title=Προσθήκη όλων των αποθετηρίω
teams.add_all_repos_desc=Αυτό θα προσθέσει όλα τα αποθετήρια του οργανισμού στην ομάδα.
teams.add_nonexistent_repo=Το αποθετήριο που προσπαθείτε να προσθέσετε δεν υπάρχει, παρακαλώ δημιουργήστε το πρώτα.
teams.add_duplicate_users=Ο χρήστης είναι ήδη μέλος της ομάδας.
-teams.repos.none=Δεν ήταν δυνατή η πρόσβαση στα αποθετήρια από αυτήν την ομάδα.
+teams.repos.none=Αυτή η ομάδα δεν έχει πρόσβαση σε κανένα αποθετήριο.
teams.members.none=Δεν υπάρχουν μέλη σε αυτήν την ομάδα.
teams.specific_repositories=Συγκεκριμένα αποθετήρια
teams.specific_repositories_helper=Τα μέλη θα έχουν πρόσβαση μόνο σε αποθετήρια που προστίθενται ρητά στην ομάδα. Επιλέγοντας το δεν θα θα αφαιρεθούν αυτόματα τα αποθετήρια που έχουν ήδη προστεθεί με το Όλα τα αποθετήρια .
@@ -2695,6 +2763,7 @@ teams.all_repositories_admin_permission_desc=Αυτή η ομάδα παρέχε
teams.invite.title=Έχετε προσκληθεί να συμμετάσχετε στην ομάδα %s του οργανισμού %s .
teams.invite.by=Προσκλήθηκε από %s
teams.invite.description=Παρακαλώ κάντε κλικ στον παρακάτω σύνδεσμο για συμμετοχή στην ομάδα.
+follow_blocked_user = Δεν μπορείτε να ακολουθήσετε αυτόν τον οργανισμό, επειδή ο οργανισμός σας έχει αποκλείσει.
[admin]
dashboard=Πίνακας Ελέγχου
@@ -2715,10 +2784,10 @@ last_page=Τελευταίο
total=Σύνολο: %d
settings=Ρυθμίσεις Διαχειριστή
-dashboard.new_version_hint=Το Forgejo %s είναι διαθέσιμο, τώρα εκτελείτε το %s. Ανατρέξτε στο blog για περισσότερες λεπτομέρειες.
+dashboard.new_version_hint=Το Forgejo %s είναι διαθέσιμο, χρησιμοποιείτε το %s. Ανατρέξτε στο blog για περισσότερες λεπτομέρειες.
dashboard.statistic=Περίληψη
-dashboard.operations=Λειτουργίες Συντήρησης
-dashboard.system_status=Κατάσταση Συστήματος
+dashboard.operations=Λειτουργίες συντήρησης
+dashboard.system_status=Κατάσταση συστήματος
dashboard.operation_name=Όνομα Λειτουργίας
dashboard.operation_switch=Αλλαγή
dashboard.operation_run=Εκτέλεση
@@ -2737,7 +2806,7 @@ dashboard.cron.error=Σφάλμα στη Προγραμματισμένη Εργ
dashboard.cron.finished=Προγραμματισμένη Εργασία: %[1]s τελείωσε
dashboard.delete_inactive_accounts=Διαγραφή όλων των μη ενεργοποιημένων λογαριασμών
dashboard.delete_inactive_accounts.started=Η διαγραφή όλων των μη ενεργοποιημένων λογαριασμών ξεκίνησε.
-dashboard.delete_repo_archives=Διαγραφή όλων των αρχείων λήψης του αποθετηρίου (ZIP, TAR.GZ, κλπ..)
+dashboard.delete_repo_archives=Διαγραφή όλων των αρχείων λήψης του αποθετηρίου (ZIP, TAR.GZ, κλπ.)
dashboard.delete_repo_archives.started=Η διαγραφή όλων των αρχείων λήψης του αποθετηρίου ξεκίνησε.
dashboard.delete_missing_repos=Διαγραφή όλων των αποθετηρίων που δεν έχουν τα αρχεία Git τους
dashboard.delete_missing_repos.started=Η διαγραφή όλων των αποθετηρίων που δεν έχουν αρχεία Git τους, ξεκίνησε.
@@ -2750,43 +2819,43 @@ dashboard.archive_cleanup=Διαγραφή παλαιών αρχείων λήψ
dashboard.deleted_branches_cleanup=Εκκαθάριση διαγραμμένων κλάδων
dashboard.update_migration_poster_id=Ενημέρωση των ID συντακτών στη μεταγκατάσταση
dashboard.git_gc_repos=Garbage collect όλων των αποθετηρίων
-dashboard.resync_all_sshkeys=Ενημέρωση του αρχείου '.ssh/authorized_keys' με τα κλειδιά SSH του Forgejo.
-dashboard.resync_all_sshprincipals=Ενημέρωση του αρχείου '.ssh/authorized_principals' με τις αρχές SSH του Forgejo.
+dashboard.resync_all_sshkeys=Ενημέρωση του αρχείου «.ssh/authorized_keys» με τα κλειδιά SSH του Forgejo.
+dashboard.resync_all_sshprincipals=Ενημέρωση του αρχείου «.ssh/authorized_principals» με τις αρχές SSH του Forgejo.
dashboard.resync_all_hooks=Επανασυγχρονισμός των hook pre-receive, update και post-receive όλων των αποθετηρίων.
dashboard.reinit_missing_repos=Επανεκκινήστε όλα τα αποθετήρια Git που λείπουν και για τα οποία υπάρχουν εγγραφές
dashboard.sync_external_users=Συγχρονισμός δεδομένων εξωτερικών χρηστών
dashboard.cleanup_hook_task_table=Εκκαθάριση πίνακα hook_task
dashboard.cleanup_packages=Εκκαθάριση ληγμένων πακέτων
-dashboard.cleanup_actions=Οι ενέργειες καθαρισμού καταγραφές και αντικείμενα
-dashboard.server_uptime=Διάρκεια Διακομιστή
+dashboard.cleanup_actions=Καθαρισμός παλιών αρχείων καταγραφής και προϊόντων από Actions
+dashboard.server_uptime=Uptime διακομιστή
dashboard.current_goroutine=Τρέχουσες Goroutines
-dashboard.current_memory_usage=Τρέχουσα Χρήση Μνήμης
-dashboard.total_memory_allocated=Συνολική Μνήμη Που Χρησιμοποιείται
-dashboard.memory_obtained=Μνήμη Που Λαμβάνεται
-dashboard.pointer_lookup_times=Πλήθος Αναζητήσεων Δείκτη
-dashboard.memory_allocate_times=Κατανομές Μνήμης
-dashboard.memory_free_times=Ελευθερώσεις Μνήμης
-dashboard.current_heap_usage=Τρέχουσα Χρήση Heap
-dashboard.heap_memory_obtained=Μνήμη Heap Που Λαμβάνεται
-dashboard.heap_memory_idle=Αδρανής Μνήμη Heap
-dashboard.heap_memory_in_use=Μνήμη Heap Σε Χρήση
-dashboard.heap_memory_released=Μνήμη Heap Που Απελευθερώθηκε
-dashboard.heap_objects=Αντικείμενα στο Heap
-dashboard.bootstrap_stack_usage=Χρήση Στοίβας Bootstrap
-dashboard.stack_memory_obtained=Μνήμη Στοίβας Που Λαμβάνεται
-dashboard.mspan_structures_usage=Χρήση Δομών Mspan
-dashboard.mspan_structures_obtained=Δομές MSpan Που Λαμβάνονται
-dashboard.mcache_structures_usage=Χρήση Δομών MCache
-dashboard.mcache_structures_obtained=Λαμβάνουν Οι Δομές MCache
-dashboard.profiling_bucket_hash_table_obtained=Λαμβάνει ο Profiling Bucket Hash Table
-dashboard.gc_metadata_obtained=Λαμβάνουν Τα Μεταδεδομένα GC
-dashboard.other_system_allocation_obtained=Λαμβάνουν Άλλες Αναθέσεις Συστήματος
-dashboard.next_gc_recycle=Επόμενη Ανακύκλωση GC
+dashboard.current_memory_usage=Τρέχουσα χρήση μνήμης
+dashboard.total_memory_allocated=Συνολική χρησιμοποιούμενη μνήμη
+dashboard.memory_obtained=Μνήμη που λαμβάνεται
+dashboard.pointer_lookup_times=Πλήθος αναζητήσεων δείκτη
+dashboard.memory_allocate_times=Κατανομές μνήμης
+dashboard.memory_free_times=Ελευθερώσεις μνήμης
+dashboard.current_heap_usage=Τρέχουσα χρήση heap
+dashboard.heap_memory_obtained=Μνήμη heap που λαμβάνεται
+dashboard.heap_memory_idle=Αδρανής μνήμη heap
+dashboard.heap_memory_in_use=Μνήμη heap σε χρήση
+dashboard.heap_memory_released=Μνήμη heap που απελευθερώθηκε
+dashboard.heap_objects=Αντικείμενα στο heap
+dashboard.bootstrap_stack_usage=Χρήση στοίβας bootstrap
+dashboard.stack_memory_obtained=Μνήμη στοίβας που λαμβάνεται
+dashboard.mspan_structures_usage=Χρήση δομών MSpan
+dashboard.mspan_structures_obtained=Δομές MSpan που έχουν ληφθεί
+dashboard.mcache_structures_usage=Χρήση δομών MCache
+dashboard.mcache_structures_obtained=Δομές MCache που έχουν ληφθεί
+dashboard.profiling_bucket_hash_table_obtained=Profiling Bucket Hash Table που έχει ληφθεί
+dashboard.gc_metadata_obtained=Μεταδεδομένα GC που έχουν ληφθεί
+dashboard.other_system_allocation_obtained=Άλλες αναθέσεις συστήματος που έχουν ληφθεί
+dashboard.next_gc_recycle=Επόμενη ανακύκλωση GC
dashboard.last_gc_time=Από Την Τελευταία Φορά GC
dashboard.total_gc_time=Σύνολο Παύσης GC
-dashboard.total_gc_pause=Σύνολο Παύσης GC
-dashboard.last_gc_pause=Τελευταία Παύση GC
-dashboard.gc_times=Πλήθος GC
+dashboard.total_gc_pause=Σύνολο παύσης GC
+dashboard.last_gc_pause=Τελευταία παύση GC
+dashboard.gc_times=Χρόνοι GC
dashboard.delete_old_actions=Διαγραφή όλων των παλαιών ενεργειών από τη βάση δεδομένων
dashboard.delete_old_actions.started=Η διαγραφή όλων των παλιών ενεργειών από τη βάση δεδομένων ξεκίνησε.
dashboard.update_checker=Ελεγκτής ενημερώσεων
@@ -2799,7 +2868,7 @@ dashboard.start_schedule_tasks=Έναρξη προγραμματισμένων
dashboard.sync_branch.started=Ο Συγχρονισμός των Κλάδων ξεκίνησε
dashboard.rebuild_issue_indexer=Αναδόμηση ευρετηρίου ζητημάτων
-users.user_manage_panel=Διαχείριση Λογαριασμών Χρηστών
+users.user_manage_panel=Διαχείριση λογαριασμών χρηστών
users.new_account=Δημιουργία Λογαριασμού Χρήστη
users.name=Όνομα Χρήστη
users.full_name=Πλήρες Όνομα
@@ -2807,15 +2876,15 @@ users.activated=Ενεργοποιήθηκε
users.admin=Διαχειριστής
users.restricted=Περιορισμένος
users.reserved=Δεσμευμένο
-users.bot=Bot
+users.bot=Ρομπότ
users.remote=Απομακρυσμένο
users.2fa=2FA
users.repos=Αποθετήρια
users.created=Δημιουργήθηκε
users.last_login=Τελευταία Σύνδεση
users.never_login=Καμία Σύνδεση
-users.send_register_notify=Αποστολή Ειδοποίησης Εγγραφής Χρήστη
-users.new_success=Ο λογαριασμός χρήστη "%s" δημιουργήθηκε.
+users.send_register_notify=Αποστολή ειδοποιήσεων εγγραφής χρήστη
+users.new_success=Ο λογαριασμός χρήστη «%s» δημιουργήθηκε.
users.edit=Επεξεργασία
users.auth_source=Πηγή Ταυτοποίησης
users.local=Τοπική
@@ -2826,11 +2895,11 @@ users.edit_account=Επεξεργασία Λογαριασμού Χρήστη
users.max_repo_creation=Μέγιστος Αριθμός Αποθετηρίων
users.max_repo_creation_desc=(Ορίστε -1 για να χρησιμοποιήσετε το προκαθορισμένο όριο.)
users.is_activated=Ο Λογαριασμός Χρήστη Ενεργοποιήθηκε
-users.prohibit_login=Απενεργοποίηση Σύνδεσης
+users.prohibit_login=Απενεργοποίηση εισόδου
users.is_admin=Είναι Διαχειριστής
users.is_restricted=Είναι Περιορισμένος
users.allow_git_hook=Μπορεί Να Δημιουργεί Git Hooks
-users.allow_git_hook_tooltip=Τα Git Hooks εκτελούνται ως ο χρήστης του ΛΣ που εκτελεί το Forgejo και θα έχουν το ίδιο επίπεδο πρόσβασης στο διακομιστή. Ως αποτέλεσμα, οι χρήστες με αυτό το ειδικό προνόμιο Git Hook μπορούν να έχουν πρόσβαση και να τροποποιήσουν όλα τα αποθετήρια του Forgejo, καθώς και τη βάση δεδομένων που χρησιμοποιεί το Forgejo. Κατά συνέπεια, είναι επίσης σε θέση να αποκτήσουν προνόμια διαχειριστή του Forgejo.
+users.allow_git_hook_tooltip=Τα Git Hooks εκτελούνται μέσω του χρήστη που εκτελεί το Forgejo και θα έχουν το ίδιο επίπεδο πρόσβασης στο διακομιστή. Ως αποτέλεσμα, οι χρήστες με αυτό το ειδικό προνόμιο Git Hook μπορούν να έχουν πρόσβαση και να τροποποιήσουν όλα τα αποθετήρια του Forgejo, καθώς και τη βάση δεδομένων που χρησιμοποιεί το Forgejo. Κατά συνέπεια, αυτοί οι χρήστες θα είναι σε θέση να αποκτήσουν δικαιώματα διαχειριστή στο Forgejo.
users.allow_import_local=Μπορεί Να Εισάγει Τοπικά Αποθετήρια
users.allow_create_organization=Μπορεί Να Δημιουργεί Οργανισμούς
users.update_profile=Ενημέρωση Λογαριασμού Χρήστη
@@ -2839,7 +2908,7 @@ users.cannot_delete_self=Δεν μπορείτε να διαγράψετε το
users.still_own_repo=Αυτός ο χρήστης εξακολουθεί να κατέχει ένα ή περισσότερα αποθετήρια. Διαγράψτε ή μεταφέρετε αυτά τα αποθετήρια πρώτα.
users.still_has_org=Αυτός ο χρήστης είναι μέλος ενός οργανισμού. Αφαιρέστε πρώτα τον χρήστη από οποιονδήποτε οργανισμό.
users.purge=Εκκαθάριση Χρήστη
-users.purge_help=Αναγκαστική διαγραφή χρήστη και των αποθετηρίων, οργανισμών και πακέτων που του ανήκουν. Όλα τα σχόλια επίσης θα διαγραφούν.
+users.purge_help=Εξαναγκαστική διαγραφή χρήστη καθώς και των αποθετηρίων, οργανισμών και πακέτων που του ανήκουν. Όλα τα σχόλια και τα ζητήματα του χρήστη θα διαγραφούν επίσης.
users.still_own_packages=Αυτός ο χρήστης εξακολουθεί να κατέχει ένα ή περισσότερα πακέτα, διαγράψτε αυτά τα πακέτα πρώτα.
users.deletion_success=Ο λογαριασμός χρήστη έχει διαγραφεί.
users.reset_2fa=Επαναφορά 2FA
@@ -2857,7 +2926,7 @@ users.list_status_filter.is_2fa_enabled=2FA Ενεργοποιημένο
users.list_status_filter.not_2fa_enabled=2FA Απενεργοποιημένο
users.details=Λεπτομέρειες Χρήστη
-emails.email_manage_panel=Διαχείριση Email Χρήστη
+emails.email_manage_panel=Διαχείριση email χρηστών
emails.primary=Κύριο
emails.activated=Ενεργοποιήθηκε
emails.filter_sort.email=Email
@@ -2870,13 +2939,13 @@ emails.duplicate_active=Αυτή η διεύθυνση email είναι ήδη
emails.change_email_header=Ενημέρωση Ιδιοτήτων Email
emails.change_email_text=Είστε βέβαιοι ότι θέλετε να ενημερώσετε αυτή τη διεύθυνση email;
-orgs.org_manage_panel=Διαχείριση Οργανισμού
+orgs.org_manage_panel=Διαχείριση οργανισμών
orgs.name=Όνομα
orgs.teams=Ομάδες
orgs.members=Μέλη
orgs.new_orga=Νέος Οργανισμός
-repos.repo_manage_panel=Διαχείριση Αποθετηρίου
+repos.repo_manage_panel=Διαχείριση αποθετηρίων
repos.unadopted=Μη Υιοθετημένα Αποθετήρια
repos.unadopted.no_more=Δεν βρέθηκαν μη υιοθετημένα αποθετήρια
repos.owner=Ιδιοκτήτης
@@ -2889,7 +2958,7 @@ repos.issues=Ζητήματα
repos.size=Μέγεθος
repos.lfs_size=Μέγεθος LFS
-packages.package_manage_panel=Διαχείριση Πακέτων
+packages.package_manage_panel=Διαχείριση πακέτων
packages.total_size=Συνολικό Μέγεθος: %s
packages.unreferenced_size=Μέγεθος Χωρίς Αναφορά: %s
packages.cleanup=Εκκαθάριση ληγμένων δεδομένων
@@ -2903,17 +2972,17 @@ packages.repository=Αποθετήριο
packages.size=Μέγεθος
packages.published=Δημοσιευμένα
-defaulthooks=Προεπιλεγμένα Webhooks
+defaulthooks=Προεπιλεγμένα webhooks
defaulthooks.desc=Τα Webhooks κάνουν αυτόματα αιτήσεις HTTP POST σε ένα διακομιστή όταν ενεργοποιούν ορισμένα γεγονότα στο Gitea. Τα Webhooks που ορίζονται εδώ είναι προκαθορισμένα και θα αντιγραφούν σε όλα τα νέα αποθετήρια. Διαβάστε περισσότερα στον οδηγό webhooks .
defaulthooks.add_webhook=Προσθήκη Προεπιλεγμένου Webhook
defaulthooks.update_webhook=Ενημέρωση Προεπιλεγμένου Webhook
-systemhooks=Webhooks Συστήματος
+systemhooks=Webhooks συστήματος
systemhooks.desc=Τα Webhooks κάνουν αυτόματα αιτήσεις HTTP POST σε ένα διακομιστή όταν ενεργοποιούνται ορισμένα γεγονότα στο Gitea. Τα Webhooks που ορίζονται εδώ θα ενεργούν σε όλα τα αποθετήρια του συστήματος, γι 'αυτό παρακαλώ εξετάστε τυχόν επιπτώσεις απόδοσης που μπορεί να έχει. Διαβάστε περισσότερα στον οδηγό webhooks .
systemhooks.add_webhook=Προσθήκη Webhook Συστήματος
systemhooks.update_webhook=Ενημέρωση Webhook Συστήματος
-auths.auth_manage_panel=Διαχείριση ΠηγήςΤαυτοποίησης
+auths.auth_manage_panel=Διαχείριση πηγών ταυτοποίησης
auths.new=Προσθήκη Πηγής Ταυτοποίησης
auths.name=Όνομα
auths.type=Τύπος
@@ -2944,7 +3013,7 @@ auths.search_page_size=Μέγεθος Σελίδας
auths.filter=Φίλτρο Χρηστών
auths.admin_filter=Φίλτρο Διαχειριστών
auths.restricted_filter=Φίλτρο Περιορισμένων
-auths.restricted_filter_helper=Αφήστε κενό για να μην ορίσετε κανέναν χρήστη ως περιορισμένο. Χρησιμοποιήστε έναν αστερίσκο ('*') για να ορίσετε όλους τους χρήστες που δεν ταιριάζουν με το φίλτρο διαχειριστή ως περιορισμένους.
+auths.restricted_filter_helper=Αφήστε κενό για να μην περιορίσετε κανέναν χρήστη. Χρησιμοποιήστε έναν αστερίσκο ('*') για να περιορίσετε όλους τους χρήστες που δεν είναι διαχειριστές.
auths.verify_group_membership=Επαλήθευση της συμμετοχής σε ομάδα στο LDAP (αφήστε το φίλτρο κενό για παράλειψη)
auths.group_search_base=DN Βάσης Αναζήτησης Ομάδων
auths.group_attribute_list_users=Χαρακτηριστικό Ομάδας Που Περιέχει Τη Λίστα Χρηστών
@@ -2953,12 +3022,12 @@ auths.map_group_to_team=Αντιστοίχιση ομάδων LDAP σε ομάδ
auths.map_group_to_team_removal=Αφαίρεση χρηστών από τις συγχρονισμένες ομάδες αν ο χρήστης δεν ανήκει στην αντίστοιχη ομάδα LDAP
auths.enable_ldap_groups=Ενεργοποίηση ομάδων LDAP
auths.ms_ad_sa=Χαρακτηριστικά Αναζήτησης Στο MS AD
-auths.smtp_auth=Τύπος Ταυτοποίησης SMTP
+auths.smtp_auth=Τύπος ταυτοποίησης SMTP
auths.smtphost=Διακομιστής SMTP
auths.smtpport=Θύρα SMTP
auths.allowed_domains=Επιτρεπόμενα Domains
auths.allowed_domains_helper=Αφήστε κενό για να επιτρέψετε όλα τα domains. Διαχωρίστε τα πολλαπλά domains με κόμμα (',').
-auths.skip_tls_verify=Παράλειψη TLS Verify
+auths.skip_tls_verify=Παράλειψη επαλήθευσης TLS
auths.force_smtps=Αναγκαστικό SMTPS
auths.force_smtps_helper=Το SMTPS χρησιμοποιείται συνήθως στη θύρα 465. Ορίστε αυτό το πεδίο για χρήση του SMTPS σε άλλες θύρες. (Αλλιώς το STARTTLS θα χρησιμοποιηθεί σε άλλες θύρες αν υποστηρίζεται από τον διακομιστή.)
auths.helo_hostname=Όνομα διακομιστή στο HELO
@@ -2989,7 +3058,7 @@ auths.oauth2_admin_group=Τιμή Group Claim για διαχειριστές. (
auths.oauth2_restricted_group=Τιμή Group Claim για περιορισμένους χρήστες (Προαιρετικό - απαιτεί όνομα claim παραπάνω)
auths.oauth2_map_group_to_team=Αντιστοίχιση των απαιτούμενων ομάδων σε ομάδες Οργανισμού. (Προαιρετικό - απαιτείται το όνομα της απαίτησης παραπάνω)
auths.oauth2_map_group_to_team_removal=Αφαίρεση χρηστών από τις συγχρονισμένες ομάδες, εάν ένας χρήστης δεν ανήκει στην αντίστοιχη ομάδα.
-auths.enable_auto_register=Ενεργοποίηση Αυτόματης Εγγραφής
+auths.enable_auto_register=Ενεργοποίηση αυτόματης εγγραφής
auths.sspi_auto_create_users=Αυτόματη δημιουργία χρηστών
auths.sspi_auto_create_users_helper=Επιτρέψτε στη μέθοδο πιστοποίησης SSPI να δημιουργεί αυτόματα νέους λογαριασμούς για χρήστες που συνδέονται για πρώτη φορά
auths.sspi_auto_activate_users=Αυτόματη ενεργοποίηση χρηστών
@@ -3004,10 +3073,10 @@ auths.tips=Συμβουλές
auths.tips.oauth2.general=Ταυτοποίηση OAuth2
auths.tips.oauth2.general.tip=Κατά την εγγραφή μιας νέας ταυτοποίησης OAuth2, το URL κλήσης/ανακατεύθυνσης πρέπει να είναι:
auths.tip.oauth2_provider=Πάροχος OAuth2
-auths.tip.bitbucket=Καταχωρήστε ένα νέο καταναλωτή OAuth στο https://bitbucket.org/account/user//oauth-consumers/new και προσθέστε το δικαίωμα 'Account' - 'Read'
-auths.tip.nextcloud=`Καταχωρήστε ένα νέο καταναλωτή OAuth στην υπηρεσία σας χρησιμοποιώντας το παρακάτω μενού "Settings -> Security -> OAuth 2.0 client"`
+auths.tip.bitbucket=Καταχωρήστε έναν νέο καταναλωτή OAuth στο https://bitbucket.org/account/user//oauth-consumers/new και προσθέστε το δικαίωμα 'Account' - 'Read'
+auths.tip.nextcloud=Καταχωρήστε ένα νέο καταναλωτή OAuth στην υπηρεσία σας χρησιμοποιώντας το παρακάτω μενού "Settings -> Security -> OAuth 2.0 client"
auths.tip.dropbox=Δημιουργήστε μια νέα εφαρμογή στο https://www.dropbox.com/developers/apps
-auths.tip.facebook=`Καταχωρήστε μια νέα εφαρμογή στο https://developers.facebook.com/apps και προσθέστε το προϊόν "Facebook Login"`
+auths.tip.facebook=Καταχωρήστε μια νέα εφαρμογή στο https://developers.facebook.com/apps και προσθέστε το προϊόν "Facebook Login"
auths.tip.github=Καταχωρήστε μια νέα εφαρμογή OAuth στο https://github.com/settings/applications/new
auths.tip.gitlab=Καταχωρήστε μια νέα εφαρμογή στο https://gitlab.com/profile/applications
auths.tip.google_plus=Αποκτήστε τα διαπιστευτήρια πελάτη OAuth2 από την κονσόλα API της Google στο https://console.developers.google.com/
@@ -3018,57 +3087,57 @@ auths.tip.gitea=Καταχωρήστε μια νέα εφαρμογή OAuth2. Μ
auths.tip.yandex=`Δημιουργήστε μια νέα εφαρμογή στο https://oauth.yandex.com/client/new. Επιλέξτε τα ακόλουθα δικαιώματα από την ενότητα "Yandex.Passport API": "Access to email address", "Access to user avatar" και "Access to username, first name and surname, gender"`
auths.tip.mastodon=Εισαγάγετε ένα προσαρμομένο URL για την υπηρεσία mastodon με την οποία θέλετε να πιστοποιήσετε (ή να χρησιμοποιήσετε την προεπιλεγμένη)
auths.edit=Επεξεργασία Πηγής Ταυτοποίησης
-auths.activated=Αυτή η Πηγή Ταυτοποίησης είναι Ενεργοποιημένη
-auths.new_success=Ο ταυτοποίηση "%s" προστέθηκε.
-auths.update_success=Η πηγή ταυτοποίησης έχει ενημερωθεί.
+auths.activated=Αυτή η πηγή είναι ενεργοποιημένη
+auths.new_success=Το μέσο ταυτοποίησης «%s» προστέθηκε.
+auths.update_success=Η πηγή ενημερώθηκε.
auths.update=Ενημέρωση Πηγής Ταυτοποίησης
auths.delete=Διαγραφή Πηγής Ταυτοποίησης
auths.delete_auth_title=Διαγραφή Πηγής Ταυτοποίησης
-auths.delete_auth_desc=Η διαγραφή μιας πηγής ταυτοποίησης αποτρέπει τους χρήστες να τη χρησιμοποιούν για να συνδεθούν. Συνέχεια;
-auths.still_in_used=Η πηγή ταυτοποίησης είναι ακόμα σε χρήση. Μετατρέψτε ή διαγράψτε χρηστών που χρησιμοποιούν αυτήν την πηγή πρώτα.
+auths.delete_auth_desc=Αν διαγράψετε την πηγής ταυτοποίησης, οι χρήστες σας δεν θα μπορέσουν να τη χρησιμοποιήσουν πλέον για να συνδεθούν. Συνέχεια;
+auths.still_in_used=Η πηγή ταυτοποίησης βρίσκεται ακόμα σε χρήση. Για να συνεχίσετε, πρέπει πρώτα να διαγράψετε ή να μετατρέψετε τους χρήστες που χρησιμοποιούν αυτήν την πηγή.
auths.deletion_success=Η πηγή ταυτοποίησης έχει διαγραφεί.
-auths.login_source_exist=Υπάρχει ήδη η πηγή ταυτοποίησης "%s".
-auths.login_source_of_type_exist=Υπάρχει ήδη πηγή ταυτοποίησης αυτού του τύπου.
+auths.login_source_exist=Η πηγή ταυτοποίησης «%s» υπάρχει ήδη.
+auths.login_source_of_type_exist=Υπάρχει ήδη μία πηγή ταυτοποίησης αυτού του τύπου.
auths.unable_to_initialize_openid=Αδυναμία εκκίνησης του παρόχου OpenID Connect: %s
auths.invalid_openIdConnectAutoDiscoveryURL=Μη έγκυρο Auto Discovery URL (πρέπει να είναι ένα έγκυρο URL που ξεκινά με http:// ή https://)
-config.server_config=Ρυθμίσεις Διακομιστή
-config.app_name=Τίτλος Ιστοτόπου
+config.server_config=Ρυθμίσεις διακομιστή
+config.app_name=Τίτλος ιστοτόπου
config.app_ver=Έκδοση Forgejo
-config.app_url=Βασικό URL Του Forgejo
-config.custom_conf=Διαδρομή Αρχείου Ρυθμίσεων
-config.custom_file_root_path=Προσαρμοσμένη Βασική Διαδρομή Αρχείου
-config.domain=Domain Διακομιστή
-config.offline_mode=Τοπική Λειτουργία
-config.disable_router_log=Απενεργοποίηση Καταγραφής Δρομολογητή
-config.run_user=Εκτέλεση Σαν Χρήστη
-config.run_mode=Λειτουργία Εκτέλεσης
+config.app_url=Βασικό URL
+config.custom_conf=Τοποθεσία αρχείου ρυθμίσεων
+config.custom_file_root_path=Προσαρμοσμένη τοποθεσία αρχείων
+config.domain=Domain διακομιστή
+config.offline_mode=Τοπική λειτουργία
+config.disable_router_log=Απενεργοποίηση καταγραφής δρομολογητή
+config.run_user=Εκτέλεση ως
+config.run_mode=Λειτουργία εκτέλεσης
config.git_version=Έκδοση Git
-config.app_data_path=Διαδρομή Δεδομένων Εφαρμογής
-config.repo_root_path=Ριζική Διαδρομή Αποθετηρίων
-config.lfs_root_path=Ριζική Διαδρομή LFS
-config.log_file_root_path=Διαδρομή Καταγραφών
-config.script_type=Τύπος Σεναρίου
-config.reverse_auth_user=Χρήστης ΑντίστροφηςΠιστοποίησης
+config.app_data_path=Τοποθεσία δεδομένων εφαρμογής
+config.repo_root_path=Τοποθεσία αποθετηρίων
+config.lfs_root_path=Τοποθεσία LFS
+config.log_file_root_path=Τοποθεσία αρχείων καταγραφής
+config.script_type=Τύπος σεναρίου
+config.reverse_auth_user=Χρήστης αντίστροφης πιστοποίησης
-config.ssh_config=Ρύθμιση SSH
+config.ssh_config=Ρυθμίσεις SSH
config.ssh_enabled=Ενεργοποιημένο
-config.ssh_start_builtin_server=Χρήση Ενσωματωμένου Διακομιστή
-config.ssh_domain=Domain Διακομιστή SSH
+config.ssh_start_builtin_server=Χρήση ενσωματωμένου διακομιστή
+config.ssh_domain=Domain διακομιστή SSH
config.ssh_port=Θύρα
config.ssh_listen_port=Θύρα Ακρόασης
config.ssh_root_path=Ριζική Διαδρομή
-config.ssh_key_test_path=Διαδρομή Δοκιμής Κλειδιού
-config.ssh_keygen_path=Διαδρομή Keygen ('ssh-keygen')
-config.ssh_minimum_key_size_check=Έλεγχος Ελάχιστου Μεγέθους Κλειδιού
-config.ssh_minimum_key_sizes=Ελάχιστα Μεγέθη Κλειδιών
+config.ssh_key_test_path=Διαδρομή δοκιμής κλειδιού
+config.ssh_keygen_path=Διαδρομή keygen («ssh-keygen»)
+config.ssh_minimum_key_size_check=Έλεγχος ελάχιστου μεγέθους κλειδιού
+config.ssh_minimum_key_sizes=Ελάχιστα μεγέθη κλειδιών
-config.lfs_config=Ρύθμιση LFS
+config.lfs_config=Ρυθμίσεις LFS
config.lfs_enabled=Ενεργοποιημένο
-config.lfs_content_path=Διαδρομή Περιεχομένου LFS
-config.lfs_http_auth_expiry=LFS Λήξη Ταυτοποίησης HTTP
+config.lfs_content_path=Τοποθεσία περιεχομένου LFS
+config.lfs_http_auth_expiry=Χρονικό όριο ταυτοποίησης HTTP LFS
-config.db_config=Ρύθμιση Βάσης Δεδομένων
+config.db_config=Ρυθμίσεις βάσης δεδομένων
config.db_type=Τύπος
config.db_host=Διακομιστής
config.db_name=Όνομα
@@ -3077,34 +3146,34 @@ config.db_schema=Σχήμα
config.db_ssl_mode=SSL
config.db_path=Διαδρομή
-config.service_config=Ρυθμίσεις Υπηρεσίας
-config.register_email_confirm=Απαιτείται Επιβεβαίωση του Email για Εγγραφή
-config.disable_register=Απενεργοποίηση Αυτοεγγραφής
-config.allow_only_internal_registration=Να Επιτρέπεται η Εγγραφή Μόνο Μέσω του Forgejo
-config.allow_only_external_registration=Να Επιτρέπεται Η Εγγραφή Μόνο Μέσω Εξωτερικών Υπηρεσιών
-config.enable_openid_signup=Ενεργοποίηση Αυτο-Εγγραφής OpenID
-config.enable_openid_signin=Ενεργοποίηση Σύνδεσης μέσω OpenID
-config.show_registration_button=Εμφάνιση Κουμπιού Εγγραφής
-config.require_sign_in_view=Απαιτείται Είσοδος για Προβολή Σελίδων
-config.mail_notify=Ενεργοποίηση Ειδοποιήσεων Email
+config.service_config=Ρυθμίσεις υπηρεσίας
+config.register_email_confirm=Να απαιτείται η επιβεβαίωση της διεύθυνσης email για την δημιουργία ενός λογαριασμού
+config.disable_register=Απενεργοποίηση αυτο-εγγραφής
+config.allow_only_internal_registration=Να επιτρέπονται εγγραφές μόνο μέσω του Forgejo
+config.allow_only_external_registration=Να επιτρέπονται εγγραφές μόνο με την χρήση εξωτερικών υπηρεσιών
+config.enable_openid_signup=Ενεργοποίηση αυτο-εγγραφής OpenID
+config.enable_openid_signin=Ενεργοποίηση σύνδεσης μέσω OpenID
+config.show_registration_button=Εμφάνιση κουμπιού εγγραφής
+config.require_sign_in_view=Να απαιτείται είσοδος για την προβολή σελίδων
+config.mail_notify=Ενεργοποίηση ειδοποιήσεων email
config.enable_captcha=Ενεργοποίηση CAPTCHA
-config.active_code_lives=Ζωή Ενεργού Κωδικού
-config.reset_password_code_lives=Λήξη Χρόνου Κωδικού Ανάκτησης του Λογαριασμού
-config.default_keep_email_private=Απόκρυψη Διευθύνσεων Email από Προεπιλογή
-config.default_allow_create_organization=Να Επιτρέπεται η Δημιουργία Οργανισμών από Προεπιλογή
-config.enable_timetracking=Ενεργοποίηση Καταγραφής Χρόνου
-config.default_enable_timetracking=Ενεργοποίηση Καταγραφής Χρόνου σαν Προεπιλογή
-config.default_allow_only_contributors_to_track_time=Επιτρέπονται Μόνο οι Συμμετέχοντες να Καταγράφουν Χρόνο
-config.no_reply_address=Κρυφό Email Domain
-config.default_visibility_organization=Προεπιλεγμένη ορατότητα για νέους οργανισμούς
-config.default_enable_dependencies=Ενεργοποίηση Εξαρτήσεων Ζητημάτων από Προεπιλογή
+config.active_code_lives=Χρόνος λήξης κωδικών ενεργοποίησης
+config.reset_password_code_lives=Χρόνος λήξης κωδικού ανάκτησης ενός λογαριασμού
+config.default_keep_email_private=Να αποκρύπτονται οι διευθύνσεις email από προεπιλογή
+config.default_allow_create_organization=Να επιτρέπεται η δημιουργία οργανισμών από προεπιλογή
+config.enable_timetracking=Ενεργοποίηση καταγραφής χρόνου
+config.default_enable_timetracking=Ενεργοποίηση καταγραφής χρόνου από προεπιλογή
+config.default_allow_only_contributors_to_track_time=Να επιτρέπεται η καταγραφή χρόνου μόνο από συνεισφέροντες
+config.no_reply_address=Κρυφό email domain
+config.default_visibility_organization=Προεπιλεγμένη ορατότητα νέων οργανισμών
+config.default_enable_dependencies=Ενεργοποίηση εξαρτήσεων ζητημάτων από προεπιλογή
-config.webhook_config=Ρύθμιση Webhook
-config.queue_length=Μέγεθος Ουράς
-config.deliver_timeout=Χρονικό Όριο Παράδοσης
-config.skip_tls_verify=Παράλειψη Επαλήθευσης TLS
+config.webhook_config=Ρύθμιση webhook
+config.queue_length=Μέγεθος ουράς
+config.deliver_timeout=Χρονικό όριο παράδοσης
+config.skip_tls_verify=Παράλειψη επαλήθευσης TLS
-config.mailer_config=Ρυθμίσεις Αλληλογραφίας
+config.mailer_config=Ρυθμίσεις αλληλογραφίας
config.mailer_enabled=Ενεργοποιημένο
config.mailer_enable_helo=Ενεργοποίηση HELO
config.mailer_name=Όνομα
@@ -3120,56 +3189,56 @@ config.mailer_use_dummy=Ψεύτικο
config.test_email_placeholder=Email (π.χ. test@example.com)
config.send_test_mail=Αποστολή Δοκιμαστικού Email
config.send_test_mail_submit=Αποστολή
-config.test_mail_failed=Αποτυχία αποστολής ενός δοκιμαστικού email στο"%s": %v
-config.test_mail_sent=Στάλθηκε ένα δοκιμαστικό email στο "%s".
+config.test_mail_failed=Η αποστολή δοκιμαστικού email στο «%s» απέτυχε: %v
+config.test_mail_sent=Στάλθηκε ένα δοκιμαστικό email στο «%s».
config.oauth_config=Ρύθμιση Oauth
config.oauth_enabled=Ενεργό
-config.cache_config=Ρύθμιση Προσωρινής Αποθήκευσης
-config.cache_adapter=Προσαρμογέας Προσωρινής Αποθήκευσης
-config.cache_interval=Διάστημα Προσωρινής Αποθήκευσης
+config.cache_config=Ρύθμιση προσωρινής αποθήκευσης
+config.cache_adapter=Προσαρμογέας προσωρινής αποθήκευσης
+config.cache_interval=Διάστημα προσωρινής αποθήκευσης
config.cache_conn=Σύνδεση Προσωρινής Αποθήκευσης
config.cache_item_ttl=TTL Στοιχείων Προσωρινής Αποθήκευσης
-config.session_config=Ρύθμιση Συνεδρίας
-config.session_provider=Πάροχος Συνεδρίας
-config.provider_config=Ρυθμίσεις Πάροχου
-config.cookie_name=Όνομα Cookie
-config.gc_interval_time=Χρόνος Διαστήματος GC
-config.session_life_time=Χρόνος Ζωής Συνεδρίας
+config.session_config=Ρυθμίσεις συνεδρίας
+config.session_provider=Πάροχος συνεδρίας
+config.provider_config=Ρυθμίσεις παρόχου
+config.cookie_name=Όνομα cookie
+config.gc_interval_time=Χρόνος διαστήματος GC
+config.session_life_time=Χρόνος ζωής συνεδρίας
config.https_only=Μόνο HTTPS
-config.cookie_life_time=Χρόνος Ζωής Cookie
+config.cookie_life_time=Χρόνος ζωής Cookie
-config.picture_config=Ρύθμιση Εικόνας και Avatar
-config.picture_service=Υπηρεσία Εικόνας
+config.picture_config=Ρυθμίσεις εικόνας και avatar
+config.picture_service=Υπηρεσία εικόνας
config.disable_gravatar=Απενεργοποίηση Gravatar
-config.enable_federated_avatar=Ενεργοποίηση Ομόσπονδων Avatars
+config.enable_federated_avatar=Ενεργοποίηση αποκεντρωμένων avatar
-config.git_config=Ρύθμιση Git
-config.git_disable_diff_highlight=Απενεργοποίηση Επισήμανσης Σύνταξης Diff
-config.git_max_diff_lines=Μέγιστες γραμμές Diff (για ένα μόνο αρχείο)
-config.git_max_diff_line_characters=Μέγιστος αριθμός χαρακτήρων Diff (για μία γραμμή)
-config.git_max_diff_files=Μέγιστος αριθμός Diff αρχείων (για εμφάνιση)
+config.git_config=Ρυθμίσεις Git
+config.git_disable_diff_highlight=Απενεργοποίηση επισήμανσης σύνταξης diff
+config.git_max_diff_lines=Μέγιστες γραμμές diff ανά αρχείο
+config.git_max_diff_line_characters=Μέγιστος αριθμός χαρακτήρων diff ανά γραμμή
+config.git_max_diff_files=Μέγιστος αριθμός εμφανιζόμενων αρχείων ανά diff
config.git_gc_args=Παράμετροι GC
-config.git_migrate_timeout=Χρονικό Όριο Μεταφοράς
-config.git_mirror_timeout=Χρονικό Όριο Ενημέρωσης Ειδώλου
-config.git_clone_timeout=Χρονικό Όριο Κλωνοποίησης
-config.git_pull_timeout=Χρονικό Όριο Pull
-config.git_gc_timeout=Χρονικό Όριο Λειτουργίας GC
+config.git_migrate_timeout=Χρονικό όριο μεταφοράς
+config.git_mirror_timeout=Χρονικό όριο ενημέρωσης ειδώλου
+config.git_clone_timeout=Χρονικό όριο κλωνοποίησης
+config.git_pull_timeout=Χρονικό όριο pull
+config.git_gc_timeout=Χρονικό όριο λειτουργίας GC
-config.log_config=Ρύθμιση Καταγραφών
+config.log_config=Ρυθμίσεις Καταγραφών
config.logger_name_fmt=Καταγραφέας: %s
config.disabled_logger=Απενεργοποιημένο
-config.access_log_mode=Λειτουργία Καταγραφών Πρόσβασης
-config.access_log_template=Πρότυπο Καταγραφής Προσβάσεων
+config.access_log_mode=Λειτουργία καταγραφών πρόσβασης
+config.access_log_template=Πρότυπο καταγραφής προσβάσεων
config.xorm_log_sql=Καταγραφή SQL
-config.set_setting_failed=Αποτυχία ορισμού της ρύθμισης %s
+config.set_setting_failed=Ο ορισμός της ρύθμισης %s απέτυχε
monitor.stats=Στατιστικά
-monitor.cron=Προγραμματισμένες Εργασίες
+monitor.cron=Προγραμματισμένες εργασίες
monitor.name=Όνομα
monitor.schedule=Πρόγραμμα
monitor.next=Επόμενη Ώρα
@@ -3208,8 +3277,8 @@ monitor.queue.settings.changed=Οι Ρυθμίσεις Ενημερώθηκαν
monitor.queue.settings.remove_all_items=Αφαίρεση όλων
monitor.queue.settings.remove_all_items_done=Όλα τα αντικείμενα στην ουρά αφαιρέθηκαν.
-notices.system_notice_list=Ειδοποιήσεις Συστήματος
-notices.view_detail_header=Προβολή Λεπτομερειών Ειδοποίησης
+notices.system_notice_list=Ειδοποιήσεις συστήματος
+notices.view_detail_header=Προβολή λεπτομερειών ειδοποίησης
notices.operations=Λειτουργίες
notices.select_all=Επιλογή Όλων
notices.deselect_all=Αποεπιλογή Όλων
@@ -3222,6 +3291,7 @@ notices.type_2=Εργασία
notices.desc=Περιγραφή
notices.op=Λειτ.
notices.delete_success=Οι ειδοποιήσεις του συστήματος έχουν διαγραφεί.
+self_check.no_problem_found = Μέχρι τώρα, δεν έχει βρεθεί κάποιο πρόβλημα.
[action]
@@ -3303,8 +3373,8 @@ error.extract_sign=Αποτυχία εξαγωγής υπογραφής
error.generate_hash=Αποτυχία δημιουργίας του κατακερματισμού (hash) της υποβολής
error.no_committer_account=Δεν υπάρχει λογαριασμός συνδεδεμένος με τη διεύθυνση email του υποβολέα
error.no_gpg_keys_found=Δεν βρέθηκε γνωστό κλειδί για αυτήν την υπογραφή στη βάση δεδομένων
-error.not_signed_commit=Δεν είναι υπογεγραμμένη υποβολή
-error.failed_retrieval_gpg_keys=Αποτυχία ανάκτησης ενός κλειδιού που είναι συνδεδεμένο στο λογαριασμό του υποβολέα
+error.not_signed_commit=Η υποβολή δεν είναι υπογεγραμμένη
+error.failed_retrieval_gpg_keys=Αποτυχία ανάκτησης κλειδιού που είναι συνδεδεμένο στο λογαριασμό του υποβολέα
error.probable_bad_signature=ΠΡΟΣΟΧΗ! Αν και υπάρχει ένα κλειδί με αυτό το ID στη βάση δεδομένων δεν επαληθεύει αυτή την υποβολή! Αυτή η υποβολή είναι ΥΠΟΠΤΗ.
error.probable_bad_default_signature=ΠΡΟΣΟΧΗ! Αν και το προεπιλεγμένο κλειδί έχει αυτό το ID, δεν επαληθεύει αυτή την υποβολή! Αυτή η υποβολή είναι ΥΠΟΠΤΗ.
@@ -3428,10 +3498,10 @@ settings.link.success=Ο σύνδεσμος αποθετηρίου ενημερ
settings.link.error=Αποτυχία ενημέρωσης συνδέσμου αποθετηρίου.
settings.delete=Διαγραφή πακέτου
settings.delete.description=Η διαγραφή ενός πακέτου είναι μόνιμη και δεν μπορεί να αναιρεθεί.
-settings.delete.notice=Πρόκειται να διαγράψετε %s (%s). Αυτή η λειτουργία είναι μη αναστρέψιμη, είστε σίγουροι;
+settings.delete.notice=Πρόκειται να διαγράψετε το %s (%s). Αυτή η διαδικασία είναι μη αναστρέψιμη, είστε σίγουροι;
settings.delete.success=Το πακέτο έχει διαγραφεί.
settings.delete.error=Αποτυχία διαγραφής του πακέτου.
-owner.settings.cargo.title=Ευρετήριο Μητρώου Cargo
+owner.settings.cargo.title=Ευρετήριο μητρώου Cargo
owner.settings.cargo.initialize=Αρχικοποίηση Ευρετηρίου
owner.settings.cargo.initialize.description=Απαιτείται ένα ειδικό αποθετήριο ευρετηρίου Git για τη χρήση του μητρώου Cargo. Χρησιμοποιώντας αυτή την επιλογή θα δημιουργηθεί ξανά το αποθετήριο και θα ρυθμιστεί αυτόματα.
owner.settings.cargo.initialize.error=Αποτυχία αρχικοποίησης ευρετηρίου Cargo: %v
@@ -3440,7 +3510,7 @@ owner.settings.cargo.rebuild=Αναδημιουργία Ευρετηρίου
owner.settings.cargo.rebuild.description=Η ανοικοδόμηση μπορεί να είναι χρήσιμη εάν ο δείκτης δεν είναι συγχρονισμένος με τα αποθηκευμένα πακέτα Cargo.
owner.settings.cargo.rebuild.error=Αποτυχία αναδόμησης του ευρετηρίου Cargo: %v
owner.settings.cargo.rebuild.success=Το ευρετήριο Cargo αναδομήθηκε με επιτυχία.
-owner.settings.cleanuprules.title=Διαχείριση Κανόνων Εκκαθάρισης
+owner.settings.cleanuprules.title=Διαχείριση κανόνων εκκαθάρισης
owner.settings.cleanuprules.add=Προσθήκη Κανόνα Εκκαθάρισης
owner.settings.cleanuprules.edit=Επεξεργασία Κανόνα Εκκαθάρισης
owner.settings.cleanuprules.none=Δεν υπάρχουν διαθέσιμοι κανόνες εκκαθάρισης. Παρακαλούμε συμβουλευτείτε την τεκμηρίωση.
@@ -3463,6 +3533,7 @@ owner.settings.cleanuprules.success.delete=Ο κανόνας καθαρισμο
owner.settings.chef.title=Μητρώο Chef
owner.settings.chef.keypair=Δημιουργία ζεύγους κλειδιών
owner.settings.chef.keypair.description=Ένα ζεύγος κλειδιών είναι απαραίτητο για ταυτοποίηση στο μητρώο Chef. Αν έχετε δημιουργήσει ένα ζεύγος κλειδιών πριν, η δημιουργία ενός νέου ζεύγους κλειδιών θα απορρίψει το παλιό ζεύγος κλειδιών.
+rpm.repository.multiple_groups = Αυτό το πακέτο είναι διαθέσιμο σε διαφορετικά group.
[secrets]
secrets=Μυστικά
@@ -3471,7 +3542,7 @@ none=Δεν υπάρχουν ακόμα μυστικά.
creation=Προσθήκη Μυστικού
creation.name_placeholder=αλφαριθμητικοί χαρακτήρες ή κάτω παύλες μόνο, δεν μπορούν να ξεκινούν με GITEA_ ή GITHUB_
creation.value_placeholder=Εισάγετε οποιοδήποτε περιεχόμενο. Τα κενά στην αρχή παραλείπονται.
-creation.success=Το μυστικό "%s" προστέθηκε.
+creation.success=Το μυστικό «%s» προστέθηκε.
creation.failed=Αποτυχία δημιουργίας μυστικού.
deletion=Αφαίρεση μυστικού
deletion.description=Η αφαίρεση ενός μυστικού είναι μόνιμη και δεν μπορεί να αναιρεθεί. Συνέχεια;
@@ -3480,21 +3551,21 @@ deletion.failed=Αποτυχία αφαίρεσης μυστικού.
management=Διαχείριση Μυστικών
[actions]
-actions=Δράσεις
+actions=Actions
unit.desc=Διαχείριση δράσεων
-status.unknown=Άγνωστη
-status.waiting=Αναμονή
-status.running=Εκτελείται
-status.success=Επιτυχές
+status.unknown=Απροσδιόριστη
+status.waiting=Σε αναμονή
+status.running=Σε εκτέλεση
+status.success=Επιτυχία
status.failure=Αποτυχία
status.cancelled=Ακυρώθηκε
status.skipped=Παρακάμφθηκε
status.blocked=Αποκλείστηκε
runners=Εκτελεστές
-runners.runner_manage_panel=Διαχείριση Εκτελεστών
+runners.runner_manage_panel=Διαχείριση εκτελεστών
runners.new=Δημιουργία νέου Εκτελεστή
runners.new_notice=Πώς να ξεκινήσετε έναν εκτελεστή
runners.status=Κατάσταση
@@ -3546,9 +3617,9 @@ runs.no_runs=Η ροή εργασίας δεν έχει τρέξει ακόμα.
runs.empty_commit_message=(κενό μήνυμα υποβολής)
workflow.disable=Απενεργοποίηση Ροής Εργασιών
-workflow.disable_success=Η ροή εργασίας '%s' απενεργοποιήθηκε επιτυχώς.
+workflow.disable_success=Η ροή εργασίας «%s» απενεργοποιήθηκε επιτυχώς.
workflow.enable=Ενεργοποίηση Ροής Εργασίας
-workflow.enable_success=Η ροή εργασίας '%s' ενεργοποιήθηκε επιτυχώς.
+workflow.enable_success=Η ροή εργασίας «%s» ενεργοποιήθηκε επιτυχώς.
workflow.disabled=Η ροή εργασιών είναι απενεργοποιημένη.
need_approval_desc=Πρέπει να εγκριθεί η εκτέλεση ροών εργασίας για pull request από fork.
@@ -3564,12 +3635,13 @@ variables.edit=Επεξεργασία Μεταβλητής
variables.deletion.failed=Αποτυχία αφαίρεσης της μεταβλητής.
variables.deletion.success=Η μεταβλητή έχει αφαιρεθεί.
variables.creation.failed=Αποτυχία προσθήκης μεταβλητής.
-variables.creation.success=Η μεταβλητή "%s" έχει προστεθεί.
+variables.creation.success=Η μεταβλητή «%s» προστέθηκε.
variables.update.failed=Αποτυχία επεξεργασίας μεταβλητής.
variables.update.success=Η μεταβλητή έχει τροποποιηθεί.
variables.id_not_exist = Η μεταβλητή με id %d δεν υπάρχει.
runs.no_workflows.documentation = Για περισσότερες πληροφορίες σχετικά με τη Δράση Gitea, ανατρέξτε στην τεκμηρίωση .
runs.no_workflows.quick_start = Δεν ξέρετε πώς να ξεκινήσετε με τις Δράσεις Gitea; Συμβουλευτείτε τον οδηγό για γρήγορη αρχή .
+runs.workflow = Ροή εργασίας
[projects]
type-1.display_name=Ατομικό Έργο
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index f01542ed23..01d2af0f9b 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -34,8 +34,8 @@ password = Password
access_token = Access token
re_type = Confirm password
captcha = CAPTCHA
-twofa = Two-Factor Authentication
-twofa_scratch = Two-Factor Scratch Code
+twofa = Two-factor authentication
+twofa_scratch = Two-factor scratch code
passcode = Passcode
webauthn_insert_key = Insert your security key
@@ -55,15 +55,15 @@ webauthn_reload = Reload
repository = Repository
organization = Organization
mirror = Mirror
-new_repo = New Repository
-new_migrate = New Migration
-new_mirror = New Mirror
-new_fork = New Repository Fork
-new_org = New Organization
-new_project = New Project
-new_project_column = New Column
-admin_panel = Site Administration
-account_settings = Account Settings
+new_repo = New repository
+new_migrate = New migration
+new_mirror = New mirror
+new_fork = New repository fork
+new_org = New organization
+new_project = New project
+new_project_column = New column
+admin_panel = Site administration
+account_settings = Account settings
settings = Settings
your_profile = Profile
your_starred = Starred
@@ -76,7 +76,7 @@ collaborative = Collaborative
forks = Forks
activities = Activities
-pull_requests = Pull Requests
+pull_requests = Pull requests
issues = Issues
milestones = Milestones
@@ -143,6 +143,19 @@ confirm_delete_selected = Confirm to delete all selected items?
name = Name
value = Value
+filter = Filter
+filter.clear = Clear Filter
+filter.is_archived = Archived
+filter.not_archived = Not Archived
+filter.is_fork = Forked
+filter.not_fork = Not Forked
+filter.is_mirror = Mirrored
+filter.not_mirror = Not Mirrored
+filter.is_template = Template
+filter.not_template = Not Template
+filter.public = Public
+filter.private = Private
+
[aria]
navbar = Navigation Bar
footer = Footer
@@ -197,15 +210,15 @@ license_desc = Go get documentation before changing any settings.
require_db_desc = Forgejo requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol).
-db_title = Database Settings
-db_type = Database Type
+db_title = Database settings
+db_type = Database type
host = Host
user = Username
password = Password
-db_name = Database Name
+db_name = Database name
db_schema = Schema
db_schema_helper = Leave blank for database default ("public").
ssl_mode = SSL
@@ -224,61 +237,62 @@ err_admin_name_is_reserved = Administrator Username is invalid, username is rese
err_admin_name_pattern_not_allowed = Administrator username is invalid, the username matches a reserved pattern
err_admin_name_is_invalid = Administrator Username is invalid
-general_title = General Settings
-app_name = Site Title
+general_title = General settings
+app_name = Instance title
app_name_helper = You can enter your company name here.
-repo_path = Repository Root Path
+repo_path = Repository root path
repo_path_helper = Remote Git repositories will be saved to this directory.
-lfs_path = Git LFS Root Path
+lfs_path = Git LFS root path
lfs_path_helper = Files tracked by Git LFS will be stored in this directory. Leave empty to disable.
-run_user = Run As Username
+run_user = User to run as
run_user_helper = The operating system username that Forgejo runs as. Note that this user must have access to the repository root path.
-domain = Server Domain
+domain = Server domain
domain_helper = Domain or host address for the server.
-ssh_port = SSH Server Port
-ssh_port_helper = Port number your SSH server listens on. Leave empty to disable.
-http_port = Forgejo HTTP Listen Port
-http_port_helper = Port number the Forgejos web server will listen on.
-app_url = Forgejo Base URL
+ssh_port = SSH server port
+ssh_port_helper = Port number that will be used by the SSH server. Leave empty to disable SSH server.
+http_port = HTTP listen port
+http_port_helper = Port number that will be used by the Forgejo web server.
+app_url = Base URL
app_url_helper = Base address for HTTP(S) clone URLs and email notifications.
-log_root_path = Log Path
+log_root_path = Log path
log_root_path_helper = Log files will be written to this directory.
-optional_title = Optional Settings
-email_title = Email Settings
-smtp_addr = SMTP Host
-smtp_port = SMTP Port
-smtp_from = Send Email As
+optional_title = Optional settings
+email_title = Email settings
+smtp_addr = SMTP host
+smtp_port = SMTP port
+smtp_from = Send email as
smtp_from_invalid = The "Send Email As" address is invalid
smtp_from_helper = Email address Forgejo will use. Enter a plain email address or use the "Name" format.
-mailer_user = SMTP Username
-mailer_password = SMTP Password
-register_confirm = Require Email Confirmation to Register
-mail_notify = Enable Email Notifications
-server_service_title = Server and Third-Party Service Settings
-offline_mode = Enable Local Mode
+mailer_user = SMTP username
+mailer_password = SMTP password
+register_confirm = Require email confirmation to register
+mail_notify = Enable email notifications
+server_service_title = Server and third-party service settings
+offline_mode = Enable local mode
offline_mode_popup = Disable third-party content delivery networks and serve all resources locally.
disable_gravatar = Disable Gravatar
disable_gravatar_popup = Disable Gravatar and third-party avatar sources. A default avatar will be used unless a user locally uploads an avatar.
-federated_avatar_lookup = Enable Federated Avatars
+federated_avatar_lookup = Enable federated avatars
federated_avatar_lookup_popup = Enable federated avatar lookup using Libravatar.
-disable_registration = Disable Self-Registration
+disable_registration = Disable self-registration
disable_registration_popup = Disable user self-registration. Only administrators will be able to create new user accounts.
-allow_only_external_registration_popup = Allow Registration Only Through External Services
-openid_signin = Enable OpenID Sign-In
+allow_only_external_registration_popup = Allow registration only through external services
+openid_signin = Enable OpenID sign-in
openid_signin_popup = Enable user sign-in via OpenID.
-openid_signup = Enable OpenID Self-Registration
+openid_signup = Enable OpenID self-registration
openid_signup_popup = Enable OpenID-based user self-registration.
enable_captcha = Enable registration CAPTCHA
enable_captcha_popup = Require a CAPTCHA for user self-registration.
-require_sign_in_view = Require Sign-In to View Pages
+require_sign_in_view = Require to sign-in to view instance content
require_sign_in_view_popup = Limit page access to signed-in users. Visitors will only see the sign-in and registration pages.
admin_setting_desc = Creating an administrator account is optional. The first registered user will automatically become an administrator.
-admin_title = Administrator Account Settings
-admin_name = Administrator Username
+admin_title = Administrator account settings
+admin_name = Administrator username
admin_password = Password
-confirm_password = Confirm Password
-admin_email = Email Address
+confirm_password = Confirm password
+admin_email = Email address
+config_location_hint = These configuration options will be saved in:
install_btn_confirm = Install Forgejo
test_git_failed = Could not test "git" command: %v
sqlite3_not_available = This Forgejo version does not support SQLite3. Please download the official binary version from %s (not the "gobuild" version).
@@ -286,26 +300,26 @@ invalid_db_setting = The database settings are invalid: %v
invalid_db_table = The database table "%s" is invalid: %v
invalid_repo_path = The repository root path is invalid: %v
invalid_app_data_path = The app data path is invalid: %v
-run_user_not_match = The "run as" username is not the current username: %s -> %s
+run_user_not_match = The "user to run as" username is not the current username: %s -> %s
internal_token_failed = Failed to generate internal token: %v
secret_key_failed = Failed to generate secret key: %v
save_config_failed = Failed to save configuration: %v
-enable_update_checker_helper_forgejo = Periodically checks for new Forgejo versions by checking a DNS TXT record at release.forgejo.org.
+enable_update_checker_helper_forgejo = It will periodically check for new Forgejo versions by checking a TXT DNS record at release.forgejo.org.
invalid_admin_setting = Administrator account setting is invalid: %v
invalid_log_root_path = The log path is invalid: %v
-default_keep_email_private = Hide Email Addresses by Default
+default_keep_email_private = Hide email addresses by default
default_keep_email_private_popup = Hide email addresses of new user accounts by default.
-default_allow_create_organization = Allow Creation of Organizations by Default
+default_allow_create_organization = Allow creation of organizations by default
default_allow_create_organization_popup = Allow new user accounts to create organizations by default.
-default_enable_timetracking = Enable Time Tracking by Default
+default_enable_timetracking = Enable time tracking by default
default_enable_timetracking_popup = Enable time tracking for new repositories by default.
allow_dots_in_usernames = Allow users to use dots in their usernames. Doesn't affect existing accounts.
-no_reply_address = Hidden Email Domain
+no_reply_address = Hidden email domain
no_reply_address_helper = Domain name for users with a hidden email address. For example, the username "joe" will be logged in Git as "joe@noreply.example.org" if the hidden email domain is set to "noreply.example.org".
-password_algorithm = Password Hash Algorithm
+password_algorithm = Password hash algorithm
invalid_password_algorithm = Invalid password hash algorithm
password_algorithm_helper = Set the password hashing algorithm. Algorithms have differing requirements and strength. The argon2 algorithm is rather secure but uses a lot of memory and may be inappropriate for small systems.
-enable_update_checker = Enable Update Checker
+enable_update_checker = Enable update checker
enable_update_checker_helper = Checks for new version releases periodically by connecting to gitea.io.
env_config_keys = Environment Configuration
env_config_keys_prompt = The following environment variables will also be applied to your configuration file:
@@ -378,7 +392,7 @@ allow_password_change = Require user to change password (recommended)
reset_password_mail_sent_prompt = A confirmation email has been sent to %s . Please check your inbox within the next %s to complete the account recovery process.
active_your_account = Activate Your Account
account_activated = Account has been activated
-prohibit_login = Sign In Prohibited
+prohibit_login = Signing in is prohibited
prohibit_login_desc = Your account is prohibited from signing in, please contact your site administrator.
resent_limit_prompt = You have already requested an activation email recently. Please wait 3 minutes and try again.
has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (%s ). If you haven't received a confirmation email or need to resend a new one, please click on the button below.
@@ -450,7 +464,7 @@ activate_email.title = %s, please verify your email address
activate_email.text = Please click the following link to verify your email address within %s :
admin.new_user.subject = New user %s just signed up
-admin.new_user.user_info = User Information
+admin.new_user.user_info = User information
admin.new_user.text = Please click here to manage this user from the admin panel.
register_notify = Welcome to Forgejo
@@ -491,13 +505,13 @@ release.downloads = Downloads:
release.download.zip = Source Code (ZIP)
release.download.targz = Source Code (TAR.GZ)
-repo.transfer.subject_to = %s would like to transfer "%s" to %s
-repo.transfer.subject_to_you = %s would like to transfer "%s" to you
+repo.transfer.subject_to = %s wants to transfer repository "%s" to %s
+repo.transfer.subject_to_you = %s wants to transfer repository "%s" to you
repo.transfer.to_you = you
repo.transfer.body = To accept or reject it visit %s or just ignore it.
-repo.collaborator.added.subject = %s added you to %s
-repo.collaborator.added.text = You have been added as a collaborator of repository:
+repo.collaborator.added.subject = %s added you to %s as collaborator
+repo.collaborator.added.text = You have been added as a collaborator to repository:
team_invite.subject = %[1]s has invited you to join the %[2]s organization
team_invite.text_1 = %[1]s has invited you to join team %[2]s in organization %[3]s.
@@ -516,7 +530,7 @@ UserName = Username
RepoName = Repository name
Email = Email address
Password = Password
-Retype = Confirm Password
+Retype = Confirm password
SSHTitle = SSH key name
HttpsUrl = HTTPS URL
PayloadUrl = Payload URL
@@ -581,6 +595,8 @@ enterred_invalid_repo_name = The repository name you entered is incorrect.
enterred_invalid_org_name = The organization name you entered is incorrect.
enterred_invalid_owner_name = The new owner name is not valid.
enterred_invalid_password = The password you entered is incorrect.
+unset_password = The login user has not set the password.
+unsupported_login_type = The login type is not supported to delete account.
user_not_exist = The user does not exist.
team_not_exist = The team does not exist.
last_org_owner = You cannot remove the last user from the "owners" team. There must be at least one owner for an organization.
@@ -609,16 +625,16 @@ admin_cannot_delete_self = You cannot delete yourself when you are an admin. Ple
change_avatar = Change your avatar…
joined_on = Joined on %s
repositories = Repositories
-activity = Public Activity
+activity = Public activity
followers = Followers
-block_user = Block User
+block_user = Block user
block_user.detail = Please understand that if you block this user, other actions will be taken. Such as:
block_user.detail_1 = You are being unfollowed from this user.
block_user.detail_2 = This user cannot interact with your repositories, created issues and comments.
block_user.detail_3 = This user cannot add you as a collaborator, nor can you add them as a collaborator.
follow_blocked_user = You cannot follow this user because you have blocked this user or this user has blocked you.
-starred = Starred Repositories
-watched = Watched Repositories
+starred = Starred repositories
+watched = Watched repositories
code = Code
projects = Projects
overview = Overview
@@ -632,7 +648,7 @@ disabled_public_activity = This user has disabled the public visibility of the a
email_visibility.limited = Your email address is visible to all authenticated users
email_visibility.private = Your email address is only visible to you and administrators
show_on_map = Show this place on a map
-settings = User Settings
+settings = User settings
form.name_reserved = The username "%s" is reserved.
form.name_pattern_not_allowed = The pattern "%s" is not allowed in a username.
@@ -645,30 +661,30 @@ appearance = Appearance
password = Password
security = Security
avatar = Avatar
-ssh_gpg_keys = SSH / GPG Keys
+ssh_gpg_keys = SSH / GPG keys
social = Social Accounts
applications = Applications
orgs = Manage organizations
repos = Repositories
delete = Delete Account
-twofa = Two-Factor Authentication (TOTP)
+twofa = Two-factor authentication (TOTP)
account_link = Linked Accounts
organization = Organizations
uid = UID
-webauthn = Two-Factor Authentication (Security Keys)
-blocked_users = Blocked Users
+webauthn = Two-factor authentication (Security keys)
+blocked_users = Blocked users
public_profile = Public profile
biography_placeholder = Tell us a little bit about yourself! (You can use Markdown)
location_placeholder = Share your approximate location with others
profile_desc = Control how your profile is shown to other users. Your primary email address will be used for notifications, password recovery and web-based Git operations.
password_username_disabled = Non-local users are not allowed to change their username. Please contact your site administrator for more details.
-full_name = Full Name
+full_name = Full name
website = Website
location = Location
-update_theme = Update Theme
-update_profile = Update Profile
-update_language = Update Language
+update_theme = Change theme
+update_profile = Update profile
+update_language = Change language
update_language_not_found = Language "%s" is not available.
update_language_success = Language has been updated.
update_profile_success = Your profile has been updated.
@@ -689,32 +705,32 @@ comment_type_group_milestone = Milestone
comment_type_group_assignee = Assignee
comment_type_group_title = Title
comment_type_group_branch = Branch
-comment_type_group_time_tracking = Time Tracking
+comment_type_group_time_tracking = Time tracking
comment_type_group_deadline = Deadline
comment_type_group_dependency = Dependency
-comment_type_group_lock = Lock Status
+comment_type_group_lock = Lock status
comment_type_group_review_request = Review request
comment_type_group_pull_request_push = Added commits
comment_type_group_project = Project
comment_type_group_issue_ref = Issue reference
saved_successfully = Your settings were saved successfully.
privacy = Privacy
-keep_activity_private = Hide Activity from profile page
-keep_activity_private_popup = Makes the activity visible only for you and the admins
+keep_activity_private = Hide activity from profile page
+keep_activity_private_popup = Your activity will only be visible to you and the instance admins
-lookup_avatar_by_mail = Look Up Avatar by Email Address
-federated_avatar_lookup = Federated Avatar Lookup
+lookup_avatar_by_mail = Lookup avatar by email address
+federated_avatar_lookup = Federated avatar lookup
enable_custom_avatar = Use custom avatar
choose_new_avatar = Choose new avatar
-update_avatar = Update Avatar
-delete_current_avatar = Delete Current Avatar
+update_avatar = Update avatar
+delete_current_avatar = Delete current avatar
uploaded_avatar_not_a_image = The uploaded file is not an image.
uploaded_avatar_is_too_big = The uploaded file size (%d KiB) exceeds the maximum size (%d KiB).
update_avatar_success = Your avatar has been updated.
update_user_avatar_success = The user's avatar has been updated.
change_password = Change password
-update_password = Update Password
+update_password = Update password
old_password = Current password
new_password = New password
retype_new_password = Confirm new password
@@ -722,10 +738,10 @@ password_incorrect = The current password is incorrect.
change_password_success = Your password has been updated. Sign in using your new password from now on.
password_change_disabled = Non-local users cannot update their password through the Forgejo web interface.
-emails = Email Addresses
+emails = Email addresses
manage_emails = Manage email addresses
manage_themes = Select default theme
-manage_openid = Manage OpenID Addresses
+manage_openid = Manage OpenID addresses
email_desc = Your primary email address will be used for notifications, password recovery and, provided that it is not hidden, web-based Git operations.
theme_desc = This will be your default theme across the site.
primary = Primary
@@ -746,7 +762,7 @@ openid_deletion_desc = Removing this OpenID address from your account will preve
openid_deletion_success = The OpenID address has been removed.
add_new_email = Add email address
add_new_openid = Add New OpenID URI
-add_email = Add Email Address
+add_email = Add email address
add_openid = Add OpenID URI
add_email_confirmation_sent = A confirmation email has been sent to "%s". Please check your inbox within the next %s to confirm your email address.
add_email_success = The new email address has been added.
@@ -759,14 +775,14 @@ openid_desc = OpenID lets you delegate authentication to an external provider.
manage_ssh_keys = Manage SSH keys
manage_ssh_principals = Manage SSH Certificate Principals
manage_gpg_keys = Manage GPG keys
-add_key = Add Key
+add_key = Add key
ssh_desc = These public SSH keys are associated with your account. The corresponding private keys allow full access to your repositories. SSH keys that have been verified can be used to verify SSH-signed Git commits.
principal_desc = These SSH certificate principals are associated with your account and allow full access to your repositories.
gpg_desc = These public GPG keys are associated with your account and used to verify your commits. Keep your private keys safe as they allow to sign commits with your identity.
ssh_helper = Need help? Have a look at the guide to create your own SSH keys or solve common problems you may encounter using SSH.
gpg_helper = Need help? Have a look at the guide about GPG .
-add_new_key = Add SSH Key
-add_new_gpg_key = Add GPG Key
+add_new_key = Add SSH key
+add_new_gpg_key = Add GPG key
key_content_ssh_placeholder = Begins with "ssh-ed25519", "ssh-rsa", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "sk-ecdsa-sha2-nistp256@openssh.com", or "sk-ssh-ed25519@openssh.com"
key_content_gpg_placeholder = Begins with "-----BEGIN PGP PUBLIC KEY BLOCK-----"
add_new_principal = Add Principal
@@ -777,7 +793,7 @@ gpg_key_id_used = A public GPG key with same ID already exists.
gpg_no_key_email_found = This GPG key does not match any activated email address associated with your account. It may still be added if you sign the provided token.
gpg_key_matched_identities = Matched Identities:
gpg_key_matched_identities_long=The embedded identities in this key match the following activated email addresses for this user. Commits matching these email addresses can be verified with this key.
-gpg_key_verified=Verified Key
+gpg_key_verified=Verified key
gpg_key_verified_long=Key has been verified with a token and can be used to verify commits matching any activated email addresses for this user in addition to any matched identities for this key.
gpg_key_verify=Verify
gpg_invalid_token_signature = The provided GPG key, signature and token do not match or token is out-of-date.
@@ -788,7 +804,7 @@ gpg_token_code = echo "%s" | gpg -a --default-key %s --detach-sig
gpg_token_signature = Armored GPG signature
key_signature_gpg_placeholder = Begins with "-----BEGIN PGP SIGNATURE-----"
verify_gpg_key_success = GPG key "%s" has been verified.
-ssh_key_verified=Verified Key
+ssh_key_verified=Verified key
ssh_key_verified_long=Key has been verified with a token and can be used to verify commits matching any activated email addresses for this user.
ssh_key_verify=Verify
ssh_invalid_token_signature = The provided SSH key, signature or token do not match or token is out-of-date.
@@ -807,8 +823,8 @@ add_key_success = The SSH key "%s" has been added.
add_gpg_key_success = The GPG key "%s" has been added.
add_principal_success = The SSH certificate principal "%s" has been added.
delete_key = Remove
-ssh_key_deletion = Remove SSH Key
-gpg_key_deletion = Remove GPG Key
+ssh_key_deletion = Remove SSH key
+gpg_key_deletion = Remove GPG key
ssh_principal_deletion = Remove SSH Certificate Principal
ssh_key_deletion_desc = Removing an SSH key revokes its access to your account. Continue?
gpg_key_deletion_desc = Removing a GPG key un-verifies commits signed by it. Continue?
@@ -840,11 +856,11 @@ manage_access_token = Manage access tokens
generate_new_token = Generate new token
tokens_desc = These tokens grant access to your account using the Forgejo API.
token_name = Token name
-generate_token = Generate Token
+generate_token = Generate token
generate_token_success = Your new token has been generated. Copy it now as it will not be shown again.
generate_token_name_duplicate = %s has been used as an application name already. Please use a new one.
delete_token = Delete
-access_token_deletion = Delete Access Token
+access_token_deletion = Delete access token
access_token_deletion_cancel_action = Cancel
access_token_deletion_confirm_action = Delete
access_token_deletion_desc = Deleting a token will revoke access to your account for applications using it. This cannot be undone. Continue?
@@ -867,11 +883,11 @@ remove_oauth2_application = Remove OAuth2 Application
remove_oauth2_application_desc = Removing an OAuth2 application will revoke access to all signed access tokens. Continue?
remove_oauth2_application_success = The application has been deleted.
create_oauth2_application = Create a new OAuth2 application
-create_oauth2_application_button = Create Application
+create_oauth2_application_button = Create application
create_oauth2_application_success = You have successfully created a new OAuth2 application.
update_oauth2_application_success = You have successfully updated the OAuth2 application.
oauth2_application_name = Application name
-oauth2_confidential_client = Confidential Client. Select for apps that keep the secret confidential, such as web apps. Do not select for native apps including desktop and mobile apps.
+oauth2_confidential_client = Confidential client. Select for apps that keep the secret confidential, such as web apps. Do not select for native apps including desktop and mobile apps.
oauth2_redirect_uris = Redirect URIs. Please use a new line for every URI.
save_application = Save
oauth2_client_id = Client ID
@@ -895,10 +911,10 @@ twofa_desc = To protect your account against password theft, you can use a smart
twofa_recovery_tip = If you lose your device, you will be able to use a single-use recovery key to regain access to your account.
twofa_is_enrolled = Your account is currently enrolled in two-factor authentication.
twofa_not_enrolled = Your account is not currently enrolled in two-factor authentication.
-twofa_disable = Disable Two-Factor Authentication
-twofa_scratch_token_regenerate = Regenerate Single-Use Recovery Key
+twofa_disable = Disable two-factor authentication
+twofa_scratch_token_regenerate = Regenerate single-use recovery key
twofa_scratch_token_regenerated = Your single-use recovery key is now %s. Store it in a safe place, as it will not be shown again.
-twofa_enroll = Enroll into Two-Factor Authentication
+twofa_enroll = Enroll into two-factor authentication
twofa_disable_note = You can disable two-factor authentication if needed.
twofa_disable_desc = Disabling two-factor authentication will make your account less secure. Continue?
regenerate_scratch_token_desc = If you misplaced your recovery key or have already used it to sign in, you can reset it here.
@@ -911,9 +927,9 @@ twofa_enrolled = Your account has been successfully enrolled. Store your single-
twofa_failed_get_secret = Failed to get secret.
webauthn_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the WebAuthn Authenticator standard.
-webauthn_register_key = Add Security Key
+webauthn_register_key = Add security key
webauthn_nickname = Nickname
-webauthn_delete_key = Remove Security Key
+webauthn_delete_key = Remove security key
webauthn_delete_key_desc = If you remove a security key you can no longer sign in with it. Continue?
webauthn_key_loss_warning = If you lose your security keys, you will lose access to your account.
webauthn_alternative_tip = You may want to configure an additional authentication method.
@@ -932,18 +948,18 @@ orgs_none = You are not a member of any organizations.
repos_none = You do not own any repositories.
blocked_users_none = There are no blocked users.
-delete_account = Delete Your Account
+delete_account = Delete your account
delete_prompt = This operation will permanently delete your user account. It CANNOT be undone.
delete_with_all_comments = Your account is younger than %s. To avoid ghost comments, all issue/PR comments will be deleted with it.
-confirm_delete_account = Confirm Deletion
-delete_account_title = Delete User Account
+confirm_delete_account = Confirm deletion
+delete_account_title = Delete user account
delete_account_desc = Are you sure you want to permanently delete this user account?
-email_notifications.enable = Enable Email Notifications
-email_notifications.onmention = Only Email on Mention
-email_notifications.disable = Disable Email Notifications
-email_notifications.submit = Set Email Preference
-email_notifications.andyourown = And Your Own Notifications
+email_notifications.enable = Enable email notifications
+email_notifications.onmention = Only email on mention
+email_notifications.disable = Disable email notifications
+email_notifications.submit = Set email preference
+email_notifications.andyourown = And your own notifications
visibility = User visibility
visibility.public = Public
@@ -980,10 +996,10 @@ visibility = Visibility
visibility_description = Only the owner or the organization members if they have rights, will be able to see it.
visibility_helper = Make repository private
visibility_helper_forced = Your site administrator forces new repositories to be private.
-visibility_fork_helper = (Changing this will affect all forks.)
+visibility_fork_helper = (Changing this will affect visibility of all forks.)
clone_helper = Need help cloning? Visit Help .
-fork_repo = Fork Repository
-fork_from = Fork From
+fork_repo = Fork repository
+fork_from = Fork from
already_forked = You've already forked %s
fork_to_different_account = Fork to a different account
fork_visibility_helper = The visibility of a forked repository cannot be changed.
@@ -996,48 +1012,48 @@ clone_in_vscodium = Clone in VSCodium
download_zip = Download ZIP
download_tar = Download TAR.GZ
download_bundle = Download BUNDLE
-generate_repo = Generate Repository
-generate_from = Generate From
+generate_repo = Generate repository
+generate_from = Generate from
repo_desc = Description
repo_desc_helper = Enter short description (optional)
repo_lang = Language
repo_gitignore_helper = Select .gitignore templates.
repo_gitignore_helper_desc = Choose which files not to track from a list of templates for common languages. Typical artifacts generated by each language's build tools are included on .gitignore by default.
-issue_labels = Issue Labels
+issue_labels = Issue labels
issue_labels_helper = Select an issue label set.
license = License
license_helper = Select a license file.
license_helper_desc = A license governs what others can and can't do with your code. Not sure which one is right for your project? See Choose a license.
-object_format = Object Format
+object_format = Object format
object_format_helper = Object format of the repository. Cannot be changed later. SHA1 is most compatible.
readme = README
readme_helper = Select a README file template.
readme_helper_desc = This is the place where you can write a complete description for your project.
-auto_init = Initialize Repository (Adds .gitignore, License and README)
+auto_init = Initialize repository (Adds .gitignore, License and README)
trust_model_helper = Select trust model for signature verification. Possible options are:
trust_model_helper_collaborator = Collaborator: Trust signatures by collaborators
trust_model_helper_committer = Committer: Trust signatures that match committers
trust_model_helper_collaborator_committer = Collaborator+Committer: Trust signatures by collaborators which match the committer
trust_model_helper_default = Default: Use the default trust model for this installation
-create_repo = Create Repository
-default_branch = Default Branch
+create_repo = Create repository
+default_branch = Default branch
default_branch_label = default
default_branch_helper = The default branch is the base branch for pull requests and code commits.
mirror_prune = Prune
mirror_prune_desc = Remove obsolete remote-tracking references
-mirror_interval = Mirror Interval (valid time units are "h", "m", "s"). 0 to disable periodic sync. (Minimum interval: %s)
+mirror_interval = Mirror interval (valid time units are "h", "m", "s"). 0 to disable periodic sync. (Minimum interval: %s)
mirror_interval_invalid = The mirror interval is not valid.
mirror_sync = synced
mirror_sync_on_commit = Sync when commits are pushed
-mirror_address = Clone From URL
+mirror_address = Clone from URL
mirror_address_desc = Put any required credentials in the Authorization section.
mirror_address_url_invalid = The provided URL is invalid. You must escape all components of the URL correctly.
mirror_address_protocol_invalid = The provided URL is invalid. Only http(s):// or git:// locations can be used for mirroring.
mirror_lfs = Large File Storage (LFS)
mirror_lfs_desc = Activate mirroring of LFS data.
-mirror_lfs_endpoint = LFS Endpoint
+mirror_lfs_endpoint = LFS endpoint
mirror_lfs_endpoint_desc = Sync will attempt to use the clone url to determine the LFS server . You can also specify a custom endpoint if the repository LFS data is stored somewhere else.
-mirror_last_synced = Last Synchronized
+mirror_last_synced = Last synchronized
mirror_password_placeholder = (Unchanged)
mirror_password_blank_placeholder = (Unset)
mirror_password_help = Change the username to erase a stored password.
@@ -1049,7 +1065,7 @@ reactions_more = and %d more
unit_disabled = The site administrator has disabled this repository section.
language_other = Other
adopt_search = Enter username to search for unadopted repositories... (leave blank to find all)
-adopt_preexisting_label = Adopt Files
+adopt_preexisting_label = Adopt files
adopt_preexisting = Adopt pre-existing files
adopt_preexisting_content = Create repository from %s
adopt_preexisting_success = Adopted files and created repository from %s
@@ -1066,9 +1082,9 @@ tree_path_not_found_commit = Path %[1]s doesn't exist in commit %[2]s
tree_path_not_found_branch = Path %[1]s doesn't exist in branch %[2]s
tree_path_not_found_tag = Path %[1]s doesn't exist in tag %[2]s
-transfer.accept = Accept Transfer
+transfer.accept = Accept transfer
transfer.accept_desc = Transfer to "%s"
-transfer.reject = Reject Transfer
+transfer.reject = Reject transfer
transfer.reject_desc = Cancel transfer to "%s"
transfer.no_permission_to_accept = You do not have permission to accept this transfer.
transfer.no_permission_to_reject = You do not have permission to reject this transfer.
@@ -1080,14 +1096,14 @@ desc.internal = Internal
desc.archived = Archived
desc.sha256 = SHA256
-template.items = Template Items
-template.git_content = Git Content (Default Branch)
-template.git_hooks = Git Hooks
-template.git_hooks_tooltip = You are currently unable to modify or remove Git Hooks once added. Select this only if you trust the template repository.
+template.items = Template items
+template.git_content = Git content (Default branch)
+template.git_hooks = Git hooks
+template.git_hooks_tooltip = You are currently unable to modify or remove Git hooks once added. Select this only if you trust the template repository.
template.webhooks = Webhooks
template.topics = Topics
template.avatar = Avatar
-template.issue_labels = Issue Labels
+template.issue_labels = Issue labels
template.one_item = Must select at least one template item
template.invalid = Must select a template repository
@@ -1103,10 +1119,10 @@ form.name_pattern_not_allowed = The pattern "%s" is not allowed in a repository
need_auth = Authorization
migrate_options = Migration options
-migrate_service = Migration Service
+migrate_service = Migration service
migrate_options_mirror_helper = This repository will be a mirror
migrate_options_lfs = Migrate LFS files
-migrate_options_lfs_endpoint.label = LFS Endpoint
+migrate_options_lfs_endpoint.label = LFS endpoint
migrate_options_lfs_endpoint.description = Migration will attempt to use your Git remote to determine the LFS server . You can also specify a custom endpoint if the repository LFS data is stored somewhere else.
migrate_options_lfs_endpoint.description.local = A local server path is supported too.
migrate_options_lfs_endpoint.placeholder = If left blank, the endpoint will be derived from the clone URL
@@ -1115,10 +1131,10 @@ migrate_items_wiki = Wiki
migrate_items_milestones = Milestones
migrate_items_labels = Labels
migrate_items_issues = Issues
-migrate_items_pullrequests = Pull Requests
-migrate_items_merge_requests = Merge Requests
+migrate_items_pullrequests = Pull requests
+migrate_items_merge_requests = Merge requests
migrate_items_releases = Releases
-migrate_repo = Migrate Repository
+migrate_repo = Migrate repository
migrate.clone_address = Migrate / Clone from URL
migrate.clone_address_desc = The HTTP(S) or Git "clone" URL of an existing repository
migrate.github_token_desc = You can put one or more tokens with comma separated here to make migrating faster because of GitHub API rate limit. WARN: Abusing this feature may violate the service provider's policy and lead to account blocking.
@@ -1128,15 +1144,15 @@ migrate.permission_denied_blocked = You cannot import from disallowed hosts, ple
migrate.invalid_local_path = The local path is invalid. It doesn't exist or is not a directory.
migrate.invalid_lfs_endpoint = The LFS endpoint is not valid.
migrate.failed = Migration failed: %v
-migrate.migrate_items_options = Access Token is required to migrate additional items
+migrate.migrate_items_options = Access token is required to migrate additional items
migrated_from = Migrated from %[2]s
-migrated_from_fake = Migrated From %[1]s
-migrate.migrate = Migrate From %s
+migrated_from_fake = Migrated from %[1]s
+migrate.migrate = Migrate from %s
migrate.migrating = Migrating from %s ...
migrate.migrating_failed = Migrating from %s failed.
migrate.migrating_failed.error = Failed to migrate: %s
migrate.migrating_failed_no_addr = Migration failed.
-migrate.github.description = Migrate data from github.com or other GitHub instances.
+migrate.github.description = Migrate data from github.com or GitHub Enterprise server.
migrate.git.description = Migrate a repository only from any Git service.
migrate.gitlab.description = Migrate data from gitlab.com or other GitLab instances.
migrate.forgejo.description = Migrate data from codeberg.org or other Forgejo instances.
@@ -1145,14 +1161,14 @@ migrate.gogs.description = Migrate data from notabug.org or other Gogs instances
migrate.onedev.description = Migrate data from code.onedev.io or other OneDev instances.
migrate.codebase.description = Migrate data from codebasehq.com.
migrate.gitbucket.description = Migrate data from GitBucket instances.
-migrate.migrating_git = Migrating Git Data
-migrate.migrating_topics = Migrating Topics
-migrate.migrating_milestones = Migrating Milestones
-migrate.migrating_labels = Migrating Labels
-migrate.migrating_releases = Migrating Releases
-migrate.migrating_issues = Migrating Issues
-migrate.migrating_pulls = Migrating Pull Requests
-migrate.cancel_migrating_title = Cancel Migration
+migrate.migrating_git = Migrating Git data
+migrate.migrating_topics = Migrating topics
+migrate.migrating_milestones = Migrating milestones
+migrate.migrating_labels = Migrating labels
+migrate.migrating_releases = Migrating releases
+migrate.migrating_issues = Migrating issues
+migrate.migrating_pulls = Migrating pull requests
+migrate.cancel_migrating_title = Cancel migration
migrate.cancel_migrating_confirm = Do you want to cancel this migration?
mirror_from = mirror of
@@ -1167,11 +1183,11 @@ watch = Watch
unstar = Unstar
star = Star
fork = Fork
-download_archive = Download Repository
-more_operations = More Operations
+download_archive = Download repository
+more_operations = More operations
-no_desc = No Description
-quick_guide = Quick Guide
+no_desc = No description
+quick_guide = Quick guide
clone_this_repo = Clone this repository
cite_this_repo = Cite this repository
create_new_repo_command = Creating a new repository on the command line
@@ -1189,7 +1205,7 @@ find_tag = Find tag
branches = Branches
tags = Tags
issues = Issues
-pulls = Pull Requests
+pulls = Pull requests
project_board = Projects
packages = Packages
actions = Actions
@@ -1223,18 +1239,18 @@ ambiguous_character = `%[1]c [U+%04[1]X] can be confused with %[2]c [U+%04[2]X]`
escape_control_characters = Escape
unescape_control_characters = Unescape
-file_copy_permalink = Copy Permalink
-view_git_blame = View Git Blame
+file_copy_permalink = Copy permalink
+view_git_blame = View git blame
video_not_supported_in_browser = Your browser does not support the HTML5 "video" tag.
audio_not_supported_in_browser = Your browser does not support the HTML5 "audio" tag.
stored_lfs = Stored with Git LFS
symbolic_link = Symbolic link
-executable_file = Executable File
+executable_file = Executable file
vendored = Vendored
generated = Generated
-commit_graph = Commit Graph
+commit_graph = Commit graph
commit_graph.select = Select branches
-commit_graph.hide_pr_refs = Hide Pull Requests
+commit_graph.hide_pr_refs = Hide pull requests
commit_graph.monochrome = Mono
commit_graph.color = Color
commit.contained_in = This commit is contained in:
@@ -1247,18 +1263,18 @@ line = line
lines = lines
from_comment = (comment)
-editor.add_file = Add File
-editor.new_file = New File
-editor.upload_file = Upload File
-editor.edit_file = Edit File
-editor.preview_changes = Preview Changes
+editor.add_file = Add file
+editor.new_file = New file
+editor.upload_file = Upload file
+editor.edit_file = Edit file
+editor.preview_changes = Preview changes
editor.cannot_edit_lfs_files = LFS files cannot be edited in the web interface.
editor.cannot_edit_non_text_files = Binary files cannot be edited in the web interface.
-editor.edit_this_file = Edit File
+editor.edit_this_file = Edit file
editor.this_file_locked = File is locked
editor.must_be_on_a_branch = You must be on a branch to make or propose changes to this file.
editor.fork_before_edit = You must fork this repository to make or propose changes to this file.
-editor.delete_this_file = Delete File
+editor.delete_this_file = Delete file
editor.must_have_write_access = You must have write access to make or propose changes to this file.
editor.file_delete_success = File "%s" has been deleted.
editor.name_your_file = Name your file…
@@ -1301,8 +1317,8 @@ editor.commit_empty_file_text = The file you're about to commit is empty. Procee
editor.no_changes_to_show = There are no changes to show.
editor.fail_to_update_file = Failed to update/create file "%s".
editor.fail_to_update_file_summary = Error Message:
-editor.push_rejected_no_message = The change was rejected by the server without a message. Please check Git Hooks.
-editor.push_rejected = The change was rejected by the server. Please check Git Hooks.
+editor.push_rejected_no_message = The change was rejected by the server without a message. Please check Git hooks.
+editor.push_rejected = The change was rejected by the server. Please check Git hooks.
editor.push_rejected_summary = Full Rejection Message:
editor.add_subdir = Add a directory…
editor.unable_to_upload_files = Failed to upload files to "%s" with error: %v
@@ -1333,8 +1349,8 @@ commits.newer = Newer
commits.signed_by = Signed by
commits.signed_by_untrusted_user = Signed by untrusted user
commits.signed_by_untrusted_user_unmatched = Signed by untrusted user who does not match committer
-commits.gpg_key_id = GPG Key ID
-commits.ssh_key_fingerprint = SSH Key Fingerprint
+commits.gpg_key_id = GPG key ID
+commits.ssh_key_fingerprint = SSH key fingerprint
commits.view_path=View at this point in history
commit.operations = Operations
@@ -1350,24 +1366,24 @@ commitstatus.failure = Failure
commitstatus.pending = Pending
commitstatus.success = Success
-ext_issues = Access to External Issues
+ext_issues = Access to external issues
ext_issues.desc = Link to an external issue tracker.
projects = Projects
projects.desc = Manage issues and pulls in project boards.
projects.description = Description (optional)
projects.description_placeholder = Description
-projects.create = Create Project
+projects.create = Create project
projects.title = Title
-projects.new = New Project
+projects.new = New project
projects.new_subheader = Coordinate, track, and update your work in one place, so projects stay transparent and on schedule.
projects.create_success = The project "%s" has been created.
-projects.deletion = Delete Project
+projects.deletion = Delete project
projects.deletion_desc = Deleting a project removes it from all related issues. Continue?
projects.deletion_success = The project has been deleted.
-projects.edit = Edit Project
+projects.edit = Edit project
projects.edit_subheader = Projects organize issues and track progress.
-projects.modify = Edit Project
+projects.modify = Edit project
projects.edit_success = Project "%s" has been updated.
projects.type.none = None
projects.type.basic_kanban = Basic Kanban
@@ -1400,43 +1416,43 @@ issues.filter_milestones = Filter Milestone
issues.filter_projects = Filter Project
issues.filter_labels = Filter Label
issues.filter_reviewers = Filter Reviewer
-issues.new = New Issue
+issues.new = New issue
issues.new.title_empty = Title cannot be empty
issues.new.labels = Labels
-issues.new.no_label = No Label
+issues.new.no_label = No label
issues.new.clear_labels = Clear labels
issues.new.projects = Projects
issues.new.clear_projects = Clear projects
issues.new.no_projects = No project
-issues.new.open_projects = Open Projects
-issues.new.closed_projects = Closed Projects
+issues.new.open_projects = Open projects
+issues.new.closed_projects = Closed projects
issues.new.no_items = No items
issues.new.milestone = Milestone
-issues.new.no_milestone = No Milestone
+issues.new.no_milestone = No milestone
issues.new.clear_milestone = Clear milestone
-issues.new.open_milestone = Open Milestones
-issues.new.closed_milestone = Closed Milestones
+issues.new.open_milestone = Open milestones
+issues.new.closed_milestone = Closed milestones
issues.new.assignees = Assignees
issues.new.clear_assignees = Clear assignees
-issues.new.no_assignees = No Assignees
+issues.new.no_assignees = No assignees
issues.new.no_reviewers = No reviewers
-issues.choose.get_started = Get Started
+issues.choose.get_started = Get started
issues.choose.open_external_link = Open
issues.choose.blank = Default
issues.choose.blank_about = Create an issue from default template.
issues.choose.ignore_invalid_templates = Invalid templates have been ignored
issues.choose.invalid_templates = %v invalid template(s) found
issues.choose.invalid_config = The issue config contains errors:
-issues.no_ref = No Branch/Tag Specified
-issues.create = Create Issue
-issues.new_label = New Label
+issues.no_ref = No Branch/Tag specified
+issues.create = Create issue
+issues.new_label = New label
issues.new_label_placeholder = Label name
issues.new_label_desc_placeholder = Description
-issues.create_label = Create Label
-issues.label_templates.title = Load a predefined set of labels
-issues.label_templates.info = No labels exist yet. Create a label with "New Label" or use a predefined label set:
-issues.label_templates.helper = Select a label set
-issues.label_templates.use = Use Label Set
+issues.create_label = Create label
+issues.label_templates.title = Load a label preset
+issues.label_templates.info = No labels exist yet. Create a label with "New label" or use a label preset:
+issues.label_templates.helper = Select a label preset
+issues.label_templates.use = Use label preset
issues.label_templates.fail_to_load_file = Failed to load label template file "%s": %v
issues.add_label = added the %s label %s
issues.add_labels = added the %s labels %s
@@ -1522,18 +1538,18 @@ issues.num_comments_1 = %d comment
issues.num_comments = %d comments
issues.commented_at = `commented %s `
issues.delete_comment_confirm = Are you sure you want to delete this comment?
-issues.context.copy_link = Copy Link
-issues.context.quote_reply = Quote Reply
-issues.context.reference_issue = Reference in New Issue
+issues.context.copy_link = Copy link
+issues.context.quote_reply = Quote reply
+issues.context.reference_issue = Reference in a new issue
issues.context.edit = Edit
issues.context.delete = Delete
issues.no_content = No description provided.
-issues.close = Close Issue
+issues.close = Close issue
issues.comment_pull_merged_at = merged commit %[1]s into %[2]s %[3]s
issues.comment_manually_pull_merged_at = manually merged commit %[1]s into %[2]s %[3]s
-issues.close_comment_issue = Comment and Close
+issues.close_comment_issue = Comment and close
issues.reopen_issue = Reopen
-issues.reopen_comment_issue = Comment and Reopen
+issues.reopen_comment_issue = Comment and reopen
issues.create_comment = Comment
issues.closed_at = `closed this issue %[2]s `
issues.reopened_at = `reopened this issue %[2]s `
@@ -1571,7 +1587,7 @@ issues.label_title = Name
issues.label_description = Description
issues.label_color = Color
issues.label_exclusive = Exclusive
-issues.label_archive = Archive Label
+issues.label_archive = Archive label
issues.label_archived_filter = Show archived labels
issues.label_archive_tooltip = Archived labels are excluded by default from the suggestions when searching by label.
issues.label_exclusive_desc = Name the label scope/item
to make it mutually exclusive with other scope/
labels.
@@ -1580,20 +1596,20 @@ issues.label_count = %d labels
issues.label_open_issues = %d open issues/pull requests
issues.label_edit = Edit
issues.label_delete = Delete
-issues.label_modify = Edit Label
-issues.label_deletion = Delete Label
+issues.label_modify = Edit label
+issues.label_deletion = Delete label
issues.label_deletion_desc = Deleting a label removes it from all issues. Continue?
issues.label_deletion_success = The label has been deleted.
issues.label.filter_sort.alphabetically = Alphabetically
issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically
issues.label.filter_sort.by_size = Smallest size
issues.label.filter_sort.reverse_by_size = Largest size
-issues.num_participants = %d Participants
+issues.num_participants = %d participants
issues.attachment.open_tab = `Click to see "%s" in a new tab`
issues.attachment.download = `Click to download "%s"`
issues.subscribe = Subscribe
issues.unsubscribe = Unsubscribe
-issues.unpin_issue = Unpin Issue
+issues.unpin_issue = Unpin issue
issues.max_pinned = You can't pin more issues
issues.pin_comment = pinned this %s
issues.unpin_comment = unpinned this %s
@@ -1619,28 +1635,28 @@ issues.comment_on_locked = You cannot comment on a locked issue.
issues.delete = Delete
issues.delete.title = Delete this issue?
issues.delete.text = Do you really want to delete this issue? (This will permanently remove all content. Consider closing it instead, if you intend to keep it archived)
-issues.tracker = Time Tracker
-issues.start_tracking_short = Start Timer
-issues.start_tracking = Start Time Tracking
+issues.tracker = Time tracker
+issues.start_tracking_short = Start timer
+issues.start_tracking = Start time tracking
issues.start_tracking_history = `started working %s`
issues.tracker_auto_close = Timer will be stopped automatically when this issue gets closed
issues.tracking_already_started = `You have already started time tracking on another issue !`
-issues.stop_tracking = Stop Timer
+issues.stop_tracking = Stop timer
issues.stop_tracking_history = `stopped working %s`
issues.cancel_tracking = Discard
issues.cancel_tracking_history = `canceled time tracking %s`
-issues.add_time = Manually Add Time
+issues.add_time = Manually add time
issues.del_time = Delete this time log
-issues.add_time_short = Add Time
+issues.add_time_short = Add time
issues.add_time_cancel = Cancel
issues.add_time_history = `added spent time %s`
issues.del_time_history= `deleted spent time %s`
issues.add_time_hours = Hours
issues.add_time_minutes = Minutes
issues.add_time_sum_to_small = No time was entered.
-issues.time_spent_total = Total Time Spent
-issues.time_spent_from_all_authors = `Total Time Spent: %s`
-issues.due_date = Due Date
+issues.time_spent_total = Total time spent
+issues.time_spent_from_all_authors = `Total time spent: %s`
+issues.due_date = Due date
issues.invalid_due_date_format = Due date format must be "yyyy-mm-dd".
issues.error_modifying_due_date = Failed to modify the due date.
issues.error_removing_due_date = Failed to remove the due date.
@@ -1683,7 +1699,7 @@ issues.dependency.blocked_by_short = Depends on
issues.dependency.remove_header = Remove Dependency
issues.dependency.issue_remove_text = This will remove the dependency from this issue. Continue?
issues.dependency.pr_remove_text = This will remove the dependency from this pull request. Continue?
-issues.dependency.setting = Enable Dependencies For Issues and Pull Requests
+issues.dependency.setting = Enable dependencies for issues and pull requests
issues.dependency.add_error_same_issue = You cannot make an issue depend on itself.
issues.dependency.add_error_dep_issue_not_exist = Dependent issue does not exist.
issues.dependency.add_error_dep_not_exist = Dependency does not exist.
@@ -1734,9 +1750,9 @@ compare.compare_base = base
compare.compare_head = compare
pulls.desc = Enable pull requests and code reviews.
-pulls.new = New Pull Request
-pulls.view = View Pull Request
-pulls.compare_changes = New Pull Request
+pulls.new = New pull request
+pulls.view = View pull request
+pulls.compare_changes = New pull request
pulls.allow_edits_from_maintainers = Allow edits from maintainers
pulls.allow_edits_from_maintainers_desc = Users with write access to the base branch can also push to this branch
pulls.allow_edits_from_maintainers_err = Updating failed
@@ -1763,13 +1779,13 @@ pulls.nothing_to_compare = These branches are equal. There is no need to create
pulls.nothing_to_compare_have_tag = The selected branch/tag are equal.
pulls.nothing_to_compare_and_allow_empty_pr = These branches are equal. This PR will be empty.
pulls.has_pull_request = `A pull request between these branches already exists: %[2]s#%[3]d `
-pulls.create = Create Pull Request
+pulls.create = Create pull request
pulls.title_desc = wants to merge %[1]d commits from %[2]s
into %[3]s
pulls.merged_title_desc = merged %[1]d commits from %[2]s
into %[3]s
%[4]s
pulls.change_target_branch_at = `changed target branch from %s to %s %s`
pulls.tab_conversation = Conversation
pulls.tab_commits = Commits
-pulls.tab_files = Files Changed
+pulls.tab_files = Files changed
pulls.reopen_to_merge = Please reopen this pull request to perform a merge.
pulls.cant_reopen_deleted_branch = This pull request cannot be reopened because the branch was deleted.
pulls.merged = Merged
@@ -1834,9 +1850,9 @@ pulls.unrelated_histories = Merge Failed: The merge head and base do not share a
pulls.merge_out_of_date = Merge Failed: Whilst generating the merge, the base was updated. Hint: Try again.
pulls.head_out_of_date = Merge Failed: Whilst generating the merge, the head was updated. Hint: Try again.
pulls.has_merged = Failed: The pull request has been merged, you cannot merge again or change the target branch.
-pulls.push_rejected = Merge Failed: The push was rejected. Review the Git Hooks for this repository.
+pulls.push_rejected = Push Failed: The push was rejected. Review the Git hooks for this repository.
pulls.push_rejected_summary = Full Rejection Message
-pulls.push_rejected_no_message = Merge Failed: The push was rejected but there was no remote message. Review the Git Hooks for this repository
+pulls.push_rejected_no_message = Push Failed: The push was rejected but there was no remote message. Review the Git hooks for this repository
pulls.open_unmerged_pull_exists = `You cannot perform a reopen operation because there is a pending pull request (#%d) with identical properties.`
pulls.status_checking = Some checks are pending
pulls.status_checks_success = All checks were successful
@@ -1852,7 +1868,7 @@ pulls.update_branch_rebase = Update branch by rebase
pulls.update_branch_success = Branch update was successful
pulls.update_not_allowed = You are not allowed to update branch
pulls.outdated_with_base_branch = This branch is out-of-date with the base branch
-pulls.close = Close Pull Request
+pulls.close = Close pull request
pulls.closed_at = `closed this pull request %[2]s `
pulls.reopened_at = `reopened this pull request %[2]s `
pulls.commit_ref_at = `referenced this pull request from a commit %[2]s `
@@ -1888,7 +1904,7 @@ pulls.recently_pushed_new_branches = You pushed on branch %d%% Completed
-milestones.create = Create Milestone
+milestones.create = Create milestone
milestones.title = Title
milestones.desc = Description
-milestones.due_date = Due Date (optional)
+milestones.due_date = Due date (optional)
milestones.clear = Clear
milestones.invalid_due_date_format = Due date format must be "yyyy-mm-dd".
milestones.create_success = The milestone "%s" has been created.
-milestones.edit = Edit Milestone
+milestones.edit = Edit milestone
milestones.edit_subheader = Milestones organize issues and track progress.
milestones.cancel = Cancel
-milestones.modify = Update Milestone
+milestones.modify = Update milestone
milestones.edit_success = Milestone "%s" has been updated.
-milestones.deletion = Delete Milestone
+milestones.deletion = Delete milestone
milestones.deletion_desc = Deleting a milestone removes it from all related issues. Continue?
milestones.deletion_success = The milestone has been deleted.
-milestones.filter_sort.earliest_due_data = Earliest due date
-milestones.filter_sort.latest_due_date = Latest due date
+milestones.filter_sort.earliest_due_data = Nearest due date
+milestones.filter_sort.latest_due_date = Farthest due date
milestones.filter_sort.least_complete = Least complete
milestones.filter_sort.most_complete = Most complete
milestones.filter_sort.most_issues = Most issues
@@ -1965,9 +1981,9 @@ wiki.original_git_entry_tooltip = View original Git file instead of using friend
activity = Activity
activity.navbar.pulse = Pulse
-activity.navbar.code_frequency = Code Frequency
+activity.navbar.code_frequency = Code frequency
activity.navbar.contributors = Contributors
-activity.navbar.recent_commits = Recent Commits
+activity.navbar.recent_commits = Recent commits
activity.period.filter_label = Period:
activity.period.daily = 1 day
activity.period.halfweekly = 3 days
@@ -1977,38 +1993,38 @@ activity.period.quarterly = 3 months
activity.period.semiyearly = 6 months
activity.period.yearly = 1 year
activity.overview = Overview
-activity.active_prs_count_1 = %d Active Pull Request
-activity.active_prs_count_n = %d Active Pull Requests
-activity.merged_prs_count_1 = Merged Pull Request
-activity.merged_prs_count_n = Merged Pull Requests
-activity.opened_prs_count_1 = Proposed Pull Request
-activity.opened_prs_count_n = Proposed Pull Requests
+activity.active_prs_count_1 = %d Active pull request
+activity.active_prs_count_n = %d Active pull requests
+activity.merged_prs_count_1 = Merged pull request
+activity.merged_prs_count_n = Merged pull requests
+activity.opened_prs_count_1 = Proposed pull request
+activity.opened_prs_count_n = Proposed pull requests
activity.title.user_1 = %d user
activity.title.user_n = %d users
-activity.title.prs_1 = %d Pull request
-activity.title.prs_n = %d Pull requests
+activity.title.prs_1 = %d pull request
+activity.title.prs_n = %d pull requests
activity.title.prs_merged_by = %s merged by %s
activity.title.prs_opened_by = %s proposed by %s
activity.merged_prs_label = Merged
activity.opened_prs_label = Proposed
-activity.active_issues_count_1 = %d Active Issue
-activity.active_issues_count_n = %d Active Issues
-activity.closed_issues_count_1 = Closed Issue
-activity.closed_issues_count_n = Closed Issues
-activity.title.issues_1 = %d Issue
-activity.title.issues_n = %d Issues
+activity.active_issues_count_1 = %d active issue
+activity.active_issues_count_n = %d active issues
+activity.closed_issues_count_1 = Closed issue
+activity.closed_issues_count_n = Closed issues
+activity.title.issues_1 = %d issue
+activity.title.issues_n = %d issues
activity.title.issues_closed_from = %s closed from %s
activity.title.issues_created_by = %s created by %s
activity.closed_issue_label = Closed
-activity.new_issues_count_1 = New Issue
-activity.new_issues_count_n = New Issues
+activity.new_issues_count_1 = New issue
+activity.new_issues_count_n = New issues
activity.new_issue_label = Opened
-activity.title.unresolved_conv_1 = %d Unresolved Conversation
-activity.title.unresolved_conv_n = %d Unresolved Conversations
+activity.title.unresolved_conv_1 = %d unresolved conversation
+activity.title.unresolved_conv_n = %d unresolved conversations
activity.unresolved_conv_desc = These recently changed issues and pull requests have not been resolved yet.
activity.unresolved_conv_label = Open
-activity.title.releases_1 = %d Release
-activity.title.releases_n = %d Releases
+activity.title.releases_1 = %d release
+activity.title.releases_n = %d releases
activity.title.releases_published_by = %s published by %s
activity.published_release_label = Published
activity.no_git_activity = There has not been any commit activity in this period.
@@ -2059,9 +2075,9 @@ settings.collaboration.read = Read
settings.collaboration.owner = Owner
settings.collaboration.undefined = Undefined
settings.hooks = Webhooks
-settings.githooks = Git Hooks
-settings.basic_settings = Basic Settings
-settings.mirror_settings = Mirror Settings
+settings.githooks = Git hooks
+settings.basic_settings = Basic settings
+settings.mirror_settings = Mirror settings
settings.mirror_settings.docs = Set up your repository to automatically synchronize commits, tags and branches with another repository.
settings.mirror_settings.docs.disabled_pull_mirror.instructions = Set up your project to automatically push commits, tags and branches to another repository. Pull mirrors have been disabled by your site administrator.
settings.mirror_settings.docs.disabled_push_mirror.instructions = Set up your project to automatically pull commits, tags and branches from another repository.
@@ -2081,81 +2097,81 @@ settings.mirror_settings.direction.pull = Pull
settings.mirror_settings.direction.push = Push
settings.mirror_settings.last_update = Last update
settings.mirror_settings.push_mirror.none = No push mirrors configured
-settings.mirror_settings.push_mirror.remote_url = Git Remote Repository URL
-settings.mirror_settings.push_mirror.add = Add Push Mirror
+settings.mirror_settings.push_mirror.remote_url = Git remote repository URL
+settings.mirror_settings.push_mirror.add = Add push mirror
settings.mirror_settings.push_mirror.edit_sync_time = Edit mirror sync interval
-settings.units.units = Repository Units
+settings.units.units = Repository units
settings.units.overview = Overview
settings.units.add_more = Add more...
-settings.sync_mirror = Synchronize Now
+settings.sync_mirror = Synchronize now
settings.pull_mirror_sync_in_progress = Pulling changes from the remote %s at the moment.
settings.push_mirror_sync_in_progress = Pushing changes to the remote %s at the moment.
settings.site = Website
-settings.update_settings = Update Settings
-settings.update_mirror_settings = Update Mirror Settings
-settings.branches.switch_default_branch = Switch Default Branch
-settings.branches.update_default_branch = Update Default Branch
-settings.branches.add_new_rule = Add New Rule
-settings.advanced_settings = Advanced Settings
-settings.wiki_desc = Enable Repository Wiki
-settings.wiki_globally_editable = Allow anyone to edit the Wiki
-settings.use_internal_wiki = Use Built-In Wiki
-settings.use_external_wiki = Use External Wiki
-settings.external_wiki_url = External Wiki URL
+settings.update_settings = Update settings
+settings.update_mirror_settings = Update mirror settings
+settings.branches.switch_default_branch = Switch default branch
+settings.branches.update_default_branch = Update default branch
+settings.branches.add_new_rule = Add new rule
+settings.advanced_settings = Advanced settings
+settings.wiki_desc = Enable repository wiki
+settings.wiki_globally_editable = Allow anyone to edit the wiki
+settings.use_internal_wiki = Use built-in wiki
+settings.use_external_wiki = Use external wiki
+settings.external_wiki_url = External wiki URL
settings.external_wiki_url_error = The external wiki URL is not a valid URL.
settings.external_wiki_url_desc = Visitors are redirected to the external wiki URL when clicking the wiki tab.
-settings.issues_desc = Enable Repository Issue Tracker
-settings.use_internal_issue_tracker = Use Built-In Issue Tracker
-settings.use_external_issue_tracker = Use External Issue Tracker
-settings.external_tracker_url = External Issue Tracker URL
+settings.issues_desc = Enable repository issue tracker
+settings.use_internal_issue_tracker = Use built-in issue tracker
+settings.use_external_issue_tracker = Use external issue tracker
+settings.external_tracker_url = External issue tracker URL
settings.external_tracker_url_error = The external issue tracker URL is not a valid URL.
settings.external_tracker_url_desc = Visitors are redirected to the external issue tracker URL when clicking on the issues tab.
-settings.tracker_url_format = External Issue Tracker URL Format
+settings.tracker_url_format = External issue tracker URL Format
settings.tracker_url_format_error = The external issue tracker URL format is not a valid URL.
-settings.tracker_issue_style = External Issue Tracker Number Format
+settings.tracker_issue_style = External issue tracker Number Format
settings.tracker_issue_style.numeric = Numeric
settings.tracker_issue_style.alphanumeric = Alphanumeric
settings.tracker_issue_style.regexp = Regular Expression
settings.tracker_issue_style.regexp_pattern = Regular Expression Pattern
settings.tracker_issue_style.regexp_pattern_desc = The first captured group will be used in place of {index}
.
settings.tracker_url_format_desc = Use the placeholders {user}
, {repo}
and {index}
for the username, repository name and issue index.
-settings.enable_timetracker = Enable Time Tracking
-settings.allow_only_contributors_to_track_time = Let Only Contributors Track Time
-settings.pulls_desc = Enable Repository Pull Requests
-settings.pulls.ignore_whitespace = Ignore Whitespace for Conflicts
+settings.enable_timetracker = Enable time tracking
+settings.allow_only_contributors_to_track_time = Let only contributors track time
+settings.pulls_desc = Enable repository pull requests
+settings.pulls.ignore_whitespace = Ignore whitespace for conflicts
settings.pulls.enable_autodetect_manual_merge = Enable autodetect manual merge (Note: In some special cases, misjudgments can occur)
settings.pulls.allow_rebase_update = Enable updating pull request branch by rebase
settings.pulls.default_delete_branch_after_merge = Delete pull request branch after merge by default
settings.pulls.default_allow_edits_from_maintainers = Allow edits from maintainers by default
-settings.releases_desc = Enable Repository Releases
-settings.packages_desc = Enable Repository Packages Registry
-settings.projects_desc = Enable Repository Projects
-settings.actions_desc = Enable Repository Actions
-settings.admin_settings = Administrator Settings
-settings.admin_enable_health_check = Enable Repository Health Checks (git fsck)
-settings.admin_code_indexer = Code Indexer
+settings.releases_desc = Enable repository releases
+settings.packages_desc = Enable repository package registry
+settings.projects_desc = Enable repository projects
+settings.actions_desc = Enable repository actions
+settings.admin_settings = Administrator settings
+settings.admin_enable_health_check = Enable repository health checks (git fsck)
+settings.admin_code_indexer = Code indexer
settings.admin_stats_indexer = Code statistics indexer
-settings.admin_indexer_commit_sha = Last Indexed SHA
+settings.admin_indexer_commit_sha = Last indexed SHA
settings.admin_indexer_unindexed = Unindexed
-settings.reindex_button = Add to Reindex Queue
-settings.reindex_requested=Reindex Requested
+settings.reindex_button = Add to reindex queue
+settings.reindex_requested=Reindex requested
settings.admin_enable_close_issues_via_commit_in_any_branch = Close an issue via a commit made in a non default branch
-settings.danger_zone = Danger Zone
+settings.danger_zone = Danger zone
settings.new_owner_has_same_repo = The new owner already has a repository with same name. Please choose another name.
settings.new_owner_blocked_doer = The new owner has blocked you.
-settings.convert = Convert to Regular Repository
+settings.convert = Convert to regular repository
settings.convert_desc = You can convert this mirror into a regular repository. This cannot be undone.
settings.convert_notices_1 = This operation will convert the mirror into a regular repository and cannot be undone.
-settings.convert_confirm = Convert Repository
+settings.convert_confirm = Convert repository
settings.convert_succeed = The mirror has been converted into a regular repository.
-settings.convert_fork = Convert to Regular Repository
+settings.convert_fork = Convert to regular repository
settings.convert_fork_desc = You can convert this fork into a regular repository. This cannot be undone.
settings.convert_fork_notices_1 = This operation will convert the fork into a regular repository and cannot be undone.
-settings.convert_fork_confirm = Convert Repository
+settings.convert_fork_confirm = Convert repository
settings.convert_fork_succeed = The fork has been converted into a regular repository.
-settings.transfer = Transfer Ownership
+settings.transfer = Transfer ownership
settings.transfer.rejected = Repository transfer was rejected.
settings.transfer.success = Repository transfer was successful.
settings.transfer_abort = Cancel transfer
@@ -2168,13 +2184,13 @@ settings.transfer_in_progress = There is currently an ongoing transfer. Please c
settings.transfer_notices_1 = - You will lose access to the repository if you transfer it to an individual user.
settings.transfer_notices_2 = - You will keep access to the repository if you transfer it to an organization that you (co-)own.
settings.transfer_notices_3 = - If the repository is private and is transferred to an individual user, this action makes sure that the user does have at least read permission (and changes permissions if necessary).
-settings.transfer_owner = New Owner
-settings.transfer_perform = Perform Transfer
+settings.transfer_owner = New owner
+settings.transfer_perform = Perform transfer
settings.transfer_started = This repository has been marked for transfer and awaits confirmation from "%s"
settings.transfer_succeed = The repository has been transferred.
-settings.signing_settings = Signing Verification Settings
-settings.trust_model = Signature Trust Model
-settings.trust_model.default = Default Trust Model
+settings.signing_settings = Signing verification settings
+settings.trust_model = Signature trust model
+settings.trust_model.default = Default trust model
settings.trust_model.default.desc= Use the default repository trust model for this installation.
settings.trust_model.collaborator = Collaborator
settings.trust_model.collaborator.long = Collaborator: Trust signatures by collaborators
@@ -2188,16 +2204,16 @@ settings.trust_model.collaboratorcommitter.desc = Valid signatures by collaborat
settings.wiki_rename_branch_main = Normalize the Wiki branch name
settings.wiki_rename_branch_main_desc = Rename the branch used internally by the Wiki to "%s". This is a permanent and cannot be undone.
settings.wiki_rename_branch_main_notices_1 = This operation CANNOT be undone.
-settings.wiki_rename_branch_main_notices_2 = This will premanently rename the the internal branch of %s's repository wiki. Existing checkouts will need to be updated.
+settings.wiki_rename_branch_main_notices_2 = This will permanently rename the the internal branch of %s's repository wiki. Existing checkouts will need to be updated.
settings.wiki_branch_rename_success = The repository wiki's branch name has been successfully normalized.
settings.wiki_branch_rename_failure = Failed to normalize the repository wiki's branch name.
settings.confirm_wiki_branch_rename = Rename the wiki branch
-settings.wiki_delete = Delete Wiki Data
+settings.wiki_delete = Delete wiki data
settings.wiki_delete_desc = Deleting repository wiki data is permanent and cannot be undone.
settings.wiki_delete_notices_1 = - This will permanently delete and disable the repository wiki for %s.
-settings.confirm_wiki_delete = Delete Wiki Data
+settings.confirm_wiki_delete = Delete wiki data
settings.wiki_deletion_success = The repository wiki data has been deleted.
-settings.delete = Delete This Repository
+settings.delete = Delete this repository
settings.delete_desc = Deleting a repository is permanent and cannot be undone.
settings.delete_notices_1 = - This operation CANNOT be undone.
settings.delete_notices_2 = - This operation will permanently delete the %s repository including code, issues, comments, wiki data and collaborator settings.
@@ -2205,8 +2221,8 @@ settings.delete_notices_fork_1 = - Forks of this repository will become independ
settings.deletion_success = The repository has been deleted.
settings.update_settings_success = The repository settings have been updated.
settings.update_settings_no_unit = The repository should allow at least some sort of interaction.
-settings.confirm_delete = Delete Repository
-settings.add_collaborator = Add Collaborator
+settings.confirm_delete = Delete repository
+settings.add_collaborator = Add collaborator
settings.add_collaborator_success = The collaborator has been added.
settings.add_collaborator_inactive_user = Cannot add an inactive user as a collaborator.
settings.add_collaborator_owner = Cannot add an owner as a collaborator.
@@ -2222,20 +2238,20 @@ settings.org_not_allowed_to_be_collaborator = Organizations cannot be added as a
settings.change_team_access_not_allowed = Changing team access for repository has been restricted to organization owner
settings.team_not_in_organization = The team is not in the same organization as the repository
settings.teams = Teams
-settings.add_team = Add Team
+settings.add_team = Add team
settings.add_team_duplicate = Team already has the repository
settings.add_team_success = The team now have access to the repository.
-settings.search_team = Search Team…
+settings.search_team = Search team…
settings.change_team_permission_tip = Team's permission is set on the team setting page and can't be changed per repository
settings.delete_team_tip = This team has access to all repositories and can't be removed
settings.remove_team_success = The team's access to the repository has been removed.
-settings.add_webhook = Add Webhook
+settings.add_webhook = Add webhook
settings.add_webhook.invalid_channel_name = Webhook channel name cannot be empty and cannot contain only a # character.
settings.hooks_desc = Webhooks automatically make HTTP POST requests to a server when certain Forgejo events trigger. Read more in the webhooks guide .
-settings.webhook_deletion = Remove Webhook
+settings.webhook_deletion = Remove webhook
settings.webhook_deletion_desc = Removing a webhook deletes its settings and delivery history. Continue?
settings.webhook_deletion_success = The webhook has been removed.
-settings.webhook.test_delivery = Test Delivery
+settings.webhook.test_delivery = Test delivery
settings.webhook.test_delivery_desc = Test this webhook with a fake event.
settings.webhook.test_delivery_desc_disabled = To test this webhook with a fake event, activate it.
settings.webhook.request = Request
@@ -2246,26 +2262,26 @@ settings.webhook.body = Body
settings.webhook.replay.description = Replay this webhook.
settings.webhook.replay.description_disabled = To replay this webhook, activate it.
settings.webhook.delivery.success = An event has been added to the delivery queue. It may take few seconds before it shows up in the delivery history.
-settings.githooks_desc = Git Hooks are powered by Git itself. You can edit hook files below to set up custom operations.
+settings.githooks_desc = Git hooks are powered by Git itself. You can edit hook files below to set up custom operations.
settings.githook_edit_desc = If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook.
-settings.githook_name = Hook Name
-settings.githook_content = Hook Content
-settings.update_githook = Update Hook
-settings.add_webhook_desc = Forgejo will send POST
requests with a specified content type to the target URL. Read more in the webhooks guide .
+settings.githook_name = Hook name
+settings.githook_content = Hook content
+settings.update_githook = Update hook
+settings.add_webhook_desc = Forgejo will send POST
requests with a specified Content-Type to the target URL. Read more in the webhooks guide .
settings.payload_url = Target URL
-settings.http_method = HTTP Method
-settings.content_type = POST Content Type
+settings.http_method = HTTP method
+settings.content_type = POST content type
settings.secret = Secret
settings.slack_username = Username
settings.slack_icon_url = Icon URL
settings.slack_color = Color
settings.discord_username = Username
settings.discord_icon_url = Icon URL
-settings.event_desc = Trigger On:
-settings.event_push_only = Push Events
-settings.event_send_everything = All Events
-settings.event_choose = Custom Events…
-settings.event_header_repository = Repository Events
+settings.event_desc = Trigger on:
+settings.event_push_only = Push events
+settings.event_send_everything = All events
+settings.event_choose = Custom events…
+settings.event_header_repository = Repository events
settings.event_create = Create
settings.event_create_desc = Branch or tag created.
settings.event_delete = Delete
@@ -2280,50 +2296,50 @@ settings.event_push = Push
settings.event_push_desc = Git push to a repository.
settings.event_repository = Repository
settings.event_repository_desc = Repository created or deleted.
-settings.event_header_issue = Issue Events
+settings.event_header_issue = Issue events
settings.event_issues = Issues
settings.event_issues_desc = Issue opened, closed, reopened, or edited.
-settings.event_issue_assign = Issue Assigned
+settings.event_issue_assign = Issue assigned
settings.event_issue_assign_desc = Issue assigned or unassigned.
-settings.event_issue_label = Issue Labeled
+settings.event_issue_label = Issue labeled
settings.event_issue_label_desc = Issue labels updated or cleared.
-settings.event_issue_milestone = Issue Milestoned
+settings.event_issue_milestone = Issue milestoned
settings.event_issue_milestone_desc = Issue milestoned or demilestoned.
-settings.event_issue_comment = Issue Comment
+settings.event_issue_comment = Issue comment
settings.event_issue_comment_desc = Issue comment created, edited, or deleted.
-settings.event_header_pull_request = Pull Request Events
-settings.event_pull_request = Pull Request
+settings.event_header_pull_request = Pull request events
+settings.event_pull_request = Pull request
settings.event_pull_request_desc = Pull request opened, closed, reopened, or edited.
-settings.event_pull_request_assign = Pull Request Assigned
+settings.event_pull_request_assign = Pull request assigned
settings.event_pull_request_assign_desc = Pull request assigned or unassigned.
-settings.event_pull_request_label = Pull Request Labeled
+settings.event_pull_request_label = Pull request labeled
settings.event_pull_request_label_desc = Pull request labels updated or cleared.
-settings.event_pull_request_milestone = Pull Request Milestoned
+settings.event_pull_request_milestone = Pull request milestoned
settings.event_pull_request_milestone_desc = Pull request milestoned or demilestoned.
-settings.event_pull_request_comment = Pull Request Comment
+settings.event_pull_request_comment = Pull request comment
settings.event_pull_request_comment_desc = Pull request comment created, edited, or deleted.
-settings.event_pull_request_review = Pull Request Reviewed
+settings.event_pull_request_review = Pull request reviewed
settings.event_pull_request_review_desc = Pull request approved, rejected, or review comment.
-settings.event_pull_request_sync = Pull Request Synchronized
+settings.event_pull_request_sync = Pull request synchronized
settings.event_pull_request_sync_desc = Pull request synchronized.
-settings.event_pull_request_review_request = Pull Request Review Requested
+settings.event_pull_request_review_request = Pull request review requested
settings.event_pull_request_review_request_desc = Pull request review requested or review request removed.
-settings.event_pull_request_approvals = Pull Request Approvals
-settings.event_pull_request_merge = Pull Request Merge
+settings.event_pull_request_approvals = Pull request approvals
+settings.event_pull_request_merge = Pull request merge
settings.event_package = Package
settings.event_package_desc = Package created or deleted in a repository.
settings.branch_filter = Branch filter
settings.branch_filter_desc = Branch whitelist for push, branch creation and branch deletion events, specified as glob pattern. If empty or *
, events for all branches are reported. See github.com/gobwas/glob documentation for syntax. Examples: master
, {master,release*}
.
-settings.authorization_header = Authorization Header
+settings.authorization_header = Authorization header
settings.authorization_header_desc = Will be included as authorization header for requests when present. Examples: %s.
settings.active = Active
settings.active_helper = Information about triggered events will be sent to this webhook URL.
settings.add_hook_success = The webhook has been added.
-settings.update_webhook = Update Webhook
+settings.update_webhook = Update webhook
settings.update_hook_success = The webhook has been updated.
-settings.delete_webhook = Remove Webhook
-settings.recent_deliveries = Recent Deliveries
-settings.hook_type = Hook Type
+settings.delete_webhook = Remove webhook
+settings.recent_deliveries = Recent deliveries
+settings.hook_type = Hook type
settings.slack_token = Token
settings.slack_domain = Domain
settings.slack_channel = Channel
@@ -2345,10 +2361,10 @@ settings.web_hook_name_packagist = Packagist
settings.packagist_username = Packagist username
settings.packagist_api_token = API token
settings.packagist_package_url = Packagist package URL
-settings.deploy_keys = Deploy Keys
-settings.add_deploy_key = Add Deploy Key
+settings.deploy_keys = Deploy keys
+settings.add_deploy_key = Add deploy key
settings.deploy_key_desc = Deploy keys have read-only pull access to the repository.
-settings.is_writable = Enable Write Access
+settings.is_writable = Enable write access
settings.is_writable_info = Allow this deploy key to push to the repository.
settings.no_deploy_keys = There are no deploy keys yet.
settings.title = Title
@@ -2356,37 +2372,37 @@ settings.deploy_key_content = Content
settings.key_been_used = A deploy key with identical content is already in use.
settings.key_name_used = A deploy key with the same name already exists.
settings.add_key_success = The deploy key "%s" has been added.
-settings.deploy_key_deletion = Remove Deploy Key
+settings.deploy_key_deletion = Remove reploy key
settings.deploy_key_deletion_desc = Removing a deploy key will revoke its access to this repository. Continue?
settings.deploy_key_deletion_success = The deploy key has been removed.
settings.branches = Branches
-settings.protected_branch = Branch Protection
-settings.protected_branch.save_rule = Save Rule
-settings.protected_branch.delete_rule = Delete Rule
+settings.protected_branch = Branch protection
+settings.protected_branch.save_rule = Save rule
+settings.protected_branch.delete_rule = Delete rule
settings.protected_branch_can_push = Allow push?
settings.protected_branch_can_push_yes = You can push
settings.protected_branch_can_push_no = You cannot push
-settings.branch_protection = Branch Protection Rules for Branch "%s "
-settings.protect_this_branch = Enable Branch Protection
+settings.branch_protection = Branch protection rules for branch "%s "
+settings.protect_this_branch = Enable branch protection
settings.protect_this_branch_desc = Prevents deletion and restricts Git pushing and merging to the branch.
-settings.protect_disable_push = Disable Push
+settings.protect_disable_push = Disable push
settings.protect_disable_push_desc = No pushing will be allowed to this branch.
-settings.protect_enable_push = Enable Push
+settings.protect_enable_push = Enable push
settings.protect_enable_push_desc = Anyone with write access will be allowed to push to this branch (but not force push).
-settings.protect_enable_merge = Enable Merge
+settings.protect_enable_merge = Enable merge
settings.protect_enable_merge_desc = Anyone with write access will be allowed to merge the pull requests into this branch.
-settings.protect_whitelist_committers = Whitelist Restricted Push
+settings.protect_whitelist_committers = Whitelist restricted push
settings.protect_whitelist_committers_desc = Only whitelisted users or teams will be allowed to push to this branch (but not force push).
settings.protect_whitelist_deploy_keys = Whitelist deploy keys with write access to push.
settings.protect_whitelist_users = Whitelisted users for pushing:
settings.protect_whitelist_search_users = Search users…
settings.protect_whitelist_teams = Whitelisted teams for pushing:
settings.protect_whitelist_search_teams = Search teams…
-settings.protect_merge_whitelist_committers = Enable Merge Whitelist
+settings.protect_merge_whitelist_committers = Enable merge whitelist
settings.protect_merge_whitelist_committers_desc = Allow only whitelisted users or teams to merge pull requests into this branch.
settings.protect_merge_whitelist_users = Whitelisted users for merging:
settings.protect_merge_whitelist_teams = Whitelisted teams for merging:
-settings.protect_check_status_contexts = Enable Status Check
+settings.protect_check_status_contexts = Enable status check
settings.protect_status_check_patterns = Status check patterns:
settings.protect_status_check_patterns_desc = Enter patterns to specify which status checks must pass before branches can be merged into a branch that matches this rule. Each line specifies a pattern. Patterns cannot be empty.
settings.protect_check_status_contexts_desc = Require status checks to pass before merging. When enabled, commits must first be pushed to another branch, then merged or pushed directly to a branch that matches this rule after status checks have passed. If no contexts are matched, the last commit must be successful regardless of context.
@@ -2404,9 +2420,9 @@ settings.dismiss_stale_approvals = Dismiss stale approvals
settings.dismiss_stale_approvals_desc = When new commits that change the content of the pull request are pushed to the branch, old approvals will be dismissed.
settings.ignore_stale_approvals = Ignore stale approvals
settings.ignore_stale_approvals_desc = Do not count approvals that were made on older commits (stale reviews) towards how many approvals the PR has. Irrelevant if stale reviews are already dismissed.
-settings.require_signed_commits = Require Signed Commits
+settings.require_signed_commits = Require signed commits
settings.require_signed_commits_desc = Reject pushes to this branch if they are unsigned or unverifiable.
-settings.protect_branch_name_pattern = Protected Branch Name Pattern
+settings.protect_branch_name_pattern = Protected branch name pattern
settings.protect_branch_name_pattern_desc = Protected branch name patterns. See the documentation for pattern syntax. Examples: main, release/**
settings.protect_patterns = Patterns
settings.protect_protected_file_patterns = Protected file patterns (separated using semicolon ";"):
@@ -2418,7 +2434,7 @@ settings.delete_protected_branch = Disable protection
settings.update_protect_branch_success = Branch protection for rule "%s" has been updated.
settings.remove_protected_branch_success = Branch protection for rule "%s" has been removed.
settings.remove_protected_branch_failed = Removing branch protection rule "%s" failed.
-settings.protected_branch_deletion = Delete Branch Protection
+settings.protected_branch_deletion = Delete branch protection
settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue?
settings.block_rejected_reviews = Block merge on rejected reviews
settings.block_rejected_reviews_desc = Merging will not be possible when changes are requested by official reviewers, even if there are enough approvals.
@@ -2427,8 +2443,8 @@ settings.block_on_official_review_requests_desc = Merging will not be possible w
settings.block_outdated_branch = Block merge if pull request is outdated
settings.block_outdated_branch_desc = Merging will not be possible when head branch is behind base branch.
settings.default_branch_desc = Select a default repository branch for pull requests and code commits:
-settings.merge_style_desc = Merge Styles
-settings.default_merge_style_desc = Default Merge Style
+settings.merge_style_desc = Merge styles
+settings.default_merge_style_desc = Default merge style
settings.choose_branch = Choose a branch…
settings.no_protected_branch = There are no protected branches.
settings.edit_protected_branch = Edit
@@ -2436,23 +2452,23 @@ settings.protected_branch_required_rule_name = Required rule name
settings.protected_branch_duplicate_rule_name = There is already a rule for this set of branches
settings.protected_branch_required_approvals_min = Required approvals cannot be negative.
settings.tags = Tags
-settings.tags.protection = Tag Protection
-settings.tags.protection.pattern = Tag Pattern
+settings.tags.protection = Tag protection
+settings.tags.protection.pattern = Tag pattern
settings.tags.protection.allowed = Allowed
settings.tags.protection.allowed.users = Allowed users
settings.tags.protection.allowed.teams = Allowed teams
-settings.tags.protection.allowed.noone = No One
-settings.tags.protection.create = Protect Tag
+settings.tags.protection.allowed.noone = No one
+settings.tags.protection.create = Add rule
settings.tags.protection.none = There are no protected tags.
settings.tags.protection.pattern.description = You can use a single name or a glob pattern or regular expression to match multiple tags. Read more in the protected tags guide .
-settings.bot_token = Bot Token
+settings.bot_token = Bot token
settings.chat_id = Chat ID
settings.thread_id = Thread ID
settings.matrix.homeserver_url = Homeserver URL
settings.matrix.room_id = Room ID
-settings.matrix.message_type = Message Type
-settings.archive.button = Archive Repo
-settings.archive.header = Archive This Repo
+settings.matrix.message_type = Message type
+settings.archive.button = Archive repo
+settings.archive.header = Archive this repo
settings.archive.text = Archiving the repo will make it entirely read-only. It will be hidden from the dashboard. Nobody (not even you!) will be able to make new commits, or open any issues or pull requests.
settings.archive.success = The repo was successfully archived.
settings.archive.error = An error occurred while trying to archive the repo. See the log for more details.
@@ -2498,17 +2514,17 @@ settings.rename_branch_from=old branch name
settings.rename_branch_to=new branch name
settings.rename_branch=Rename branch
-diff.browse_source = Browse Source
+diff.browse_source = Browse source
diff.parent = parent
diff.commit = commit
diff.git-notes = Notes
-diff.data_not_available = Diff Content Not Available
-diff.options_button = Diff Options
-diff.show_diff_stats = Show Stats
-diff.download_patch = Download Patch File
-diff.download_diff = Download Diff File
-diff.show_split_view = Split View
-diff.show_unified_view = Unified View
+diff.data_not_available = Diff content is not available
+diff.options_button = Diff options
+diff.show_diff_stats = Show stats
+diff.download_patch = Download patch file
+diff.download_diff = Download diff file
+diff.show_split_view = Split view
+diff.show_unified_view = Unified view
diff.whitespace_button = Whitespace
diff.whitespace_show_everything = Show all changes
diff.whitespace_ignore_all_whitespace = Ignore whitespace when comparing lines
@@ -2518,7 +2534,7 @@ diff.stats_desc = %d changed files with %d additions
diff.stats_desc_file = %d changes: %d additions and %d deletions
diff.bin = BIN
diff.bin_not_shown = Binary file not shown.
-diff.view_file = View File
+diff.view_file = View file
diff.file_before = Before
diff.file_after = After
diff.file_image_width = Width
@@ -2527,8 +2543,8 @@ diff.file_byte_size = Size
diff.file_suppressed = File diff suppressed because it is too large
diff.file_suppressed_line_too_long = File diff suppressed because one or more lines are too long
diff.too_many_files = Some files were not shown because too many files have changed in this diff
-diff.show_more = Show More
-diff.load = Load Diff
+diff.show_more = Show more
+diff.load = Load diff
diff.generated = generated
diff.vendored = vendored
diff.comment.add_line_comment = Add line comment
@@ -2603,11 +2619,11 @@ release.add_tag = Create Tag Only
release.releases_for = Releases for %s
release.tags_for = Tags for %s
-branch.name = Branch Name
+branch.name = Branch name
branch.already_exists = A branch named "%s" already exists.
branch.delete_head = Delete
-branch.delete = Delete Branch "%s"
-branch.delete_html = Delete Branch
+branch.delete = Delete branch "%s"
+branch.delete_html = Delete branch
branch.delete_desc = Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
branch.deletion_success = Branch "%s" has been deleted.
branch.deletion_failed = Failed to delete branch "%s".
@@ -2623,10 +2639,10 @@ branch.restore_success = Branch "%s" has been restored.
branch.restore_failed = Failed to restore branch "%s".
branch.protected_deletion_failed = Branch "%s" is protected. It cannot be deleted.
branch.default_deletion_failed = Branch "%s" is the default branch. It cannot be deleted.
-branch.restore = Restore Branch "%s"
-branch.download = Download Branch "%s"
-branch.rename = Rename Branch "%s"
-branch.search = Search Branch
+branch.restore = Restore branch "%s"
+branch.download = Download branch "%s"
+branch.rename = Rename branch "%s"
+branch.search = Search branch
branch.included_desc = This branch is part of the default branch
branch.included = Included
branch.create_new_branch = Create branch from branch:
@@ -2657,6 +2673,7 @@ find_file.no_matching = No matching file found
error.csv.too_large = Can't render this file because it is too large.
error.csv.unexpected = Can't render this file because it contains an unexpected character in line %d and column %d.
error.csv.invalid_field_count = Can't render this file because it has a wrong number of fields in line %d.
+error.broken_git_hook = Git hooks of this repository seem to be broken. Please follow the documentation to fix them, then push some commits to refresh the status.
[graphs]
component_loading = Loading %s...
@@ -2668,26 +2685,26 @@ contributors.what = contributions
recent_commits.what = recent commits
[org]
-org_name_holder = Organization Name
-org_full_name_holder = Organization Full Name
+org_name_holder = Organization name
+org_full_name_holder = Organization full name
org_name_helper = Organization names should be short and memorable.
-create_org = Create Organization
+create_org = Create organization
repo_updated = Updated
members = Members
teams = Teams
code = Code
lower_members = members
lower_repositories = repositories
-create_new_team = New Team
-create_team = Create Team
+create_new_team = New team
+create_team = Create team
org_desc = Description
-team_name = Team Name
+team_name = Team name
team_desc = Description
team_name_helper = Team names should be short and memorable.
team_desc_helper = Describe the purpose or role of the team.
team_access_desc = Repository access
team_permission_desc = Permission
-team_unit_desc = Allow Access to Repository Sections
+team_unit_desc = Allow access to tepository sections
team_unit_disabled = (Disabled)
follow_blocked_user = You cannot follow this organisation because this organisation has blocked you.
@@ -2697,40 +2714,40 @@ form.create_org_not_allowed = You are not allowed to create an organization.
settings = Settings
settings.options = Organization
-settings.full_name = Full Name
-settings.email = Contact Email
+settings.full_name = Full name
+settings.email = Contact email
settings.website = Website
settings.location = Location
settings.permission = Permissions
settings.repoadminchangeteam = Repository admin can add and remove access for teams
settings.visibility = Visibility
settings.visibility.public = Public
-settings.visibility.limited = Limited (Visible to authenticated users only)
+settings.visibility.limited = Limited (visible only to authenticated users)
settings.visibility.limited_shortname = Limited
-settings.visibility.private = Private (Visible only to organization members)
+settings.visibility.private = Private (visible only to organization members)
settings.visibility.private_shortname = Private
-settings.update_settings = Update Settings
+settings.update_settings = Update settings
settings.update_setting_success = Organization settings have been updated.
settings.change_orgname_prompt = Note: Changing the organization name will also change your organization's URL and free the old name.
settings.change_orgname_redirect_prompt = The old name will redirect until it is claimed.
settings.update_avatar_success = The organization's avatar has been updated.
-settings.delete = Delete Organization
-settings.delete_account = Delete This Organization
+settings.delete = Delete organization
+settings.delete_account = Delete this organization
settings.delete_prompt = The organization will be permanently removed. This CANNOT be undone!
-settings.confirm_delete_account = Confirm Deletion
-settings.delete_org_title = Delete Organization
+settings.confirm_delete_account = Confirm deletion
+settings.delete_org_title = Delete organization
settings.delete_org_desc = This organization will be deleted permanently. Continue?
settings.hooks_desc = Add webhooks which will be triggered for all repositories under this organization.
settings.labels_desc = Add labels which can be used on issues for all repositories under this organization.
-members.membership_visibility = Membership Visibility:
+members.membership_visibility = Membership visibility:
members.public = Visible
members.public_helper = make hidden
members.private = Hidden
members.private_helper = make visible
-members.member_role = Member Role:
+members.member_role = Member role:
members.owner = Owner
members.member = Member
members.remove = Remove
@@ -2738,40 +2755,40 @@ members.remove.detail = Remove %[1]s from %[2]s?
members.leave = Leave
members.leave.detail = Leave %s?
members.invite_desc = Add a new member to %s:
-members.invite_now = Invite Now
+members.invite_now = Invite now
teams.join = Join
teams.leave = Leave
teams.leave.detail = Leave %s?
teams.can_create_org_repo = Create repositories
teams.can_create_org_repo_helper = Members can create new repositories in organization. Creator will get administrator access to the new repository.
-teams.none_access = No Access
+teams.none_access = No access
teams.none_access_helper = Members cannot view or do any other action on this unit. It has no effect for public repositories.
-teams.general_access = General Access
+teams.general_access = General access
teams.general_access_helper = Members permissions will be decided by below permission table.
teams.read_access = Read
teams.read_access_helper = Members can view and clone team repositories.
teams.write_access = Write
teams.write_access_helper = Members can read and push to team repositories.
-teams.admin_access = Administrator Access
+teams.admin_access = Administrator access
teams.admin_access_helper = Members can pull and push to team repositories and add collaborators to them.
teams.no_desc = This team has no description
teams.settings = Settings
teams.owners_permission_desc = Owners have full access to all repositories and have administrator access to the organization.
-teams.members = Team Members
-teams.update_settings = Update Settings
-teams.delete_team = Delete Team
-teams.add_team_member = Add Team Member
+teams.members = Team members
+teams.update_settings = Update settings
+teams.delete_team = Delete team
+teams.add_team_member = Add team member
teams.invite_team_member = Invite to %s
-teams.invite_team_member.list = Pending Invitations
-teams.delete_team_title = Delete Team
+teams.invite_team_member.list = Pending invitations
+teams.delete_team_title = Delete team
teams.delete_team_desc = Deleting a team revokes repository access from its members. Continue?
teams.delete_team_success = The team has been deleted.
teams.read_permission_desc = This team grants Read access: members can view and clone team repositories.
teams.write_permission_desc = This team grants Write access: members can read from and push to team repositories.
teams.admin_permission_desc = This team grants Admin access: members can read from, push to and add collaborators to team repositories.
teams.create_repo_permission_desc = Additionally, this team grants Create repository permission: members can create new repositories in organization.
-teams.repositories = Team Repositories
+teams.repositories = Team repositories
teams.search_repo_placeholder = Search repository…
teams.remove_all_repos_title = Remove all team repositories
teams.remove_all_repos_desc = This will remove all repositories from the team.
@@ -2794,28 +2811,28 @@ teams.invite.description = Please click the button below to join the team.
[admin]
dashboard = Dashboard
-self_check = Self Check
-identity_access = Identity & Access
-users = User Accounts
+self_check = Self check
+identity_access = Identity & access
+users = User accounts
organizations = Organizations
-assets = Code Assets
+assets = Code assets
repositories = Repositories
hooks = Webhooks
integrations = Integrations
-authentication = Authentication Sources
-emails = User Emails
+authentication = Authentication sources
+emails = User emails
config = Configuration
-notices = System Notices
+notices = System notices
monitor = Monitoring
first_page = First
last_page = Last
total = Total: %d
-settings = Admin Settings
+settings = Admin settings
dashboard.new_version_hint = Forgejo %s is now available, you are running %s. Check the blog for more details.
dashboard.statistic = Summary
-dashboard.operations = Maintenance Operations
-dashboard.system_status = System Status
+dashboard.operations = Maintenance operations
+dashboard.system_status = System status
dashboard.operation_name = Operation Name
dashboard.operation_switch = Switch
dashboard.operation_run = Run
@@ -2839,9 +2856,9 @@ dashboard.delete_repo_archives.started = Delete all repository archives task sta
dashboard.delete_missing_repos = Delete all repositories missing their Git files
dashboard.delete_missing_repos.started = Delete all repositories missing their Git files task started.
dashboard.delete_generated_repository_avatars = Delete generated repository avatars
-dashboard.sync_repo_branches = Sync missed branches from git data to database
-dashboard.sync_repo_tags = Sync tags from git data to database
-dashboard.update_mirrors = Update Mirrors
+dashboard.sync_repo_branches = Sync missed branches from Git data to database
+dashboard.sync_repo_tags = Sync tags from Git data to database
+dashboard.update_mirrors = Update mirrors
dashboard.repo_health_check = Health check all repositories
dashboard.check_repo_stats = Check all repository statistics
dashboard.archive_cleanup = Delete old repository archives
@@ -2856,35 +2873,34 @@ dashboard.sync_external_users = Synchronize external user data
dashboard.cleanup_hook_task_table = Cleanup hook_task table
dashboard.cleanup_packages = Cleanup expired packages
dashboard.cleanup_actions = Cleanup expired logs and artifacts from actions
-dashboard.server_uptime = Server Uptime
-dashboard.current_goroutine = Current Goroutines
-dashboard.current_memory_usage = Current Memory Usage
-dashboard.total_memory_allocated = Total Memory Allocated
-dashboard.memory_obtained = Memory Obtained
-dashboard.pointer_lookup_times = Pointer Lookup Times
-dashboard.memory_allocate_times = Memory Allocations
-dashboard.memory_free_times = Memory Frees
-dashboard.current_heap_usage = Current Heap Usage
-dashboard.heap_memory_obtained = Heap Memory Obtained
-dashboard.heap_memory_idle = Heap Memory Idle
-dashboard.heap_memory_in_use = Heap Memory In Use
-dashboard.heap_memory_released = Heap Memory Released
-dashboard.heap_objects = Heap Objects
-dashboard.bootstrap_stack_usage = Bootstrap Stack Usage
-dashboard.stack_memory_obtained = Stack Memory Obtained
-dashboard.mspan_structures_usage = MSpan Structures Usage
-dashboard.mspan_structures_obtained = MSpan Structures Obtained
-dashboard.mcache_structures_usage = MCache Structures Usage
-dashboard.mcache_structures_obtained = MCache Structures Obtained
-dashboard.profiling_bucket_hash_table_obtained = Profiling Bucket Hash Table Obtained
-dashboard.gc_metadata_obtained = GC Metadata Obtained
-dashboard.other_system_allocation_obtained = Other System Allocation Obtained
-dashboard.next_gc_recycle = Next GC Recycle
-dashboard.last_gc_time = Since Last GC Time
-dashboard.total_gc_time = Total GC Pause
-dashboard.total_gc_pause = Total GC Pause
-dashboard.last_gc_pause = Last GC Pause
-dashboard.gc_times = GC Times
+dashboard.server_uptime = Server uptime
+dashboard.current_goroutine = Current goroutines
+dashboard.current_memory_usage = Current memory usage
+dashboard.total_memory_allocated = Total memory allocated
+dashboard.memory_obtained = Memory obtained
+dashboard.pointer_lookup_times = Pointer lookup times
+dashboard.memory_allocate_times = Memory allocations
+dashboard.memory_free_times = Memory frees
+dashboard.current_heap_usage = Current heap usage
+dashboard.heap_memory_obtained = Heap memory obtained
+dashboard.heap_memory_idle = Heap memory idle
+dashboard.heap_memory_in_use = Heap memory in use
+dashboard.heap_memory_released = Heap memory released
+dashboard.heap_objects = Heap objects
+dashboard.bootstrap_stack_usage = Bootstrap stack usage
+dashboard.stack_memory_obtained = Stack memory obtained
+dashboard.mspan_structures_usage = MSpan structures usage
+dashboard.mspan_structures_obtained = MSpan structures obtained
+dashboard.mcache_structures_usage = MCache structures usage
+dashboard.mcache_structures_obtained = MCache structures obtained
+dashboard.profiling_bucket_hash_table_obtained = Profiling bucket hash table obtained
+dashboard.gc_metadata_obtained = GC metadata obtained
+dashboard.other_system_allocation_obtained = Other system allocation obtained
+dashboard.next_gc_recycle = Next GC recycle
+dashboard.last_gc_time = Time since last GC
+dashboard.total_gc_pause = Total GC pause
+dashboard.last_gc_pause = Last GC pause
+dashboard.gc_times = GC times
dashboard.delete_old_actions = Delete all old actions from database
dashboard.delete_old_actions.started = Delete all old actions from database started.
dashboard.update_checker = Update checker
@@ -2898,10 +2914,10 @@ dashboard.sync_branch.started = Branches Sync started
dashboard.sync_tag.started = Tags Sync started
dashboard.rebuild_issue_indexer = Rebuild issue indexer
-users.user_manage_panel = User Account Management
+users.user_manage_panel = Manage user accounts
users.new_account = Create User Account
users.name = Username
-users.full_name = Full Name
+users.full_name = Full name
users.activated = Activated
users.admin = Admin
users.restricted = Restricted
@@ -2911,29 +2927,29 @@ users.remote = Remote
users.2fa = 2FA
users.repos = Repos
users.created = Created
-users.last_login = Last Sign-In
-users.never_login = Never Signed-In
-users.send_register_notify = Send User Registration Notification
+users.last_login = Last sign-in
+users.never_login = Never signed in
+users.send_register_notify = Send user registration notification
users.new_success = The user account "%s" has been created.
users.edit = Edit
-users.auth_source = Authentication Source
+users.auth_source = Authentication source
users.local = Local
-users.auth_login_name = Authentication Sign-In Name
+users.auth_login_name = Authentication sign-in name
users.password_helper = Leave the password empty to keep it unchanged.
users.update_profile_success = The user account has been updated.
-users.edit_account = Edit User Account
-users.max_repo_creation = Maximum Number of Repositories
+users.edit_account = Edit user account
+users.max_repo_creation = Maximum number of repositories
users.max_repo_creation_desc = (Enter -1 to use the global default limit.)
users.is_activated = User Account Is Activated
-users.prohibit_login = Disable Sign-In
-users.is_admin = Is Administrator
-users.is_restricted = Is Restricted
-users.allow_git_hook = May Create Git Hooks
-users.allow_git_hook_tooltip = Git Hooks are executed as the OS user running Forgejo and will have the same level of host access. As a result, users with this special Git Hook privilege can access and modify all Forgejo repositories as well as the database used by Forgejo. Consequently they are also able to gain Forgejo administrator privileges.
-users.allow_import_local = May Import Local Repositories
-users.allow_create_organization = May Create Organizations
-users.update_profile = Update User Account
-users.delete_account = Delete User Account
+users.prohibit_login = Disable sign-in
+users.is_admin = Is administrator
+users.is_restricted = Is restricted
+users.allow_git_hook = Can create Git hooks
+users.allow_git_hook_tooltip = Git hooks are executed as the OS user running Forgejo and will have the same level of host access. As a result, users with this special Git hook privilege can access and modify all Forgejo repositories as well as the database used by Forgejo. Consequently they are also able to gain Forgejo administrator privileges.
+users.allow_import_local = Can import local repositories
+users.allow_create_organization = Can create organizations
+users.update_profile = Update user account
+users.delete_account = Delete user account
users.cannot_delete_self = You cannot delete yourself
users.still_own_repo = This user still owns one or more repositories. Delete or transfer these repositories first.
users.still_has_org = This user is a member of an organization. Remove the user from any organizations first.
@@ -2956,27 +2972,27 @@ users.list_status_filter.is_2fa_enabled = 2FA Enabled
users.list_status_filter.not_2fa_enabled = 2FA Disabled
users.details = User Details
-emails.email_manage_panel = User Email Management
+emails.email_manage_panel = Manage user emails
emails.primary = Primary
emails.activated = Activated
emails.filter_sort.email = Email
emails.filter_sort.email_reverse = Email (reverse)
-emails.filter_sort.name = User Name
-emails.filter_sort.name_reverse = User Name (reverse)
+emails.filter_sort.name = Username
+emails.filter_sort.name_reverse = Username (reverse)
emails.updated = Email updated
emails.not_updated = Failed to update the requested email address: %v
emails.duplicate_active = This email address is already active for a different user.
emails.change_email_header = Update Email Properties
emails.change_email_text = Are you sure you want to update this email address?
-orgs.org_manage_panel = Organization Management
+orgs.org_manage_panel = Manage organizations
orgs.name = Name
orgs.teams = Teams
orgs.members = Members
-orgs.new_orga = New Organization
+orgs.new_orga = New organization
-repos.repo_manage_panel = Repository Management
-repos.unadopted = Unadopted Repositories
+repos.repo_manage_panel = Manage repositories
+repos.unadopted = Unadopted repositories
repos.unadopted.no_more = No more unadopted repositories found
repos.owner = Owner
repos.name = Name
@@ -2988,7 +3004,7 @@ repos.issues = Issues
repos.size = Size
repos.lfs_size = LFS Size
-packages.package_manage_panel = Package Management
+packages.package_manage_panel = Manage packages
packages.total_size = Total Size: %s
packages.unreferenced_size = Unreferenced Size: %s
packages.cleanup = Clean up expired data
@@ -3002,70 +3018,70 @@ packages.repository = Repository
packages.size = Size
packages.published = Published
-defaulthooks = Default Webhooks
+defaulthooks = Default webhooks
defaulthooks.desc = Webhooks automatically make HTTP POST requests to a server when certain Forgejo events trigger. Webhooks defined here are defaults and will be copied into all new repositories. Read more in the webhooks guide .
defaulthooks.add_webhook = Add Default Webhook
defaulthooks.update_webhook = Update Default Webhook
-systemhooks = System Webhooks
+systemhooks = System webhooks
systemhooks.desc = Webhooks automatically make HTTP POST requests to a server when certain Forgejo events trigger. Webhooks defined here will act on all repositories on the system, so please consider any performance implications this may have. Read more in the webhooks guide .
systemhooks.add_webhook = Add System Webhook
systemhooks.update_webhook = Update System Webhook
-auths.auth_manage_panel = Authentication Source Management
-auths.new = Add Authentication Source
+auths.auth_manage_panel = Manage authentication sources
+auths.new = Add authentication source
auths.name = Name
auths.type = Type
auths.enabled = Enabled
-auths.syncenabled = Enable User Synchronization
+auths.syncenabled = Enable user synchronization
auths.updated = Updated
-auths.auth_type = Authentication Type
-auths.auth_name = Authentication Name
-auths.security_protocol = Security Protocol
+auths.auth_type = Authentication type
+auths.auth_name = Authentication name
+auths.security_protocol = Security protocol
auths.domain = Domain
auths.host = Host
auths.port = Port
auths.bind_dn = Bind DN
-auths.bind_password = Bind Password
-auths.user_base = User Search Base
+auths.bind_password = Bind password
+auths.user_base = User search base
auths.user_dn = User DN
-auths.attribute_username = Username Attribute
+auths.attribute_username = Username attribute
auths.attribute_username_placeholder = Leave empty to use the username entered in Forgejo.
-auths.attribute_name = First Name Attribute
-auths.attribute_surname = Surname Attribute
-auths.attribute_mail = Email Attribute
-auths.attribute_ssh_public_key = Public SSH Key Attribute
-auths.attribute_avatar = Avatar Attribute
-auths.attributes_in_bind = Fetch Attributes in Bind DN Context
+auths.attribute_name = First name attribute
+auths.attribute_surname = Surname attribute
+auths.attribute_mail = Email attribute
+auths.attribute_ssh_public_key = Public SSH key attribute
+auths.attribute_avatar = Avatar attribute
+auths.attributes_in_bind = Fetch attributes in nind DN context
auths.allow_deactivate_all = Allow an empty search result to deactivate all users
-auths.use_paged_search = Use Paged Search
-auths.search_page_size = Page Size
-auths.filter = User Filter
-auths.admin_filter = Admin Filter
-auths.restricted_filter = Restricted Filter
-auths.restricted_filter_helper = Leave empty to not set any users as restricted. Use an asterisk ("*") to set all users that do not match Admin Filter as restricted.
+auths.use_paged_search = Use paged search
+auths.search_page_size = Page size
+auths.filter = User filter
+auths.admin_filter = Admin filter
+auths.restricted_filter = Restricted filter
+auths.restricted_filter_helper = Leave empty to not set any users as restricted. Use an asterisk ("*") to set all users that do not match Admin filter as restricted.
auths.verify_group_membership = Verify group membership in LDAP (leave the filter empty to skip)
-auths.group_search_base = Group Search Base DN
-auths.group_attribute_list_users = Group Attribute Containing List Of Users
-auths.user_attribute_in_group = User Attribute Listed In Group
+auths.group_search_base = Group search base DN
+auths.group_attribute_list_users = Group attribute containing list of users
+auths.user_attribute_in_group = User attribute listed in group
auths.map_group_to_team = Map LDAP groups to Organization teams (leave the field empty to skip)
auths.map_group_to_team_removal = Remove users from synchronized teams if user does not belong to corresponding LDAP group
auths.enable_ldap_groups = Enable LDAP groups
-auths.ms_ad_sa = MS AD Search Attributes
-auths.smtp_auth = SMTP Authentication Type
-auths.smtphost = SMTP Host
-auths.smtpport = SMTP Port
-auths.allowed_domains = Allowed Domains
+auths.ms_ad_sa = MS AD search attributes
+auths.smtp_auth = SMTP authentication type
+auths.smtphost = SMTP host
+auths.smtpport = SMTP port
+auths.allowed_domains = Allowed domains
auths.allowed_domains_helper = Leave empty to allow all domains. Separate multiple domains with a comma (",").
-auths.skip_tls_verify = Skip TLS Verify
+auths.skip_tls_verify = Skip TLS verification
auths.force_smtps = Force SMTPS
auths.force_smtps_helper = SMTPS is always used on port 465. Set this to force SMTPS on other ports. (Otherwise STARTTLS will be used on other ports if it is supported by the host.)
-auths.helo_hostname = HELO Hostname
+auths.helo_hostname = HELO hostname
auths.helo_hostname_helper = Hostname sent with HELO. Leave blank to send current hostname.
auths.disable_helo = Disable HELO
-auths.pam_service_name = PAM Service Name
-auths.pam_email_domain = PAM Email Domain (optional)
-auths.oauth2_provider = OAuth2 Provider
+auths.pam_service_name = PAM service name
+auths.pam_email_domain = PAM email domain (optional)
+auths.oauth2_provider = OAuth2 provider
auths.oauth2_icon_url = Icon URL
auths.oauth2_clientID = Client ID (Key)
auths.oauth2_clientSecret = Client Secret
@@ -3078,17 +3094,17 @@ auths.oauth2_emailURL = Email URL
auths.skip_local_two_fa = Skip local 2FA
auths.skip_local_two_fa_helper = Leaving unset means local users with 2FA set will still have to pass 2FA to log on
auths.oauth2_tenant = Tenant
-auths.oauth2_scopes = Additional Scopes
-auths.oauth2_required_claim_name = Required Claim Name
+auths.oauth2_scopes = Additional scopes
+auths.oauth2_required_claim_name = Required claim name
auths.oauth2_required_claim_name_helper = Set this name to restrict login from this source to users with a claim with this name
-auths.oauth2_required_claim_value = Required Claim Value
+auths.oauth2_required_claim_value = Required claim value
auths.oauth2_required_claim_value_helper = Set this value to restrict login from this source to users with a claim with this name and value
auths.oauth2_group_claim_name = Claim name providing group names for this source. (Optional)
-auths.oauth2_admin_group = Group Claim value for administrator users. (Optional - requires claim name above)
-auths.oauth2_restricted_group = Group Claim value for restricted users. (Optional - requires claim name above)
-auths.oauth2_map_group_to_team = Map claimed groups to Organization teams. (Optional - requires claim name above)
+auths.oauth2_admin_group = Group claim value for administrator users. (Optional - requires claim name above)
+auths.oauth2_restricted_group = Group claim value for restricted users. (Optional - requires claim name above)
+auths.oauth2_map_group_to_team = Map claimed groups to organization teams. (Optional - requires claim name above)
auths.oauth2_map_group_to_team_removal = Remove users from synchronized teams if user does not belong to corresponding group.
-auths.enable_auto_register = Enable Auto Registration
+auths.enable_auto_register = Enable auto registration
auths.sspi_auto_create_users = Automatically create users
auths.sspi_auto_create_users_helper = Allow SSPI auth method to automatically create new accounts for users that login for the first time
auths.sspi_auto_activate_users = Automatically activate users
@@ -3100,9 +3116,10 @@ auths.sspi_separator_replacement_helper = The character to use to replace the se
auths.sspi_default_language = Default user language
auths.sspi_default_language_helper = Default language for users automatically created by SSPI auth method. Leave empty if you prefer language to be automatically detected.
auths.tips = Tips
-auths.tips.oauth2.general = OAuth2 Authentication
+auths.tips.gmail_settings = Gmail settings:
+auths.tips.oauth2.general = OAuth2 authentication
auths.tips.oauth2.general.tip = When registering a new OAuth2 authentication, the callback/redirect URL should be:
-auths.tip.oauth2_provider = OAuth2 Provider
+auths.tip.oauth2_provider = OAuth2 provider
auths.tip.bitbucket = Register a new OAuth consumer on https://bitbucket.org/account/user//oauth-consumers/new and add the permission "Account" - "Read"
auths.tip.nextcloud = Register a new OAuth consumer on your instance using the following menu "Settings -> Security -> OAuth 2.0 client"
auths.tip.dropbox = Create a new application at https://www.dropbox.com/developers/apps
@@ -3116,13 +3133,13 @@ auths.tip.discord = Register a new application on https://discordapp.com/develop
auths.tip.gitea = Register a new OAuth2 application. Guide can be found at https://docs.gitea.com/development/oauth2-provider
auths.tip.yandex = Create a new application at https://oauth.yandex.com/client/new. Select following permissions from the "Yandex.Passport API" section: "Access to email address", "Access to user avatar" and "Access to username, first name and surname, gender"
auths.tip.mastodon = Input a custom instance URL for the mastodon instance you want to authenticate with (or use the default one)
-auths.edit = Edit Authentication Source
-auths.activated = This Authentication Source is Activated
+auths.edit = Edit authentication source
+auths.activated = This authentication source is activated
auths.new_success = The authentication "%s" has been added.
auths.update_success = The authentication source has been updated.
-auths.update = Update Authentication Source
-auths.delete = Delete Authentication Source
-auths.delete_auth_title = Delete Authentication Source
+auths.update = Update authentication source
+auths.delete = Delete authentication source
+auths.delete_auth_title = Delete authentication source
auths.delete_auth_desc = Deleting an authentication source prevents users from using it to sign in. Continue?
auths.still_in_used = The authentication source is still in use. Convert or delete any users using this authentication source first.
auths.deletion_success = The authentication source has been deleted.
@@ -3131,43 +3148,43 @@ auths.login_source_of_type_exist = An authentication source of this type already
auths.unable_to_initialize_openid = Unable to initialize OpenID Connect Provider: %s
auths.invalid_openIdConnectAutoDiscoveryURL = Invalid Auto Discovery URL (this must be a valid URL starting with http:// or https://)
-config.server_config = Server Configuration
-config.app_name = Site Title
-config.app_ver = Forgejo Version
-config.app_url = Forgejo Base URL
-config.custom_conf = Configuration File Path
-config.custom_file_root_path = Custom File Root Path
-config.domain = Server Domain
-config.offline_mode = Local Mode
-config.disable_router_log = Disable Router Log
-config.run_user = Run As Username
-config.run_mode = Run Mode
-config.git_version = Git Version
-config.app_data_path = App Data Path
-config.repo_root_path = Repository Root Path
-config.lfs_root_path = LFS Root Path
-config.log_file_root_path = Log Path
-config.script_type = Script Type
-config.reverse_auth_user = Reverse Authentication User
+config.server_config = Server configuration
+config.app_name = Instance title
+config.app_ver = Forgejo version
+config.app_url = Base URL
+config.custom_conf = Configuration file path
+config.custom_file_root_path = Custom file root path
+config.domain = Server domain
+config.offline_mode = Local mode
+config.disable_router_log = Disable router log
+config.run_user = User to run as
+config.run_mode = Run mode
+config.git_version = Git version
+config.app_data_path = App data path
+config.repo_root_path = Repository root path
+config.lfs_root_path = LFS root path
+config.log_file_root_path = Log path
+config.script_type = Script type
+config.reverse_auth_user = Reverse authentication user
-config.ssh_config = SSH Configuration
+config.ssh_config = SSH configuration
config.ssh_enabled = Enabled
-config.ssh_start_builtin_server = Use Built-In Server
-config.ssh_domain = SSH Server Domain
+config.ssh_start_builtin_server = Use built-in server
+config.ssh_domain = SSH server domain
config.ssh_port = Port
-config.ssh_listen_port = Listen Port
-config.ssh_root_path = Root Path
-config.ssh_key_test_path = Key Test Path
-config.ssh_keygen_path = Keygen ("ssh-keygen") Path
-config.ssh_minimum_key_size_check = Minimum Key Size Check
-config.ssh_minimum_key_sizes = Minimum Key Sizes
+config.ssh_listen_port = Listen port
+config.ssh_root_path = Root path
+config.ssh_key_test_path = Key test path
+config.ssh_keygen_path = Keygen ("ssh-keygen") path
+config.ssh_minimum_key_size_check = Minimum key size check
+config.ssh_minimum_key_sizes = Minimum key sizes
-config.lfs_config = LFS Configuration
+config.lfs_config = LFS configuration
config.lfs_enabled = Enabled
-config.lfs_content_path = LFS Content Path
-config.lfs_http_auth_expiry = LFS HTTP Auth Expiry
+config.lfs_content_path = LFS content path
+config.lfs_http_auth_expiry = LFS HTTP auth expiration time
-config.db_config = Database Configuration
+config.db_config = Database configuration
config.db_type = Type
config.db_host = Host
config.db_name = Name
@@ -3176,99 +3193,99 @@ config.db_schema = Schema
config.db_ssl_mode = SSL
config.db_path = Path
-config.service_config = Service Configuration
-config.register_email_confirm = Require Email Confirmation to Register
-config.disable_register = Disable Self-Registration
-config.allow_only_internal_registration = Allow Registration Only Through Forgejo itself
-config.allow_only_external_registration = Allow Registration Only Through External Services
-config.enable_openid_signup = Enable OpenID Self-Registration
-config.enable_openid_signin = Enable OpenID Sign-In
-config.show_registration_button = Show Register Button
-config.require_sign_in_view = Require Sign-In to View Pages
-config.mail_notify = Enable Email Notifications
+config.service_config = Service configuration
+config.register_email_confirm = Require email confirmation to register
+config.disable_register = Disable self-registration
+config.allow_only_internal_registration = Allow registration only through Forgejo itself
+config.allow_only_external_registration = Allow registration only through external Services
+config.enable_openid_signup = Enable OpenID self-registration
+config.enable_openid_signin = Enable OpenID sign-in
+config.show_registration_button = Show register button
+config.require_sign_in_view = Require to sign-in to view content
+config.mail_notify = Enable email notifications
config.enable_captcha = Enable CAPTCHA
-config.active_code_lives = Active Code Lives
-config.reset_password_code_lives = Recover Account Code Expiry Time
-config.default_keep_email_private = Hide Email Addresses by Default
-config.default_allow_create_organization = Allow Creation of Organizations by Default
-config.enable_timetracking = Enable Time Tracking
-config.default_enable_timetracking = Enable Time Tracking by Default
-config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time
-config.no_reply_address = Hidden Email Domain
-config.default_visibility_organization = Default visibility for new Organizations
-config.default_enable_dependencies = Enable Issue Dependencies by Default
+config.active_code_lives = Activation code expiration time
+config.reset_password_code_lives = Recovery code expiration time
+config.default_keep_email_private = Hide email addresses by default
+config.default_allow_create_organization = Allow creation of organizations by default
+config.enable_timetracking = Enable time tracking
+config.default_enable_timetracking = Enable time tracking by default
+config.default_allow_only_contributors_to_track_time = Let only contributors track time
+config.no_reply_address = Hidden email domain
+config.default_visibility_organization = Default visibility of new organizations
+config.default_enable_dependencies = Enable issue dependencies by default
-config.webhook_config = Webhook Configuration
-config.queue_length = Queue Length
-config.deliver_timeout = Deliver Timeout
-config.skip_tls_verify = Skip TLS Verification
+config.webhook_config = Webhook configuration
+config.queue_length = Queue length
+config.deliver_timeout = Deliver timeout
+config.skip_tls_verify = Skip TLS verification
-config.mailer_config = Mailer Configuration
+config.mailer_config = Mailer configuration
config.mailer_enabled = Enabled
config.mailer_enable_helo = Enable HELO
config.mailer_name = Name
config.mailer_protocol = Protocol
-config.mailer_smtp_addr = SMTP Addr
-config.mailer_smtp_port = SMTP Port
+config.mailer_smtp_addr = SMTP host
+config.mailer_smtp_port = SMTP port
config.mailer_user = User
config.mailer_use_sendmail = Use Sendmail
-config.mailer_sendmail_path = Sendmail Path
+config.mailer_sendmail_path = Sendmail path
config.mailer_sendmail_args = Extra Arguments to Sendmail
-config.mailer_sendmail_timeout = Sendmail Timeout
+config.mailer_sendmail_timeout = Sendmail timeout
config.mailer_use_dummy = Dummy
config.test_email_placeholder = Email (e.g. test@example.com)
-config.send_test_mail = Send Testing Email
+config.send_test_mail = Send test email
config.send_test_mail_submit = Send
-config.test_mail_failed = Failed to send a testing email to "%s": %v
-config.test_mail_sent = A testing email has been sent to "%s".
+config.test_mail_failed = Failed to send a test email to "%s": %v
+config.test_mail_sent = A test email has been sent to "%s".
-config.oauth_config = OAuth Configuration
+config.oauth_config = OAuth configuration
config.oauth_enabled = Enabled
-config.cache_config = Cache Configuration
-config.cache_adapter = Cache Adapter
-config.cache_interval = Cache Interval
-config.cache_conn = Cache Connection
-config.cache_item_ttl = Cache Item TTL
+config.cache_config = Cache configuration
+config.cache_adapter = Cache adapter
+config.cache_interval = Cache interval
+config.cache_conn = Cache connection
+config.cache_item_ttl = Cache item TTL
-config.session_config = Session Configuration
-config.session_provider = Session Provider
-config.provider_config = Provider Config
-config.cookie_name = Cookie Name
-config.gc_interval_time = GC Interval Time
-config.session_life_time = Session Life Time
-config.https_only = HTTPS Only
-config.cookie_life_time = Cookie Life Time
+config.session_config = Session configuration
+config.session_provider = Session provider
+config.provider_config = Provider config
+config.cookie_name = Cookie name
+config.gc_interval_time = GC interval time
+config.session_life_time = Session lifetime
+config.https_only = HTTPS only
+config.cookie_life_time = Cookie lifetime
-config.picture_config = Picture and Avatar Configuration
-config.picture_service = Picture Service
+config.picture_config = Picture and avatar configuration
+config.picture_service = Picture service
config.disable_gravatar = Disable Gravatar
-config.enable_federated_avatar = Enable Federated Avatars
+config.enable_federated_avatar = Enable federated avatars
-config.git_config = Git Configuration
-config.git_disable_diff_highlight = Disable Diff Syntax Highlight
-config.git_max_diff_lines = Max Diff Lines (for a single file)
-config.git_max_diff_line_characters = Max Diff Characters (for a single line)
-config.git_max_diff_files = Max Diff Files (to be shown)
-config.git_gc_args = GC Arguments
-config.git_migrate_timeout = Migration Timeout
-config.git_mirror_timeout = Mirror Update Timeout
-config.git_clone_timeout = Clone Operation Timeout
-config.git_pull_timeout = Pull Operation Timeout
-config.git_gc_timeout = GC Operation Timeout
+config.git_config = Git configuration
+config.git_disable_diff_highlight = Disable diff syntax highlighting
+config.git_max_diff_lines = Max diff lines per file
+config.git_max_diff_line_characters = Max diff characters per line
+config.git_max_diff_files = Max diff files shown
+config.git_gc_args = GC arguments
+config.git_migrate_timeout = Migration timeout
+config.git_mirror_timeout = Mirror Update timeout
+config.git_clone_timeout = Clone Operation timeout
+config.git_pull_timeout = Pull Operation timeout
+config.git_gc_timeout = GC Operation timeout
-config.log_config = Log Configuration
+config.log_config = Log configuration
config.logger_name_fmt = Logger: %s
config.disabled_logger = Disabled
-config.access_log_mode = Access Log Mode
-config.access_log_template = Access Log Template
+config.access_log_mode = Access log mode
+config.access_log_template = Access log template
config.xorm_log_sql = Log SQL
config.set_setting_failed = Set setting %s failed
monitor.stats = Stats
-monitor.cron = Cron Tasks
+monitor.cron = Cron tasks
monitor.name = Name
monitor.schedule = Schedule
monitor.next = Next Time
@@ -3292,29 +3309,29 @@ monitor.queue = Queue: %s
monitor.queue.name = Name
monitor.queue.type = Type
monitor.queue.exemplar = Exemplar Type
-monitor.queue.numberworkers = Number of Workers
-monitor.queue.activeworkers = Active Workers
-monitor.queue.maxnumberworkers = Max Number of Workers
-monitor.queue.numberinqueue = Number in Queue
-monitor.queue.review_add = Review / Add Workers
-monitor.queue.settings.title = Pool Settings
+monitor.queue.numberworkers = Number of workers
+monitor.queue.activeworkers = Active workers
+monitor.queue.maxnumberworkers = Max Number of workers
+monitor.queue.numberinqueue = Number in queue
+monitor.queue.review_add = Review / add workers
+monitor.queue.settings.title = Pool settings
monitor.queue.settings.desc = Pools dynamically grow in response to their worker queue blocking.
monitor.queue.settings.maxnumberworkers = Max Number of workers
monitor.queue.settings.maxnumberworkers.placeholder = Currently %[1]d
monitor.queue.settings.maxnumberworkers.error = Max number of workers must be a number
-monitor.queue.settings.submit = Update Settings
-monitor.queue.settings.changed = Settings Updated
+monitor.queue.settings.submit = Update settings
+monitor.queue.settings.changed = Settings updated
monitor.queue.settings.remove_all_items = Remove all
monitor.queue.settings.remove_all_items_done = All items in the queue have been removed.
-notices.system_notice_list = System Notices
-notices.view_detail_header = View Notice Details
+notices.system_notice_list = System notices
+notices.view_detail_header = Notice details
notices.operations = Operations
-notices.select_all = Select All
-notices.deselect_all = Deselect All
-notices.inverse_selection = Inverse Selection
-notices.delete_selected = Delete Selected
-notices.delete_all = Delete All Notices
+notices.select_all = Select all
+notices.deselect_all = Deselect all
+notices.inverse_selection = Inverse selection
+notices.delete_selected = Delete selected
+notices.delete_all = Delete all notices
notices.type = Type
notices.type_1 = Repository
notices.type_2 = Task
@@ -3439,9 +3456,9 @@ dependencies = Dependencies
keywords = Keywords
details = Details
details.author = Author
-details.project_site = Project Site
-details.repository_site = Repository Site
-details.documentation_site = Documentation Site
+details.project_site = Project website
+details.repository_site = Repository website
+details.documentation_site = Documentation website
details.license = License
assets = Assets
versions = Versions
@@ -3538,19 +3555,19 @@ settings.delete.notice = You are about to delete %s (%s). This operation is irre
settings.delete.success = The package has been deleted.
settings.delete.error = Failed to delete the package.
owner.settings.cargo.title = Cargo registry index
-owner.settings.cargo.initialize = Initialize Index
+owner.settings.cargo.initialize = Initialize index
owner.settings.cargo.initialize.description = A special index Git repository is needed to use the Cargo registry. Using this option will (re-)create the repository and configure it automatically.
owner.settings.cargo.initialize.error = Failed to initialize Cargo index: %v
owner.settings.cargo.initialize.success = The Cargo index was successfully created.
-owner.settings.cargo.rebuild = Rebuild Index
+owner.settings.cargo.rebuild = Rebuild index
owner.settings.cargo.rebuild.description = Rebuilding can be useful if the index is not synchronized with the stored Cargo packages.
owner.settings.cargo.rebuild.error = Failed to rebuild Cargo index: %v
owner.settings.cargo.rebuild.success = The Cargo index was successfully rebuild.
owner.settings.cleanuprules.title = Manage cleanup rules
-owner.settings.cleanuprules.add = Add Cleanup Rule
-owner.settings.cleanuprules.edit = Edit Cleanup Rule
+owner.settings.cleanuprules.add = Add cleanup rule
+owner.settings.cleanuprules.edit = Edit cleanup rule
owner.settings.cleanuprules.none = There are no cleanup rules yet.
-owner.settings.cleanuprules.preview = Cleanup Rule Preview
+owner.settings.cleanuprules.preview = Cleanup rule preview
owner.settings.cleanuprules.preview.overview = %d packages are scheduled to be removed.
owner.settings.cleanuprules.preview.none = Cleanup rule does not match any packages.
owner.settings.cleanuprules.enabled = Enabled
@@ -3583,7 +3600,7 @@ deletion = Remove secret
deletion.description = Removing a secret is permanent and cannot be undone. Continue?
deletion.success = The secret has been removed.
deletion.failed = Failed to remove secret.
-management = Secrets Management
+management = Manage secrets
[actions]
actions = Actions
@@ -3600,8 +3617,8 @@ status.skipped = Skipped
status.blocked = Blocked
runners = Runners
-runners.runner_manage_panel = Runners Management
-runners.new = Create new Runner
+runners.runner_manage_panel = Manage runners
+runners.new = Create new runner
runners.new_notice = How to start a runner
runners.status = Status
runners.id = ID
@@ -3609,7 +3626,7 @@ runners.name = Name
runners.owner_type = Type
runners.description = Description
runners.labels = Labels
-runners.last_online = Last Online Time
+runners.last_online = Last online time
runners.runner_title = Runner
runners.task_list = Recent tasks on this runner
runners.task_list.no_tasks = There is no task yet.
@@ -3619,7 +3636,7 @@ runners.task_list.repository = Repository
runners.task_list.commit = Commit
runners.task_list.done_at = Done At
runners.edit_runner = Edit Runner
-runners.update_runner = Update Changes
+runners.update_runner = Update changes
runners.update_runner_success = Runner updated successfully
runners.update_runner_failed = Failed to update runner
runners.delete_runner = Delete this runner
@@ -3636,7 +3653,7 @@ runners.version = Version
runners.reset_registration_token = Reset registration token
runners.reset_registration_token_success = Runner registration token reset successfully
-runs.all_workflows = All Workflows
+runs.all_workflows = All workflows
runs.commit = Commit
runs.scheduled = Scheduled
runs.pushed_by = pushed by
@@ -3654,17 +3671,17 @@ runs.no_workflows.documentation = For more information on Forgejo Actions, see <
runs.no_runs = The workflow has no runs yet.
runs.empty_commit_message = (empty commit message)
-workflow.disable = Disable Workflow
+workflow.disable = Disable workflow
workflow.disable_success = Workflow "%s" disabled successfully.
-workflow.enable = Enable Workflow
+workflow.enable = Enable workflow
workflow.enable_success = Workflow "%s" enabled successfully.
workflow.disabled = Workflow is disabled.
need_approval_desc = Need approval to run workflows for fork pull request.
variables = Variables
-variables.management = Variables Management
-variables.creation = Add Variable
+variables.management = Manage variables
+variables.creation = Add variable
variables.none = There are no variables yet.
variables.deletion = Remove variable
variables.deletion.description = Removing a variable is permanent and cannot be undone. Continue?
@@ -3679,9 +3696,9 @@ variables.update.failed = Failed to edit variable.
variables.update.success = The variable has been edited.
[projects]
-type-1.display_name = Individual Project
-type-2.display_name = Repository Project
-type-3.display_name = Organization Project
+type-1.display_name = Individual project
+type-2.display_name = Repository project
+type-3.display_name = Organization project
[git.filemode]
changed_filemode = %[1]s → %[2]s
diff --git a/options/locale/locale_eo.ini b/options/locale/locale_eo.ini
index 0f3f2ea010..7f56810d6f 100644
--- a/options/locale/locale_eo.ini
+++ b/options/locale/locale_eo.ini
@@ -92,7 +92,7 @@ copy_error = Malsukcesis kopii
copy = Kopii
enabled = Ŝaltita
rerun = Reruli
-milestones = Celpunktoj
+milestones = Celoj
show_timestamps = Montri datojn
rss_feed = RSS-fluo
never = Neniam
@@ -118,6 +118,23 @@ new_project_column = Novan kolumnon
new_migrate = Novan enporton
mirror = Spegulo
powered_by = Servas vin %s
+remove = Forigi
+filter = Filtri
+filter.is_archived = Arĥivita
+filter.not_archived = Nearĥivita
+filter.is_fork = Disbranĉigita
+filter.not_fork = Nedisbranĉigita
+filter.is_mirror = Spegulita
+filter.not_mirror = Nespegulita
+filter.is_template = Ŝablono
+filter.not_template = Neŝablono
+filter.public = Publika
+filter.private = Privata
+dashboard = Labortablo
+toggle_menu = Baskuli menuon
+access_token = Alira ĵetono
+remove_all = Forigi ĉion
+remove_label_str = Forigi «%s»
[editor]
buttons.list.ordered.tooltip = Aldoni nombran liston
@@ -152,6 +169,7 @@ network_error = Reteraro
invalid_csrf = Malvalida peto: malvalida CSRF-kodo
occurred = Eraris iel
missing_csrf = Malvalida peto: neniu CSRF-kodo
+server_internal = Eraris interno de servilo
[heatmap]
less = Malpli
@@ -280,6 +298,8 @@ env_config_keys = Mediagordoj
enable_update_checker_helper = Foje kontrolas ĉu estas novaj versioj per konektiĝo al gitea.io.
invalid_password_algorithm = Malvalida pasvorthakeita algoritmo
password_algorithm_helper = Agordas la pasvorthaketigan algoritmon. Algoritmoj havas malsamajn postulojn kaj efikecojn. La algoritmo argon2 sufiĉe sekuras, sed postulas multan memoron kaj eble ne taŭgas por nepotencaj serviloj.
+internal_token_failed = Malsukcesis krei internan ĵetonon: %v
+smtp_from_invalid = La «Sendu retleterojn kiel» adreso malvalidas
[admin]
config.app_data_path = Programdatuja doseiervojo
@@ -306,6 +326,7 @@ show_only_unarchived = Montras sole nearĥivitajn
my_mirrors = Miaj speguloj
show_only_archived = Montras sole arĥivitajn
view_home = Vidi %s
+switch_dashboard_context = Ŝanĝi labortablon
[explore]
search.match.tooltip = Inkluzivu sole rezultojn kiuj akordas precize la serĉomendon
@@ -389,6 +410,13 @@ openid_register_desc = La elektita OpenID URI estas nekonata. Ligi ĝin al nova
reset_password = Rehavigo de konto
sspi_auth_failed = SSPI aŭtentikigo malsukcesis
must_change_password = Ŝanĝu vian pasvorton
+remember_me = Memoru ĉi tiun aparaton
+account_activated = Konto aktivigita
+resent_limit_prompt = Vi jam petis freŝe aktivigan retleteron. Bonvolu atendi 3 minutojn kaj reprovu.
+change_unconfirmed_email_summary = Ŝanĝi al retpoŝtadreson al kiu la aktiviga retletero sendiĝu.
+invalid_code_forgot_password = Via konfirmkodo malvalidas aŭ jam eksdatiĝis. Klaku ĉi tien por komenci novan saluton.
+authorize_redirect_notice = Vi alidirektiĝos al %s se vi aprobus ĉi tiun programon.
+active_your_account = Aktivigi vian konton
[mail]
activate_account.text_1 = Saluton %[1]s , dankon pro via registriĝo ĉe %[2]s!
@@ -437,6 +465,9 @@ reset_password = Rehavigi vian konton
issue.action.merge = @%[1]s kunfandis #%[2]d al %[3]s.
issue.action.push_1 = @%[1]s puŝis %[3]d enmeton al %[2]s
issue.action.push_n = @%[1]s puŝis %[3]d enmetojn al %[2]s
+activate_account = Bonvolu aktivigi vian konton
+activate_account.title = %s, bonvolu aktivigi vian konton
+activate_account.text_2 = Bonvolu klaki la jenan ligilon por aktivigi vian konton antaŭ %s :
[form]
TeamName = Gruponomo
@@ -715,3 +746,60 @@ followers = Abonantoj
block_user.detail_2 = La uzanto ne povos interagi viajn deponejojn, erarojn, kaj komentojn.
block_user = Bloki uzanton
change_avatar = Ŝanĝi vian profilbildon…
+
+
+[repo]
+editor.add_file = Aldoni dosieron
+settings.tags = Etikedoj
+archive.title_date = Ĉi tiu deponejo arĥiviĝis je %s. Vi povas vidi kaj elŝuti dosierojn, sed ne povas puŝi nek raporti problemojn nek tirpeti.
+archive.pull.nocomment = Ĉi tiu deponejo estas arĥivigita. Vi ne povas respondi tirpetojn.
+migrate_items_pullrequests = Tirpetoj
+migrate.migrating_pulls = Migrante tirpetojn
+unwatch = Malspuri
+watch = Spuri
+star = Stelumi
+unstar = Malstelumi
+fork = Disbranĉigi
+code = Fontkodo
+branch = Branĉo
+pulls = Tirpetoj
+commits = Enmetoj
+releases = Eldonoj
+commit_graph.hide_pr_refs = Kaŝi tirpetojn
+activity.title.prs_n = %d tirpetoj
+activity.opened_prs_count_n = Proponitaj tirpetoj
+contributors.contribution_type.commits = Enmetoj
+settings = Agordoj
+settings.pulls_desc = Ŝalti tirpetojn por deponejo
+settings.event_fork = Disbranĉigi
+release.tags = Etikedoj
+release.releases = Eldonoj
+release.tag_name = Etikeda nomo
+release.delete_tag = Forigi etikedon
+find_file.go_to_file = Iri al dosiero
+settings.archive.tagsettings_unavailable = Etikedaj agordoj neredakteblas je arĥivita deponejo.
+pulls.tab_commits = Enmetoj
+topic.manage_topics = Mastrumi temojn
+tag.create_success = Etikedo «%s» kreita.
+default_branch_helper = La implicita branĉo estas la baza branĉo por tirpetoj kaj enmetoj.
+tags = Etikedoj
+issues.no_ref = Neniu branĉo/etikedo donita
+release.tags_for = Etikedoj por %s
+tag = Etikedo
+settings.tags.protection = Gardado de etikedoj
+settings.tags.protection.create = Gardi etikedon
+release.add_tag = Krei sole etikedon
+settings.default_branch_desc = Elekti implicutan deponejan branĉon por tirpetoj kaj enmetoj:
+archive.title = Ĉi tiu deponejo estas arĥivigita. Vi povas vidi kaj elŝuti dosierojn, sed ne povas puŝi nek raporti problemojn nek tirpeti.
+activity.active_prs_count_n = %d aktivaj tirpetoj
+settings.archive.text = Arĥivigi la deponejon igus ĝin sole legebla. Ĝi kaŝiĝos de la labortablo. Neniu (ne eĉ vi!) povos fari enmetojn, raportojn, kaj tirpetojn.
+migrate_items_releases = Eldonoj
+commits.commits = Enmetoj
+
+[org]
+code = Fontkodo
+settings = Agordoj
+teams.settings = Agordoj
+
+[packages]
+npm.details.tag = Etikedo
\ No newline at end of file
diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini
index 98c524ac80..3875ea8385 100644
--- a/options/locale/locale_fr-FR.ini
+++ b/options/locale/locale_fr-FR.ini
@@ -124,6 +124,7 @@ pin=Épingler
unpin=Désépingler
artifacts=Artefacts
+confirm_delete_artifact=Êtes-vous sûr de vouloir supprimer l‘artefact « %s » ?
archived=Archivé
@@ -142,6 +143,19 @@ confirm_delete_selected=Êtes-vous sûr de vouloir supprimer tous les éléments
name=Nom
value=Valeur
confirm_delete_artifact = Êtes-vous certain de vouloir supprimer l'artefect "%s" ?
+filter.clear = Effacer le filtre
+filter.is_archived = Archivé
+filter.not_archived = Non archivé
+filter.is_fork = Dupliqué
+filter.not_fork = Non dupliqué
+filter.not_mirror = Non répliqué
+filter.is_template = Modèle
+filter.not_template = Pas un modèle
+filter.public = Public
+filter.private = Privé
+filter = Filtre
+filter.is_mirror = Répliqué
+toggle_menu = Menu va-et-vient
[aria]
navbar=Barre de navigation
@@ -182,6 +196,7 @@ missing_csrf=Requête incorrecte : aucun jeton CSRF présent
invalid_csrf=Requête incorrecte : jeton CSRF invalide
not_found=La cible n'a pu être trouvée.
network_error=Erreur réseau
+server_internal = Erreur interne du serveur
[startpage]
app_desc=Un service Git auto-hébergé sans prise de tête
@@ -279,7 +294,7 @@ confirm_password=Confirmez le mot de passe
admin_email=Courriel
install_btn_confirm=Installer Forgejo
test_git_failed=Le test de la commande "git" a échoué : %v
-sqlite3_not_available=Cette version de Forgejo ne supporte pas SQLite3. Veuillez télécharger la version binaire officielle de %s (pas la version 'gobuild').
+sqlite3_not_available=Cette version de Forgejo ne supporte pas SQLite3. Veuillez télécharger la version binaire officielle de %s (pas la version "gobuild").
invalid_db_setting=Les paramètres de la base de données sont invalides : %v
invalid_db_table=La table "%s" de la base de données est invalide : %v
invalid_repo_path=Le chemin racine du dépôt est invalide : %v
@@ -366,6 +381,7 @@ disable_register_prompt=Les inscriptions sont désactivées. Veuillez contacter
disable_register_mail=La confirmation par courriel à l’inscription est désactivée.
manual_activation_only=Contactez l'administrateur de votre site pour terminer l'activation.
remember_me=Mémoriser cet appareil
+remember_me.compromised=Le jeton de connexion n’est plus valide, ce qui peut indiquer un compte compromis. Veuillez inspecter les activités inhabituelles de votre compte.
forgot_password_title=Mot de passe oublié
forgot_password=Mot de passe oublié ?
sign_up_now=Pas de compte ? Inscrivez-vous maintenant.
@@ -602,6 +618,7 @@ target_branch_not_exist=La branche cible n'existe pas.
username_error_no_dots = ` peut uniquement contenir des caractères alphanumériques ('0-9','a-z','A-Z'), tiret ('-') et souligné ('_'). Ne peut commencer ou terminer avec un caractère non-alphanumérique, et l'utilisation de caractères non-alphanumériques consécutifs n'est pas permise.`
admin_cannot_delete_self = Vous ne pouvez supprimer votre compte lorsque vous disposez de droits d'administration. Veuillez d'abord renoncer à vos droits d'administration.
+admin_cannot_delete_self=Vous ne pouvez pas vous supprimer vous-même lorsque vous êtes admin. Veuillez d’abord supprimer vos privilèges d’administrateur.
[user]
change_avatar=Changer votre avatar…
@@ -817,7 +834,7 @@ valid_until_date=Valable jusqu'au %s
valid_forever=Valide pour toujours
last_used=Dernière utilisation le
no_activity=Aucune activité récente
-can_read_info=Lue(s)
+can_read_info=Lecture
can_write_info=Écriture
key_state_desc=Cette clé a été utilisée au cours des 7 derniers jours
token_state_desc=Ce jeton a été utilisé au cours des 7 derniers jours
@@ -850,7 +867,7 @@ permissions_public_only=Publique uniquement
permissions_access_all=Tout (public, privé et limité)
select_permissions=Sélectionner les autorisations
permission_no_access=Aucun accès
-permission_read=Lue(s)
+permission_read=Lecture
permission_write=Lecture et écriture
access_token_desc=Les autorisations des jetons sélectionnées se limitent aux routes API correspondantes. Lisez la documentation pour plus d’informations.
at_least_one_permission=Vous devez sélectionner au moins une permission pour créer un jeton
@@ -1013,6 +1030,7 @@ mirror_prune=Purger
mirror_prune_desc=Supprimer les références externes obsolètes
mirror_interval=Intervalle de synchronisation (les unités de temps valides sont 'h', 'm' et 's'). 0 pour désactiver la synchronisation automatique. (Intervalle minimum : %s)
mirror_interval_invalid=L'intervalle de synchronisation est invalide.
+mirror_sync=synchronisé
mirror_sync_on_commit=Synchroniser quand les révisions sont soumis
mirror_address=Cloner depuis une URL
mirror_address_desc=Insérez tous les identifiants requis dans la section Autorisation.
@@ -1063,6 +1081,7 @@ desc.public=Publique
desc.template=Modèle
desc.internal=Interne
desc.archived=Archivé
+desc.sha256=SHA256
template.items=Élément du modèle
template.git_content=Contenu Git (branche par défaut)
@@ -1213,6 +1232,8 @@ audio_not_supported_in_browser=Votre navigateur ne supporte pas la balise « au
stored_lfs=Stocké avec Git LFS
symbolic_link=Lien symbolique
executable_file=Fichiers exécutables
+vendored=Externe
+generated=Générée
commit_graph=Graphe des révisions
commit_graph.select=Sélectionner les branches
commit_graph.hide_pr_refs=Masquer les demandes d'ajout
@@ -1794,6 +1815,7 @@ pulls.merge_pull_request=Créer une révision de fusion
pulls.rebase_merge_pull_request=Rebaser puis avancer rapidement
pulls.rebase_merge_commit_pull_request=Rebaser puis créer une révision de fusion
pulls.squash_merge_pull_request=Créer une révision de concaténation
+pulls.fast_forward_only_merge_pull_request=Avance rapide uniquement
pulls.merge_manually=Fusionner manuellement
pulls.merge_commit_id=L'ID de la révision de fusion
pulls.require_signed_wont_sign=La branche nécessite des révisions signées mais cette fusion ne sera pas signée
@@ -1930,6 +1952,7 @@ wiki.page_name_desc=Entrez un nom pour cette page Wiki. Certains noms spéciaux
wiki.original_git_entry_tooltip=Voir le fichier Git original au lieu d'utiliser un lien convivial.
activity=Activité
+activity.navbar.contributors=Contributeurs
activity.period.filter_label=Période :
activity.period.daily=1 jour
activity.period.halfweekly=3 jours
@@ -1995,7 +2018,10 @@ activity.git_stats_and_deletions=et
activity.git_stats_deletion_1=%d suppression
activity.git_stats_deletion_n=%d suppressions
+contributors.contribution_type.filter_label=Type de contribution :
contributors.contribution_type.commits=Révisions
+contributors.contribution_type.additions=Ajouts
+contributors.contribution_type.deletions=Suppressions
search=Chercher
search.search_repo=Rechercher dans le dépôt
@@ -2344,6 +2370,8 @@ settings.protect_approvals_whitelist_users=Évaluateurs autorisés :
settings.protect_approvals_whitelist_teams=Équipes d’évaluateurs autorisés :
settings.dismiss_stale_approvals=Révoquer automatiquement les approbations périmées
settings.dismiss_stale_approvals_desc=Lorsque des nouvelles révisions changent le contenu de la demande d’ajout, les approbations existantes sont révoquées.
+settings.ignore_stale_approvals=Ignorer les approbations obsolètes
+settings.ignore_stale_approvals_desc=Ignorer les approbations d’anciennes révisions (évaluations obsolètes) du décompte des approbations de la demande d’ajout. Non pertinent quand les évaluations obsolètes sont déjà révoquées.
settings.require_signed_commits=Exiger des révisions signées
settings.require_signed_commits_desc=Rejeter les soumissions sur cette branche lorsqu'ils ne sont pas signés ou vérifiables.
settings.protect_branch_name_pattern=Motif de nom de branche protégé
@@ -2399,6 +2427,7 @@ settings.archive.error=Une erreur s'est produite lors de l'archivage du dépôt.
settings.archive.error_ismirror=Vous ne pouvez pas archiver un dépôt en miroir.
settings.archive.branchsettings_unavailable=Le paramétrage des branches n'est pas disponible quand le dépôt est archivé.
settings.archive.tagsettings_unavailable=Le paramétrage des étiquettes n'est pas disponible si le dépôt est archivé.
+settings.archive.mirrors_unavailable=Les miroirs ne sont pas disponibles lorsque le dépôt est archivé.
settings.unarchive.button=Réhabiliter
settings.unarchive.header=Réhabiliter ce dépôt
settings.unarchive.text=Réhabiliter un dépôt dégèle les actions de révisions et de soumissions, la gestion des tickets et des demandes d'ajouts.
@@ -2652,6 +2681,11 @@ activity.navbar.code_frequency = Fréquence de code
activity.navbar.recent_commits = Commits récents
[graphs]
+component_loading=Chargement de %s…
+component_loading_failed=Impossible de charger %s.
+component_loading_info=Ça prend son temps…
+component_failed_to_load=Une erreur inattendue s’est produite.
+contributors.what=contributions
[org]
org_name_holder=Nom de l'organisation
@@ -2711,7 +2745,7 @@ settings.hooks_desc=Vous pouvez ajouter des webhooks qui seront activés pour tous les dépôts de cette organisation.
members.membership_visibility=Visibilité des membres:
-members.public=Public
+members.public=Visible
members.public_helper=rendre caché
members.private=Caché
members.private_helper=rendre visible
@@ -2780,6 +2814,7 @@ follow_blocked_user = Vous ne pouvez pas suivre cette organisation car elle vous
[admin]
dashboard=Tableau de bord
+self_check=Autodiagnostique
identity_access=Identité et accès
users=Comptes utilisateurs
organizations=Organisations
@@ -2825,6 +2860,7 @@ dashboard.delete_missing_repos=Supprimer tous les dépôts dont les fichiers Git
dashboard.delete_missing_repos.started=Tâche de suppression de tous les dépôts sans fichiers Git démarrée.
dashboard.delete_generated_repository_avatars=Supprimer les avatars de dépôt générés
dashboard.sync_repo_branches=Synchroniser les branches manquantes depuis Git vers la base de donnée.
+dashboard.sync_repo_tags=Synchroniser les étiquettes git depuis les dépôts vers la base de données
dashboard.update_mirrors=Actualiser les miroirs
dashboard.repo_health_check=Vérifier l'état de santé de tous les dépôts
dashboard.check_repo_stats=Voir les statistiques de tous les dépôts
@@ -2879,6 +2915,7 @@ dashboard.stop_endless_tasks=Arrêter les tâches sans fin
dashboard.cancel_abandoned_jobs=Annuler les jobs abandonnés
dashboard.start_schedule_tasks=Démarrer les tâches planifiées
dashboard.sync_branch.started=Début de la synchronisation des branches
+dashboard.sync_tag.started=Synchronisation des étiquettes
dashboard.rebuild_issue_indexer=Reconstruire l’indexeur des tickets
users.user_manage_panel=Gestion du compte utilisateur
@@ -3314,6 +3351,12 @@ self_check.database_inconsistent_collation_columns = La base de donnée utilise
self_check.database_fix_mysql = Les utilisateurs de MySQL/MariaDB peuvent utiliser la commande "forgejo doctor convert" pour corriger les problèmes de collation, ou bien manuellement avec la commande SQL "ALTER ... COLLATE ...".
self_check.database_fix_mssql = Les utilisateurs de MSSQL sont pour l'instant contraint d'utiliser la commande SQL "ALTER ... COLLATE ..." pour corriger ce problème.
+self_check.no_problem_found=Aucun problème trouvé pour l’instant.
+self_check.database_collation_mismatch=Exige que la base de données utilise la collation %s.
+self_check.database_collation_case_insensitive=La base de données utilise la collation %s, insensible à la casse. Bien que Gitea soit compatible, il peut y avoir quelques rares cas qui ne fonctionnent pas comme prévu.
+self_check.database_inconsistent_collation_columns=La base de données utilise la collation %s, mais ces colonnes utilisent des collations différentes. Cela peut causer des problèmes imprévus.
+self_check.database_fix_mysql=Pour les utilisateurs de MySQL ou MariaDB, vous pouvez utiliser la commande « gitea doctor convert » dans un terminal ou exécuter une requête du type « ALTER … COLLATE ... » pour résoudre les problèmes de collation.
+self_check.database_fix_mssql=Pour les utilisateurs de MSSQL, vous ne pouvez résoudre le problème qu’en exécutant une requête SQL du type « ALTER … COLLATE … ».
[action]
create_repo=a créé le dépôt %s
@@ -3501,6 +3544,7 @@ rpm.distros.suse=sur les distributions basées sur SUSE
rpm.install=Pour installer le paquet, exécutez la commande suivante :
rpm.repository=Informations sur le Dépôt
rpm.repository.architectures=Architectures
+rpm.repository.multiple_groups=Ce paquet est disponible en plusieurs groupes.
rubygems.install=Pour installer le paquet en utilisant gem, exécutez la commande suivante :
rubygems.install2=ou ajoutez-le au Gemfile :
rubygems.dependencies.runtime=Dépendances d'exécution
@@ -3636,6 +3680,8 @@ runs.actors_no_select=Tous les acteurs
runs.status_no_select=Touts les statuts
runs.no_results=Aucun résultat correspondant.
runs.no_workflows=Il n'y a pas encore de workflows.
+runs.no_workflows.quick_start=Vous découvrez les Actions Gitea ? Consultez le didacticiel .
+runs.no_workflows.documentation=Pour plus d’informations sur les actions Gitea, voir la documentation .
runs.no_runs=Le flux de travail n'a pas encore d'exécution.
runs.empty_commit_message=(message de révision vide)
@@ -3654,6 +3700,7 @@ variables.none=Il n'y a pas encore de variables.
variables.deletion=Retirer la variable
variables.deletion.description=La suppression d’une variable est permanente et ne peut être défaite. Continuer ?
variables.description=Les variables sont passées aux actions et ne peuvent être lues autrement.
+variables.id_not_exist=La variable avec l’ID %d n’existe pas.
variables.edit=Modifier la variable
variables.deletion.failed=Impossible de retirer la variable.
variables.deletion.success=La variable a bien été retirée.
diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini
index 49f4288b7d..9a3979808a 100644
--- a/options/locale/locale_it-IT.ini
+++ b/options/locale/locale_it-IT.ini
@@ -25,10 +25,10 @@ licenses=Licenze
return_to_gitea=Ritorna a Forgejo
username=Nome utente
-email=Indirizzo Email
+email=Indirizzo email
password=Password
access_token=Token di accesso
-re_type=Conferma Password
+re_type=Conferma password
captcha=CAPTCHA
twofa=Verifica in due passaggi
twofa_scratch=Codice di recupero per la verifica in due passaggi
@@ -116,14 +116,65 @@ concept_user_organization=Organizzazione
name=Nome
value=Valore
+enable_javascript = Questo sito richiede JavaScript.
+tracked_time_summary = Resoconto del tempo tracciato in base ai filtri dell'elenco dei problemi
+retry = Riprova
+rerun = Ri-esegui
+rerun_all = Ri-esegui tutti gli incarichi
+show_timestamps = Mostra timestamps
+remove_label_str = Rimuovi elemento "%s"
+view = Visualizza
+copy_hash = Copia hash
+copy_content = Copia contenuto
+copy_type_unsupported = Questo tipo di file non può essere copiato
+locked = Bloccato
+go_back = Torna Indietro
+unknown = Sconosciuto
+pin = Fissa
+unpin = Sblocca
+artifacts = Artefatti
+confirm_delete_artifact = Sei sicuro di voler eliminare l'artefatto "%s" ?
+concept_system_global = Globale
+concept_user_individual = Individuale
+show_log_seconds = Mostra secondi
+show_full_screen = Mostra a schermo intero
+download_logs = Scarica logs
+confirm_delete_selected = Confermare l'eliminazione di tutti gli elementi selezionati?
+sign_in_with_provider = Accedi con %s
+new_project_column = Nuova Colonna
+toggle_menu = Mostra/Nascondi Menu
[aria]
+footer.links = Collegamenti
+navbar = Barra di Navigazione
+footer = Piè di Pagina
+footer.software = A proposito del Software
[heatmap]
+more = Più
+no_contributions = Nessun contributo
+less = Meno
+number_of_contributions_in_the_last_12_months = %s contributi negli ultimi 12 mesi
[editor]
+buttons.heading.tooltip = Aggiungi intestazione
+buttons.bold.tooltip = Aggiungi testo in grassetto
+buttons.italic.tooltip = Aggiungi testo in corsivo
+buttons.quote.tooltip = Cita testo
+buttons.code.tooltip = Aggiungi codice
+buttons.link.tooltip = Aggiungi un collegamento
+buttons.list.unordered.tooltip = Aggiungi un elenco puntato
+buttons.list.ordered.tooltip = Aggiungi un elenco numerato
+buttons.list.task.tooltip = Aggiungi un elenco di attività
+buttons.mention.tooltip = Menziona un utente o gruppo
+buttons.ref.tooltip = Fai riferimento ad un problema o pull request
+buttons.switch_to_legacy.tooltip = Passa all'editor legacy
+buttons.enable_monospace_font = Attiva font monospace
+buttons.disable_monospace_font = Disattiva font monospace
[filter]
+string.asc = A - Z
+string.desc = Z - A
[error]
occurred=Si è verificato un errore
@@ -131,16 +182,19 @@ missing_csrf=Richiesta errata: nessun token CSRF presente
invalid_csrf=Richiesta errata: token CSRF non valido
not_found=Il bersaglio non è stato trovato.
network_error=Errore di rete
+report_message = Se credi che questo sia un bug di Forgejo, per favore verifica i problemi su Codeberg o pubblica un nuovo problema se necessario.
+server_internal = Errore Interno del Server
[startpage]
app_desc=Un servizio auto-ospitato per Git pronto all'uso
install=Facile da installare
platform=Multipiattaforma
-platform_desc=Forgejo funziona ovunque Go possa essere compilato: Windows, macOS, Linux, ARM, etc. Scegli ciò che ami!
+platform_desc=Forgejo funziona ovunque Go possa essere compilato: Windows, macOS, Linux, ARM, etc. Scegli ciò che ami!
lightweight=Leggero
lightweight_desc=Forgejo ha requisiti minimi bassi e può funzionare su un economico Raspberry Pi. Risparmia l'energia della tua macchina!
license=Open Source
license_desc=Ottieni Forgejo ! Partecipa per contribuire a rendere questo progetto ancora migliore. Non aver paura di diventare un collaboratore!
+install_desc = Semplicemente avvia l'eseguibile per la tua piattaforma, distribuiscilo con Docker , oppure scarica il pacchetto .
[install]
install=Installazione
@@ -224,12 +278,12 @@ admin_password=Password
confirm_password=Conferma Password
admin_email=Indirizzo Email
install_btn_confirm=Installare Forgejo
-test_git_failed=Fallito il test del comando git: %v
-sqlite3_not_available=Questa versione di Forgejo non supporta SQLite3. Si prega di scaricare la versione binaria ufficiale da %s (not the 'gobuild' version).
+test_git_failed=Non è stato possibile testare il comando "git": %v
+sqlite3_not_available=Questa versione di Forgejo non supporta SQLite3. Si prega di scaricare la versione binaria ufficiale da %s (non la versione "gobuild").
invalid_db_setting=Le impostazioni del database sono invalide: %v
invalid_repo_path=Il percorso radice del Repository è invalido: %v
invalid_app_data_path=Il percorso dati dell'app non è valido: %v
-run_user_not_match=Il nome utente 'esegui come' non è il nome utente attuale: %s -> %s
+run_user_not_match=Il nome utente "Esegui come Nome Utente" non è il nome utente attuale: %s -> %s
internal_token_failed=Generazione del token interno non riuscita: %v
secret_key_failed=Generazione della chiave segreta non riuscita: %v
save_config_failed=Salvataggio della configurazione non riuscito: %v
@@ -242,11 +296,23 @@ default_allow_create_organization_popup=Consenti ai nuovi account utente di crea
default_enable_timetracking=Attiva il cronografo di Default
default_enable_timetracking_popup=Attiva il cronografo per le nuove repositories di default.
no_reply_address=Dominio email nascosto
-no_reply_address_helper=Nome dominio per utenti con un indirizzo email nascosto. Ad esempio, il nome utente 'joe' accederà a Git come 'joe@noreply.example.org' se il dominio email nascosto è impostato a 'noreply.example.org'.
+no_reply_address_helper=Nome di dominio per utenti con un indirizzo email nascosto. Ad esempio, il nome utente "joe" accederà a Git come "joe@noreply.example.org" se il dominio email nascosto è impostato a "noreply.example.org".
password_algorithm=Algoritmo Password Hash
+smtp_from_invalid = L'indirizzo "Invia Email come" non è valido
+enable_update_checker_helper_forgejo = Verifica periodicamente nuove versioni di Forgejo controllando il record DNS TXT di release.forgejo.org.
+invalid_db_table = La tabella del database "%s" non è valida: %v
+invalid_password_algorithm = Algoritmo di hash della password non valido
+enable_update_checker = Attiva il Controllo degli Aggiornamenti
+enable_update_checker_helper = Verifica periodicamente la presenza di nuove versioni tramite gitea.io.
+env_config_keys = Configurazione Ambiente
+env_config_keys_prompt = Le seguenti variabili di ambiente saranno anche applicate al tuo file di configurazione:
+run_user_helper = Il nome utente del sistema operativo con il quale Forgejo viene eseguito. Questo utente deve avere accesso alla cartella principale delle repository.
+password_algorithm_helper = Imposta l'algoritmo di hashing della password. Gli algoritmi hanno requisiti e punti di forza diversi. L'algoritmo argon2 è relativamente sicuro ma usa un sacco di memoria e potrebbe non essere appropriato a piccoli sistemi.
+require_sign_in_view_popup = Limita l'accesso ad utenti autenticati. I visitatori vedranno solo le pagine di accesso e registrazione.
+allow_dots_in_usernames = Consenti l'uso del punto nel nome utente. Non impatta gli account già esistenti.
[home]
-uname_holder=Nome utente o Indirizzo Email
+uname_holder=Nome utente o indirizzo Email
password_holder=Password
switch_dashboard_context=Cambia Dashboard Context
my_repos=Repositories
@@ -286,6 +352,13 @@ user_no_results=Nessun utente corrispondente.
org_no_results=Nessun'organizzazione corrispondente trovata.
code_no_results=Nessun codice sorgente corrispondente ai termini di ricerca.
code_last_indexed_at=Ultimo indicizzato %s
+go_to = Vai a
+search.type.tooltip = Tipo di ricerca
+search.fuzzy.tooltip = Includi anche i risultati che corrispondono parzialmente ai termini di ricerca
+code_search_results = Risultati di ricerca per "%s"
+relevant_repositories_tooltip = Repository che sono fork o che non hanno un argomento, icona, né descrizione sono nascosti.
+relevant_repositories = Solo le repository pertinenti sono visibili, mostra risultati non filtrati .
+search.match.tooltip = Includi solo risultati che combaciano perfettamente con i termini di ricerca
[auth]
create_new_account=Registra un account
@@ -348,10 +421,23 @@ authorize_title=Vuoi autorizzare "%s" ad accedere al tuo account?
authorization_failed=Autorizzazione fallita
sspi_auth_failed=Autenticazione SSPI fallita
password_pwned_err=Impossibile completare la richiesta a HaveIBeenPwned
+authorization_failed_desc = L'autorizzazione è fallita perchè abbiamo rilevato una richiesta non valida. Contatta il gestore dell'app che hai provato ad autorizzare.
+change_unconfirmed_email = Se hai fornito l'indirizzo email sbagliato durante la registrazione, puoi cambiarlo sotto, e una mail di conferma sarà inviata al nuovo indirizzo.
+change_unconfirmed_email_error = Impossibile cambiare l'indirizzo email: %v
+invalid_code_forgot_password = Il tuo codice di conferma non è valido oppure è scaduto. Clicca qui per avviare una nuova sessione.
+remember_me.compromised = Il token di login non è più valido, il che potrebbe indicare un account compromesso. Verifica la presenza di attività insolite dal tuo account.
+sign_up_successful = L'account è stato creato con successo. Benvenuto!
+change_unconfirmed_email_summary = Modifica l'indirizzo email a cui deve essere inviata la mail di attivazione.
+invalid_password = La tua password non combacia con la password usata in fase di creazione dell'account.
+reset_password_wrong_user = Hai eseguito l'accesso come %s, ma il link per il ripristino dell'account è destinato a %s
+last_admin = Non puoi rimuovere l'ultimo amministratore. Deve esserci almeno un amministratore.
+prohibit_login_desc = Al tuo account non è consentito effettuare il login, contatta l'amministratore del sito.
+openid_signin_desc = Inserisci il tuo URI OpenID. Per esempio: alice.openid.example.org o https://openid.example.org/alice.
+password_pwned = La password che hai scelto è in un elenco di password rubate precedentemente esposte a violazioni di dati pubblici. Riprova con una password diversa e valuta di modificare questa password anche altrove.
[mail]
view_it_on=Visualizza su %s
-link_not_working_do_paste=Non funziona? Prova a copiare e incollare sul tuo browser.
+link_not_working_do_paste=Il link non funziona? Prova a copiarlo e incollarlo nella barra dell'indirizzo del tuo browser.
hi_user_x=Ciao %s ,
activate_account=Per favore attiva il tuo account
@@ -365,12 +451,12 @@ activate_email.text=Clicca sul seguente link per verificare il tuo indirizzo ema
register_notify=Benvenuto su Forgejo
register_notify.title=%[1]s, benvenuto in %[2]s
register_notify.text_1=questa è la tua email di conferma di registrazione per %s!
-register_notify.text_2=Ora è possibile accedere tramite nome utente: %s.
-register_notify.text_3=Se questo account è stato creato per te, per favore imposta prima la tua password .
+register_notify.text_2=Puoi accedere al tuo account tramite il tuo nome utente: %s
+register_notify.text_3=Se qualcun altro ha impostato questo account per te, dovrai prima impostare la tua password .
reset_password=Recupera il tuo account
-reset_password.title=%s, hai richiesto di recuperare il tuo account
-reset_password.text=Clicca sul seguente link per recuperare il tuo account entro %s :
+reset_password.title=%s, abbiamo ricevuto una richiesta di recupero del tuo account
+reset_password.text=Se sei stato tu, clicca sul seguente link per recuperare il tuo account entro %s :
register_success=Registrazione completata con successo
@@ -407,6 +493,15 @@ repo.transfer.body=Per accettare o respingerla visita %s o semplicemente ignorar
repo.collaborator.added.subject=%s ti ha aggiunto a %s
repo.collaborator.added.text=Sei stato aggiunto come collaboratore del repository:
+reply = o rispondi direttamente a questa email
+admin.new_user.subject = Il nuovo utente %s si è appena registrato
+admin.new_user.user_info = Informazioni Utente
+team_invite.text_2 = Fai click sul seguente link per far parte del team:
+team_invite.subject = %[1]s ti ha invitato a far parte dell'organizzazione %[2]s
+activate_email.title = %s, verifica il tuo indirizzo email
+admin.new_user.text = Clicca qui per gestire questo utente dal pannello di amministrazione.
+team_invite.text_1 = %[1]s ti ha invitato a far parte del team %[2]s nell'organizzazione %[3]s.
+team_invite.text_3 = Nota: Questo invito è destinato a %[1]s. Se non ti aspettavi questo invito, puoi ignorare questa email.
[modal]
@@ -414,6 +509,7 @@ yes=Sì
no=No
cancel=Annulla
modify=Aggiorna
+confirm = Conferma
[form]
UserName=Nome utente
@@ -439,8 +535,8 @@ SSPISeparatorReplacement=Separatore
SSPIDefaultLanguage=Lingua predefinita
require_error=` non può essere vuoto.`
-alpha_dash_error=` può contenere solo caratteri alfanumerici, dash ('-') e underscore ('_').`
-alpha_dash_dot_error=` può contenere solo caratteri alfanumerici, dash ('-'), underscore ('_') e dot ('.').`
+alpha_dash_error=` può contenere solo caratteri alfanumerici, trattini ("-") e underscore ("_").`
+alpha_dash_dot_error=` può contenere solo caratteri alfanumerici, trattini("-"), underscore ("_") e punti (".").`
git_ref_name_error=` deve essere un Git reference name ben formato.`
size_error='deve essere %s.'
min_size_error=` deve contenere almeno %s caratteri.`
@@ -480,7 +576,7 @@ enterred_invalid_owner_name=Il nuovo nome del proprietario non è valido.
enterred_invalid_password=La password inserita non è corretta.
user_not_exist=L'utente non esiste.
team_not_exist=Questo team non esiste.
-last_org_owner=Non è possibile rimuovere l'ultimo utente dal team 'proprietari'. Ci deve essere almeno un proprietario per un'organizzazione.
+last_org_owner=Non è possibile rimuovere l'ultimo utente dal team "owners". Ci deve essere almeno un proprietario per un'organizzazione.
cannot_add_org_to_team=Un'organizzazione non può essere aggiunto come membro del team.
invalid_ssh_key=Impossibile verificare la tua chiave SSH: %s
@@ -490,6 +586,23 @@ auth_failed=Autenticazione non riuscita: %v
target_branch_not_exist=Il ramo (branch) di destinazione non esiste.
+org_still_own_packages = Questa organizzazione è ancora proprietaria di uno o più pacchetti, devi prima eliminarli.
+org_still_own_repo = Questa organizzazione è ancora proprietaria di una o più repository, devi prima eliminarle o trasferirle.
+still_own_packages = Il tuo account è ancora proprietario di uno o più pacchetti, devi prima eliminarli.
+openid_been_used = L'indirizzo OpenID "%s" è già in uso.
+url_error = `"%s" non è un URL valido.`
+include_error = ` deve contenere la sottostringa "%s".`
+username_error = ` può solo contenere caratteri alfanumerici ("0-9","a-z","A-Z"), trattini ("-"), underscore ("_") e punti ("."). Non può iniziare o finire con caratteri non-alfanumerici, e sono vietati anche più caratteri non-alfanumerici consecutivi.`
+invalid_group_team_map_error = ` mappatura non valida: %s`
+organization_leave_success = Hai lasciato con successo l'organizzazione %s.
+unable_verify_ssh_key = Non è stato possibile verificare la chiave SSH, ricontrollala per eventuali errori.
+admin_cannot_delete_self = Non puoi eliminare il tuo account mentre sei un amministratore. Devi prima abbandonare i tuoi privilegi di amministratore.
+username_error_no_dots = ` può solo contenere caratteri alfanumerici ("0-9","a-z","A-Z"), trattini ("-") e underscore ("_"). Non può iniziare o finire con caratteri non-alfanumerici, e sono vietati anche più caratteri non-alfanumerici consecutivi.`
+username_has_not_been_changed = Il nome utente non è stato cambiato
+must_use_public_key = La chiave che hai fornito è una chiave privata. Non caricare la tua chiave privata da nessuna parte. Usa invece la tua chiave pubblica.
+still_own_repo = Il tuo account è ancora proprietario di una o più repository, devi prima eliminarle o trasferirle.
+duplicate_invite_to_team = L'utente è già stato invitato ad essere un membro del team.
+still_has_org = Il tuo account è ancora membro di una o più organizzazioni, devi prima abbandonarle.
[user]
@@ -506,6 +619,23 @@ follow=Segui
unfollow=Non seguire più
user_bio=Biografia
disabled_public_activity=L'utente ha disabilitato la vista pubblica dell'attività.
+joined_on = Membro dal %s
+block_user = Blocca Utente
+block_user.detail_1 = Questo utente non ti seguirà più.
+block_user.detail_2 = Questo utente non potrà interagire con le tue repository, con i problemi che hai creato o con i tuoi commenti.
+block_user.detail_3 = Questo utente non ti potrà aggiungere come un collaboratore, né potrai tu aggiungerlo come un collaboratore.
+code = Codice
+block = Blocca
+unblock = Sblocca
+email_visibility.limited = Il tuo indirizzo email è visibile a tutti gli utenti autenticati
+email_visibility.private = Il tuo indirizzo email è visibile solo a te e agli amministratori
+show_on_map = Mostra questo posto su una mappa
+settings = Impostazioni Utente
+form.name_reserved = Il nome utente "%s" è riservato.
+form.name_chars_not_allowed = Il nome utente "%s" contiene caratteri non validi.
+block_user.detail = Tieni presente che se blocchi questo utente, verranno eseguite altre azioni. Per esempio:
+form.name_pattern_not_allowed = La sequenza "%s" non è consentita in un nome utente.
+follow_blocked_user = Non puoi seguire questo utente perchè hai bloccato questo utente o perchè questo utente ha bloccato te.
[settings]
@@ -572,7 +702,7 @@ update_user_avatar_success=L'avatar dell'utente è stato aggiornato.
update_password=Aggiorna Password
old_password=Password attuale
-new_password=Nuova Password
+new_password=Nuova password
password_incorrect=La password attuale non è corretta.
change_password_success=La password è stata aggiornata. Utilizza la nuova password la prossima volta che effettui il login.
password_change_disabled=Gli utenti non locali non possono cambiare la loro password attraverso l'interfaccia web.
@@ -597,7 +727,7 @@ theme_update_error=Il tema selezionato non esiste.
openid_deletion=Rimuovi Indirizzo OpenID
openid_deletion_desc=La rimozione di questo indirizzo OpenID della tua conta ti impedirà di accedere con esso. Sei sicuro di voler continuare?
openid_deletion_success=L'indirizzo OpenID è stato eliminato.
-add_new_email=Aggiungi nuovo indirizzo email
+add_new_email=Aggiungi indirizzo email
add_new_openid=Aggiungi nuovo URI OpenID
add_email=Aggiungi indirizzo email
add_openid=Aggiungere OpenID URI
@@ -609,9 +739,9 @@ openid_desc=OpenID consente di delegare l'autenticazione ad un provider esterno.
manage_ssh_keys=Gestisci chiavi SSH
manage_ssh_principals=Gestisci i Certificati SSH
-manage_gpg_keys=Gestisci Chiavi GPG
+manage_gpg_keys=Gestisci chiavi GPG
add_key=Aggiungi Chiave
-ssh_desc=Queste chiavi SSH pubbliche sono associate con il tuo account. Le corrispondenti chiavi private consentono l'accesso completo alle tue repositories.
+ssh_desc=Queste chiavi SSH pubbliche sono associate con il tuo account. Le corrispondenti chiavi private consentono l'accesso completo alle tue repositories. Le chiavi SSH che sono state verificate possono essere usate per verificare commit Git firmati tramite SSH.
principal_desc=Questi certificati SSH principali sono associati al tuo account e permettono l'accesso completo alle tue repository.
gpg_desc=Queste chiavi GPG pubbliche sono associate con il tuo account. Proteggi le tue chiavi private perché permettono di verificare i commits.
ssh_helper= Hai bisogno di aiuto? Dai un'occhiata alla guida di GitHub percrea le tue chiavi SSH o risolvere problemi comuni che potresti trovare utilizzando SSH.
@@ -766,6 +896,24 @@ visibility=Visibilità utente
visibility.public=Pubblico
visibility.limited=Limitato
visibility.private=Privato
+permissions_list = Permessi:
+select_permissions = Seleziona permessi
+biography_placeholder = Parlaci un po' di te! (Puoi usare Markdown)
+location_placeholder = Condividi la tua posizione approssimativa con gli altri
+update_language_not_found = Il linguaggio "%s" non è disponibile.
+change_username_prompt = Nota: Il cambiamento del tuo nome utente cambierà anche l'URL del tuo account.
+keep_activity_private = Nascondi Attività dalla pagina del profilo
+retype_new_password = Conferma nuova password
+can_not_add_email_activations_pending = C'è una verifica attualmente in corso, riprova tra qualche minuto se vuoi aggiungere una nuova email.
+blocked_users = Utenti Bloccati
+change_password = Modifica password
+uploaded_avatar_is_too_big = La dimensione del file caricato (%d KiB) supera il limite massimo (%d KiB).
+uid = UID
+change_username_redirect_prompt = Il tuo vecchio nome utente sarà reindirizzato finché qualcuno non lo reclamerà.
+permissions_public_only = Solo pubblico
+profile_desc = Controlla come il tuo profilo viene mostrato agli altri utenti. Il tuo indirizzo email principale sarà usato per inviarti notifiche, ripristino di password e per le operazioni Git effettuate da web.
+email_desc = Il tuo indirizzo email principale sarà usato per inviarti notifiche, ripristino di password e, se non è stato nascosto, per le operazioni Git effettuate da web.
+add_email_confirmation_sent = Una email di conferma è stata inviata a "%s". Verifica la posta in arrivo entro %s per confermare il tuo indirizzo email.
[repo]
owner=Proprietario
@@ -1016,7 +1164,7 @@ editor.fork_before_edit=È necessario effettuare il fork di questo repository pe
editor.delete_this_file=Elimina file
editor.must_have_write_access=È necessaria l'autorizzazione di scrittura per eseguire o proporre modifiche su questo file.
editor.name_your_file=Dai un nome al file…
-editor.filename_help=Aggiungi una directory digitando il suo nome nome seguito da il carattere slash ('/'). Rimuovi una directory digitando backspace all'inizio del campo di input.
+editor.filename_help=Aggiungi una directory digitando il suo nome seguito da un carattere slash ("/"). Rimuovi una directory digitando backspace all'inizio del campo di input.
editor.or=o
editor.cancel_lower=Annulla
editor.commit_signed_changes=Conferma modifiche firmate
@@ -2121,8 +2269,63 @@ find_file.no_matching=Nessun file corrispondente trovato
error.csv.too_large=Impossibile visualizzare questo file perché è troppo grande.
error.csv.unexpected=Impossibile visualizzare questo file perché contiene un carattere inatteso alla riga %d e alla colonna %d.
error.csv.invalid_field_count=Impossibile visualizzare questo file perché ha un numero errato di campi alla riga %d.
+pulls.cmd_instruction_merge_desc = Unisci le modifiche e aggiornale su Forgejo.
+pulls.cmd_instruction_merge_title = Merge
+pulls.cmd_instruction_checkout_desc = Dalla tua repository del progetto, accedi ad un nuovo ramo e prova le modifiche.
+milestones.new_subheader = I traguardi possono aiutarti ad organizzare i problemi e a tracciare i loro progressi.
+activity.navbar.contributors = Contributori
+migrate.cancel_migrating_title = Annulla Migrazione
+more_operations = Più Operazioni
+actions = Azioni
+commit.operations = Operazioni
+issues.action_check = Seleziona/Deseleziona
+issues.close = Chiudi Problema
+issues.role.collaborator = Collaboratore
+desc.sha256 = SHA256
+editor.add = Aggiungi %s
+editor.update = Aggiorna %s
+editor.delete = Elimina %s
+wiki.cancel = Annulla
+contributors.contribution_type.additions = Aggiunte
+contributors.contribution_type.deletions = Rimozioni
+settings.protect_patterns = Sequenze
+milestones.update_ago = Aggiornato %s
+mirror_sync = sincronizzato
+object_format = Formato Oggetti
+from_comment = (commento)
+executable_file = File Eseguibile
+commits.browse_further = Esplora di più
+commitstatus.success = Successo
+projects.column.edit = Modifica Colonna
+projects.column.new_submit = Crea Colonna
+projects.column.new = Nuova Colonna
+projects.column.set_default = Imposta Default
+projects.column.unset_default = Annulla Default
+projects.column.delete = Elimina Colonna
+projects.card_type.desc = Anteprima Carte
+projects.card_type.text_only = Solo Testo
+issues.filter_milestone_open = Traguardi aperti
+issues.filter_milestone_closed = Traguardi chiusi
+issues.filter_milestone_all = Tutti i traguardi
+issues.filter_milestone_none = Senza traguardi
+issues.role.contributor = Contributore
+issues.label_exclusive = Esclusivo
+pulls.made_using_agit = AGit
+milestones.create_success = Il traguardo "%s" è stato creato.
+search.fuzzy.tooltip = Includi risultati di ricerca che combaciano anche approssimativamente al termine di ricerca
+default_branch_label = default
+pulls.cmd_instruction_checkout_title = Checkout
+pull.deleted_branch = (eliminato):%s
+issues.filter_label_select_no_label = Senza etichette
+milestones.edit_success = Il traguardo "%s" è stato aggiornato.
+commitstatus.failure = Errore
+settings.units.overview = Panoramica
+all_branches = Tutti i rami
+projects.column.assigned_to = Assegnato a
+pulls.cmd_instruction_hint = `Visualizza istruzioni per la riga di comando .`
[graphs]
+contributors.what = contribuzioni
[org]
org_name_holder=Nome dell'Organizzazione
@@ -2235,6 +2438,7 @@ teams.all_repositories_helper=Il team ha accesso a tutti i repository. Seleziona
teams.all_repositories_read_permission_desc=Questo team concede permessi lettura l'accesso a tutte le repository : i membri possono visualizzare e clonare i repository.
teams.all_repositories_write_permission_desc=Questo team concede permessi di scrittura accesso a tutte le repository : i membri possono leggere e pushare le repository.
teams.all_repositories_admin_permission_desc=Questo team concede a Amministratore l'accesso a tutte le repository : i membri possono leggere, pushare e aggiungere collaboratori alle repository.
+code = Codice
[admin]
dashboard=Pannello di Controllo
@@ -2709,6 +2913,13 @@ notices.type_2=Attività
notices.desc=Descrizione
notices.op=Op.
notices.delete_success=Gli avvisi di sistema sono stati eliminati.
+users.remote = Remoto
+monitor.stats = Statistiche
+integrations = Integrazioni
+users.reserved = Riservato
+notices.operations = Operazioni
+users.bot = Bot
+config.send_test_mail_submit = Invia
[action]
@@ -2778,6 +2989,7 @@ pin=Appunta notifica
mark_as_read=Segna come letto
mark_as_unread=Segna come non letto
mark_all_as_read=Segna tutti come letti
+subscriptions = Sottoscrizioni
[gpg]
default_key=Firmato con la chiave predefinita
@@ -2885,8 +3097,16 @@ settings.delete.notice=Stai per eliminare %s (%s). Questa operazione è irrevers
settings.delete.success=Il pacchetto è stato eliminato.
settings.delete.error=Impossibile eliminare il pacchetto.
owner.settings.cleanuprules.enabled=Attivo
+debian.repository.architectures = Architetture
+rpm.repository.architectures = Architetture
+container.digest = Digest:
+debian.repository.components = Componenti
+alpine.repository.architectures = Architetture
+debian.repository.distributions = Distribuzioni
[secrets]
+secrets = Segreti
+management = Gestione Segreti
[actions]
@@ -2904,6 +3124,29 @@ runners.status.active=Attivo
runners.version=Versione
runs.commit=Commit
+status.unknown = Sconosciuto
+status.waiting = In attesa
+status.success = Successo
+runners.task_list.status = Stato
+variables = Variabili
+runs.workflow = Workflow
+runs.actor = Attore
+runs.status = Stato
+runners.status.idle = Inattivo
+status.cancelled = Annullato
+status.skipped = Saltato
+status.blocked = Bloccato
+runners = Runners
+runners.status.unspecified = Sconosciuto
+runners.status.offline = Offline
+runs.scheduled = Pianificato
+unit.desc = Gestisci azioni
+runners.runner_manage_panel = Gestione Runners
+actions = Azioni
+status.running = In corso
+status.failure = Errore
+runners.status = Stato
+runners.runner_title = Runner
@@ -2913,4 +3156,6 @@ runs.commit=Commit
[git.filemode]
; Ordered by git filemode value, ascending. E.g. directory has "040000", normal file has "100644", …
symbolic_link=Link Simbolico
+submodule = Submodule
+directory = Directory
diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini
index 2808ac6054..8922eaaecc 100644
--- a/options/locale/locale_ja-JP.ini
+++ b/options/locale/locale_ja-JP.ini
@@ -124,6 +124,7 @@ pin=ピン留め
unpin=ピン留め解除
artifacts=成果物
+confirm_delete_artifact=アーティファクト %s を削除してよろしいですか?
archived=アーカイブ
@@ -141,6 +142,18 @@ confirm_delete_selected=選択したすべてのアイテムを削除してよ
name=名称
value=値
+filter.is_archived = アーカイブ
+filter.not_archived = 非アーカイブ
+filter.is_fork = フォーク
+filter.is_mirror = ミラー
+filter.not_mirror = 非ミラー
+filter.is_template = テンプレート
+filter = フィルター
+filter.not_fork = 非フォーク
+filter.clear = フィルタをクリアする
+filter.public = 公開
+filter.private = 非公開
+toggle_menu = トグルメニュー
[aria]
navbar=ナビゲーションバー
@@ -149,8 +162,8 @@ footer.software=ソフトウェアについて
footer.links=リンク
[heatmap]
-number_of_contributions_in_the_last_12_months=過去 12 か月間で %s 個の貢献
-no_contributions=貢献なし
+number_of_contributions_in_the_last_12_months=過去 12 か月間で %s 件の実績
+no_contributions=実績なし
less=少
more=多
@@ -181,6 +194,7 @@ missing_csrf=不正なリクエスト: CSRFトークンが不明です
invalid_csrf=不正なリクエスト: CSRFトークンが無効です
not_found=ターゲットが見つかりませんでした。
network_error=ネットワークエラー
+server_internal = 内部サーバーエラー
[startpage]
app_desc=自分で立てる、超簡単 Git サービス
@@ -223,7 +237,7 @@ err_admin_name_pattern_not_allowed=管理者のユーザー名が不正です。
err_admin_name_is_invalid=管理者のユーザー名が不正です
general_title=基本設定
-app_name=サイトタイトル
+app_name=インスタンスの名前
app_name_helper=企業名をここに入れることができます。
repo_path=リポジトリのルートパス
repo_path_helper=リモートGitリポジトリはこのディレクトリに保存されます。
@@ -277,8 +291,8 @@ admin_password=パスワード
confirm_password=パスワード確認
admin_email=メールアドレス
install_btn_confirm=Forgejoをインストール
-test_git_failed='git'コマンドが確認できません: %v
-sqlite3_not_available=ForgejoのこのバージョンはSQLite3をサポートしていません。 公式のバイナリ版を %s からダウンロードしてください。 ('gobuild'版でないもの)
+test_git_failed="git"コマンドが確認できません: %v
+sqlite3_not_available=ForgejoのこのバージョンはSQLite3をサポートしていません。 公式のバイナリ版を %s からダウンロードしてください。 ("gobuild"版でないもの)
invalid_db_setting=データベース設定が無効です: %v
invalid_db_table=データベーステーブルの "%s" が無効です: %v
invalid_repo_path=リポジトリのルートパスが無効です: %v
@@ -296,7 +310,7 @@ default_allow_create_organization_popup=新しいユーザーアカウントに
default_enable_timetracking=デフォルトでタイムトラッキング有効
default_enable_timetracking_popup=新しいリポジトリのタイムトラッキングをデフォルトで有効にします。
no_reply_address=メールを隠すときのドメイン
-no_reply_address_helper=メールアドレスを隠しているユーザーに使用するドメイン名。 例えば 'noreply.example.org' と設定した場合、ユーザー名 'joe' はGitに 'joe@noreply.example.org' としてログインすることになります。
+no_reply_address_helper=メールアドレスを隠しているユーザーに使用するドメイン名。 例えば "noreply.example.org" と設定した場合、ユーザー名 "joe" はGitに "joe@noreply.example.org" としてログインすることになります。
password_algorithm=パスワードハッシュアルゴリズム
invalid_password_algorithm=無効なパスワードハッシュアルゴリズム
password_algorithm_helper=パスワードハッシュアルゴリズムを設定します。 アルゴリズムにより動作要件と強度が異なります。 argon2アルゴリズムはかなり安全ですが、多くのメモリを使用するため小さなシステムには適さない場合があります。
@@ -305,6 +319,7 @@ enable_update_checker_helper=gitea.ioに接続して定期的に新しいバー
env_config_keys=環境設定
env_config_keys_prompt=以下の環境変数も設定ファイルに適用されます:
allow_dots_in_usernames = ユーザー名にドットを使用できるようにします。既存のアカウントには影響しません。
+smtp_from_invalid = メール送信者のアドレスが無効です
[home]
uname_holder=ユーザー名またはメールアドレス
@@ -428,7 +443,7 @@ password_pwned_err=HaveIBeenPwnedへのリクエストを完了できません
change_unconfirmed_email = 登録時に間違ったメール アドレスを入力した場合は、以下で変更できます。代わりに確認メールが新しいアドレスに送信されます。
change_unconfirmed_email_error = メール アドレスを変更できません: %v
change_unconfirmed_email_summary = アクティベーションメールの送信先メールアドレスを変更します。
-last_admin = 最後の管理者を削除することはできません。少なくとも 1 人の管理者が必要です。
+last_admin=最後の管理者は削除できません。少なくとも一人の管理者が必要です。
[mail]
view_it_on=%s で見る
@@ -452,7 +467,7 @@ register_notify.text_2=あなたはユーザー名 %s でログインできる
register_notify.text_3=このアカウントがあなたに作成されたものであれば、最初にパスワードを設定 してください。
reset_password=アカウントを回復
-reset_password.title=%s さん、あなたのアカウントの回復がリクエストされました
+reset_password.title=%s さん、あなたのアカウントの復元がリクエストされました
reset_password.text=あなたのアカウントを回復するには、%s 以内に次のリンクをクリックしてください:
register_success=登録が完了しました
@@ -530,7 +545,7 @@ SSPISeparatorReplacement=セパレーター
SSPIDefaultLanguage=デフォルトの言語
require_error=`は空にできません。`
-alpha_dash_error=`は、英数字、ダッシュ('-')、アンダースコア('_')だけを含めることができます。`
+alpha_dash_error=`は、英数字、ダッシュ("-")、アンダースコア("_")だけを含めることができます。`
alpha_dash_dot_error=`は、英数字、ダッシュ('-')、アンダースコア('_')、ドット('.')だけを含めることができます。`
git_ref_name_error=`は有効なGit Ref名である必要があります。`
size_error=は%s文字である必要があります。
@@ -577,7 +592,7 @@ enterred_invalid_owner_name=新しいオーナーの名前が正しくありま
enterred_invalid_password=入力されたパスワードが間違っています。
user_not_exist=指定されたユーザーは存在しません。
team_not_exist=チームが存在していません。
-last_org_owner='Owners'チームから最後のユーザーを削除することはできません。ひとつの組織には少なくとも一人のオーナーが必要です。
+last_org_owner="Owners"チームから最後のユーザーを削除することはできません。ひとつの組織には少なくとも一人のオーナーが必要です。
cannot_add_org_to_team=組織はチームメンバーとして追加できません。
duplicate_invite_to_team=指定したユーザーはすでにチームメンバーに招待されています。
organization_leave_success=あなたは組織 %s から脱退しました。
@@ -599,6 +614,7 @@ target_branch_not_exist=ターゲットのブランチが存在していませ
admin_cannot_delete_self = 管理者である場合、自分自身を削除することはできません。最初に管理者権限を削除してください。
username_error_no_dots = `英数字 (「0-9」、「a-z」、「A-Z」)、ダッシュ (「-」)、およびアンダースコア (「_」) のみを含めることができます。英数字以外の文字で開始または終了することはできず、連続した英数字以外の文字も禁止されています。`
+admin_cannot_delete_self=あなたが管理者である場合、自分自身を削除することはできません。最初に管理者権限を削除してください。
[user]
change_avatar=アバターを変更…
@@ -628,6 +644,9 @@ block_user.detail_2 = このユーザーは、リポジトリ、作成された
block_user.detail_1 = このユーザーからのフォローが解除されています。
follow_blocked_user = あなたはこのユーザーをフォローできません。なぜなら、あなたはこのユーザーをブロックしたか、このユーザーはあなたをブロックしているからです。
block_user.detail_3 = このユーザーはあなたをコラボレーターとして追加することはできませんし、あなたも彼らをコラボレーターに追加できません。
+block_user = ユーザーをブロック
+unblock = ブロックを解除
+block = ブロック
[settings]
profile=プロフィール
@@ -757,7 +776,7 @@ gpg_helper=ヘルプが必要ですか? GitHubのガイドを
add_new_key=SSHキーの追加
add_new_gpg_key=GPGキーの追加
key_content_ssh_placeholder=先頭は次のいずれか 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com'
-key_content_gpg_placeholder=先頭は '-----BEGIN PGP PUBLIC KEY BLOCK-----'
+key_content_gpg_placeholder=先頭は "-----BEGIN PGP PUBLIC KEY BLOCK-----"
add_new_principal=プリンシパルを追加
ssh_key_been_used=このSSHキーは既にサーバーに追加されています。
ssh_key_name_used=同じ名前のSSHキーが既にアカウントに存在しています。
@@ -775,7 +794,7 @@ gpg_token=トークン
gpg_token_help=署名はこの方法で生成できます:
gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
gpg_token_signature=Armor形式のGPG署名
-key_signature_gpg_placeholder=先頭は '-----BEGIN PGP SIGNATURE-----'
+key_signature_gpg_placeholder=先頭は "-----BEGIN PGP SIGNATURE-----"
verify_gpg_key_success=GPG鍵 "%s" を確認しました。
ssh_key_verified=確認済みの鍵
ssh_key_verified_long=鍵はトークンを使用して確認済みです。このユーザーのいずれかのアクティベート済みメールアドレスに一致するコミットについても、この鍵を使用して検証することができます。
@@ -785,7 +804,7 @@ ssh_token_required=以下のトークンの署名を入力する必要があり
ssh_token=トークン
ssh_token_help=署名はこの方法で生成できます:
ssh_token_signature=Armor形式のSSH署名
-key_signature_ssh_placeholder=先頭は '-----BEGIN SSH SIGNATURE-----'
+key_signature_ssh_placeholder=先頭は "-----BEGIN SSH SIGNATURE-----"
verify_ssh_key_success=SSH 鍵 "%s" を確認しました。
subkeys=サブキー
key_id=キーID
@@ -945,6 +964,7 @@ blocked_users = ブロックしたユーザー
user_unblock_success = このユーザーをアンブロックするのに成功しました。
blocked_since = %s からブロック中
user_block_success = このユーザーをブロックするのに成功しました。
+change_password = パスワードを変更
[repo]
new_repo_helper=リポジトリには、プロジェクトのすべてのファイルとリビジョン履歴が入ります。 すでにほかの場所でホストしていますか? リポジトリを移行 もどうぞ。
@@ -988,6 +1008,8 @@ issue_labels_helper=イシューのラベルセットを選択
license=ライセンス
license_helper=ライセンス ファイルを選択してください。
license_helper_desc=ライセンスにより、他人があなたのコードに対して何ができて何ができないのかを規定します。 どれがプロジェクトにふさわしいか迷っていますか? ライセンス選択サイト も確認してみてください。
+object_format=オブジェクトのフォーマット
+object_format_helper=リポジトリのオブジェクトフォーマット。後で変更することはできません。SHA1 は最も互換性があります。
readme=README
readme_helper=READMEファイル テンプレートを選択してください。
readme_helper_desc=プロジェクトについての説明をひととおり書く場所です。
@@ -1055,6 +1077,7 @@ desc.public=公開
desc.template=テンプレート
desc.internal=組織内
desc.archived=アーカイブ
+desc.sha256=SHA256
template.items=テンプレート項目
template.git_content=Gitコンテンツ (デフォルトブランチ)
@@ -1240,7 +1263,7 @@ editor.or=または
editor.cancel_lower=キャンセル
editor.commit_signed_changes=署名した変更をコミット
editor.commit_changes=変更をコミット
-editor.add_tmpl='<ファイル名>' を追加
+editor.add_tmpl="<ファイル名>" を追加
editor.add=%s を追加
editor.update=%s を更新
editor.delete=%s を削除
@@ -1524,7 +1547,7 @@ issues.role.member_helper=このユーザーはこのリポジトリを所有し
issues.role.collaborator=共同作業者
issues.role.collaborator_helper=このユーザーはリポジトリ上で共同作業するように招待されています。
issues.role.first_time_contributor=初めての貢献者
-issues.role.first_time_contributor_helper=これは、このユーザーのリポジトリへの最初の貢献です。
+issues.role.first_time_contributor_helper=これは、このユーザーによるリポジトリへの最初の貢献です。
issues.role.contributor=貢献者
issues.role.contributor_helper=このユーザーは以前にリポジトリにコミットしています。
issues.re_request_review=レビューを再依頼
@@ -1860,7 +1883,7 @@ milestones.title=タイトル
milestones.desc=説明
milestones.due_date=期日 (オプション)
milestones.clear=消去
-milestones.invalid_due_date_format=期日は 'yyyy-mm-dd' の形式で入力してください。
+milestones.invalid_due_date_format=期日は "yyyy-mm-dd" の形式で入力してください。
milestones.create_success=マイルストーン "%s" を作成しました。
milestones.edit=マイルストーンを編集
milestones.edit_subheader=マイルストーンはイシューをまとめ、進捗を管理します。
@@ -2025,7 +2048,8 @@ settings.mirror_settings.docs.more_information_if_disabled=プッシュミラー
settings.mirror_settings.docs.doc_link_title=リポジトリをミラーリングするには?
settings.mirror_settings.docs.doc_link_pull_section=ドキュメントの「リモートリポジトリからのプル」セクション。
settings.mirror_settings.docs.pulling_remote_title=リモートリポジトリからのプル
-settings.mirror_settings.mirrored_repository=同期するリポジトリ
+settings.mirror_settings.mirrored_repository=ミラー元のリポジトリ
+settings.mirror_settings.pushed_repository=プッシュ先のリポジトリ
settings.mirror_settings.direction=方向
settings.mirror_settings.direction.pull=プル
settings.mirror_settings.direction.push=プッシュ
@@ -2591,6 +2615,10 @@ error.csv.invalid_field_count=このファイルは %d 行目のフィールド
admin.enabled_flags = このリポジトリで有効になっているフラグたち:
clone_in_vscodium = VSCodiumでcloneする
desc.sha256 = SHA256
+wiki.cancel = キャンセル
+activity.navbar.contributors = 貢献者
+contributors.contribution_type.filter_label = 貢献の種類:
+activity.navbar.recent_commits = 最近の貢献者
[graphs]
@@ -3564,13 +3592,15 @@ runs.actors_no_select=すべてのアクター
runs.status_no_select=すべてのステータス
runs.no_results=一致する結果はありません。
runs.no_workflows=ワークフローはまだありません。
+runs.no_workflows.quick_start=Gitea Actions の始め方がわからない? ではクイックスタートガイド をご覧ください。
+runs.no_workflows.documentation=Gitea Actions の詳細については、ドキュメント を参照してください。
runs.no_runs=ワークフローはまだ実行されていません。
runs.empty_commit_message=(空のコミットメッセージ)
workflow.disable=ワークフローを無効にする
-workflow.disable_success=ワークフロー '%s' が無効になりました。
+workflow.disable_success=ワークフロー "%s" が無効になりました。
workflow.enable=ワークフローを有効にする
-workflow.enable_success=ワークフロー '%s' が有効になりました。
+workflow.enable_success=ワークフロー "%s" が有効になりました。
workflow.disabled=ワークフローは無効です。
need_approval_desc=フォークプルリクエストのワークフローを実行するには承認が必要です。
@@ -3592,6 +3622,7 @@ variables.update.success=変数を更新しました。
runs.no_workflows.quick_start = Forgejo Action の始め方がわからない? クイックスタートガイド をご覧ください。
runs.no_workflows.documentation = Forgejo Action の詳細については、ドキュメント を参照してください。
variables.id_not_exist = idが%dの変数は存在しません。
+runs.workflow = ワークフロー
[projects]
type-1.display_name=個人プロジェクト
diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini
index 15d24558df..718f3dc9a4 100644
--- a/options/locale/locale_lv-LV.ini
+++ b/options/locale/locale_lv-LV.ini
@@ -1036,6 +1036,7 @@ desc.public=Publisks
desc.template=Sagatave
desc.internal=Iekšējs
desc.archived=Arhivēts
+desc.sha256=SHA256
template.items=Sagataves ieraksti
template.git_content=Git saturs (noklusētais atzars)
@@ -2571,6 +2572,10 @@ error.csv.unexpected=Nevar attēlot šo failu, jo tas satur neparedzētu simbolu
error.csv.invalid_field_count=Nevar attēlot šo failu, jo tas satur nepareizu skaitu ar laukiem %d. līnijā.
[graphs]
+component_loading=Ielādē %s...
+component_loading_failed=Nevarēja ielādēt %s
+component_loading_info=Šis var aizņemt kādu brīdi…
+component_failed_to_load=Atgadījās neparedzēta kļūda.
[org]
org_name_holder=Organizācijas nosaukums
@@ -2698,6 +2703,7 @@ teams.invite.description=Nospiediet pogu zemāk, lai pievienotos komandai.
[admin]
dashboard=Infopanelis
+self_check=Pašpārbaude
identity_access=Identitāte un piekļuve
users=Lietotāju konti
organizations=Organizācijas
@@ -3223,6 +3229,7 @@ notices.desc=Apraksts
notices.op=Op.
notices.delete_success=Sistēmas paziņojumi ir dzēsti.
+self_check.no_problem_found=Pašlaik nav atrasta neviena problēma.
[action]
create_repo=izveidoja repozitoriju %s
@@ -3560,6 +3567,7 @@ variables.none=Vēl nav neviena mainīgā.
variables.deletion=Noņemt mainīgo
variables.deletion.description=Mainīgā noņemšana ir neatgriezeniska un nav atsaucama. Vai turpināt?
variables.description=Mainīgie tiks padoti noteiktām darbībām, un citādāk tos nevar nolasīt.
+variables.id_not_exist=Mainīgais ar identifikatoru %d nepastāv.
variables.edit=Labot mainīgo
variables.deletion.failed=Neizdevās noņemt mainīgo.
variables.deletion.success=Mainīgais tika noņemts.
diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini
index 919edc0150..fe30475936 100644
--- a/options/locale/locale_nl-NL.ini
+++ b/options/locale/locale_nl-NL.ini
@@ -142,6 +142,19 @@ pin = Vastpinnen
unpin = Ontpinnen
remove_label_str = Verwijder punt "%s"
confirm_delete_artifact = Weet u zeker dat u het artefact "%s" wilt verwijderen?
+toggle_menu = Menu schakelen
+filter.clear = Filter wissen
+filter.is_archived = Gearchiveerd
+filter.is_fork = Geforkt
+filter.not_fork = Niet geforkt
+filter.is_mirror = Gespiegeld
+filter.not_mirror = Niet gespiegeld
+filter.is_template = Sjabloon
+filter.not_template = Geen sjabloon
+filter.public = Publiek
+filter.private = Privé
+filter = Filter
+filter.not_archived = Niet gearchiveerd
[aria]
navbar = Navigatiebalk
@@ -224,10 +237,10 @@ err_admin_name_is_reserved=Gebruikersnaam van beheerder is ongeldig, gebruikersn
err_admin_name_pattern_not_allowed=Gebruikersnaam van beheerder is ongeldig, de gebruikersnaam is gereserveerd
err_admin_name_is_invalid=Gebruikersnaam van beheerder is ongeldig
-general_title=Algemene Instellingen
-app_name=Naam site
+general_title=Algemene instellingen
+app_name=Instantienaam
app_name_helper=U kan de naam van uw bedrijf hier invullen.
-repo_path=Repositories basis map
+repo_path=Repository hoofdpad
repo_path_helper=Externe git repositories worden opgeslagen in deze map.
lfs_path=Git LFS root pad
lfs_path_helper=Bestanden bijgehouden door Git LFS zullenworden opgeslagen in deze map. Laat leeg om uit te schakelen.
@@ -436,7 +449,7 @@ remember_me.compromised = De login-sleutel is niet meer geldig, dit kan wijzen o
[mail]
view_it_on=Bekijk het op %s
-link_not_working_do_paste=Werkt dit niet? Probeer het te kopiëren en te plakken naar uw browser.
+link_not_working_do_paste=Werkt de link niet? Kopieer en plak de link dan in de URL-balk van je browser.
hi_user_x=Hoi %s ,
activate_account=Activeer uw account
@@ -450,12 +463,12 @@ activate_email.text=Klik op de volgende link om je e-mailadres te bevestigen in
register_notify=Welkom bij Forgejo
register_notify.title=%[1]s, welkom bij %[2]s
register_notify.text_1=dit is uw registratie bevestigingsemail voor %s!
-register_notify.text_2=U kunt nu inloggen via de gebruikersnaam: %s.
-register_notify.text_3=Als dit account voor u is aangemaakt, kunt u eerst uw wachtwoord instellen .
+register_notify.text_2=U kunt zich aanmelden bij uw account met uw gebruikersnaam: %s
+register_notify.text_3=Als iemand anders dit account voor u heeft gemaakt, moet u eerst uw wachtwoord instellen .
reset_password=Account herstellen
-reset_password.title=%s, u heeft verzocht om uw account te herstellen
-reset_password.text=Klik op de volgende link om je account te herstellen binnen %s :
+reset_password.title=%s, we hebben een verzoek ontvangen om uw account te herstellen
+reset_password.text=Als u dit was, klik dan op de volgende link om uw account te herstellen binnen %s :
register_success=Registratie succesvol
@@ -1660,9 +1673,9 @@ pulls.rebase_conflict_summary=Foutmelding
pulls.unrelated_histories=Samenvoegen mislukt: de HEAD en base delen geen gemeenschappelijke geschiedenis. Tip: Probeer een andere strategie
pulls.merge_out_of_date=Samenvoegen mislukt: Tijdens het samenvoegen is de basis bijgewerkt. Tip: Probeer het opnieuw.
pulls.head_out_of_date=Samenvoegen mislukt: tijdens het genereren van de samenvoeging is de kop bijgewerkt. Tip: Probeer het opnieuw.
-pulls.push_rejected=Samenvoegen mislukt: De push is geweigerd. Controleer de Git Hooks voor deze repository.
+pulls.push_rejected=Push mislukt: De push is geweigerd. Controleer de Git Hooks voor deze repository.
pulls.push_rejected_summary=Volledig afwijzingsbericht
-pulls.push_rejected_no_message=Samenvoegen mislukt: De push is afgewezen, maar er was geen extern bericht. Controleer de Git Hooks voor deze repository
+pulls.push_rejected_no_message=Push mislukt: De push is afgewezen maar er was geen remote bericht. Bekijk de Git Hooks voor dit repository
pulls.open_unmerged_pull_exists=`Je kan deze pull request niet opnieuw openen omdat er een andere (#%d) met identieke eigenschappen open staat.`
pulls.status_checking=Sommige controles zijn in behandeling
pulls.status_checks_success=Alle checks waren succesvol
@@ -2088,7 +2101,7 @@ settings.matrix.homeserver_url=Homeserver URL
settings.matrix.room_id=Kamer ID
settings.matrix.message_type=Bericht type
settings.archive.button=Repo archiveren
-settings.archive.header=Deze Repo archiveren
+settings.archive.header=Archiveer deze repo
settings.archive.success=De repo is succesvol gearchiveerd.
settings.archive.error=Er is een fout opgetreden tijdens het archiveren van de repo. Zie het logboek voor meer informatie.
settings.archive.error_ismirror=U kunt geen gespiegelde repo archiveren.
@@ -2649,6 +2662,7 @@ pulls.agit_explanation = Gemaakt met behulp van de AGit workflow. AGit laat bijd
settings.confirmation_string = Confirmatie string
activity.navbar.code_frequency = Code Frequentie
activity.navbar.recent_commits = Recente commits
+file_follow = Volg Symlink
@@ -2795,7 +2809,7 @@ total=Totaal: %d
dashboard.statistic=Overzicht
dashboard.operations=Onderhoudswerkzaamheden
-dashboard.system_status=Systeemtatus
+dashboard.system_status=Systeemstatus
dashboard.operation_name=Bewerking naam
dashboard.operation_switch=Omschakelen
dashboard.operation_run=Uitvoeren
@@ -2829,17 +2843,17 @@ dashboard.resync_all_hooks=Opnieuw synchroniseren van pre-ontvangst, bewerk en p
dashboard.reinit_missing_repos=Herinitialiseer alle ontbrekende Git repositories waarvoor records bestaan
dashboard.sync_external_users=Externe gebruikersgegevens synchroniseren
dashboard.server_uptime=Uptime server
-dashboard.current_goroutine=Huidige Goroutines
+dashboard.current_goroutine=Huidige goroutines
dashboard.current_memory_usage=Huidig geheugen gebruik
dashboard.total_memory_allocated=Totaal toegewezen geheugen
dashboard.memory_obtained=Geheugen gebruikt
dashboard.pointer_lookup_times=Aanwijzer Lookup keer
dashboard.memory_allocate_times=Geheugentoewijzingen
dashboard.memory_free_times=Geheugen vrjigemaakt
-dashboard.current_heap_usage=Huidige Heap gebruik
+dashboard.current_heap_usage=Huidige heap gebruik
dashboard.heap_memory_obtained=Heap geheugen verkregen
dashboard.heap_memory_idle=Heap geheugen inactief
-dashboard.heap_memory_in_use=Heap geheugen In gebruik
+dashboard.heap_memory_in_use=Heap geheugen in gebruik
dashboard.heap_memory_released=Heap geheugen vrijgegeven
dashboard.heap_objects=Heap-objecten
dashboard.bootstrap_stack_usage=Bootstrap Stack gebruik
@@ -2849,7 +2863,7 @@ dashboard.mspan_structures_obtained=MSpan structuren verkregen
dashboard.mcache_structures_usage=MCache structuren gebruik
dashboard.mcache_structures_obtained=MCache structuren verkregen
dashboard.profiling_bucket_hash_table_obtained=Profilering emmer hashtabel verkregen
-dashboard.gc_metadata_obtained=GC Metadada verkregen
+dashboard.gc_metadata_obtained=GC metadada verkregen
dashboard.other_system_allocation_obtained=Andere systeem toewijzing verkregen
dashboard.next_gc_recycle=Volgende GC recycle
dashboard.last_gc_time=Sinds vorige GC verwerkingstijd
@@ -3048,10 +3062,10 @@ config.ssh_keygen_path=Pad van keygen ("ssh-keygen")
config.ssh_minimum_key_size_check=Controleer minimale key-lengte
config.ssh_minimum_key_sizes=Minimale key-lengtes
-config.lfs_config=LFS Configuratie
+config.lfs_config=LFS configuratie
config.lfs_enabled=Ingeschakeld
config.lfs_content_path=LFS inhoudspad
-config.lfs_http_auth_expiry=LFS HTTP Auth Vervaltijd
+config.lfs_http_auth_expiry=LFS HTTP auth vervaltijd
config.db_config=Databaseconfiguratie
config.db_type=Type
@@ -3072,8 +3086,8 @@ config.show_registration_button=Registeren knop weergeven
config.require_sign_in_view=Vereis inloggen om pagina's te kunnen bekijken
config.mail_notify=Activeer e-mailnotificaties
config.enable_captcha=CAPTCHA inschakelen
-config.active_code_lives=Actieve Code leven
-config.reset_password_code_lives=Herstel accountcode vervaltijd
+config.active_code_lives=Vervaltijd activeringscode
+config.reset_password_code_lives=Vervaltijd herstelcode
config.default_keep_email_private=Verberg standaard alle e-mailadressen
config.default_allow_create_organization=Standaard toestaan om organisaties aan te maken
config.enable_timetracking=Tijdregistratie inschakelen
@@ -3111,12 +3125,12 @@ config.cache_item_ttl=Cache-item TTL
config.session_config=Sessieconfiguratie
config.session_provider=Sessieprovider
-config.provider_config=Provider config
+config.provider_config=Configuratie provider
config.cookie_name=Cookie naam
-config.gc_interval_time=GC interval time
+config.gc_interval_time=GC intervaltijd
config.session_life_time=Sessie duur
config.https_only=Alleen HTTPS
-config.cookie_life_time=Cookie duur leeftijd
+config.cookie_life_time=Levensduur cookie
config.picture_config=Foto en avatar configuratie
config.picture_service=Foto service
@@ -3124,12 +3138,12 @@ config.disable_gravatar=Gravatar uitschakelen
config.enable_federated_avatar=Federated avatars toestaan
config.git_config=Git configuratie
-config.git_disable_diff_highlight=Uitschakelen Diff Syntaxis-Highlight
-config.git_max_diff_lines=Maximum Diff Lijnen (voor een enkel bestand)
-config.git_max_diff_files=Max Diff bestanden (om weer te geven)
-config.git_gc_args=GC Parameters
+config.git_disable_diff_highlight=Diff syntax highlighting uitschakelen
+config.git_max_diff_lines=Max diff regels per bestand
+config.git_max_diff_files=Max. getoonde diff-bestanden
+config.git_gc_args=GC-argumenten
config.git_migrate_timeout=Migratie time-out
-config.git_mirror_timeout=Kopie Update Timeout
+config.git_mirror_timeout=Time-out spiegelupdate
config.git_clone_timeout=Kloon operatie timeout
config.git_pull_timeout=Pull operatie timeout
config.git_gc_timeout=GC operatie timeout
@@ -3170,7 +3184,7 @@ monitor.queue.settings.maxnumberworkers.error=Maximaal aantal workers moet een n
monitor.queue.settings.submit=Instellingen bijwerken
monitor.queue.settings.changed=Instellingen bijgewerkt
-notices.system_notice_list=Systeem aankondigingen
+notices.system_notice_list=Systeemmeldingen
notices.view_detail_header=Bekijk notificatie details
notices.select_all=Alles selecteren
notices.deselect_all=Alles deselecteren
@@ -3215,7 +3229,7 @@ packages.version = Versie
packages.published = Gepubliceerd
defaulthooks = Standaard webhooks
defaulthooks.update_webhook = Standaard webhook bijwerken
-auths.auth_manage_panel = Authenticatie Bronbeheer
+auths.auth_manage_panel = Authenticatie bronbeheer
auths.oauth2_required_claim_value = Vereiste claimwaarde
auths.oauth2_group_claim_name = Claimnaam die groepsnamen geeft voor deze bron. (Optioneel)
auths.oauth2_admin_group = Groepsclaimwaarde voor beheerdersgebruikers. (Optioneel - vereist bovenstaande claimnaam)
@@ -3291,7 +3305,7 @@ config.test_mail_sent = Er is een testmail verzonden naar "%s".
config.test_mail_failed = Er is geen testmail verzonden naar "%s": %v
config.access_log_template = Sjabloon voor toegangslogboek
config.logger_name_fmt = Logger: %s
-config.git_max_diff_line_characters = Max Diff tekens (voor een enkele regel)
+config.git_max_diff_line_characters = Max diff tekens per regel
auths.sspi_strip_domain_names_helper = Als deze optie is aangevinkt, worden domeinnamen verwijderd uit inlognamen (bijv. "DOMEIN\gebruiker" en "gebruiker@example.org" worden beide gewoon "gebruiker").
auths.map_group_to_team_removal = Gebruikers verwijderen uit gesynchroniseerde teams als gebruiker niet tot overeenkomstige LDAP-groep behoort
config.send_test_mail_submit = Stuur
@@ -3660,6 +3674,7 @@ runs.no_workflows.quick_start = Weet je niet hoe je moet beginnen met Forgejo Ac
variables.description = Variabelen worden doorgegeven aan bepaalde acties en kunnen anders niet worden gelezen.
runners.delete_runner_success = Runner succesvol verwijderd
runs.no_matching_online_runner_helper = Geen overeenkomende online runner met label: %s
+runs.workflow = Workflow
diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini
index a55f466bd9..e6fc0df5ef 100644
--- a/options/locale/locale_pt-BR.ini
+++ b/options/locale/locale_pt-BR.ini
@@ -182,6 +182,7 @@ missing_csrf=Pedido inválido: não tem token CSRF presente
invalid_csrf=Requisição Inválida: token CSRF inválido
not_found=Não foi possível encontrar o destino.
network_error=Erro de rede
+server_internal = Erro interno do servidor
[startpage]
app_desc=Um serviço de hospedagem Git amigável
@@ -364,7 +365,7 @@ social_register_helper_msg=Já tem uma conta? Vincule agora!
disable_register_prompt=Cadastro está desabilitado. Entre em contato com o administrador do site.
disable_register_mail=E-mail de confirmação de cadastro está desabilitado.
manual_activation_only=Entre em contato com o administrador do site para concluir a ativação.
-remember_me=Lembrar deste Dispositivo
+remember_me=Lembrar este dispositivo
forgot_password_title=Esqueci minha senha
forgot_password=Esqueceu sua senha?
sign_up_now=Precisa de uma conta? Cadastre-se agora.
@@ -435,7 +436,7 @@ remember_me.compromised = O token de login foi invalidado, o que pode indicar qu
[mail]
view_it_on=Veja em %s
reply=ou responda diretamente a este email
-link_not_working_do_paste=Não está funcionando? Tente copiá-lo e colá-lo no seu navegador.
+link_not_working_do_paste=O link não está funcionando? Tente copiar e colá-lo em seu navegador.
hi_user_x=Olá %s ,
activate_account=Por favor, ative sua conta
@@ -454,7 +455,7 @@ register_notify.text_2=Agora você pode entrar com o nome de usuário: %s.
register_notify.text_3=Se esta conta foi criada para você, defina sua senha primeiro.
reset_password=Recuperar sua conta
-reset_password.title=%s, você pediu para recuperar a sua conta
+reset_password.title=%s, recebemos um pedido para recuperar a sua conta
reset_password.text=Por favor clique no link a seguir para recuperar sua conta em %s :
register_success=Cadastro bem-sucedido
@@ -543,7 +544,7 @@ url_error= `"%s" não é um URL válido.`
include_error=` deve conter "%s".`
glob_pattern_error=` padrão glob é inválido: %s.`
regex_pattern_error=` o regex é inválido: %s.`
-username_error=` só pode conter caracteres alfanuméricos ('0-9','a-z','A-Z'), traço ('-'), sublinhado ('_') e ponto ('.'). Não pode começar ou terminar com caracteres não alfanuméricos, e caracteres não-alfanuméricos consecutivos também são proibidos.`
+username_error=` pode conter apenas caracteres alfanuméricos ("0-9, "a-z", "A-Z"), hífens ("-"), traços inferiores ("_") e pontos ("."). Não é permitido conter caracteres não alfanuméricos no início ou fim. Caracteres não alfanuméricos consecutivos também não são permitidos.`
invalid_group_team_map_error=` mapeamento é inválido: %s`
unknown_error=Erro desconhecido:
captcha_incorrect=O código CAPTCHA está incorreto.
@@ -598,7 +599,7 @@ org_still_own_repo=Esta organização ainda possui repositórios, exclua ou tran
org_still_own_packages=Esta organização ainda possui pacotes, exclua-os primeiro.
target_branch_not_exist=O branch de destino não existe.
-username_error_no_dots = ` pode conter apenas caracteres alfanuméricos ("0-9, "a-z", "A-Z"), hífens ("-") e traços inferiores ("_"). Não é permitido conter caracteres não alfanuméricos no início ou fim e caracteres não alfanuméricos consecutivos.`
+username_error_no_dots = ` pode conter apenas caracteres alfanuméricos ("0-9, "a-z", "A-Z"), hífens ("-") e traços inferiores ("_"). Não é permitido conter caracteres não alfanuméricos no início ou fim. Caracteres não alfanuméricos consecutivos também não são permitidos.`
admin_cannot_delete_self = Você não pode excluir a si mesmo quando você é um administrador. Por favor, remova suas permissões de administrador primeiro.
@@ -625,7 +626,7 @@ settings=Configurações do usuário
form.name_reserved=O nome de usuário "%s" está reservado.
form.name_pattern_not_allowed=O padrão de "%s" não é permitido em um nome de usuário.
-form.name_chars_not_allowed=Nome de usuário "%s" contém caracteres inválidos.
+form.name_chars_not_allowed=O usuário "%s" contém caracteres inválidos.
block_user = Bloquear usuário
unblock = Desbloquear
block = Bloquear
@@ -699,7 +700,7 @@ keep_activity_private_popup=Torna a atividade visível somente para você e os a
lookup_avatar_by_mail=Procurar o avatar do endereço de e-mail
federated_avatar_lookup=Busca de avatar federativo
-enable_custom_avatar=Habilitar avatar customizado
+enable_custom_avatar=Usar avatar personalizado
choose_new_avatar=Escolha um novo avatar
update_avatar=Atualizar o avatar
delete_current_avatar=Excluir o avatar atual
@@ -750,7 +751,7 @@ keep_email_private=Ocultar endereço de e-mail
keep_email_private_popup=Isso ocultará seu endereço de e-mail do seu perfil, bem como quando você fizer um pull request ou editar um arquivo usando a interface Web. Os commits enviados não serão modificados.
openid_desc=OpenID permite delegar autenticação para um provedor externo.
-manage_ssh_keys=Gerenciar Chaves SSH
+manage_ssh_keys=Gerenciar chaves SSH
manage_ssh_principals=Gerenciar Nomes Principais do certificado SSH
manage_gpg_keys=Gerenciar chaves GPG
add_key=Adicionar chave
@@ -761,8 +762,8 @@ ssh_helper=Precisa de ajuda? Dê uma olhada no guia do GitHub p
gpg_helper=Precisa de ajuda? Dê uma olhada no guia do GitHub sobre GPG .
add_new_key=Adicionar Chave SSH
add_new_gpg_key=Adicionar chave GPG
-key_content_ssh_placeholder=Começa por 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'sk-ecdsa-sha2-nistp256@openssh.com', ou 'sk-ssh-ed25519@openssh.com'
-key_content_gpg_placeholder=Começa com '-----BEGIN PGP PUBLIC KEY BLOCK-----'
+key_content_ssh_placeholder=Começa com "ssh-ed25519", "ssh-rsa", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "sk-ecdsa-sha2-nistp256@openssh.com" ou "sk-ssh-ed25519@openssh.com"
+key_content_gpg_placeholder=Começa com "-----BEGIN PGP PUBLIC KEY BLOCK-----"
add_new_principal=Adicionar Nome Principal
ssh_key_been_used=Esta chave SSH já foi adicionada ao servidor.
ssh_key_name_used=Uma chave SSH com o mesmo nome já existe em sua conta.
@@ -780,7 +781,7 @@ gpg_token=Token
gpg_token_help=Você pode gerar uma assinatura usando:
gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
gpg_token_signature=Assinatura GPG blindada
-key_signature_gpg_placeholder=Começa com '-----BEGIN PGP SIGNATURE-----'
+key_signature_gpg_placeholder=Começa com "-----BEGIN PGP SIGNATURE-----"
verify_gpg_key_success=A chave GPG "%s" foi validada.
ssh_key_verified=Chave validada
ssh_key_verified_long=A chave foi validada com um token e pode ser usada para validar commits que correspondam a qualquer dos endereços de e-mail ativados deste usuário.
@@ -790,11 +791,11 @@ ssh_token_required=Você tem que fornecer uma assinatura para o token abaixo
ssh_token=Token
ssh_token_help=Você pode gerar uma assinatura usando:
ssh_token_signature=Assinatura SSH blindada
-key_signature_ssh_placeholder=Começa com '-----BEGIN SSH SIGNATURE-----'
+key_signature_ssh_placeholder=Começa com "-----BEGIN SSH SIGNATURE-----"
verify_ssh_key_success=A chave SSH "%s" foi validada.
subkeys=Subchaves
key_id=ID da chave
-key_name=Nome da Chave
+key_name=Nome da chave
key_content=Conteúdo
principal_content=Conteúdo
add_key_success=A chave SSH "%s" foi adicionada.
@@ -877,8 +878,8 @@ oauth2_application_create_description=Os aplicativos OAuth2 fornecem ao seus apl
oauth2_application_remove_description=A remoção de um aplicativo OAuth2 impedirá que ele acesse contas de usuários autorizados nesta instância. Continuar?
oauth2_application_locked=O Forgejo pré-registra alguns aplicativos OAuth2 na inicialização, se habilitados na configuração. Para evitar um comportamento inesperado, eles não podem ser editados nem removidos. Consulte a documentação do OAuth2 para obter mais informações.
-authorized_oauth2_applications=Aplicações OAuth2 autorizadas
-authorized_oauth2_applications_description=Você concedeu acesso à sua conta pessoal da Forgejo para esses aplicativos de terceiros. Revogue o acesso aos aplicativos de que não precisa mais.
+authorized_oauth2_applications=Aplicativos OAuth2 autorizados
+authorized_oauth2_applications_description=Você concedeu acesso à sua conta do Forgejo a estes aplicativos de terceiros. Revogue o acesso aos aplicativos que não estão em uso.
revoke_key=Revogar
revoke_oauth2_grant=Revogar acesso
revoke_oauth2_grant_description=Revogando o acesso para este aplicativo de terceiros impedirá este aplicativo de acessar seus dados. Tem certeza?
@@ -1076,7 +1077,7 @@ form.name_reserved=O nome de repositório "%s" está reservado.
form.name_pattern_not_allowed=O padrão "%s" não é permitido em um nome de repositório.
need_auth=Autorização
-migrate_options=Opções de Migração
+migrate_options=Opções de migração
migrate_service=Serviço de Migração
migrate_options_mirror_helper=Este repositório será um espelho
migrate_options_lfs=Migrar arquivos LFS
@@ -1094,7 +1095,7 @@ migrate_items_merge_requests=Requisições de merge
migrate_items_releases=Versões
migrate_repo=Migrar repositório
migrate.clone_address=Migrar / Clonar de URL
-migrate.clone_address_desc=URL HTTP (S) ou Git 'clone' de um repositório existente
+migrate.clone_address_desc=URL HTTP(S) ou comando git "clone" de um repositório existente
migrate.github_token_desc=Você pode colocar aqui um ou mais tokens separados por vírgulas para tornar a migração mais rápida para compensar o limite de taxa de API do GitHub. AVISO: abusar desse recurso pode violar a política do provedor de serviços e levar ao bloqueio da conta.
migrate.clone_local_path=ou um caminho de servidor local
migrate.permission_denied=Você não pode importar repositórios locais.
@@ -1238,7 +1239,7 @@ editor.or=ou
editor.cancel_lower=Cancelar
editor.commit_signed_changes=Commit de alteradores assinadas
editor.commit_changes=Aplicar commit das alterações
-editor.add_tmpl=Adicionar ''
+editor.add_tmpl=Adicionar ""
editor.add=Adicionar %s
editor.update=Atualizar %s
editor.delete=Excluir %s
@@ -1654,11 +1655,11 @@ issues.review.dismissed=rejeitou a revisão de %s %s
issues.review.dismissed_label=Rejeitada
issues.review.left_comment=deixou um comentário
issues.review.content.empty=Você precisa deixar um comentário indicando as alterações solicitadas.
-issues.review.reject=alterações solicitadas %s
+issues.review.reject=solicitou alterações %s
issues.review.wait=foi solicitada para revisão %s
-issues.review.add_review_request=solicitou revisão de %s %s
+issues.review.add_review_request=solicitou uma revisão de %s %s
issues.review.remove_review_request=removeu a solicitação de revisão para %s %s
-issues.review.remove_review_request_self=recusou revisar %s
+issues.review.remove_review_request_self=recusou-se a revisar %s
issues.review.pending=Pendente
issues.review.pending.tooltip=Este comentário não está atualmente visível para outros usuários. Para enviar seus comentários pendentes, selecione "%s" -> "%s/%s/%s" no topo da página.
issues.review.review=Revisão
@@ -2583,6 +2584,7 @@ settings.units.add_more = Adicionar mais...
pulls.commit_ref_at = `referenciou este pedido de mesclagem no commit %[2]s `
pulls.cmd_instruction_merge_title = Mesclar
settings.units.units = Funcionalidades
+vendored = Externo
[graphs]
diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini
index 31b7b5d605..0f495a2d19 100644
--- a/options/locale/locale_ru-RU.ini
+++ b/options/locale/locale_ru-RU.ini
@@ -5,7 +5,7 @@ explore=Обзор
help=Помощь
logo=Логотип
sign_in=Вход
-sign_in_with_provider=Войти с помощью %s
+sign_in_with_provider=Войти через %s
sign_in_or=или
sign_out=Выход
sign_up=Регистрация
@@ -56,7 +56,7 @@ mirror=Зеркало
new_repo=Новый репозиторий
new_migrate=Новая миграция
new_mirror=Новое зеркало
-new_fork=Новый форк репозитория
+new_fork=Новое ответвление репозитория
new_org=Новая организация
new_project=Новый проект
new_project_column=Новый столбец
@@ -72,7 +72,7 @@ all=Все
sources=Собственные
mirrors=Зеркала
collaborative=Совместные
-forks=Форки
+forks=Ответвления
activities=Активности
pull_requests=Слияния
@@ -142,6 +142,19 @@ value=Значение
tracked_time_summary = Сводка отслеженного времени на основе фильтров списка задач
view = Просмотр
confirm_delete_artifact = Вы точно хотите удалить артефакт «%s»?
+toggle_menu = Показать/скрыть меню
+filter.not_archived = Не архивированные
+filter = Фильтры
+filter.clear = Очистить фильтры
+filter.is_fork = Ответвления
+filter.not_fork = Не ответвления
+filter.is_mirror = Зеркала
+filter.is_template = Шаблоны
+filter.not_template = Не шаблоны
+filter.public = Публичные
+filter.private = Приватные
+filter.is_archived = Архивированные
+filter.not_mirror = Не зеркала
[aria]
navbar=Панель навигации
@@ -166,7 +179,7 @@ buttons.list.unordered.tooltip=Добавить маркированный сп
buttons.list.ordered.tooltip=Добавить нумерованный список
buttons.list.task.tooltip=Добавить список заданий
buttons.mention.tooltip=Упомянуть пользователя или команду
-buttons.ref.tooltip=Сослаться на задачу или запрос на слияние
+buttons.ref.tooltip=Сослаться на задачу или запрос слияния
buttons.switch_to_legacy.tooltip=Использовать старый редактор
buttons.enable_monospace_font=Включить моноширинный шрифт
buttons.disable_monospace_font=Выключить моноширинный шрифт
@@ -225,7 +238,7 @@ err_admin_name_pattern_not_allowed=Неверное имя администра
err_admin_name_is_invalid=Неверное имя администратора
general_title=Основные настройки
-app_name=Название сайта
+app_name=Название сервера
app_name_helper=Здесь вы можете ввести название своей компании.
repo_path=Путь до каталога репозиториев
repo_path_helper=Все удалённые Git репозитории будут сохранены в этом каталоге.
@@ -241,14 +254,14 @@ http_port=Forgejo HTTP порт
http_port_helper=Номер порта, который будет прослушиваться Forgejo веб-сервером.
app_url=Базовый URL Forgejo
app_url_helper=Этот параметр влияет на URL для клонирования по HTTP/HTTPS и на некоторые уведомления по эл. почте.
-log_root_path=Путь к журналу
+log_root_path=Путь журналов
log_root_path_helper=Файлы журнала будут записываться в этот каталог.
optional_title=Расширенные настройки
email_title=Настройки эл. почты
-smtp_addr=Узел SMTP
-smtp_port=SMTP-порт
-smtp_from=Отправить эл. почту как
+smtp_addr=Адрес SMTP
+smtp_port=Порт SMTP
+smtp_from=Отправлять письма от
smtp_from_helper=Адрес эл. почты, который будет использоваться Forgejo. Введите обычный адрес эл. почты или используйте формат "Имя" .
mailer_user=Логин SMTP
mailer_password=Пароль SMTP
@@ -256,7 +269,7 @@ register_confirm=Требовать подтверждение по эл. поч
mail_notify=Разрешить почтовые уведомления
server_service_title=Сервер и настройки внешних служб
offline_mode=Включить локальный режим
-offline_mode_popup=Отключить сторонние сети доставки контента и отдавать все ресурсы из их локальных копий.
+offline_mode_popup=Отключить сторонние сети доставки контента и передавать все ресурсы из их локальных копий.
disable_gravatar=Отключить Gravatar
disable_gravatar_popup=Отключить Gravatar и сторонние источники аватаров. Если пользователь не загрузит аватар локально, то по умолчанию будет использоваться стандартный аватар.
federated_avatar_lookup=Включить федерированные аватары
@@ -297,17 +310,18 @@ default_allow_create_organization=Разрешить создание орган
default_allow_create_organization_popup=Разрешить новым учётным записям пользователей создавать организации по умолчанию.
default_enable_timetracking=Включить отслеживание времени по умолчанию
default_enable_timetracking_popup=Включить отслеживание времени для новых репозиториев по умолчанию.
-no_reply_address=Скрытый почтовый домен
+no_reply_address=Домен скрытых адресов почты
no_reply_address_helper=Доменное имя для пользователей со скрытым адресом эл. почты. Например, пользователь «joe» будет зарегистрирован в Git как «joe@noreply.example.org», если скрытый домен эл. почты задан как «noreply.example.org».
-password_algorithm=Алгоритм хеширования пароля
+password_algorithm=Алгоритм хеширования паролей
invalid_password_algorithm=Некорректный алгоритм хеширования пароля
password_algorithm_helper=Задайте алгоритм хеширования паролей. Алгоритмы имеют различные требования и стойкость. Алгоритм argon2 довольно безопасен, но он использует много памяти и может не подходить для слабых систем.
-enable_update_checker=Включить проверку обновлений
+enable_update_checker=Проверка обновлений
enable_update_checker_helper=Периодически проверяет наличие новых версий, подключаясь к gitea.io.
env_config_keys=Настройка окружения
env_config_keys_prompt=Следующие переменные окружения также будут применены к вашему конфигурационному файлу:
enable_update_checker_helper_forgejo = Периодически проверять наличие новых версий Forgejo через DNS-запись TXT на release.forgejo.org.
allow_dots_in_usernames = Разрешить точки в логинах пользователей. Это не повлияет на уже созданные учётные записи.
+smtp_from_invalid = Адрес для отправки писем некорректен
[home]
uname_holder=Логин или адрес эл. почты
@@ -436,7 +450,7 @@ change_unconfirmed_email = Если при регистрации был вве
[mail]
view_it_on=Посмотреть на %s
reply=или ответьте на это письмо
-link_not_working_do_paste=Не сработало? Попробуйте скопировать ссылку и вставить адресную строку.
+link_not_working_do_paste=Ссылка не работает? Попробуйте скопировать и вставить адресную строку.
hi_user_x=Привет %s ,
activate_account=Активация учётной записи
@@ -451,12 +465,12 @@ activate_email.text=Для подтверждения эл. почты пере
register_notify=Добро пожаловать в Forgejo
register_notify.title=%[1]s, добро пожаловать в %[2]s
register_notify.text_1=это письмо с вашим подтверждением регистрации в %s!
-register_notify.text_2=Теперь вы можете войти, используя логин: %s.
-register_notify.text_3=Если эта учётная запись была создана для вас, пожалуйста, сначала установите пароль .
+register_notify.text_2=Теперь вы можете войти в учётную запись, используя логин: %s
+register_notify.text_3=Если эта учётная запись создана кем-то для вас, сперва будет необходимо задать пароль .
reset_password=Восстановление учётной записи
-reset_password.title=%s, вы запросили восстановление вашей учётной записи
-reset_password.text=Для восстановления учётной записи перейдите по следующей ссылке в течение %s :
+reset_password.title=%s, был получен запрос на восстановление вашей учётной записи
+reset_password.text=Если этот запрос ваш, для восстановления учётной записи используйте следующую ссылку в течение %s :
register_success=Регистрация прошла успешно
@@ -465,8 +479,8 @@ issue_assigned.issue=@%[1]s назначил(а) вам задачу %[2]s в р
issue.x_mentioned_you=@%s упомянул(а) вас:
issue.action.force_push=%[1]s форсировал(а) отправку в %[2]s изменений %[4]s вместо %[3]s.
-issue.action.push_1=@%[1]s отправил(а) %[3]d изменение в %[2]s
-issue.action.push_n=@%[1]s отправил(а) %[3]d изменений в %[2]s
+issue.action.push_1=@%[1]s отправлено %[3]d изменение в %[2]s
+issue.action.push_n=@%[1]s отправлены %[3]d изменений в %[2]s
issue.action.close=@%[1]s закрыл(а) #%[2]d.
issue.action.reopen=@%[1]s переоткрыл(а) #%[2]d.
issue.action.merge=@%[1]s слил(а) #%[2]d в %[3]s.
@@ -486,12 +500,12 @@ release.downloads=Загрузки:
release.download.zip=Исходный код (ZIP)
release.download.targz=Исходный код (TAR.GZ)
-repo.transfer.subject_to=%s хочет передать «%s» в %s
-repo.transfer.subject_to_you=%s хочет передать вам «%s»
+repo.transfer.subject_to=%s хочет передать репозиторий «%s» в %s
+repo.transfer.subject_to_you=%s хочет передать вам репозиторий «%s»
repo.transfer.to_you=вам
repo.transfer.body=Чтобы принять или отклонить передачу, перейдите по ссылке %s или просто проигнорируйте этот запрос.
-repo.collaborator.added.subject=%s добавил(а) вас в %s
+repo.collaborator.added.subject=%s вы добавлены как соучастник в %s
repo.collaborator.added.text=Вы были добавлены в качестве соучастника репозитория:
team_invite.subject=%[1]s приглашает вас присоединиться к организации %[2]s
@@ -605,7 +619,7 @@ username_error_no_dots = ` может состоять только из лат
[user]
change_avatar=Изменить свой аватар…
-joined_on=Зарегистрирован(а) с %s
+joined_on=Регистрация %s
repositories=Репозитории
activity=Публичная активность
followers=Подписчики
@@ -835,7 +849,7 @@ manage_access_token=Управление токенами
generate_new_token=Создать новый токен
tokens_desc=Эти токены предоставляют доступ к вашей учётной записи с помощью Forgejo API.
token_name=Имя токена
-generate_token=Генерировать токен
+generate_token=Создать токен
generate_token_success=Новый токен создан. Скопируйте и сохраните его сейчас, так как он не будет показан снова.
generate_token_name_duplicate=%s уже использовалось в качестве имени приложения. Пожалуйста, используйте другое имя.
delete_token=Удалить
@@ -968,14 +982,14 @@ visibility=Видимость
visibility_description=Это увидят только владелец организации или участники при наличии прав.
visibility_helper=Сделать репозиторий приватным
visibility_helper_forced=Администратор сайта настроил параметр видимости новых репозиториев. Репозиторий приватный по умолчанию.
-visibility_fork_helper=(Изменение этого повлияет на все форки.)
+visibility_fork_helper=(Это изменит все ответвления.)
clone_helper=Нужна помощь в клонировании? Посетите страницу помощи .
-fork_repo=Форкнуть репозиторий
-fork_from=Форк от
-already_forked=Вы уже форкнули %s
+fork_repo=Создать ответвление
+fork_from=Ответвить от
+already_forked=У вас уже есть ответвление %s
fork_to_different_account=Ответвление для другой учётной записи
-fork_visibility_helper=Видимость форкнутого репозитория изменить нельзя.
-fork_branch=Ветка для клонирования в форк
+fork_visibility_helper=Нельзя изменить видимость ответвлённого репозитория.
+fork_branch=Ветка, клонируемая в ответвление
all_branches=Все ветки
use_template=Использовать этот шаблон
clone_in_vsc=Клонировать в VS Code
@@ -1009,7 +1023,7 @@ default_branch_label=по умолчанию
default_branch_helper=Ветка по умолчанию является базовой веткой для запросов на слияние и коммитов кода.
mirror_prune=Очистить
mirror_prune_desc=Удаление устаревших отслеживаемых ссылок
-mirror_interval=Интервал зеркалирования (допустимы единицы времени 'h', 'm', 's'). Значение 0 отключает периодическую синхронизацию. (Минимальный интервал: %s)
+mirror_interval=Интервал зеркалирования (единицы времени: «h», «m», «s»). Значение 0 отключит периодическую синхронизацию. (Мин. интервал: %s)
mirror_interval_invalid=Недопустимый интервал зеркалирования.
mirror_sync_on_commit=Синхронизировать при отправке коммитов
mirror_address=Клонировать по URL
@@ -1025,7 +1039,7 @@ mirror_password_help=Смените имя пользователя для уд
watchers=Наблюдатели
stargazers=Звездочеты
stars_remove_warning=Данное действие удалит все звёзды из этого репозитория.
-forks=Форки
+forks=Ответвления
reactions_more=и ещё %d
unit_disabled=Администратор сайта отключил этот раздел репозитория.
language_other=Разное
@@ -1129,15 +1143,15 @@ migrate.cancel_migrating_confirm=Вы хотите отменить эту ми
mirror_from=зеркало из
forked_from=ответвлено от
generated_from=создано из
-fork_from_self=Вы не можете форкнуть ваш собственный репозиторий.
-fork_guest_user=Войдите, чтобы форкнуть репозиторий.
+fork_from_self=Вы не можете создать ответвление собственного репозитория.
+fork_guest_user=Войдите, чтобы создать ответвление репозитория.
watch_guest_user=Войдите, чтобы отслеживать этот репозиторий.
star_guest_user=Войдите, чтобы добавить в избранное этот репозиторий.
-unwatch=Прекратить отслеживание
+unwatch=Не отслеживать
watch=Отслеживать
-unstar=Убрать из избранного
+unstar=Убр. из избранного
star=В избранное
-fork=Форкнуть
+fork=Ответвление
download_archive=Скачать репозиторий
more_operations=Ещё действия
@@ -1169,7 +1183,7 @@ org_labels_desc=Метки уровня организации, которые
org_labels_desc_manage=управлять
milestones=Этапы
-commits=коммитов
+commits=коммиты
commit=коммит
release=Выпуск
releases=Выпуски
@@ -1224,7 +1238,7 @@ editor.cannot_edit_non_text_files=Двоичные файлы нельзя ре
editor.edit_this_file=Редактировать файл
editor.this_file_locked=Файл заблокирован
editor.must_be_on_a_branch=Чтобы внести или предложить изменения этого файла, необходимо выбрать ветку.
-editor.fork_before_edit=Необходимо сделать форк этого репозитория, чтобы внести или предложить изменения этого файла.
+editor.fork_before_edit=Необходимо сделать ответвление этого репозитория, чтобы внести или предложить изменения этого файла.
editor.delete_this_file=Удалить файл
editor.must_have_write_access=Вам необходимо иметь права на запись, чтобы вносить или предлагать изменения этого файла.
editor.file_delete_success=Файл «%s» удалён.
@@ -1282,7 +1296,7 @@ editor.cherry_pick=Перенести изменения %s в:
editor.revert=Откатить %s к:
commits.desc=Просмотр истории изменений исходного кода.
-commits.commits=Коммитов
+commits.commits=коммиты
commits.no_commits=Нет общих коммитов. «%s» и «%s» имеют совершенно разные истории.
commits.nothing_to_compare=Эти ветки одинаковы.
commits.search=Поиск коммитов…
@@ -1401,13 +1415,13 @@ issues.label_templates.info=Меток пока нет. Создайте нов
issues.label_templates.helper=Выберите метку
issues.label_templates.use=Использовать набор меток
issues.label_templates.fail_to_load_file=Не удалось загрузить файл шаблона меток «%s»: %v
-issues.add_label=добавил(а) метку %s %s
-issues.add_labels=добавил(а) метки %s %s
+issues.add_label=добавлена метка %s %s
+issues.add_labels=добавлены метки %s %s
issues.remove_label=удалил(а) метку %s %s
issues.remove_labels=удалил(а) метки %s %s
-issues.add_remove_labels=добавил(а) метки %s и удалил(а) %s %s
-issues.add_milestone_at=`добавил(а) к этапу %s %s`
-issues.add_project_at=`добавил(а) в %s проект %s`
+issues.add_remove_labels=добавлены метки %s и убраны метки %s %s
+issues.add_milestone_at=`добавлено в этап %s %s`
+issues.add_project_at=`добавлено в проект %s %s`
issues.change_milestone_at=`изменил(а) целевой этап с %s на %s %s`
issues.change_project_at=`изменил(а) проект с %s на %s %s`
issues.remove_milestone_at=`удалил(а) это из этапа %s %s`
@@ -1421,7 +1435,7 @@ issues.remove_self_assignment=`убрал(а) их назначение %s`
issues.change_title_at=`изменил(а) заголовок с %s на %s %s`
issues.change_ref_at=`изменил(а) ссылку с %s на %s %s`
issues.remove_ref_at=`убрал(а) ссылку %s %s`
-issues.add_ref_at=`добавил(а) ссылку %s %s`
+issues.add_ref_at=`добавлена ссылка %s %s`
issues.delete_branch_at=`удалил(а) ветку %s %s`
issues.filter_label=Метка
issues.filter_label_exclude=`Используйте alt
+ click/enter
, чтобы исключить метки`
@@ -1447,19 +1461,19 @@ issues.filter_type.created_by_you=Созданные вами
issues.filter_type.mentioning_you=Вы упомянуты
issues.filter_type.review_requested=Проверка запрошена
issues.filter_type.reviewed_by_you=Проверенные вами
-issues.filter_sort=Сортировать
+issues.filter_sort=Сортировка
issues.filter_sort.latest=Новейшие
issues.filter_sort.oldest=Старейшие
issues.filter_sort.recentupdate=Недавно обновленные
issues.filter_sort.leastupdate=Давно обновленные
issues.filter_sort.mostcomment=Больше комментариев
issues.filter_sort.leastcomment=Меньше комментариев
-issues.filter_sort.nearduedate=Ближайшее по дате завершения
-issues.filter_sort.farduedate=Удалённое по дате завершения
+issues.filter_sort.nearduedate=Ближайший срок выполнения
+issues.filter_sort.farduedate=Поздний срок выполнения
issues.filter_sort.moststars=Больше звезд
issues.filter_sort.feweststars=Меньше звезд
-issues.filter_sort.mostforks=Больше форков
-issues.filter_sort.fewestforks=Меньше форков
+issues.filter_sort.mostforks=Больше ответвлений
+issues.filter_sort.fewestforks=Меньше ответвлений
issues.keyword_search_unavailable=В настоящее время поиск по ключевым словам недоступен. Обратитесь к администратору сайта.
issues.action_open=Открыть
issues.action_close=Закрыть
@@ -1483,7 +1497,7 @@ issues.closed_title=Закрыто
issues.draft_title=Черновик
issues.num_comments_1=%d комментарий
issues.num_comments=комментариев: %d
-issues.commented_at=`прокомментировал(а) %s `
+issues.commented_at=`оставлен комментарий в %s `
issues.delete_comment_confirm=Вы уверены, что хотите удалить этот комментарий?
issues.context.copy_link=Копировать ссылку
issues.context.quote_reply=Цитировать ответ
@@ -1492,21 +1506,21 @@ issues.context.edit=Редактировать
issues.context.delete=Удалить
issues.no_content=Описание отсутствует.
issues.close=Закрыть задачу
-issues.comment_pull_merged_at=слил(а) коммит %[1]s в %[2]s %[3]s
-issues.comment_manually_pull_merged_at=вручную слил(а) коммит %[1]s в %[2]s %[3]s
+issues.comment_pull_merged_at=коммит %[1]s был добавлен в %[2]s %[3]s
+issues.comment_manually_pull_merged_at=коммит %[1]s был вручную добавлен в %[2]s %[3]s
issues.close_comment_issue=Прокомментировать и закрыть
issues.reopen_issue=Открыть снова
issues.reopen_comment_issue=Прокомментировать и открыть снова
issues.create_comment=Комментировать
-issues.closed_at=`закрыл(а) эту задачу %[2]s `
-issues.reopened_at=`переоткрыл(а) эту проблему %[2]s `
-issues.commit_ref_at=`сослался на эту задачу в коммите %[2]s `
-issues.ref_issue_from=`сослался на эту задачу %[4]s %[2]s `
-issues.ref_pull_from=`сослался(ась) на этот запрос слияния %[4]s %[2]s `
-issues.ref_closing_from=`сослался(ась) на запрос слияния %[4]s, который закроет эту задачу %[2]s `
-issues.ref_reopening_from=`сослался(ась) на запрос слияния %[4]s, который повторно откроет эту задачу %[2]s `
+issues.closed_at=`задача была закрыта %[2]s `
+issues.reopened_at=`задача была открыта снова %[2]s `
+issues.commit_ref_at=`упоминание этой задачи в коммите %[2]s `
+issues.ref_issue_from=`упоминание этой задачи %[4]s %[2]s `
+issues.ref_pull_from=`упоминание этого запроса слияния %[4]s %[2]s `
+issues.ref_closing_from=`упоминание запроса слияния %[4]s, закрывающего эту задачу %[2]s `
+issues.ref_reopening_from=`упоминание запроса слияния %[4]s, повторно открывающего эту задачу %[2]s `
issues.ref_closed_from=`закрыл этот запрос %[4]s %[2]s `
-issues.ref_reopened_from=`переоткрыл эту задачу %[4]s %[2]s `
+issues.ref_reopened_from=`задача была открыта снова %[4]s %[2]s `
issues.ref_from=`из %[1]s`
issues.author=Автор
issues.author_helper=Этот пользователь является автором.
@@ -1548,8 +1562,8 @@ issues.label_deletion_desc=Удаление метки удаляет ее из
issues.label_deletion_success=Метка удалена.
issues.label.filter_sort.alphabetically=По алфавиту
issues.label.filter_sort.reverse_alphabetically=С конца алфавита
-issues.label.filter_sort.by_size=Минимальный размер
-issues.label.filter_sort.reverse_by_size=Максимальный размер
+issues.label.filter_sort.by_size=Меньший размер
+issues.label.filter_sort.reverse_by_size=Больший размер
issues.num_participants=%d участвующих
issues.attachment.open_tab=`Нажмите, чтобы увидеть «%s» в новой вкладке`
issues.attachment.download=`Нажмите, чтобы скачать «%s»`
@@ -1595,7 +1609,7 @@ issues.add_time=Вручную добавить время
issues.del_time=Удалить этот журнал времени
issues.add_time_short=Добавить время
issues.add_time_cancel=Отмена
-issues.add_time_history=`добавил(а) к затраченному времени %s`
+issues.add_time_history=`добавлено затраченное время %s`
issues.del_time_history=`удалил(а) потраченное время %s`
issues.add_time_hours=Часы
issues.add_time_minutes=Минуты
@@ -1603,21 +1617,21 @@ issues.add_time_sum_to_small=Время не было введено.
issues.time_spent_total=Общее затраченное время
issues.time_spent_from_all_authors=`Общее затраченное время: %s`
issues.due_date=Срок выполнения
-issues.invalid_due_date_format=Дата окончания должна быть в формате «гггг-мм-дд».
+issues.invalid_due_date_format=Срок выполнения должен быть в формате «гггг-мм-дд».
issues.error_modifying_due_date=Не удалось изменить срок выполнения.
issues.error_removing_due_date=Не удалось убрать срок выполнения.
-issues.push_commit_1=добавил(а) %d коммит %s
-issues.push_commits_n=добавил(а) %d коммитов %s
+issues.push_commit_1=добавлен %d коммит %s
+issues.push_commits_n=добавлены %d коммита(ов) %s
issues.force_push_codes=`форсировал(а) отправку изменений %[1]s %[4]s
вместо %[2]s
%[6]s`
issues.force_push_compare=Сравнить
issues.due_date_form=гггг-мм-дд
issues.due_date_form_add=Добавить срок выполнения
-issues.due_date_form_edit=Редактировать
+issues.due_date_form_edit=Изменить
issues.due_date_form_remove=Удалить
issues.due_date_not_set=Срок выполнения не установлен.
-issues.due_date_added=добавил(а) срок выполнения %s %s
-issues.due_date_modified=изменил(а) срок выполнения с %[2]s на %[1]s %[3]s
-issues.due_date_remove=убрал(а) срок выполнения %s %s
+issues.due_date_added=добавлен срок выполнения %s %s
+issues.due_date_modified=срок выполнения передвинут с %[2]s на %[1]s %[3]s
+issues.due_date_remove=убран срок выполнения %s %s
issues.due_date_overdue=Просроченные
issues.due_date_invalid=Срок выполнения недействителен или находится за пределами допустимого диапазона. Пожалуйста, используйте формат «гггг-мм-дд».
issues.dependency.title=Зависимости
@@ -1630,7 +1644,7 @@ issues.dependency.add=Добавить зависимость…
issues.dependency.cancel=Отменить
issues.dependency.remove=Удалить
issues.dependency.remove_info=Удалить эту зависимость
-issues.dependency.added_dependency=`добавил(а) новую зависимость %s`
+issues.dependency.added_dependency=`добавлена новая зависимость %s`
issues.dependency.removed_dependency=`убрал(а) зависимость %s`
issues.dependency.pr_closing_blockedby=Закрытие этого запроса на слияние блокируется следующими задачами
issues.dependency.issue_closing_blockedby=Закрытие этой задачи блокируется следующими задачами
@@ -1738,7 +1752,7 @@ pulls.cannot_merge_work_in_progress=Этот запрос на слияние п
pulls.still_in_progress=Всё ещё в процессе?
pulls.add_prefix=Добавить %s префикс
pulls.remove_prefix=Удалить %s префикс
-pulls.data_broken=Содержимое этого запроса было нарушено вследствие удаления информации форка.
+pulls.data_broken=Содержимое этого слияния нарушено из-за удаления информации об ответвлении.
pulls.files_conflicted=Этот запрос на слияние имеет изменения конфликтующие с целевой веткой.
pulls.is_checking=Продолжается проверка конфликтов. Повторите попытку позже.
pulls.is_ancestor=Эта ветка уже включена в целевую ветку. Объединять нечего.
@@ -1764,7 +1778,7 @@ pulls.wrong_commit_id=id коммита должен быть ид коммит
pulls.no_merge_desc=Запрос на слияние не может быть принят, так как отключены все настройки слияния.
pulls.no_merge_helper=Включите опции слияния в настройках репозитория или совершите слияние этого запроса вручную.
pulls.no_merge_wip=Данный запрос на слияние не может быть принят, поскольку он помечен как находящийся в разработке.
-pulls.no_merge_not_ready=Этот запрос не готов к слиянию, обратите внимания на ревью и проверки.
+pulls.no_merge_not_ready=Этот запрос не готов к слиянию, обратите внимания на отзывы и проверки.
pulls.no_merge_access=У вас нет права для слияния данного запроса.
pulls.merge_pull_request=Создать коммит слияния
pulls.rebase_merge_pull_request=Выполнить rebase и fast-forward
@@ -1782,9 +1796,9 @@ pulls.rebase_conflict_summary=Сообщение об ошибке
pulls.unrelated_histories=Слияние не удалось: у источника и цели слияния нет общей истории. Совет: попробуйте другую стратегию
pulls.merge_out_of_date=Слияние не удалось: при создании слияния база данных была обновлена. Подсказка: попробуйте ещё раз.
pulls.head_out_of_date=Слияние не удалось: во время слияния головной коммит был обновлён. Попробуйте ещё раз.
-pulls.push_rejected=Слияние не удалось: отправка была отклонена. Проверьте Git-хуки для этого репозитория.
+pulls.push_rejected=Отправка была отклонена. Проверьте Git-хуки этого репозитория.
pulls.push_rejected_summary=Полная ошибка отклонения
-pulls.push_rejected_no_message=Слияние не удалось: отправка была отклонена, но сервер не указал причину. Проверьте Git-хуки для этого репозитория
+pulls.push_rejected_no_message=Отправка была отклонена и удалённый сервер не указал причину. Проверьте Git-хуки этого репозитория
pulls.open_unmerged_pull_exists=`Вы не можете снова открыть, поскольку уже существует запрос на слияние (#%d) из того же репозитория с той же информацией о слиянии и ожидающий слияния.`
pulls.status_checking=Выполняются некоторые проверки
pulls.status_checks_success=Все проверки выполнены успешно
@@ -1837,9 +1851,9 @@ milestones.completeness=%d%% выполнено
milestones.create=Создать этап
milestones.title=Заголовок
milestones.desc=Описание
-milestones.due_date=Дата окончания (опционально)
+milestones.due_date=Срок выполнения (опционально)
milestones.clear=Очистить
-milestones.invalid_due_date_format=Дата выполнения должна быть в формате «гггг-мм-дд».
+milestones.invalid_due_date_format=Срок выполнения должен быть в формате «гггг-мм-дд».
milestones.create_success=Этап «%s» создан.
milestones.edit=Редактировать этап
milestones.edit_subheader=Используйте лучшее описание контрольной точки, во избежание непонимания со стороны других людей.
@@ -1849,12 +1863,12 @@ milestones.edit_success=Этап «%s» обновлён.
milestones.deletion=Удалить этап
milestones.deletion_desc=Удаление этапа приведет к его удалению из всех связанных задач. Продолжить?
milestones.deletion_success=Этап успешно удалён.
-milestones.filter_sort.earliest_due_data=По возрастанию даты завершения
-milestones.filter_sort.latest_due_date=По убыванию даты завершения
+milestones.filter_sort.earliest_due_data=Ближайший срок выполнения
+milestones.filter_sort.latest_due_date=Поздний срок выполнения
milestones.filter_sort.least_complete=Менее полное
milestones.filter_sort.most_complete=Более полное
-milestones.filter_sort.most_issues=Большинство задач
-milestones.filter_sort.least_issues=Меньшинство задач
+milestones.filter_sort.most_issues=Больше задач
+milestones.filter_sort.least_issues=Меньше задач
signing.will_sign=Этот коммит будет подписан ключом «%s».
signing.wont_sign.never=Коммиты никогда не подписываются.
@@ -1915,8 +1929,8 @@ activity.opened_prs_count_1=Новый запрос на слияние
activity.opened_prs_count_n=Новых запросов на слияние
activity.title.user_1=%d пользователем
activity.title.user_n=%d пользователями
-activity.title.prs_1=%d запрос на слияние
-activity.title.prs_n=%d запросов на слияние
+activity.title.prs_1=%d запрос слияния
+activity.title.prs_n=%d запросы слияний
activity.title.prs_merged_by=%s приняты %s
activity.title.prs_opened_by=%s предложены %s
activity.merged_prs_label=Принято
@@ -2069,10 +2083,10 @@ settings.convert_notices_1=Эта операция преобразует это
settings.convert_confirm=Подтвердите преобразование
settings.convert_succeed=Репозиторий успешно преобразован в обычный.
settings.convert_fork=Преобразовать в обычный репозиторий
-settings.convert_fork_desc=Вы можете преобразовать этот форк в обычный репозиторий. Это не может быть отменено.
-settings.convert_fork_notices_1=Эта операция преобразует этот форк в обычный репозиторий, и не может быть отменена.
+settings.convert_fork_desc=Вы можете преобразовать это ответвление в обычный репозиторий. Это не может быть отменено.
+settings.convert_fork_notices_1=Эта операция преобразует этот ответвление в обычный репозиторий, и не может быть отменена.
settings.convert_fork_confirm=Преобразовать Репозиторий
-settings.convert_fork_succeed=Форк преобразован в обычный репозиторий.
+settings.convert_fork_succeed=Ответвление преобразовано в обычный репозиторий.
settings.transfer=Передать права собственности
settings.transfer.rejected=Перенос репозитория отменён.
settings.transfer.success=Перенос репозитория выполнен успешно.
@@ -2111,7 +2125,7 @@ settings.delete=Удалить этот репозиторий
settings.delete_desc=Будьте внимательны! Как только вы удалите репозиторий — пути назад не будет.
settings.delete_notices_1=- Эта операция НЕ МОЖЕТ быть отменена.
settings.delete_notices_2=- Эта операция навсегда удалит всё из репозитория %s , включая данные Git, связанные с ним задачи, комментарии и права доступа для сотрудников.
-settings.delete_notices_fork_1=- Все форки станут независимыми репозиториями после удаления.
+settings.delete_notices_fork_1=- После удаления все ответвления станут независимыми репозиториями.
settings.deletion_success=Репозиторий удалён.
settings.update_settings_success=Настройки репозитория обновлены.
settings.update_settings_no_unit=Должно быть разрешено хоть какое-то взаимодействие с репозиторием.
@@ -2150,7 +2164,7 @@ settings.webhook.response=Ответ
settings.webhook.headers=Заголовки
settings.webhook.payload=Содержимое
settings.webhook.body=Тело ответа
-settings.webhook.replay.description=Повторить этот веб-хук.
+settings.webhook.replay.description=Повторить отправку.
settings.webhook.delivery.success=Событие было добавлено в очередь доставки. Может пройти несколько секунд, прежде чем оно отобразится в истории.
settings.githooks_desc=Git-хуки предоставляются самим Git. Вы можете изменять файлы хуков из списка ниже, чтобы настроить собственные операции.
settings.githook_edit_desc=Если хук не активен, будет подставлен пример содержимого. Пустое значение в этом поле приведёт к отключению хука.
@@ -2176,8 +2190,8 @@ settings.event_create=Создать
settings.event_create_desc=Ветка или тэг созданы.
settings.event_delete=Удалить
settings.event_delete_desc=Ветка или тег удалены.
-settings.event_fork=Форкнуть
-settings.event_fork_desc=Репозиторий форкнут.
+settings.event_fork=Ответвление
+settings.event_fork_desc=Ответвление создано.
settings.event_wiki=Вики
settings.event_wiki_desc=Страница вики создана, переименована, изменена или удалена.
settings.event_release=Выпуск
@@ -2233,7 +2247,7 @@ settings.hook_type=Тип хука
settings.slack_token=Slack токен
settings.slack_domain=Домен
settings.slack_channel=Канал
-settings.add_web_hook_desc=Интегрировать %s в ваш репозиторий.
+settings.add_web_hook_desc=Интегрируйте %s с этим репозиторием .
settings.web_hook_name_gitea=Gitea
settings.web_hook_name_forgejo = Forgejo
settings.web_hook_name_gogs=Gogs
@@ -2314,9 +2328,9 @@ settings.protect_branch_name_pattern=Шаблон названий защищё
settings.protect_branch_name_pattern_desc=Шаблоны названий защищённых веток. О синтаксисе шаблонов читайте в документации . Примеры: main, release/**
settings.protect_patterns=Шаблоны
settings.protect_protected_file_patterns=Шаблоны защищённых файлов, разделённые точкой с запятой «;»:
-settings.protect_protected_file_patterns_desc=Защищенные файлы нельзя изменить напрямую, даже если пользователь имеет право добавлять, редактировать или удалять файлы в этой ветке. Можно указать несколько шаблонов, разделяя их точкой с запятой («;»). О синтаксисе шаблонов читайте в документации github.com/gobwas/glob . Примеры: .drone.yml
, /docs/**/*.txt
.
+settings.protect_protected_file_patterns_desc=Защищенные файлы нельзя изменить напрямую, даже если пользователь имеет право добавлять, редактировать или удалять файлы в этой ветке. Можно указать несколько шаблонов, разделяя их точкой с запятой («;»). О синтаксисе шаблонов читайте в документации github.com/gobwas/glob . Примеры: .drone.yml
, /docs/**/*.txt
.
settings.protect_unprotected_file_patterns=Шаблоны незащищённых файлов, разделённые точкой с запятой «;»:
-settings.protect_unprotected_file_patterns_desc=Незащищенные файлы, которые допускается изменять напрямую, если пользователь имеет право на запись, несмотря на ограничение отправки изменений. Можно указать несколько шаблонов, разделяя их точкой с запятой («;»). О синтаксисе шаблонов читайте в документации github.com/gobwas/glob . Примеры: .drone.yml
, /docs/**/*.txt
.
+settings.protect_unprotected_file_patterns_desc=Незащищенные файлы, которые допускается изменять напрямую, если пользователь имеет право на запись, несмотря на ограничение отправки изменений. Можно указать несколько шаблонов, разделяя их точкой с запятой («;»). О синтаксисе шаблонов читайте в документации github.com/gobwas/glob . Примеры: .drone.yml
, /docs/**/*.txt
.
settings.add_protected_branch=Включить защиту
settings.delete_protected_branch=Отключить защиту
settings.update_protect_branch_success=Защита веток по правилу «%s» изменена.
@@ -2372,7 +2386,7 @@ settings.lfs_findcommits=Найти коммиты
settings.lfs_lfs_file_no_commits=Для этого LFS файла не найдено коммитов
settings.lfs_noattribute=Этот путь не имеет блокируемого атрибута в ветке по умолчанию
settings.lfs_delete=Удалить файл LFS с OID %s
-settings.lfs_delete_warning=Удаление файла LFS может привести к ошибкам 'объект не существует' при проверке. Вы уверены?
+settings.lfs_delete_warning=Удаление файла LFS может привести к ошибкам «объект не существует» при проверке. Вы уверены?
settings.lfs_findpointerfiles=Найти файлы указателя
settings.lfs_locks=Заблокировать
settings.lfs_invalid_locking_path=Недопустимый путь: %s
@@ -2429,7 +2443,7 @@ diff.too_many_files=Показаны не все изменённые файлы
diff.show_more=Показать больше
diff.load=Загрузить различия
diff.generated=сгенерированный
-diff.vendored=поставляемый
+diff.vendored=предоставленный
diff.comment.placeholder=Оставить комментарий
diff.comment.markdown_info=Поддерживается синтаксис Markdown.
diff.comment.add_single_comment=Добавить простой комментарий
@@ -2555,7 +2569,7 @@ error.csv.too_large=Не удается отобразить этот файл,
error.csv.unexpected=Не удается отобразить этот файл, потому что он содержит неожиданный символ в строке %d и столбце %d.
error.csv.invalid_field_count=Не удается отобразить этот файл, потому что он имеет неправильное количество полей в строке %d.
mirror_address_protocol_invalid = Эта ссылка недействительна. Для зеркалирования можно использовать только расположения http(s):// и git:// .
-fork_no_valid_owners = Этот репозиторий не может быть форкнут, т.к. здесь нет действующих владельцев.
+fork_no_valid_owners = Невозможно создать ответвление этого репозитория, т.к. здесь нет действующих владельцев.
new_repo_helper = Репозиторий содержит все файлы проекта и историю изменений. Уже где-то есть репозиторий? Выполните миграцию.
mirror_address_url_invalid = Эта ссылка недействительна. Необходимо правильно указать все части адреса.
issues.comment.blocked_by_user = Вы не можете комментировать под этой задачей, т.к. вы заблокированы владельцем репозитория или автором задачи.
@@ -2593,7 +2607,7 @@ transfer.no_permission_to_accept = У вас недостаточно прав
transfer.no_permission_to_reject = У вас недостаточно прав для отклонения этого переноса.
commits.view_path = Просмотреть в этом моменте истории
commits.renamed_from = Переименован с %s
-issues.due_date_not_writer = Для обновления даты выполнения задачи требуются на запись в этом репозитории.
+issues.due_date_not_writer = Для обновления срока выполнения задачи требуется право на запись в этом репозитории.
issues.review.outdated_description = С момента добавления этого комментария содержимое изменилось
pulls.nothing_to_compare_have_tag = Выбранные ветки/теги идентичны.
pulls.select_commit_hold_shift_for_range = Выберите коммит. Зажмите Shift, чтобы выбрать диапазон
@@ -2620,8 +2634,8 @@ signing.wont_sign.nokey = Нет ключей для подписи этого
settings.wiki_globally_editable = Разрешить редактирование Вики всем пользователям
settings.webhook.test_delivery_desc_disabled = Активируйте этот веб-хук для проверки тестовым событием.
commits.browse_further = Смотреть далее
-vendored = Vendored
-settings.units.add_more = Добавить больше...
+vendored = Предоставленный
+settings.units.add_more = Доб. больше...
pulls.fast_forward_only_merge_pull_request = Только fast-forward
settings.units.overview = Обзор
settings.units.units = Разделы репозитория
@@ -2631,14 +2645,25 @@ settings.ignore_stale_approvals = Игнорировать устаревшие
contributors.contribution_type.commits = Коммиты
contributors.contribution_type.additions = Добавления
contributors.contribution_type.deletions = Удаления
-contributors.contribution_type.filter_label = Тип участия:
-pulls.commit_ref_at = `сослался(ась) на этот запрос слияния из комммита %[2]s `
+contributors.contribution_type.filter_label = Вид деятельности:
+pulls.commit_ref_at = `упоминание этого запроса слияния в коммите %[2]s `
settings.thread_id = ИД обсуждения
pulls.made_using_agit = AGit
activity.navbar.contributors = Соавторы
-activity.navbar.code_frequency = Частота кода
+activity.navbar.code_frequency = Частота изменений
activity.navbar.recent_commits = Недавние коммиты
settings.confirmation_string = Подтверждение
+settings.archive.text = Архивация репозитория сделает всё его содержимое доступным только для чтения. Он будет скрыт с домашнего экрана. Никто (включая вас!) не сможет добавлять коммиты, открывать задачи и запросы слияний.
+release.deletion_desc = Удаление выпуска удаляет его только в Forgejo. Это действие не затронет тег в git, содержимое репозитория и его историю. Продолжить?
+pulls.agit_explanation = Создано через рабочий поток AGit. С ним можно предлагать изменения, используя команду «git push», без необходимости в создании ответвления или новой ветки.
+settings.webhook.replay.description_disabled = Активируйте веб-хук для повторения отправки.
+activity.navbar.pulse = Недавняя активность
+settings.tags.protection.pattern.description = Можно указать название тега. Для выбора нескольких тегов можно указать поисковый шаблон или регулярное выражение. Подробнее о защищённых тегах .
+file_follow = Пройти по мягкой ссылке
+settings.pull_mirror_sync_in_progress = Идёт получение изменений из удалённого репозитория %s.
+settings.ignore_stale_approvals_desc = Не учитывать одобрения, оставленные для старых коммитов (устаревшие отзывы), при подсчёте общего числа одобрений у запроса на слияние. Не относится к отклонённым отзывам.
+settings.mirror_settings.docs.doc_link_pull_section = раздел документации «Pulling from a remote repository».
+wiki.original_git_entry_tooltip = Перейти по настоящему пути вместо читабельной ссылки.
[graphs]
@@ -2653,7 +2678,7 @@ teams=Команды
code=Код
lower_members=участники
lower_repositories=репозитории
-create_new_team=Создание команды
+create_new_team=Новая команда
create_team=Создать команду
org_desc=Описание
team_name=Название команды
@@ -2765,6 +2790,7 @@ teams.invite.title=Вас пригласили присоединиться к
teams.invite.by=Приглашен(а) %s
teams.invite.description=Нажмите на кнопку ниже, чтобы присоединиться к команде.
follow_blocked_user = Вы не можете подписаться на эту организацию, т.к. вы в ней заблокированы.
+teams.general_access = Настраиваемый доступ
[admin]
dashboard=Панель
@@ -2785,7 +2811,7 @@ total=Всего: %d
dashboard.new_version_hint=Доступна новая версия Forgejo %s, вы используете %s. Более подробную информацию читайте в блоге .
dashboard.statistic=Статистика
-dashboard.operations=Операции
+dashboard.operations=Обслуживание
dashboard.system_status=Состояние системы
dashboard.operation_name=Имя операции
dashboard.operation_switch=Переключить
@@ -2819,58 +2845,58 @@ dashboard.update_migration_poster_id=Обновить ИД плакатов ми
dashboard.git_gc_repos=Выполнить сборку мусора для всех репозиториев
dashboard.resync_all_sshkeys=Обновить файл '.ssh/authorized_keys' с ключами SSH Forgejo.
dashboard.resync_all_sshprincipals=Обновите файл '.ssh/authorized_principals' SSH данными участника Forgejo.
-dashboard.resync_all_hooks=Пересинхронизировать хуки pre-receive, update и post-receive всех репозиториев.
+dashboard.resync_all_hooks=Пересинхронизировать хуки pre-receive, update и post-receive всех репозиториев
dashboard.reinit_missing_repos=Переинициализировать все отсутствующие Git репозитории, для которых существуют записи
dashboard.sync_external_users=Синхронизировать данные сторонних пользователей
dashboard.cleanup_hook_task_table=Очистить таблицу hook_task
dashboard.cleanup_packages=Очистка устаревших пакетов
-dashboard.server_uptime=Время непрерывной работы сервера
-dashboard.current_goroutine=Текущее количество Goroutines
+dashboard.server_uptime=Время работы
+dashboard.current_goroutine=Количество goroutines
dashboard.current_memory_usage=Текущее использование памяти
-dashboard.total_memory_allocated=Всего памяти выделено
-dashboard.memory_obtained=Памяти использовано
-dashboard.pointer_lookup_times=Запросов указателя
+dashboard.total_memory_allocated=Всего памяти выделялось
+dashboard.memory_obtained=Получено памяти
+dashboard.pointer_lookup_times=Поисков указателя
dashboard.memory_allocate_times=Выделений памяти
-dashboard.memory_free_times=Освобождений памяти
+dashboard.memory_free_times=Высвобождений памяти
dashboard.current_heap_usage=Текущее использование кучи
dashboard.heap_memory_obtained=Получено динамической памяти
-dashboard.heap_memory_idle=Не используется динамической памяти
-dashboard.heap_memory_in_use=Кучи памяти в работе
+dashboard.heap_memory_idle=Простаивающая динамическая память
+dashboard.heap_memory_in_use=Используемая динамическая память
dashboard.heap_memory_released=Освобождено динамической памяти
dashboard.heap_objects=Объектов динамической памяти
dashboard.bootstrap_stack_usage=Использование стека загрузчика
-dashboard.stack_memory_obtained=Память, занятая под стек
+dashboard.stack_memory_obtained=Стековой памяти выделялось
dashboard.mspan_structures_usage=Использование структур MSpan
-dashboard.mspan_structures_obtained=Получено структур MSpan
+dashboard.mspan_structures_obtained=Структур MSpan выделялось
dashboard.mcache_structures_usage=Использование структур MCache
dashboard.mcache_structures_obtained=Получено структур MCache
-dashboard.profiling_bucket_hash_table_obtained=Хеш-таблиц получено при профилировании
-dashboard.gc_metadata_obtained=Полученных метаданных сборщика мусора
-dashboard.other_system_allocation_obtained=Получено прочих выделений системной памяти
-dashboard.next_gc_recycle=Следующая очистка сборщика мусора
+dashboard.profiling_bucket_hash_table_obtained=Хеш-таблиц получено при профайлинге
+dashboard.gc_metadata_obtained=Метаданных сборщика мусора создавалось
+dashboard.other_system_allocation_obtained=Прочие системные выделения памяти
+dashboard.next_gc_recycle=Следующее высвобождение сборщиком мусора
dashboard.last_gc_time=Прошло с последнего сбора мусора
dashboard.total_gc_time=Итоговая задержка GC
-dashboard.total_gc_pause=Итоговая задержка GC
-dashboard.last_gc_pause=Последняя пауза сборщика мусора
-dashboard.gc_times=Количество сборок мусора
-dashboard.delete_old_actions=Удалите все старые действия из базы данных
-dashboard.delete_old_actions.started=Удалите все старые действия из запущенной базы данных.
+dashboard.total_gc_pause=Итоговая задержка сборщика
+dashboard.last_gc_pause=Последняя пауза сборщика
+dashboard.gc_times=Сборок мусора
+dashboard.delete_old_actions=Удалить все старые действия из базы данных
+dashboard.delete_old_actions.started=Запущено удаление всех старых действий из БД.
dashboard.update_checker=Проверка обновлений
dashboard.delete_old_system_notices=Удалить все старые системные уведомления из базы данных
dashboard.gc_lfs=Выполнить сборку мусора метаобъектов LFS
dashboard.stop_zombie_tasks=Остановить задания-зомби
-dashboard.stop_endless_tasks=Остановить бесконечные задания
+dashboard.stop_endless_tasks=Остановить непрекращающиеся задания
dashboard.cancel_abandoned_jobs=Отменить брошенные задания
dashboard.start_schedule_tasks=Запустить запланированные задания
-users.user_manage_panel=Панель управления пользователями
+users.user_manage_panel=Управление пользователями
users.new_account=Создать новую учётную запись
users.name=Имя пользователя
users.full_name=Полное имя
users.activated=Активирован
users.admin=Администратор
users.restricted=Ограничено
-users.reserved=Зарезервировано
+users.reserved=Резерв
users.bot=Бот
users.2fa=Двухфакторная авторизация
users.repos=Репозитории
@@ -2920,13 +2946,13 @@ users.list_status_filter.is_2fa_enabled=2FA включено
users.list_status_filter.not_2fa_enabled=2FA отключено
users.details=О пользователе
-emails.email_manage_panel=Управление эл. почтой пользователя
+emails.email_manage_panel=Управление адресами эл. почты пользователей
emails.primary=Первичный
emails.activated=Активирован
-emails.filter_sort.email=Эл. почта
-emails.filter_sort.email_reverse=Эл. почта (обратный)
-emails.filter_sort.name=Имя пользователя
-emails.filter_sort.name_reverse=Имя пользователя (обратное)
+emails.filter_sort.email=По эл. почте
+emails.filter_sort.email_reverse=По эл. почте (наоборот)
+emails.filter_sort.name=По имени пользователя
+emails.filter_sort.name_reverse=По имени пользователя (наоборот)
emails.updated=Email обновлён
emails.not_updated=Не удалось обновить запрошенный адрес эл. почты: %v
emails.duplicate_active=Этот адрес эл. почты уже активирован для другого пользователя.
@@ -2947,7 +2973,7 @@ repos.name=Название
repos.private=Личный
repos.watches=Следят
repos.stars=Звезды
-repos.forks=Форки
+repos.forks=Ответвления
repos.issues=Задачи
repos.size=Размер
repos.lfs_size=Размер LFS
@@ -2966,7 +2992,7 @@ packages.repository=Репозиторий
packages.size=Размер
packages.published=Опубликовано
-defaulthooks=Стандартные Веб-хуки
+defaulthooks=Стандартные веб-хуки
defaulthooks.add_webhook=Добавить стандартный Веб-хук
defaulthooks.update_webhook=Обновить стандартный Веб-хук
@@ -2974,7 +3000,7 @@ systemhooks=Системные веб-хуки
systemhooks.add_webhook=Добавить системный веб-хук
systemhooks.update_webhook=Обновить системный веб-хук
-auths.auth_manage_panel=Управление аутентификацией
+auths.auth_manage_panel=Управление источниками аутентификации
auths.new=Добавить новый источник
auths.name=Имя
auths.type=Тип
@@ -3015,11 +3041,11 @@ auths.map_group_to_team_removal=Удалить пользователей из
auths.enable_ldap_groups=Включить группы LDAP
auths.ms_ad_sa=Атрибуты поиска MS AD
auths.smtp_auth=Тип аутентификации SMTP
-auths.smtphost=Узел SMTP
-auths.smtpport=SMTP-порт
+auths.smtphost=Адрес SMTP
+auths.smtpport=Порт SMTP
auths.allowed_domains=Разрешенные домены
-auths.allowed_domains_helper=Оставьте пустым, чтобы разрешить все домены. Разделите несколько доменов запятой (',').
-auths.skip_tls_verify=Пропустить проверку TLS
+auths.allowed_domains_helper=Разделяйте домены запятыми («,»). Оставьте пустым, чтобы разрешить все домены.
+auths.skip_tls_verify=Пропуск проверки TLS
auths.force_smtps=Принудительный SMTPS
auths.force_smtps_helper=SMTPS всегда использует 465 порт. Установите это, что бы принудительно использовать SMTPS на других портах. (Иначе STARTTLS будет использоваться на других портах, если это поддерживается хостом.)
auths.helo_hostname=HELO Hostname
@@ -3092,22 +3118,22 @@ auths.unable_to_initialize_openid=Не удалось инициализиров
auths.invalid_openIdConnectAutoDiscoveryURL=Неверный URL для автоматического обнаружения (это должен быть валидный URL, начинающийся с http:// или https://)
config.server_config=Конфигурация сервера
-config.app_name=Название сайта
+config.app_name=Название сервера
config.app_ver=Версия Forgejo
config.app_url=Базовый URL Forgejo
config.custom_conf=Путь к файлу конфигурации
-config.custom_file_root_path=Пользовательский путь до каталога с файлами
+config.custom_file_root_path=Путь до каталога с файлами для персонализации
config.domain=Домен сервера
config.offline_mode=Локальный режим
config.disable_router_log=Отключение журнала маршрутизатора
config.run_user=Запуск от имени пользователя
-config.run_mode=Режим выполнения
-config.git_version=Версия Git
+config.run_mode=Режим работы
+config.git_version=Версия git
config.app_data_path=Путь к данным приложения
config.repo_root_path=Путь до каталога репозиториев
config.lfs_root_path=Корневой путь LFS
-config.log_file_root_path=Путь к журналу
-config.script_type=Тип скрипта
+config.log_file_root_path=Путь журналов
+config.script_type=Тип сценария
config.reverse_auth_user=Имя пользователя для авторизации на reverse proxy
config.ssh_config=Конфигурация SSH
@@ -3118,14 +3144,14 @@ config.ssh_port=Порт
config.ssh_listen_port=Прослушиваемый порт
config.ssh_root_path=Корневой путь
config.ssh_key_test_path=Путь к тестовому ключу
-config.ssh_keygen_path=Путь к генератору ключей ('ssh-keygen')
+config.ssh_keygen_path=Путь до генератора ключей («ssh-keygen»)
config.ssh_minimum_key_size_check=Минимальный размер ключа проверки
config.ssh_minimum_key_sizes=Минимальные размеры ключа
config.lfs_config=Конфигурация LFS
config.lfs_enabled=Включено
-config.lfs_content_path=Путь к контенту LFS
-config.lfs_http_auth_expiry=Устаревание HTTP-аутентификации LFS
+config.lfs_content_path=Путь к содержимому LFS
+config.lfs_http_auth_expiry=Срок действия HTTP-аутентификации LFS
config.db_config=Конфигурация базы данных
config.db_type=Тип
@@ -3136,34 +3162,34 @@ config.db_schema=Схема
config.db_ssl_mode=SSL
config.db_path=Путь
-config.service_config=Сервисная конфигурация
-config.register_email_confirm=Требуется подтверждение по эл. почте
-config.disable_register=Отключить самостоятельную регистрацию
-config.allow_only_internal_registration=Разрешить регистрацию только через Forgejo
+config.service_config=Конфигурация служб
+config.register_email_confirm=Требовать подтверждение по эл. почте для регистрации
+config.disable_register=Саморегистрация отключена
+config.allow_only_internal_registration=Разрешить регистрацию только напрямую через Forgejo
config.allow_only_external_registration=Разрешить регистрацию только через сторонние сервисы
-config.enable_openid_signup=Включить cамостоятельную регистрацию OpenID
-config.enable_openid_signin=Включение входа через OpenID
-config.show_registration_button=Показать кнопку регистрации
+config.enable_openid_signup=Cаморегистрация через OpenID
+config.enable_openid_signin=Вход через OpenID
+config.show_registration_button=Кнопка регистрации
config.require_sign_in_view=Для просмотра необходима авторизация
-config.mail_notify=Почтовые уведомления
-config.enable_captcha=Включить CAPTCHA
-config.active_code_lives=Время жизни кода для активации
+config.mail_notify=Уведомления по эл. почте
+config.enable_captcha=CAPTCHA
+config.active_code_lives=Срок действия кода активации учётной записи
config.reset_password_code_lives=Срок действия кода восстановления учётной записи
config.default_keep_email_private=Скрывать адреса эл. почты по умолчанию
config.default_allow_create_organization=Разрешить создание организаций по умолчанию
-config.enable_timetracking=Включить отслеживание времени
+config.enable_timetracking=Отслеживание времени
config.default_enable_timetracking=Включить отслеживание времени по умолчанию
config.default_allow_only_contributors_to_track_time=Подсчитывать время могут только соавторы
-config.no_reply_address=No-reply адрес
-config.default_visibility_organization=Видимость по умолчанию для новых организаций
+config.no_reply_address=Домен скрытых адресов почты
+config.default_visibility_organization=Видимость новых организаций по умолчанию
config.default_enable_dependencies=Включение зависимостей для задач по умолчанию
config.webhook_config=Конфигурация веб-хуков
config.queue_length=Длина очереди
config.deliver_timeout=Задержка доставки
-config.skip_tls_verify=Пропустить проверку TLS
+config.skip_tls_verify=Пропуск проверки TLS
-config.mailer_config=Настройки почты
+config.mailer_config=Конфигурация почтового сервера
config.mailer_enabled=Почта включена
config.mailer_enable_helo=Включить HELO
config.mailer_name=Имя
@@ -3185,7 +3211,7 @@ config.test_mail_sent=Тестовое письмо было отправлен
config.oauth_config=Конфигурация OAuth
config.oauth_enabled=OAuth включен
-config.cache_config=Настройки кеша
+config.cache_config=Конфигурация кэша
config.cache_adapter=Адаптер кэша
config.cache_interval=Интервал кэширования
config.cache_conn=Подключение кэша
@@ -3200,22 +3226,22 @@ config.session_life_time=Время жизни сессии
config.https_only=Только HTTPS
config.cookie_life_time=Время жизни файла cookie
-config.picture_config=Настройка изображения
-config.picture_service=Сервис изображений
+config.picture_config=Конфигурация аватаров и изображений
+config.picture_service=Служба изображений
config.disable_gravatar=Отключить Gravatar
config.enable_federated_avatar=Включить федерированные аватары
config.git_config=Конфигурация Git
config.git_disable_diff_highlight=Отключить подсветку синтаксиса при сравнении
-config.git_max_diff_lines=Макс. количество строк (на файл) при сравнении
-config.git_max_diff_line_characters=Макс. количество символов (в строке) при сравнении
+config.git_max_diff_lines=Макс. количество строк в файле при сравнении
+config.git_max_diff_line_characters=Макс. количество символов в строке при сравнении
config.git_max_diff_files=Макс. отображаемое количество файлов при сравнении
-config.git_gc_args=Аргументы GC
-config.git_migrate_timeout=Лимит времени миграций
-config.git_mirror_timeout=Лимит времени обновления зеркал
-config.git_clone_timeout=Лимит времени операции клонирования
-config.git_pull_timeout=Лимит времени получения изменений
-config.git_gc_timeout=Лимит времени сборки мусора
+config.git_gc_args=Аргументы сборщика мусора
+config.git_migrate_timeout=Ограничение времени миграций
+config.git_mirror_timeout=Ограничение времени на синхронизацию зеркала
+config.git_clone_timeout=Ограничение времени операций клонирования
+config.git_pull_timeout=Ограничение времени на получение изменений
+config.git_gc_timeout=Ограничение времени на сборку мусора
config.log_config=Конфигурация журнала
config.logger_name_fmt=Журнал: %s
@@ -3228,7 +3254,7 @@ config.set_setting_failed=Задать параметр %s не удалось
monitor.stats=Статистика
-monitor.cron=Запланированные задания
+monitor.cron=Плановые задания
monitor.name=Название
monitor.schedule=Расписание
monitor.next=Следующий раз
@@ -3296,15 +3322,19 @@ dashboard.sync_branch.started = Начата синхронизация вето
dashboard.sync_repo_tags = Синхронизировать теги из git в базу данных
self_check.database_collation_mismatch = Ожидается, что БД использует сопоставление: %s
self_check = Самопроверка
+dashboard.rebuild_issue_indexer = Пересобрать индексатор задач
+systemhooks.desc = Веб-хуки автоматически совершают POST запросы до указанного HTTP сервера, когда в Forgejo происходят определённые события. Заданные здесь веб-хуки будут срабатывать во всех репозиториях на этом сервере и могут привести к проблемам с производительностью. Подробнее о веб-хуках .
+defaulthooks.desc = Веб-хуки автоматически совершают POST запросы до указанного HTTP сервера, когда в Forgejo происходят определённые события. Заданные здесь веб-хуки используются по умолчанию и будут добавлены во все новые репозитории. Подробнее о веб-хуках .
+users.remote = Дистанц
[action]
create_repo=создал(а) репозиторий %s
rename_repo=переименовал(а) репозиторий из %[1]s
на %[3]s
commit_repo=отправил(а) изменения в %[3]s в %[4]s
-create_issue=`открыл(а) задачу %[3]s#%[2]s `
+create_issue=`задача создана %[3]s#%[2]s `
close_issue=`закрыл(а) задачу %[3]s#%[2]s `
-reopen_issue=`переоткрыл(а) задачу %[3]s#%[2]s `
+reopen_issue=`задача снова открыта %[3]s#%[2]s `
create_pull_request=`создал(а) запрос на слияние %[3]s#%[2]s `
close_pull_request=`закрыл(а) запрос на слияние %[3]s#%[2]s `
reopen_pull_request=`переоткрыл(а) запрос на слияние %[3]s#%[2]s `
@@ -3328,7 +3358,7 @@ publish_release=`выпустил(а) "%[4]s" в %[3]s#%[2]s `
review_dismissed_reason=Причина:
create_branch=создал(а) ветку %[3]s в %[4]s
-starred_repo=добавил(а) %[2]s в избранное
+starred_repo=добавлено %[2]s в избранное
watched_repo=начала(а) наблюдение за %[2]s
[tool]
@@ -3539,6 +3569,7 @@ owner.settings.cargo.rebuild.description = Пересборка может бы
rpm.repository = О репозитории
rpm.repository.architectures = Архитектуры
rpm.repository.multiple_groups = Этот пакет доступен в нескольких группах.
+owner.settings.chef.keypair.description = Для аутентификации реестра Chef необходима пара ключей. Если до этого вы уже сгенерировали пару ключей, генерация новой приведёт к прекращению действия предыдущей.
[secrets]
secrets=Секреты
@@ -3615,7 +3646,7 @@ runs.actor=Актор
runs.status=Статус
runs.actors_no_select=Все акторы
runs.no_results=Ничего не найдено.
-runs.no_workflows=Пока нет рабочих процессов.
+runs.no_workflows=Пока нет рабочих потоков.
runs.no_runs=Рабочий поток ещё не запускался.
runs.empty_commit_message=(пустое сообщение коммита)
@@ -3644,6 +3675,7 @@ variables.update.success=Переменная изменена.
variables.id_not_exist = Переменная с идентификатором %d не существует.
runs.no_workflows.quick_start = Не знаете, как начать использовать Действия Forgejo? Читайте руководство по быстрому старту .
runs.no_workflows.documentation = Чтобы узнать больше о Действиях Forgejo, читайте документацию .
+runs.workflow = Рабочий поток
[projects]
type-1.display_name=Индивидуальный проект
@@ -3667,3 +3699,5 @@ component_failed_to_load = Случилась непредвиденная ош
contributors.what = соучастие
component_loading = Загрузка %s...
component_loading_info = Это займёт некоторое время…
+code_frequency.what = частота изменений
+recent_commits.what = недавние коммиты
diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini
index 318eb24df8..ef7285cd80 100644
--- a/options/locale/locale_si-LK.ini
+++ b/options/locale/locale_si-LK.ini
@@ -579,7 +579,7 @@ gpg_invalid_token_signature=සපයන ලද GPG යතුර, අත්ස
gpg_token_required=පහත ටෝකනය සඳහා ඔබ අත්සනක් ලබා දිය යුතුය
gpg_token=ටෝකනය
gpg_token_help=ඔබට අත්සනක් ජනනය කළ හැකිය:
-gpg_token_code=දෝංකාරය "%s" | gpg -a -පැහැර හැරීම-යතුර %s —වෙන්ච-සිග්
+gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
gpg_token_signature=සන්නද්ධ GPG අත්සන
key_signature_gpg_placeholder=ආරම්භ වන්නේ '—ආරම්භ කරන්න PGP සිග්නේටුර්—'
ssh_key_verified=සත්යාපිත යතුර
diff --git a/options/locale/locale_sl.ini b/options/locale/locale_sl.ini
index a64954c2cb..5ad4023eb9 100644
--- a/options/locale/locale_sl.ini
+++ b/options/locale/locale_sl.ini
@@ -119,6 +119,17 @@ remove = Odstrani
remove_all = Odstrani vse
remove_label_str = Odstranite element "%s"
confirm_delete_artifact = Ste prepričani, da želite izbrisati artefakt "%s"?
+concept_code_repository = Repozitorij
+error404 = Stran, ki jo poskušate doseči, ne obstaja ali niste pooblaščeni za njen ogled.
+toggle_menu = Toggle Meni
+filter.is_fork = Vtičnica
+filter.not_fork = Ni vstavljena vilica
+filter.is_mirror = Zrcalni
+filter.not_mirror = Ni zrcaljen
+filter.public = Javni
+filter.private = Zasebno
+filter.not_archived = Ni arhivirano
+pull_requests = Zahteve za umik
[install]
reinstall_confirm_check_3 = Potrjujete, da ste popolnoma prepričani, da se ta program Forgejo izvaja s pravilno lokacijo app.ini, in da ste prepričani, da ga morate znova namestiti. Potrjujete, da se zavedate zgoraj navedenih tveganj.
@@ -227,14 +238,50 @@ enable_update_checker = Omogočite preverjanje posodobitev
enable_update_checker_helper = Redno preverja izdaje novih različic tako, da se poveže s spletnim mestom gitea.io.
env_config_keys = Konfiguracija okolja
env_config_keys_prompt = V konfiguracijski datoteki bodo uporabljene tudi naslednje okoljske spremenljivke:
+smtp_from_invalid = Naslov "Pošlji e-pošto kot" je neveljaven
+smtp_from_helper = e-poštni naslov, ki ga bo uporabljal Forgejo. Vnesite navaden e-poštni naslov ali uporabite obliko "Ime" .
[admin]
users.allow_git_hook_tooltip = Kljuke Git se izvajajo kot uporabnik operacijskega sistema, v katerem je nameščen program Forgejo, in imajo enako raven dostopa do gostitelja. Uporabniki s tem posebnim privilegijem Git Hook lahko dostopajo do vseh skladišč Forgejo in spreminjajo vse zbirke Forgejo ter podatkovno bazo, ki jo uporablja Forgejo. Posledično lahko pridobijo tudi skrbniške privilegije Forgejo.
+auths.force_smtps_helper = SMTPS se vedno uporablja na vratih 465. Če želite, da se SMTPS uporablja tudi na drugih vratih, to nastavite. (V nasprotnem primeru se bo STARTTLS uporabljal na drugih vratih, če ga gostitelj podpira.)
+self_check.database_fix_mysql = Uporabniki MySQL/MariaDB lahko za odpravo težav s kollacijo uporabite ukaz "gitea doctor convert", lahko pa težavo odpravite tudi z ukazom "ALTER ... COLLATE ..." SQL ročno.
+users.purge_help = Prisilno izbrišite uporabnika in vsa skladišča, organizacije in pakete, ki so v njegovi lasti. Izbrisani bodo tudi vsi komentarji in vprašanja, ki jih je objavil ta uporabnik.
+auths.sspi_default_language_helper = Privzet jezik za uporabnike, samodejno ustvarjene z metodo avtentikacije SSPI. Pustite prazno, če želite, da se jezik zazna samodejno.
+auths.restricted_filter_helper = Pustite prazno, če ne želite nastaviti nobenega uporabnika kot omejenega. Uporabite zvezdico ("*"), če želite vse uporabnike, ki se ne ujemajo z administratorskim filtrom, nastaviti kot omejene.
+auths.tip.twitter = Pojdite na https://dev.twitter.com/apps, ustvarite aplikacijo in preverite, ali je omogočena možnost "Allow this application to be used to Sign in with Twitter"
+auths.tip.yandex = Ustvarite novo aplikacijo na spletnem mestu https://oauth.yandex.com/client/new. V razdelku "Yandex.Passport API" izberite naslednja dovoljenja: "Dostop do e-poštnega naslova", "Dostop do avatarja uporabnika" in "Dostop do uporabniškega imena, imena in priimka, spola"
+config.git_migrate_timeout = Časovna omejitev migracije
+config.git_gc_args = Argumenti GC
+config.git_max_diff_files = Prikazane največje razlike v datotekah
+notices.system_notice_list = Sistemska obvestila
+monitor.cron = Opravila Cron
+config.access_log_template = Predloga dnevnika dostopa
+config.access_log_mode = Način beleženja dostopa
+config.git_gc_timeout = GC Časovni rok delovanja
+config.git_pull_timeout = Potegnite Časovni rok delovanja
+config.git_clone_timeout = Časovni limit operacije kloniranja
+config.git_mirror_timeout = Časovna omejitev posodobitve zrcala
+config.git_max_diff_lines = Največ razlikovalnih vrstic na datoteko
+config.git_disable_diff_highlight = Onemogočite razlikovanje sintakse
+config.git_config = Konfiguracija Git
+config.git_max_diff_line_characters = Največ različnih znakov na vrstico
+notices.view_detail_header = Podrobnosti obvestila
+config.log_config = Konfiguracija dnevnika
[repo]
migrate.github_token_desc = Tu lahko vstavite enega ali več žetonov, ločenih z vejico, da bo selitev hitrejša zaradi omejitve hitrosti GitHub API. OPOZORILO: Zloraba te funkcije lahko krši pravila ponudnika storitev in povzroči blokado računa.
ambiguous_runes_description = `Ta datoteka vsebuje znake Unicode, ki bi jih lahko zamenjali z drugimi znaki. Če menite, da je to namerno, lahko to opozorilo mirno prezrete. Za njihovo razkritje uporabite gumb Escape.`
invisible_runes_description = `Ta datoteka vsebuje nevidne znake Unicode, ki jih ljudje ne razlikujejo, vendar jih lahko računalnik obdela drugače. Če menite, da je to namerno, lahko to opozorilo mirno prezrete. Za njihovo razkritje uporabite gumb Escape.`
+branch.delete_desc = Brisanje veje je trajno. Čeprav lahko izbrisana veja še nekaj časa obstaja, preden je dejansko odstranjena, je v večini primerov NI mogoče preklicati. Nadaljujte?
+issues.delete.text = Ali res želite izbrisati to vprašanje? (S tem bo trajno odstranjena vsa vsebina. Če jo nameravate ohraniti v arhivu, jo raje zaprite.)
+settings.githook_edit_desc = Če kavelj ni aktiven, se prikaže vzorčna vsebina. Če pustite vsebino prazno, bo ta kavelj onemogočen.
+editor.file_changed_while_editing = Vsebina datoteke se je od začetka urejanja spremenila. Klikni tukaj , da si jih ogledaš, ali Sprejemi spremembe znova , da jih prepišeš.
+settings.webhook.delivery.success = Dogodek je bil dodan v čakalno vrsto za dostavo. Lahko traja nekaj sekund, preden se prikaže v zgodovini dostave.
+editor.filename_help = Dodajte imenik tako, da vnesete njegovo ime, ki mu sledi poševnica ("/"). Imenik odstranite tako, da na začetku vnosnega polja vtipkate backspace.
+settings.transfer_notices_3 = - Če je skladišče zasebno in je preneseno na posameznega uporabnika, to dejanje zagotovi, da ima uporabnik vsaj dovoljenje za branje (in po potrebi spremeni dovoljenja).
+settings.protect_status_check_patterns_desc = Vnesite vzorce, s katerimi določite, kateri pregledi stanja morajo biti uspešno opravljeni, preden se veje lahko združijo v vejo, ki ustreza temu pravilu. Vsaka vrstica določa vzorec. Vzorci ne smejo biti prazni.
+settings.unarchive.button = Odprava arhiviranja repozitorija
+settings.archive.header = Arhiviranje tega repozitorija
[editor]
buttons.list.ordered.tooltip = Dodajte oštevilčen seznam
@@ -259,6 +306,73 @@ footer = Stopka
[settings]
oauth2_application_locked = Forgejo ob zagonu predhodno registrira nekatere aplikacije OAuth2, če je to omogočeno v konfiguraciji. Da bi preprečili nepričakovano obnašanje, jih ni mogoče niti urejati niti odstraniti. Za več informacij glejte dokumentacijo OAuth2.
+profile = Profil
+account = Račun
+appearance = Videz
+password = Geslo
+authorized_oauth2_applications_description = Tem aplikacijam tretjih oseb ste odobrili dostop do svojega osebnega računa Forgejo. Prosimo, da prekličete dostop do aplikacij, ki jih ne uporabljate več.
+social_desc = S temi družabnimi računi se lahko prijavite v svoj račun. Prepričajte se, da jih vse prepoznate.
+access_token_desc = Izbrana dovoljenja žetona omejujejo avtorizacijo samo na ustrezne poti API . Za več informacij preberite dokumentacijo .
+oauth2_client_secret_hint = Skrivnost se ne bo več prikazala, ko zapustite ali osvežite to stran. Prepričajte se, da ste jo shranili.
+twofa_desc = Za zaščito računa pred krajo gesla lahko uporabite pametni telefon ali drugo napravo za prejemanje časovno omejenih enkratnih gesel ("TOTP").
+twofa_recovery_tip = Če napravo izgubite, boste lahko z obnovitvenim ključem za enkratno uporabo ponovno pridobili dostop do računa.
+twofa_scratch_token_regenerated = Vaš obnovitveni ključ za enkratno uporabo je zdaj %s. Shranite ga na varnem mestu, saj ga ne boste več videli.
+regenerate_scratch_token_desc = Če ste izgubili obnovitveni ključ ali ste ga že uporabili za prijavo, ga lahko ponastavite tukaj.
+twofa_enrolled = Vaš račun je bil uspešno vpisan. Ključ za obnovitev za enkratno uporabo (%s) shranite na varno, saj ga ne boste več videli.
+can_not_add_email_activations_pending = Aktivacija je v teku, poskusite znova čez nekaj minut, če želite dodati nov e-poštni naslov.
+update_profile = Posodobitev profila
+update_language = Posodobitev jezika
+update_language_success = Jezik je bil posodobljen.
+ui = Tema
+location = Lokacija
+security = Varnost
+social = Družbeni računi
+applications = Aplikacije
+twofa = Dvofaktorsko preverjanje pristnosti (TOTP)
+account_link = Povezani računi
+organization = Organizacije
+blocked_users = Blokirani uporabniki
+biography_placeholder = Povejte nam nekaj o sebi! (Uporabite lahko Markdown)
+location_placeholder = Z drugimi delite svojo približno lokacijo
+update_theme = Posodobitev teme
+full_name = Polno ime
+update_profile_success = Vaš profil je bil posodobljen.
+change_username = Vaše uporabniško ime je bilo spremenjeno.
+change_username_prompt = Opomba: Sprememba uporabniškega imena spremeni tudi URL vašega računa.
+continue = Nadaljuj
+cancel = Prekliči
+language = Jezik
+comment_type_group_reference = Referenca
+comment_type_group_assignee = Prevzemnik
+comment_type_group_time_tracking = Sledenje času
+comment_type_group_deadline = Rok
+comment_type_group_dependency = Odvisnost
+comment_type_group_lock = Stanje zaklepanja
+comment_type_group_project = Projekt
+saved_successfully = Vaše nastavitve so bile uspešno shranjene.
+privacy = Zasebnost
+keep_activity_private = Skrivanje dejavnosti s strani profila
+federated_avatar_lookup = Federativno iskanje avatarjev
+enable_custom_avatar = Uporaba avatarja po meri
+choose_new_avatar = Izberite nov avatar
+delete_current_avatar = Izbriši trenutni avatar
+update_avatar = Posodobitev avatar
+uploaded_avatar_not_a_image = Prenesena datoteka ni slika.
+update_avatar_success = Tvoj avatar je bil posodobljen.
+update_user_avatar_success = Uporabnikov avatar je bil posodobljen.
+change_password = Sprememba gesla
+update_password = Posodabljanje gesla
+uid = UID
+password_username_disabled = Nedomovnim uporabnikom ni dovoljeno spreminjati uporabniškega imena. Za več podrobnosti se obrnite na skrbnika spletnega mesta.
+keep_activity_private_popup = Dejavnost je vidna samo za vas in upravitelje
+update_language_not_found = Jezik "%s" ni na voljo.
+delete = Brisanje računa
+uploaded_avatar_is_too_big = Velikost naložene datoteke (%d KiB) presega največjo velikost (%d KiB).
+webauthn = Dvofaktorsko preverjanje pristnosti (varnostni ključi)
+change_username_redirect_prompt = Staro uporabniško ime bo preusmerjeno, dokler ga nekdo ne prevzame.
+orgs = Upravljanje organizacij
+public_profile = Javni profil
+gpg_key_verified_long = Ključ je bil preverjen z žetonom in ga je mogoče uporabiti za preverjanje zavez, ki ustrezajo vsem aktiviranim e-poštnim naslovom tega uporabnika, poleg vseh ujemajočih se identitet za ta ključ.
[heatmap]
less = Manj
@@ -276,6 +390,7 @@ missing_csrf = Slaba zahteva: žeton CSRF ni prisoten
invalid_csrf = Slaba zahteva: neveljaven žeton CSRF
not_found = Tarče ni bilo mogoče najti.
network_error = Napaka omrežja
+server_internal = Napaka notranjega strežnika
[startpage]
app_desc = Brezskrbna, samostojno gostovana storitev Git
@@ -283,6 +398,7 @@ install = Enostaven za namestitev
lightweight = Lahka stran
lightweight_desc = Forgejo ima majhne minimalne zahteve in ga je mogoče zagnati na poceni računalniku Raspberry Pi. Prihranite energijo svojega stroja!
license = Odprta koda
+platform = Različne platforme
[explore]
code_search_unavailable = Iskanje kode trenutno ni na voljo. Obrnite se na skrbnika spletnega mesta.
@@ -378,6 +494,7 @@ email_domain_blacklisted = S svojim e-poštnim naslovom se ne morete registrirat
authorize_application = Odobritev vloge
authorize_application_description = Če mu dovolite dostop, bo lahko dostopal do vseh informacij o vašem računu, vključno z zasebnimi skladišči in organizacijami, in pisal vanje.
remember_me = Ne pozabite na to napravo
+login_openid = Odprta identiteta
[home]
show_both_archived_unarchived = Prikazovanje arhiviranih in nearhiviranih
@@ -408,7 +525,7 @@ issues.in_your_repos = V vašem repozitorijev
release.title = Naslov: %s
release.downloads = Prenosi:
activate_account.text_2 = Za aktivacijo računa v %s kliknite naslednjo povezavo:
-link_not_working_do_paste = Ne deluje? Poskusite ga kopirati in prilepiti v brskalnik.
+link_not_working_do_paste = Ali povezava ne deluje? Poskusite jo kopirati in prilepiti v vrstico URL brskalnika.
issue.action.reopen = @%[1]s ponovno odprl #%[2]d.
repo.transfer.body = Če ga želite sprejeti ali zavrniti, obiščite %s ali ga preprosto prezrite.
team_invite.text_2 = Če se želite pridružiti ekipi, kliknite naslednjo povezavo:
@@ -422,14 +539,14 @@ admin.new_user.user_info = Informacije o uporabniku
admin.new_user.text = Prosimo, da klikni tukaj za upravljanje tega uporabnika iz upraviteljske plošče.
register_notify = Dobrodošli v Forgejo
register_notify.title = %[1]s, dobrodošli v %[2]s
-register_notify.text_2 = Zdaj se lahko prijavite z uporabniškim imenom: %s.
-register_notify.text_3 = Če je bil ta račun ustvarjen za vas, prosimo, da najprej nastavite svoje geslo .
+register_notify.text_2 = V svoj račun se lahko prijavite z uporabniškim imenom: %s
+register_notify.text_3 = Če je ta račun namesto vas ustvaril nekdo drug, boste morali najprej nastaviti svoje geslo .
reset_password = Obnovite svoj račun
-reset_password.title = %s, zahtevali ste obnovitev računa
+reset_password.title = %s, prejeli smo zahtevo za izterjavo vašega računa
register_success = Registracija je bila uspešna
issue.x_mentioned_you = @%s vas je omenil:
issue.action.close = @%[1]s zaprl #%[2]d.
-reset_password.text = Za obnovitev računa v %s kliknite naslednjo povezavo:
+reset_password.text = Če se je to zgodilo vam, kliknite naslednjo povezavo in obnovite račun v %s :
release.note = Opomba:
release.download.zip = Izvorna koda (ZIP)
release.download.targz = Izvorna koda (TAR.GZ)
@@ -446,15 +563,132 @@ activate_email.text = Kliknite naslednjo povezavo, da preverite svoj e-poštni n
register_notify.text_1 = to je vaše e-poštno sporočilo s potrditvijo registracije za %s!
issue_assigned.pull = @%[1]s vam je dodelil zahtevo za poteg %[2]s v skladišču %[3]s.
issue_assigned.issue = @%[1]s vam je dodelil izdajo %[2]s v skladišču %[3]s.
-issue.action.force_push = %[1]s sila-potisnila%[2]s < /b > iz %[3]s v %[4]s.
+issue.action.force_push = %[1]s orce je potisnil %[2]s od%[3]s to %[4]s.
+release.new.subject = %s v %s sproščeno
+release.new.text = @%[1]s izdal %[2]s v %[3]s
+repo.transfer.subject_to = %s želi prenesti "%s" na %s
+repo.transfer.subject_to_you = %s želi prenesti "%s" na vas
+repo.collaborator.added.text = Dodani ste kot sodelavec repozitorija:
+issue.action.merge = @%[1]s je združil #%[2]d v %[3]s.
+issue.action.push_1 = @%[1]s potisnil %[3]d commit v %[2]s
+issue.action.push_n = @%[1]s potisnil %[3]d zavez v %[2]s
+issue.action.review = @%[1]s je komentiral/a to zahtevo.
+issue.action.review_dismissed = @%[1]s je zavrnil zadnji pregled %[2]s za to zahtevo.
+issue.action.ready_for_review = @%[1]s je to zahtevo označil kot pripravljeno za pregled.
+issue.in_tree_path = V %s:
[modal]
confirm = Potrdite
no = Ne
cancel = Prekliči
modify = Posodobitev
+yes = Da
[form]
UserName = Uporabniško ime
Password = Geslo
-Retype = Potrditev gesla
\ No newline at end of file
+Retype = Potrditev gesla
+team_name_been_taken = The name of the organisation is already taken.
+password_complexity = Geslo ne izpolnjuje zahtev glede kompleksnosti:
+enterred_invalid_org_name = Ime organizacije, ki ste ga vnesli, je napačno.
+organization_leave_success = Uspešno ste zapustili organizacijo %s.
+admin_cannot_delete_self = Ko ste administrator, se ne morete izbrisati. Najprej odstranite svoje pravice upravitelja.
+RepoName = Ime repozitorija
+Email = E-poštni naslov
+SSHTitle = Ime ključa SSH
+PayloadUrl = URL koristnega tovora
+TeamName = Ime ekipe
+AuthName = Ime avtorizacije
+Content = Vsebina
+SSPISeparatorReplacement = Ločevalnik
+SSPIDefaultLanguage = Privzet jezik
+captcha_incorrect = Koda CAPTCHA je napačna.
+password_not_match = Gesla se ne ujemajo.
+lang_select_error = S seznama izberite jezik.
+username_been_taken = Uporabniško ime je že zasedeno.
+username_change_not_local_user = Nedomovnim uporabnikom ni dovoljeno spreminjati uporabniškega imena.
+username_has_not_been_changed = Uporabniško ime ni bilo spremenjeno
+visit_rate_limit = Obisk na daljavo je obravnaval omejitev hitrosti.
+2fa_auth_required = Obisk na daljavo je zahteval preverjanje pristnosti z dvema dejavnikoma.
+org_name_been_taken = Ime organizacije je že zasedeno.
+unknown_error = Neznana napaka:
+repo_name_been_taken = Ime skladišča je že uporabljeno.
+repository_force_private = Omogočena je možnost Force Private: zasebnih skladišč ni mogoče objaviti.
+repository_files_already_exist = Datoteke za to skladišče že obstajajo. Obrnite se na skrbnika sistema.
+repository_files_already_exist.adopt = Datoteke za ta repozitorij že obstajajo in jih je mogoče samo sprejeti.
+repository_files_already_exist.delete = Datoteke za to skladišče že obstajajo. Morate jih izbrisati.
+repository_files_already_exist.adopt_or_delete = Datoteke za to skladišče že obstajajo. Sprejmite jih ali pa jih izbrišite.
+openid_been_used = Naslov OpenID "%s" je že uporabljen.
+username_password_incorrect = Uporabniško ime ali geslo je napačno.
+password_lowercase_one = Vsaj en mali črkovni znak
+password_uppercase_one = Vsaj en veliki tiskani znak
+password_digit_one = Vsaj ena številka
+password_special_one = vsaj en poseben znak (ločila, oklepaji, narekovaji itd.)
+enterred_invalid_repo_name = Ime skladišča, ki ste ga vnesli, je napačno.
+team_no_units_error = Dovolite dostop do vsaj enega oddelka repozitorija.
+email_been_used = E-poštni naslov je že uporabljen.
+email_invalid = E-poštni naslov je neveljaven.
+enterred_invalid_owner_name = Ime novega lastnika ni veljavno.
+invalid_ssh_principal = Nepravilen principal: %s
+must_use_public_key = Ključ, ki ste ga navedli, je zasebni ključ. Zasebnega ključa ne nalagajte nikamor. Namesto tega uporabite svoj javni ključ.
+auth_failed = Preverjanje pristnosti ni uspelo: %v
+enterred_invalid_password = Vneseno geslo je napačno.
+user_not_exist = Uporabnik ne obstaja.
+team_not_exist = Ekipa ne obstaja.
+duplicate_invite_to_team = Uporabnik je bil že povabljen kot član ekipe.
+username_error = ` lahko vsebuje samo alfanumerične znake ("0-9", "a-z", "A-Z"), pomišljaj ("-"), podčrtaj ("_") in piko ("."). Ne sme se začeti ali končati z nealfanumeričnimi znaki, zaporedni nealfanumerični znaki pa so prav tako prepovedani.`
+still_has_org = Vaš račun je član ene ali več organizacij, najprej zapustite te organizacije.
+git_ref_name_error = ` mora biti dobro oblikovano referenčno ime Git.`
+size_error = ` mora biti velikosti %s.`
+min_size_error = ` mora vsebovati vsaj %s znakov.`
+max_size_error = ` mora vsebovati največ %s znakov.`
+email_error = ` ni veljaven e-poštni naslov.`
+alpha_dash_error = ` mora vsebovati samo alfanumerične znake, pomišljaje ("-") in podčrtanke ("_").`
+alpha_dash_dot_error = ` mora vsebovati samo alfanumerične znake, pomišljaj ("-"), podčrtaj ("_") in piko (".").`
+invalid_group_team_map_error = ` preslikava je neveljavna: %s`
+require_error = ` ne more biti prazen.`
+include_error = ` mora vsebovati podrejeni niz "%s".`
+glob_pattern_error = ` glob vzorec je neveljaven: %s.`
+regex_pattern_error = ` regex vzorec je neveljaven: %s.`
+url_error = `"%s" ni veljaven naslov URL.`
+TreeName = Pot do datoteke
+username_error_no_dots = ` lahko vsebuje samo alfanumerične znake ("0-9", "a-z", "A-Z"), pomišljaj ("-") in podčrtaj ("_"). Ne sme se začeti ali končati z nealfanumeričnimi znaki, prepovedani pa so tudi zaporedni nealfanumerični znaki.`
+AdminEmail = E-pošta upravitelja
+CommitSummary = Povzetek obveznosti
+CommitMessage = Sporočilo o zavezanosti
+CommitChoice = Izbira obveznosti
+invalid_ssh_key = Ni mogoče preveriti vašega ključa SSH: %s
+invalid_gpg_key = Ni mogoče preveriti ključa GPG: %s
+unable_verify_ssh_key = Ključa SSH ni mogoče preveriti, zato ga dvakrat preverite, ali v njem ni napak.
+last_org_owner = Zadnjega uporabnika ne morete odstraniti iz skupine lastnikov. V organizaciji mora biti vsaj en lastnik.
+cannot_add_org_to_team = Organizacije ni mogoče dodati kot člana skupine.
+
+[user]
+form.name_chars_not_allowed = Uporabniško ime "%s" vsebuje neveljavne znake.
+disabled_public_activity = Ta uporabnik je onemogočil javno vidnost dejavnosti.
+change_avatar = Spremeni svoj avatar…
+joined_on = Pridružil se je na %s
+activity = Javna dejavnost
+followers = Sledilci
+block_user = Blokiranje uporabnika
+overview = Pregled
+following = Sledenje
+follow = Sledite
+unfollow = Neupoštevanje
+block = Blok
+unblock = Odblokiranje
+user_bio = Biografija
+projects = Projekti
+show_on_map = Prikaži to mesto na zemljevidu
+settings = Uporabniške nastavitve
+form.name_reserved = Uporabniško ime "%s" je rezervirano.
+form.name_pattern_not_allowed = Vzorec "%s" v uporabniškem imenu ni dovoljen.
+block_user.detail_1 = Ta uporabnik vam ne sledi.
+follow_blocked_user = Temu uporabniku ne morete slediti, ker ste ga blokirali ali ker je ta uporabnik blokiral vas.
+code = Koda
+
+[packages]
+owner.settings.chef.keypair.description = Za preverjanje pristnosti v registru Chef je potreben par ključev. Če ste par ključev ustvarili že prej, se pri ustvarjanju novega para ključev stari par ključev zavrže.
+
+[actions]
+runners.runner_manage_panel = Upravljanje tekačev
\ No newline at end of file
diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini
index 2265611004..69bc91b6dd 100644
--- a/options/locale/locale_zh-CN.ini
+++ b/options/locale/locale_zh-CN.ini
@@ -124,7 +124,7 @@ pin=固定
unpin=取消置顶
artifacts=制品
-confirm_delete_artifact=您确定要删除制品'%s'吗?
+confirm_delete_artifact=您确定要删除制品“%s”吗?
archived=已归档
@@ -142,6 +142,19 @@ confirm_delete_selected=确认删除所有选中项目?
name=名称
value=值
+filter = 筛选
+filter.clear = 清除筛选条件
+filter.is_archived = 已归档
+filter.not_archived = 未归档
+filter.is_fork = 已派生
+filter.not_fork = 未派生
+filter.is_mirror = 已镜像
+filter.not_mirror = 未镜像
+filter.is_template = 模板
+filter.not_template = 非模板
+filter.public = 公开
+filter.private = 私有
+toggle_menu = 菜单
[aria]
navbar=导航栏
@@ -182,6 +195,7 @@ missing_csrf=错误的请求:没有 CSRF 令牌
invalid_csrf=错误的请求:无效的 CSRF 令牌
not_found=找不到目标。
network_error=网络错误
+server_internal = 服务器内部错误
[startpage]
app_desc=一款极易搭建的自助 Git 服务
@@ -278,8 +292,8 @@ admin_password=管理员密码
confirm_password=确认密码
admin_email=电子邮件地址
install_btn_confirm=立即安装
-test_git_failed=无法识别 'git' 命令:%v
-sqlite3_not_available=您所使用的发行版不支持 SQLite3,请从 %s 下载官方构建版,而不是 gobuild 版本。
+test_git_failed=无法识别 “git” 命令:%v
+sqlite3_not_available=当前 Forgejo 版本不支持 SQLite3。请从 %s 下载官方构建版(注:请勿下载标有 “gobuild” 的版本)。
invalid_db_setting=数据库设置无效: %v
invalid_db_table=数据库表 '%s' 无效: %v
invalid_repo_path=仓库根目录设置无效:%v
@@ -297,7 +311,7 @@ default_allow_create_organization_popup=默认情况下, 允许新用户帐户
default_enable_timetracking=默认情况下启用时间跟踪
default_enable_timetracking_popup=默认情况下启用新仓库的时间跟踪。
no_reply_address=隐藏电子邮件
-no_reply_address_helper=具有隐藏电子邮件地址的用户的域名。例如, 用户名 "joe" 将以 "joe@noreply.example.org" 的身份登录到 Git 中. 如果隐藏的电子邮件域设置为 "noreply.example.org"。
+no_reply_address_helper=用于设置隐藏电子邮件地址的用户使用的电子邮件域名。例如,如果用于隐藏电子邮件地址的域名设为“noreply.example.org”,则用户名 “joe” 在 Git 中将以 “joe@noreply.example.org” 表示。
password_algorithm=密码哈希算法
invalid_password_algorithm=无效的密码哈希算法
password_algorithm_helper=设置密码散列算法。算法有不同的要求和强度。 argon2 算法相当安全,但使用大量内存,因此可能不适合小型系统。
@@ -307,6 +321,7 @@ env_config_keys=环境配置
env_config_keys_prompt=以下环境变量也将应用于您的配置文件:
allow_dots_in_usernames = 允许用户在用户名中使用英文句号。不影响已有的帐户。
enable_update_checker_helper_forgejo = 通过检查 release.forgejo.org 上的 DNS TXT 记录,定期检查 Forgejo 的新版本。
+smtp_from_invalid = 电子邮件发件人地址无效
[home]
uname_holder=用户名或邮箱
@@ -428,11 +443,14 @@ sspi_auth_failed=SSPI 认证失败
password_pwned=此密码出现在 被盗密码 列表上并且曾经被公开。 请使用另一个密码再试一次。
password_pwned_err=无法完成对 HaveIBeenPwned 的请求
last_admin=您不能删除最后一个管理员。必须至少保留一个管理员。
+change_unconfirmed_email = 如果您在注册时提供了错误的邮箱地址,您可以在下方修改,激活邮件会发送到修改后的邮箱地址。
+change_unconfirmed_email_summary = 修改用来接收激活邮件的邮箱地址。
+change_unconfirmed_email_error = 无法修改邮箱地址: %v
[mail]
view_it_on=在 %s 上查看
reply=或直接回复此邮件
-link_not_working_do_paste=不起作用?尝试复制并粘贴到您的浏览器。
+link_not_working_do_paste=链接点不了?尝试复制并粘贴到您的浏览器。
hi_user_x=%s 您好,
activate_account=请激活您的帐户
@@ -447,12 +465,12 @@ activate_email.text=请在 %s 时间内,点击以下链接,以验证
register_notify=欢迎来到 Forgejo
register_notify.title=%[1]s,欢迎来到 %[2]s
register_notify.text_1=这是您的 %s 注册确认电子邮件 !
-register_notify.text_2=您现在可以以用户名 %s 登录。
-register_notify.text_3=如果此账户已为您创建,请先 设置您的密码 。
+register_notify.text_2=您现在可以以用户名 %s 登录
+register_notify.text_3=如果此账户是别人为您创建的,请先 设置您的密码 。
reset_password=恢复您的账户
-reset_password.title=%s,您已请求恢复您的帐户
-reset_password.text=请在 %s 时间内,点击以下链接,恢复你的账户:
+reset_password.title=%s,我们收到了恢复您的帐户的请求
+reset_password.text=如果此请求是您本人作出的,则请在 %s 时间内,点击以下链接,恢复你的账户:
register_success=注册成功
@@ -529,8 +547,8 @@ SSPISeparatorReplacement=分隔符
SSPIDefaultLanguage=默认语言
require_error=不能为空。
-alpha_dash_error=应该只包含字母数字、破折号 ('-') 和下划线 ('_') 字符。
-alpha_dash_dot_error=` 应该只包含字母数字, 破折号 ('-'), 下划线 ('_') 和点 ('. ') 。`
+alpha_dash_error=` 只允许包含字母数字、破折号(“-”)和下划线(“_”)字符。
+alpha_dash_dot_error=` 应该只包含半角字母、数字、破折号(“-”)、下划线(“_”)和半角句号(“.”) 。`
git_ref_name_error=` 必须是格式良好的 git 引用名称。`
size_error=长度必须为 %s。
min_size_error=长度最小为 %s 个字符。
@@ -540,7 +558,7 @@ url_error=`'%s' 不是一个有效的 URL。`
include_error=`必须包含子字符串 "%s"。`
glob_pattern_error=`匹配模式无效:%s.`
regex_pattern_error=`正则表达式无效:%s.`
-username_error=` 只能包含字母数字字符('0-9','a-z','A-Z'), 破折号 ('-'), 下划线 ('_') 和点 ('.'). 不能以非字母数字字符开头或结尾,并且不允许连续的非字母数字字符。`
+username_error=` 只允许包含字母数字字符(“0-9”、“a-z”、“A-Z”)、破折号(“-”)、下划线(“_”)和点(“.”)。不能以非字母数字字符开头或结尾,并且不允许连续的非字母数字字符。`
invalid_group_team_map_error=`映射无效: %s`
unknown_error=未知错误:
captcha_incorrect=验证码不正确。
@@ -576,7 +594,7 @@ enterred_invalid_owner_name=新的所有者名称无效。
enterred_invalid_password=输入的密码不正确
user_not_exist=该用户不存在
team_not_exist=团队不存在
-last_org_owner=您不能从 "所有者" 团队中删除最后一个用户。组织中必须至少有一个所有者。
+last_org_owner=您不能从“所有者”团队中删除最后一个用户。组织中必须至少有一个所有者。
cannot_add_org_to_team=组织不能被加入到团队中。
duplicate_invite_to_team=此用户已被邀请为团队成员。
organization_leave_success=您已成功离开组织 %s。
@@ -589,13 +607,13 @@ unable_verify_ssh_key=无法验证 SSH 密钥,请仔细检查是否有错误
auth_failed=授权验证失败:%v
still_own_repo=此帐户仍拥有至少一个仓库,您需要先删除或转移它们。
-still_has_org=此帐户隶属于一个或多个组织,请先退出这些组织。
+still_has_org=此帐户目前是一个或多个组织的成员,请先退出这些组织。
still_own_packages=您的账户拥有一个或多个软件包,请先删除它们。
-org_still_own_repo=该组织仍然是某些仓库的拥有者,请先删除或转移它们!
-org_still_own_packages=该组织仍然是一个或多个软件包的拥有者,请先删除它们。
+org_still_own_repo=该组织下仍有仓库,请先删除或转移它们。
+org_still_own_packages=该组织下仍有软件包,请先删除它们。
target_branch_not_exist=目标分支不存在。
-username_error_no_dots = ` 只能包含英文字母与数字 ('0-9','a-z','A-Z'), 连字符 ('-') 与下划线 ('_')。 开头与结尾的字符只能使用英文字母或数字,且不能包含连续的非字母非数字字符。`
+username_error_no_dots = ` 只能包含英文字母与数字(“0-9”、“a-z”、“A-Z”)、横杠(“-”) 与下划线(“_”)。 开头与结尾的字符只能使用英文字母或数字,且不能包含连续的非字母非数字字符。`
admin_cannot_delete_self = 您无法以管理员的身份删除自己。请先移除您的管理员权限。
admin_cannot_delete_self=当您是管理员时,您不能删除自己。请先移除您的管理员权限
@@ -737,7 +755,7 @@ theme_update_error=所选主题不存在。
openid_deletion=移除 OpenID 地址
openid_deletion_desc=删除此 OpenID 地址将会阻止你使用它进行登录。你确定要继续吗?
openid_deletion_success=OpenID地址已被移除。
-add_new_email=添加新的邮箱地址
+add_new_email=添加邮箱地址
add_new_openid=添加新的 OpenID URI
add_email=增加电子邮件地址
add_openid=添加 OpenID URI
@@ -745,7 +763,7 @@ add_email_confirmation_sent=一封确认邮件已经被发送至 %s,请检查
add_email_success=新的电子邮件地址已添加。
email_preference_set_success=电子邮件首选项已成功设置。
add_openid_success=新的 OpenID 地址已添加。
-keep_email_private=隐藏电子邮件地址
+keep_email_private=隐藏邮箱地址
keep_email_private_popup=这将会隐藏您的电子邮件地址,不仅在您的个人资料中,还在您使用Web界面创建拉取请求或编辑文件时。已推送的提交将不会被修改。
openid_desc=OpenID 让你可以将认证转发到外部服务。
@@ -753,15 +771,15 @@ manage_ssh_keys=管理 SSH 密钥
manage_ssh_principals=管理SSH证书规则
manage_gpg_keys=管理 GPG 密钥
add_key=增加密钥
-ssh_desc=这些 SSH 公钥已经关联到你的账号。相应的私钥拥有完全操作你的仓库的权限。
+ssh_desc=这些 SSH 公钥已经关联到你的账号。相应的私钥拥有完全操作你的仓库的权限,且已验证的 SSH 密钥可以用于验证 SSH 签名的 Git 提交。
principal_desc=这些SSH证书规则已关联到你的账号将允许完全访问你的所有仓库。
-gpg_desc=这些 GPG 公钥已经关联到你的账号。请妥善保管你的私钥因为他们将被用于认证提交。
+gpg_desc=这些 GPG 公钥已经关联到你的账号,并用于验证您的提交。请妥善保管你的私钥,因为这些私钥可以用于以你的身份签名提交。
ssh_helper=需要帮助? 请查看有关 如何生成 SSH 密钥 或 常见 SSH 问题 寻找答案。
gpg_helper=需要帮助吗? 看一看 GitHub 关于GPG 的指导。
add_new_key=增加 SSH 密钥
add_new_gpg_key=添加的 GPG 密钥
-key_content_ssh_placeholder=以 'ssh-ed25519'、 'ssh-rsa'、 'ecdsa-sha2-nistp256'、'ecdsa-sha2-nistp384'、'ecdsa-sha2-nistp521'、 'sk-ecdsa-sha2-nistp256@openssh.com' 或 'sk-ssh-ed25519@openssh.com' 开头
-key_content_gpg_placeholder=以 '-----BEGIN PGP PUBLIC KEY BLOCK-----' 开头
+key_content_ssh_placeholder=以“ssh-ed25519”、“ssh-rsa”、“ecdsa-sha2-nistp256”、“ecdsa-sha2-nistp384”、“ecdsa-sha2-nistp521”、“sk-ecdsa-sha2-nistp256@openssh.com” 或 “sk-ssh-ed25519@openssh.com” 开头
+key_content_gpg_placeholder=以 “-----BEGIN PGP PUBLIC KEY BLOCK-----” 开头
add_new_principal=添加规则
ssh_key_been_used=此 SSH 密钥已添加到服务器。
ssh_key_name_used=使用相同名称的SSH公钥已经存在!
@@ -779,7 +797,7 @@ gpg_token=令牌
gpg_token_help=您可以使用以下方式生成签名:
gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
gpg_token_signature=GPG 增强签名
-key_signature_gpg_placeholder=以 '-----BEGIN PGP PUBLIC KEY BLOCK-----' 开头
+key_signature_gpg_placeholder=以 “-----BEGIN PGP SIGNATURE-----” 开头
verify_gpg_key_success=GPG 密钥 %s 已被验证。
ssh_key_verified=已验证的密钥
ssh_key_verified_long=密钥已经用令牌进行了验证,并且可以用来验证匹配此用户任何已激活电子邮件地址的提交。
@@ -789,7 +807,7 @@ ssh_token_required=您必须为下面的令牌提供签名
ssh_token=令牌
ssh_token_help=您可以使用以下方式生成签名:
ssh_token_signature=增强 SSH 签名
-key_signature_ssh_placeholder=以 '-----BEGIN SSH SIGNATURE -----' 开头
+key_signature_ssh_placeholder=以 “-----BEGIN SSH SIGNATURE -----” 开头
verify_ssh_key_success=SSH 密钥 %s 已被验证。
subkeys=子项
key_id=键ID
@@ -829,7 +847,7 @@ social_desc=这些社交账户可以用来登录您的账户。确保您认识
unbind=取消链接
unbind_success=社交账户已成功移除。
-manage_access_token=管理 Access Token
+manage_access_token=管理访问令牌
generate_new_token=生成新的令牌
tokens_desc=这些令牌拥有通过 Forgejo API 对您的帐户的访问权限。
token_name=令牌名称
@@ -878,7 +896,7 @@ oauth2_application_remove_description=移除一个OAuth2应用将会阻止它访
oauth2_application_locked=如果配置启用,Forgejo预注册一些OAuth2应用程序。 为了防止意外的行为, 这些应用既不能编辑也不能删除。请参阅OAuth2文档以获取更多信息。
authorized_oauth2_applications=已授权的 OAuth2 应用
-authorized_oauth2_applications_description=您已授予这些第三方应用程序访问您的个人 Forgejo 账户的权限。请撤销那些您不再需要的应用程序的访问权限。
+authorized_oauth2_applications_description=您已授予这些第三方应用程序访问您的个人 Forgejo 账户的权限。请撤销那些您不再使用的应用程序的访问权限。
revoke_key=撤销
revoke_oauth2_grant=撤回权限
revoke_oauth2_grant_description=确定撤销此三方应用程序的授权,并阻止此应用程序访问您的数据?
@@ -945,10 +963,11 @@ visibility.limited_tooltip=仅对已认证的用户可见
visibility.private=私有
visibility.private_tooltip=仅对您已加入的组织的成员可见。
blocked_users = 已屏蔽的用户
-blocked_users_none = 您未屏蔽任何用户。
+blocked_users_none = 黑名单中没有用户。
blocked_since = 自 %s 起被屏蔽
user_unblock_success = 已成功取消对该用户的屏蔽。
user_block_success = 已成功屏蔽该用户。
+change_password = 更改密码
[repo]
new_repo_helper=代码仓库包含了所有的项目文件,包括版本历史记录。已经在其他地方托管了?迁移仓库。
@@ -1009,7 +1028,7 @@ default_branch_label=默认
default_branch_helper=默认分支是用于合并请求和代码提交的基础分支。
mirror_prune=修剪
mirror_prune_desc=删除过时的远程跟踪引用
-mirror_interval=镜像间隔 (有效的时间单位是 'h', 'm', 's')。0 禁用自动定期同步 (最短间隔: %s)
+mirror_interval=镜像间隔(有效的时间单位是 “h"、“m”、“s”)。填 0 禁用自动定期同步。(最短间隔:%s)
mirror_interval_invalid=镜像间隔无效。
mirror_sync=已同步
mirror_sync_on_commit=推送提交时同步
@@ -1104,12 +1123,12 @@ migrate_items_merge_requests=合并请求
migrate_items_releases=版本发布
migrate_repo=迁移仓库
migrate.clone_address=从 URL 迁移/克隆
-migrate.clone_address_desc=现有仓库的 HTTP(s) 或 Git "clone" URL
+migrate.clone_address_desc=现有仓库的 HTTP(s) 或 Git “clone” URL
migrate.github_token_desc=由于 GitHub API 速率限制,您可以在此处放置一个或多个以逗号分隔的令牌,以加快迁移速度。 警告:滥用此功能可能会违反服务提供商的政策并导致帐户被封。
migrate.clone_local_path=或服务器本地路径
migrate.permission_denied=您没有获得导入本地仓库的权限。
migrate.permission_denied_blocked=您不能从不允许的主机导入,请询问管理员以检查 ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS 设置。
-migrate.invalid_local_path=本地路径无效。它不存在或不是一个目录。
+migrate.invalid_local_path=本地路径不存在或不是一个目录。
migrate.invalid_lfs_endpoint=LFS 网址无效。
migrate.failed=迁移失败:%v
migrate.migrate_items_options=需要访问令牌来迁移额外的内容
@@ -1208,8 +1227,8 @@ escape_control_characters=Escape
unescape_control_characters=Unescape
file_copy_permalink=复制永久链接
view_git_blame=查看 Git Blame
-video_not_supported_in_browser=您的浏览器不支持使用 HTML5 'video' 标签。
-audio_not_supported_in_browser=您的浏览器不支持使用 HTML5 'video' 标签。
+video_not_supported_in_browser=您的浏览器不支持 HTML5 “video” 标签。
+audio_not_supported_in_browser=您的浏览器不支持 HTML5 “audio” 标签。
stored_lfs=存储到Git LFS
symbolic_link=符号链接
executable_file=可执行文件
@@ -1245,12 +1264,12 @@ editor.delete_this_file=删除文件
editor.must_have_write_access=您必须具有写权限才能对此文件进行修改操作。
editor.file_delete_success=文件 %s 已被删除。
editor.name_your_file=命名文件...
-editor.filename_help=通过键入名称后跟斜线 ("/") 来添加目录。通过在输入框的开头键入 "退格" 来删除目录。
+editor.filename_help=通过键入名称后跟半角斜杠(“/”)来添加目录。在输入框的开头退格来删除目录。
editor.or=或
editor.cancel_lower=取消
editor.commit_signed_changes=提交已签名的更改
editor.commit_changes=提交变更
-editor.add_tmpl=添加 ''
+editor.add_tmpl=添加 “”
editor.add=添加 %s
editor.update=更新 %s
editor.delete=删除 %s
@@ -1361,12 +1380,12 @@ projects.column.new_title=名称
projects.column.new_submit=创建列
projects.column.new=创建列
projects.column.set_default=设为默认
-projects.column.set_default_desc=设置此列为未分类问题和合并请求的默认值
+projects.column.set_default_desc=设置此列为未分类工单和合并请求的默认值
projects.column.unset_default=取消设为默认
projects.column.unset_default_desc=取消此列为默认值
projects.column.delete=删除列
-projects.column.deletion_desc=删除项目列会将所有相关问题移到“未分类”。是否继续?
-projects.column.color=彩色
+projects.column.deletion_desc=删除项目列会将所有相关工单移到“未分类”。是否继续?
+projects.column.color=颜色
projects.open=开启
projects.close=关闭
projects.column.assigned_to=指派给
@@ -1414,7 +1433,7 @@ issues.new_label_placeholder=标签名称
issues.new_label_desc_placeholder=描述
issues.create_label=创建标签
issues.label_templates.title=加载预定义的标签模板
-issues.label_templates.info=还没有任何标签。您可以使用'创建标签'按钮或者加载预定义的标签集创建标签
+issues.label_templates.info=还没有任何标签。您可以使用“创建标签”按钮或者加载预定义的标签集创建标签:
issues.label_templates.helper=选择标签模板
issues.label_templates.use=使用标签集
issues.label_templates.fail_to_load_file=加载标签模板文件 %s 时发生错误:%v
@@ -1575,19 +1594,19 @@ issues.subscribe=订阅
issues.unsubscribe=取消订阅
issues.unpin_issue=取消置顶
issues.max_pinned=您不能置顶更多工单
-issues.pin_comment=于 %s 被置顶
-issues.unpin_comment=于 %s 取消置顶
+issues.pin_comment=于 %s 置顶本工单
+issues.unpin_comment=于 %s 取消置顶本工单
issues.lock=锁定对话
issues.unlock=解锁对话
issues.lock.unknown_reason=由于未知原因无法锁定。
issues.lock_duplicate=一个工单不能被锁定两次。
issues.unlock_error=无法解锁一个未锁定的工单。
-issues.lock_with_reason=因为 %s 而锁定,并将对话限制为协作者 %s
-issues.lock_no_reason=锁定并限制仅协作者 %s
-issues.unlock_comment=解锁此对话 %s
+issues.lock_with_reason=于 %[2]s 以 %[1]s 锁定本工单,并限制仅限协作者发言
+issues.lock_no_reason=于 %s 锁定本议题并限制仅协作者可发言
+issues.unlock_comment=于 %s 解锁此议题
issues.lock_confirm=锁定
issues.unlock_confirm=解锁
-issues.lock.notice_1=- 其他用户不能对这个工单添加新的评论。
+issues.lock.notice_1=- 其他用户不能评论此工单。
issues.lock.notice_2=- 您和仓库其他协作者仍可评论并可见。
issues.lock.notice_3=- 您可以在未来再次解锁这个工单。
issues.unlock.notice_1=- 每个人都可以再次就这一工单发表评论。
@@ -1621,24 +1640,24 @@ issues.add_time_sum_to_small=没有输入时间。
issues.time_spent_total=总用时
issues.time_spent_from_all_authors=`总花费时间:%s`
issues.due_date=到期时间
-issues.invalid_due_date_format=到期时间的格式必须是 'yyyy-mm-dd' 的形式。
+issues.invalid_due_date_format=到期时间的格式必须是“yyyy-mm-dd”。
issues.error_modifying_due_date=修改到期时间失败。
issues.error_removing_due_date=删除到期时间失败。
issues.push_commit_1=于 %[2]s 推送了 %[1]d 个提交
issues.push_commits_n=于 %[2]s 推送了 %[1]d 个提交
issues.force_push_codes=`于 %[6]s 强制推送 %[1]s,从 %[2]s
,至 %[4]s
`
issues.force_push_compare=比较
-issues.due_date_form=yyyy年mm月dd日
+issues.due_date_form=yyyy-mm-dd
issues.due_date_form_add=设置到期时间
issues.due_date_form_edit=编辑
issues.due_date_form_remove=删除
issues.due_date_not_writer=您需要该仓库的写权限才能更新工单的到期日期。
issues.due_date_not_set=未设置到期时间。
issues.due_date_added=于 %[2]s 设置到期时间为 %[1]s
-issues.due_date_modified=将到期日从 %[2]s 修改为 %[1]s %[3]s
+issues.due_date_modified=于 %[3]s 将到期日从 %[2]s 修改为 %[1]s
issues.due_date_remove=于 %[2]s 删除了到期时间 %[1]s
-issues.due_date_overdue=过期
-issues.due_date_invalid=到期日期无效或超出范围。请使用 'yyyy-mm-dd' 格式。
+issues.due_date_overdue=超期
+issues.due_date_invalid=到期日期无效或超出范围。请使用“yyyy-mm-dd”格式。
issues.dependency.title=依赖工单
issues.dependency.issue_no_dependencies=没有设置依赖项。
issues.dependency.pr_no_dependencies=没有设置依赖项。
@@ -1678,11 +1697,11 @@ issues.review.dismissed=于 %[2]s 取消了 %[1]s 的评审
issues.review.dismissed_label=已取消
issues.review.left_comment=留下了一条评论
issues.review.content.empty=您需要留下一个注释,表明需要的更改。
-issues.review.reject=请求变更 %s
-issues.review.wait=已请求 %s 审核
+issues.review.reject=于 %s 请求变更
+issues.review.wait=于 %s 请求审核
issues.review.add_review_request=于 %[2]s 请求 %[1]s 评审
-issues.review.remove_review_request=取消对 %s 的评审请求 %s
-issues.review.remove_review_request_self=拒绝审核 %s
+issues.review.remove_review_request=于 %[2]s 取消对 %[1]s 的评审请求
+issues.review.remove_review_request_self=于 %s 拒绝审核
issues.review.pending=待定
issues.review.pending.tooltip=此评论目前对其他用户不可见。 若要提交您的待定评论,请在页面顶部选择 %s -> %s/%s/%s。
issues.review.review=评审
@@ -1764,29 +1783,29 @@ pulls.remove_prefix=删除 %s 前缀
pulls.data_broken=此合并请求因为派生仓库信息缺失而中断。
pulls.files_conflicted=此合并请求有变更与目标分支冲突。
pulls.is_checking=正在进行合并冲突检测,请稍后再试。
-pulls.is_ancestor=此分支已经包含在目标分支中,没有什么可以合并。
+pulls.is_ancestor=此分支已经包含在目标分支中,没有更改可以合并。
pulls.is_empty=此分支上的更改已经在目标分支上。这将是一个空提交。
pulls.required_status_check_failed=一些必要的检查没有成功
pulls.required_status_check_missing=缺少一些必要的检查。
pulls.required_status_check_administrator=作为管理员,您仍可合并此合并请求
-pulls.blocked_by_approvals=此合并请求没有通过审批。已获取审批数%d个,共需要审批数%d个。
+pulls.blocked_by_approvals=此合并请求当前还没有通过审批。已获取审批数%d个,共需要审批数%d个。
pulls.blocked_by_rejection=此合并请求有官方审核员请求的更改。
-pulls.blocked_by_official_review_requests=此合并请求已被阻止,需要一名或多名审核员审阅批准。
+pulls.blocked_by_official_review_requests=此合并请求需要一名或多名审核员审阅批准。
pulls.blocked_by_outdated_branch=此合并请求因过期而被阻止。
-pulls.blocked_by_changed_protected_files_1=此合并请求被阻止是因为修改了被保护的文件:
-pulls.blocked_by_changed_protected_files_n=此合并请求被阻止是因为修改了被保护的文件:
+pulls.blocked_by_changed_protected_files_1=此合并请求因修改了下列被保护的文件而被阻止:
+pulls.blocked_by_changed_protected_files_n=此合并请求因修改了下列被保护的文件而被阻止:
pulls.can_auto_merge_desc=该合并请求可以进行自动合并操作。
pulls.cannot_auto_merge_desc=该合并请求存在冲突,无法进行自动合并操作。
pulls.cannot_auto_merge_helper=手动合并解决此冲突
pulls.num_conflicting_files_1=%d 个冲突文件
pulls.num_conflicting_files_n=%d 个冲突文件
pulls.approve_count_1=%d 项批准
-pulls.approve_count_n=%d 批准的
+pulls.approve_count_n=%d 项批准
pulls.reject_count_1=%d 变更请求
pulls.reject_count_n=%d 变更请求
pulls.waiting_count_1=%d 个正在等待审核
pulls.waiting_count_n=%d 个正在等待审核
-pulls.wrong_commit_id=提交 id 必须在目标分支 上
+pulls.wrong_commit_id=提交 ID 必须是目标分支上的提交的 ID
pulls.no_merge_desc=由于未启用合并选项,此合并请求无法被合并。
pulls.no_merge_helper=在仓库设置中启用合并选项或者手工合并请求。
@@ -1810,9 +1829,9 @@ pulls.unrelated_histories=合并失败:两个分支没有共同历史。提示
pulls.merge_out_of_date=合并失败:在生成合并时,主分支已更新。提示:再试一次。
pulls.head_out_of_date=合并失败:在生成合并时,head 已更新。提示:再试一次。
pulls.has_merged=失败:合并请求已经被合并,您不能再次合并或更改目标分支。
-pulls.push_rejected=合并失败:推送被拒绝。审查此仓库的 Git 钩子。
+pulls.push_rejected=合并失败:推送被拒绝。请查看此仓库的 Git 钩子。
pulls.push_rejected_summary=详细拒绝信息
-pulls.push_rejected_no_message=合并失败:此推送被拒绝但未提供其他信息。 请检查此仓库的 Git Hook。
+pulls.push_rejected_no_message=推送失败:此推送被拒绝但未提供其他信息。请检查此仓库的 Git Hook
pulls.open_unmerged_pull_exists=`您不能执行重新打开操作, 因为已经存在相同的合并请求 (#%d)。`
pulls.status_checking=一些检测仍在等待运行
pulls.status_checks_success=所有检测均成功
@@ -1871,7 +1890,7 @@ milestones.title=标题
milestones.desc=描述
milestones.due_date=截止日期(可选)
milestones.clear=清除
-milestones.invalid_due_date_format=到期时间的格式必须是 'yyyy-mm-dd' 的形式。
+milestones.invalid_due_date_format=到期时间的格式必须是“yyyy-mm-dd”。
milestones.create_success=里程碑 %s 创建成功。
milestones.edit=编辑里程碑
milestones.edit_subheader=里程碑组织工单,合并请求和跟踪进度。
@@ -1929,7 +1948,7 @@ wiki.page_already_exists=相同名称的 Wiki 页面已经存在。
wiki.reserved_page=百科页面名称 %s 是被保留的。
wiki.pages=所有页面
wiki.last_updated=最后更新于 %s
-wiki.page_name_desc=输入此 Wiki 页面的名称。特殊名称有:'Home', '_Sidebar' 和 '_Footer'。
+wiki.page_name_desc=输入此 Wiki 页面的名称。特殊名称包括“Home”、“_Sidebar”和“_Footer”。
wiki.original_git_entry_tooltip=查看原始的 Git 文件而不是使用友好链接。
activity=动态
@@ -2140,7 +2159,7 @@ settings.trust_model.collaborator.long=协作者:信任协作者的签名
settings.trust_model.collaborator.desc=此仓库中协作者的有效签名将被标记为「可信」(无论它们是否是提交者),签名只符合提交者时将标记为「不可信」,都不匹配时标记为「不匹配」。
settings.trust_model.committer=提交者
settings.trust_model.committer.long=提交者: 信任与提交者相符的签名 (此特性类似 GitHub,这会强制采用 Forgejo 作为提交者和签名者)
-settings.trust_model.committer.desc=有效签名只有和提交者相匹配才会被标记为“受信任”,否则它们将被标记为“不匹配”。这强制 Forgejo 成为签名提交的提交者,而实际提交者被加上 Co-authored-by: 和 Co-committed-by: 的标记。 默认的 Forgejo 密钥必须撇撇数据库种的一名用户。
+settings.trust_model.committer.desc=有效签名只有和提交者相匹配才会被标记为“受信任”,否则它们将被标记为“不匹配”。这强制 Forgejo 成为签名提交的提交者,而实际提交者被加上 Co-authored-by: 和 Co-committed-by: 的标记。 默认的 Forgejo 密钥必须匹配数据库中的一名用户。
settings.trust_model.collaboratorcommitter=协作者+提交者
settings.trust_model.collaboratorcommitter.long=协作者+提交者:信任协作者同时是提交者的签名
settings.trust_model.collaboratorcommitter.desc=此仓库中协作者的有效签名在他同时是提交者时将被标记为「可信」,签名只匹配了提交者时将标记为「不可信」,都不匹配时标记为「不匹配」。这会强制 Forgejo 成为签名者和提交者,实际的提交者将被标记于提交消息结尾处的「Co-Authored-By:」和「Co-Committed-By:」。默认的 Forgejo 签名密钥必须匹配数据库中的一个用户密钥。
@@ -2316,7 +2335,7 @@ settings.protected_branch.delete_rule=删除规则
settings.protected_branch_can_push=允许推吗?
settings.protected_branch_can_push_yes=你可以推
settings.protected_branch_can_push_no=你不能推
-settings.branch_protection=分支 '%s ' 的分支保护
+settings.branch_protection=分支 “%s ” 的保护规则
settings.protect_this_branch=启用分支保护
settings.protect_this_branch_desc=阻止删除并限制Git推送和合并到分支。
settings.protect_disable_push=禁用推送
@@ -2359,10 +2378,10 @@ settings.require_signed_commits_desc=拒绝推送未签名或无法验证的提
settings.protect_branch_name_pattern=受保护的分支名称模式
settings.protect_branch_name_pattern_desc=分支保护的名称匹配规则。语法请参阅 文档 。如:main, release/**
settings.protect_patterns=规则
-settings.protect_protected_file_patterns=受保护的文件模式(使用分号 ';' 分隔):
-settings.protect_protected_file_patterns_desc=即使用户有权添加、编辑或删除此分支中的文件,也不允许直接更改受保护的文件。 可以使用分号 (';') 分隔多个模式。 见github.com/gobwas/glob 文档了解模式语法。例如: .drone.yml
, /docs/**/*.txt
-settings.protect_unprotected_file_patterns=不受保护的文件模式(使用分号 ';' 分隔):
-settings.protect_unprotected_file_patterns_desc=如果用户有写权限,则允许直接更改的不受保护的文件,以绕过推送限制。可以使用分号分隔多个模式 (';')。 见 github.com/gobwas/glob 文档了解模式语法。例如: .drone.yml
, /docs/**/*.txt
+settings.protect_protected_file_patterns=受保护的文件模式(使用半角分号“;”分隔):
+settings.protect_protected_file_patterns_desc=即使用户有权添加、编辑或删除此分支中的文件,也不允许直接更改受保护的文件。 可以使用半角分号(“;”)分隔多个模式。 见github.com/gobwas/glob 文档了解模式语法。例如: .drone.yml
, /docs/**/*.txt
。
+settings.protect_unprotected_file_patterns=不受保护的文件模式(使用半角分号“;”分隔):
+settings.protect_unprotected_file_patterns_desc=在用户有写权限的情况下允许绕过限制,直接修改设为不保护的文件。如有多个匹配模式,则可用半角分号(“;”)分隔开。见 github.com/gobwas/glob 的文档以了解匹配模式的格式。例子: .drone.yml
、/docs/**/*.txt
。
settings.add_protected_branch=启用保护
settings.delete_protected_branch=禁用保护
settings.update_protect_branch_success=分支保护规则 %s 更新成功。
@@ -2383,7 +2402,7 @@ settings.choose_branch=选择一个分支...
settings.no_protected_branch=没有受保护的分支
settings.edit_protected_branch=编辑
settings.protected_branch_required_rule_name=必须填写规则名称
-settings.protected_branch_duplicate_rule_name=规则名称已存在
+settings.protected_branch_duplicate_rule_name=这些分支已设有规则
settings.protected_branch_required_approvals_min=所需的审批数不能为负数。
settings.tags=标签
settings.tags.protection=Git标签保护
@@ -2423,7 +2442,7 @@ settings.lfs_findcommits=查找提交
settings.lfs_lfs_file_no_commits=没有找到关于此 LFS 文件的提交
settings.lfs_noattribute=此路径在默认分支中没有可锁定的属性
settings.lfs_delete=删除 OID 为 %s 的 LFS 文件
-settings.lfs_delete_warning=删除一个 LFS 文件可能导致检出时显示'对象不存在'的错误。确定继续吗?
+settings.lfs_delete_warning=删除一个 LFS 文件可能导致检出时出现“对象不存在”的错误。确定继续吗?
settings.lfs_findpointerfiles=查找指针文件
settings.lfs_locks=锁定
settings.lfs_invalid_locking_path=无效路径:%s
@@ -2599,7 +2618,7 @@ tag.create_success=标签"%s"已存在
topic.manage_topics=管理主题
topic.done=保存
topic.count_prompt=您最多选择25个主题
-topic.format_prompt=主题必须以字母或数字开头,可以包含连字符 ('-') 和句点 ('.'),长度不得超过35个字符。字符必须为小写。
+topic.format_prompt=主题必须以字母或数字开头,可以包含半角连字符(“-”)和句点(“.”),长度不得超过35个字符。字符必须为小写。
find_file.go_to_file=转到文件
find_file.no_matching=没有找到匹配的文件
@@ -2631,25 +2650,33 @@ pulls.nothing_to_compare_have_tag = 所选分支/标签相同。
wiki.cancel = 取消
settings.wiki_globally_editable = 允许任何人编辑百科
settings.mirror_settings.pushed_repository = 已推送的仓库
-settings.new_owner_blocked_doer = 新所有者屏蔽了你。
-settings.enter_repo_name = 输入仓库名称以确认:
+settings.new_owner_blocked_doer = 新所有者已将你拉黑。
+settings.enter_repo_name = 输入所有者和仓库的名称:
settings.wiki_rename_branch_main = 标准化 Wiki 分支名称
settings.wiki_rename_branch_main_notices_1 = 此操作无法 撤消。
settings.wiki_branch_rename_success = wiki 仓库的分支名称已成功规范化。
settings.confirm_wiki_branch_rename = 重命名 wiki 分支
-pulls.commit_ref_at = `提交 %[2]s 引用了此合并请求`
+pulls.commit_ref_at = `在提交 %[2]s 中引用了此合并请求`
desc.sha256 = SHA256
settings.ignore_stale_approvals = 忽略过时的批准
-settings.ignore_stale_approvals_desc = 不对旧的提交(过时的审查)计入已批准的合并请求数量。
+settings.ignore_stale_approvals_desc = 不对旧的提交(过时的审查)计入已批准的合并请求数量。注:如过期的审核已被取消,则无需设置。
settings.archive.mirrors_unavailable = 如果仓库已存档,则仓库镜像不再可用。
settings.wiki_rename_branch_main_notices_2 = 这将预先重命名 %s 的存储库 wiki 的内部分支。 现存的检出方式需要更新。
settings.wiki_branch_rename_failure = 无法标准化存储库 wiki 的分支名称。
-settings.add_collaborator_blocked_our = 无法添加协作者,因为仓库所有者已屏蔽他们。
-settings.add_collaborator_blocked_them = 无法添加协作者,因为已屏蔽仓库所有者。
+settings.add_collaborator_blocked_our = 因仓库所有者已将其拉黑,不能添加该用户为协作者。
+settings.add_collaborator_blocked_them = 因该用户已将仓库所有者拉黑,不能添加该用户为协作者。
settings.units.units = 仓库单元
pulls.fast_forward_only_merge_pull_request = 仅快速向前
settings.units.overview = 概览
settings.units.add_more = 添加更多
+file_follow = 跟随符号链接
+pulls.reopen_failed.head_branch = 因头部分支不再存在,该合并请求不能再被重新打开。
+pulls.reopen_failed.base_branch = 因基础分支不再存在,该合并请求不能再被重新打开。
+pulls.made_using_agit = AGit
+activity.navbar.pulse = 动态
+activity.navbar.code_frequency = 代码频率
+activity.navbar.recent_commits = 近期提交
+pulls.agit_explanation = 该合并请求是用 AGit 创建的。AGit 是一种可以让贡献者直接通过 “git push” 提出更改代码而不需要派生或建立新分支。
[graphs]
component_loading=正在加载 %s...
@@ -2657,6 +2684,8 @@ component_loading_failed=无法加载 %s
component_loading_info=这可能需要一点…
component_failed_to_load=意外的错误发生了。
contributors.what=贡献
+recent_commits.what = 近期提交
+code_frequency.what = 代码频率
[org]
org_name_holder=组织名称
@@ -2825,12 +2854,12 @@ dashboard.cron.error=任务中的错误: %s: %[3]s
dashboard.cron.finished=任务:%[1]s 已经完成
dashboard.delete_inactive_accounts=删除所有未激活的帐户
dashboard.delete_inactive_accounts.started=删除所有未激活的账户任务已启动。
-dashboard.delete_repo_archives=删除所有代码库的存档 (ZIP、 TAR、GZ, 等...)
+dashboard.delete_repo_archives=删除所有仓库的存档(ZIP、 TAR.GZ 等…)
dashboard.delete_repo_archives.started=删除所有仓库存档任务已启动。
dashboard.delete_missing_repos=删除所有丢失 Git 文件的仓库
dashboard.delete_missing_repos.started=删除所有丢失 Git 文件的仓库任务已启动。
dashboard.delete_generated_repository_avatars=删除生成的仓库头像
-dashboard.sync_repo_branches=将缺少的分支从 git 数据同步到数据库
+dashboard.sync_repo_branches=将缺少的分支从 Git 数据同步到数据库
dashboard.sync_repo_tags=从 git 数据同步标签到数据库
dashboard.update_mirrors=更新镜像仓库
dashboard.repo_health_check=健康检查所有仓库
@@ -2839,8 +2868,8 @@ dashboard.archive_cleanup=删除旧的仓库存档
dashboard.deleted_branches_cleanup=清理已删除的分支
dashboard.update_migration_poster_id=更新迁移的发表者ID
dashboard.git_gc_repos=对仓库进行垃圾回收
-dashboard.resync_all_sshkeys=使用 Forgejo 的 SSH 密钥更新「.ssh/authorized_keys」文件。
-dashboard.resync_all_sshprincipals=使用 Forgejo 的 SSH 规则更新「.ssh/authorized_principals」文件。
+dashboard.resync_all_sshkeys=使用 Forgejo 的 SSH 密钥更新“.ssh/authorized_keys”文件。
+dashboard.resync_all_sshprincipals=使用 Forgejo 的 SSH 规则更新“.ssh/authorized_principals”文件。
dashboard.resync_all_hooks=重新同步所有仓库的 pre-receive、update 和 post-receive 钩子
dashboard.reinit_missing_repos=重新初始化所有丢失的 Git 仓库存在的记录
dashboard.sync_external_users=同步外部用户数据
@@ -3047,7 +3076,7 @@ auths.smtp_auth=SMTP 认证类型
auths.smtphost=SMTP 主机地址
auths.smtpport=SMTP 主机端口
auths.allowed_domains=域名白名单
-auths.allowed_domains_helper=置空将允许所有域名,每个域名用逗号分隔。
+auths.allowed_domains_helper=每个域名用逗号分隔,如要允许任意域名则留空。
auths.skip_tls_verify=忽略 TLS 验证
auths.force_smtps=强制 SMTPS
auths.force_smtps_helper=SMTPS 始终用于 465 端口。设置此项会强制其他端口也使用 SMTPS。(否则,如果主机支持,将在其他端口上使用 STARTTLS。)
@@ -3094,7 +3123,7 @@ auths.tips=帮助提示
auths.tips.oauth2.general=OAuth2 认证
auths.tips.oauth2.general.tip=当注册新的 OAuth2 身份验证时,回调/重定向 URL 应该是:
auths.tip.oauth2_provider=OAuth2 提供程序
-auths.tip.bitbucket=`在 https://bitbucket.org/account/user//oauth-consumers/new 注册新的 OAuth 消费者同时添加权限"帐户"-"读"`
+auths.tip.bitbucket=`在 https://bitbucket.org/account/user//oauth-consumers/new 注册新的 OAuth consumer 并添加权限“Account” 和 “Read”`
auths.tip.nextcloud=使用下面的菜单“设置(Settings) -> 安全(Security) -> OAuth 2.0 client”在您的实例上注册一个新的 OAuth 客户端。
auths.tip.dropbox=在 https://www.dropbox.com/developers/apps 上创建一个新的应用程序
auths.tip.facebook=`在 https://developers.facebook.com/apps 注册一个新的应用,并添加产品"Facebook 登录"`
@@ -3149,7 +3178,7 @@ config.ssh_port=端口
config.ssh_listen_port=监听端口
config.ssh_root_path=根目录
config.ssh_key_test_path=密钥测试路径
-config.ssh_keygen_path=密钥生成器('ssh-keygen')路径
+config.ssh_keygen_path=密钥生成器(“ssh-keygen”)路径
config.ssh_minimum_key_size_check=密钥最小长度检查
config.ssh_minimum_key_sizes=密钥最小长度限制
@@ -3325,7 +3354,9 @@ self_check.database_fix_mssql = 对于 MSSQL 用户,目前您只能通过SQL
self_check.no_problem_found=尚未发现问题。
self_check.database_collation_mismatch=期望数据库使用的校验方式:%s
self_check.database_collation_case_insensitive=数据库正在使用一个校验 %s, 这是一个不敏感的校验. 虽然Gitea可以与它合作,但可能有一些罕见的情况不如预期的那样起作用。
+self_check.database_inconsistent_collation_columns=数据库正在使用%s的排序规则,但是这些列使用了不匹配的排序规则。这可能会造成一些意外问题。
self_check.database_fix_mysql=对于MySQL/MariaDB用户,您可以使用“gitea doctor convert”命令来解决校验问题。 或者您也可以通过 "ALTER ... COLLATE ..." 这样的SQL 来手动解决这个问题。
+self_check.database_fix_mssql=对于MSSQL用户,您现在只能通过"ALTER ... COLLATE ..."SQLs手动解决这个问题。
[action]
create_repo=创建了仓库 %s
@@ -3406,10 +3437,10 @@ error.extract_sign=无法提取签名
error.generate_hash=无法生成提交的哈希
error.no_committer_account=没有帐户链接到提交者的电子邮件
error.no_gpg_keys_found=找不到此签名对应的密钥
-error.not_signed_commit=未签名的提交
+error.not_signed_commit=提交未签名
error.failed_retrieval_gpg_keys=找不到任何与该提交者账号相关的密钥
-error.probable_bad_signature=警告!虽然数据库中有一个此ID的密钥,但它没有验证此提交!此提交是有疑问的。
-error.probable_bad_default_signature=警告!虽然默认密钥拥有此ID,但它没有验证此提交!此提交是有疑问的。
+error.probable_bad_signature=警告!虽然数据库中有一个此ID对应的密钥,但这个密钥不能验证此提交!该提交可疑。
+error.probable_bad_default_signature=警告!虽然默认密钥拥有此ID,但不能验证此提交!该提交可疑。
[units]
unit=单元
@@ -3677,6 +3708,7 @@ variables.creation.failed=添加变量失败。
variables.creation.success=变量 “%s” 添加成功。
variables.update.failed=编辑变量失败。
variables.update.success=该变量已被编辑。
+runs.workflow = 工作流
[projects]
type-1.display_name=个人项目
diff --git a/package-lock.json b/package-lock.json
index c833670f72..1df2a8b941 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,7 @@
"@citation-js/plugin-csl": "0.7.6",
"@citation-js/plugin-software-formats": "0.6.1",
"@claviska/jquery-minicolors": "2.3.6",
- "@github/markdown-toolbar-element": "2.2.1",
+ "@github/markdown-toolbar-element": "2.2.3",
"@github/relative-time-element": "4.3.1",
"@github/text-expander-element": "2.6.1",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
@@ -18,13 +18,12 @@
"@webcomponents/custom-elements": "1.6.0",
"add-asset-webpack-plugin": "2.0.1",
"ansi_up": "6.0.2",
- "asciinema-player": "3.6.4",
- "chart.js": "4.4.1",
+ "asciinema-player": "3.7.0",
+ "chart.js": "4.4.2",
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.0.1",
- "clippie": "4.0.6",
+ "clippie": "4.0.7",
"css-loader": "6.10.0",
- "css-variables-parser": "1.0.1",
"dayjs": "1.11.10",
"dropzone": "6.0.0-beta.2",
"easymde": "2.18.0",
@@ -37,16 +36,16 @@
"katex": "0.16.9",
"license-checker-webpack-plugin": "0.2.1",
"mermaid": "10.8.0",
- "mini-css-extract-plugin": "2.8.0",
+ "mini-css-extract-plugin": "2.8.1",
"minimatch": "9.0.3",
"monaco-editor": "0.46.0",
"monaco-editor-webpack-plugin": "7.1.0",
"pdfobject": "2.3.0",
"postcss": "8.4.35",
- "postcss-loader": "8.1.0",
+ "postcss-loader": "8.1.1",
"pretty-ms": "9.0.0",
"sortablejs": "1.15.2",
- "swagger-ui-dist": "5.11.6",
+ "swagger-ui-dist": "5.11.8",
"tailwindcss": "3.4.1",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0",
@@ -54,25 +53,25 @@
"toastify-js": "1.12.0",
"tributejs": "5.1.3",
"uint8-to-base64": "0.2.0",
- "vue": "3.4.19",
+ "vue": "3.4.21",
"vue-bar-graph": "2.0.0",
"vue-chartjs": "5.3.0",
"vue-loader": "17.4.2",
"vue3-calendar-heatmap": "2.0.5",
- "webpack": "5.90.2",
+ "webpack": "5.90.3",
"webpack-cli": "5.1.4",
"wrap-ansi": "9.0.0"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.1.0",
- "@playwright/test": "1.41.2",
+ "@playwright/test": "1.42.1",
"@stoplight/spectral-cli": "6.11.0",
- "@stylistic/eslint-plugin-js": "1.6.2",
- "@stylistic/stylelint-plugin": "2.0.0",
+ "@stylistic/eslint-plugin-js": "1.6.3",
+ "@stylistic/stylelint-plugin": "2.1.0",
"@vitejs/plugin-vue": "5.0.4",
- "eslint": "8.56.0",
+ "eslint": "8.57.0",
"eslint-plugin-array-func": "4.0.0",
- "eslint-plugin-github": "4.10.1",
+ "eslint-plugin-github": "4.10.2",
"eslint-plugin-i": "2.29.1",
"eslint-plugin-jquery": "1.5.1",
"eslint-plugin-no-jquery": "2.7.0",
@@ -82,7 +81,7 @@
"eslint-plugin-unicorn": "51.0.1",
"eslint-plugin-vitest": "0.3.22",
"eslint-plugin-vitest-globals": "1.4.0",
- "eslint-plugin-vue": "9.21.1",
+ "eslint-plugin-vue": "9.22.0",
"eslint-plugin-vue-scoped-css": "2.7.2",
"eslint-plugin-wc": "2.0.4",
"jsdom": "24.0.0",
@@ -94,7 +93,7 @@
"svgo": "3.2.0",
"updates": "15.1.2",
"vite-string-plugin": "1.1.5",
- "vitest": "1.2.2"
+ "vitest": "1.3.1"
},
"engines": {
"node": ">= 18.0.0"
@@ -296,9 +295,9 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.23.9",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz",
- "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==",
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz",
+ "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -307,9 +306,9 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.23.9",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
- "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz",
+ "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -466,9 +465,9 @@
}
},
"node_modules/@csstools/css-parser-algorithms": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.5.0.tgz",
- "integrity": "sha512-abypo6m9re3clXA00eu5syw+oaPHbJTPapu9C4pzNsJ4hdZDzushT50Zhu+iIYXgEe1CxnRMn7ngsbV+MLrlpQ==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.0.tgz",
+ "integrity": "sha512-YfEHq0eRH98ffb5/EsrrDspVWAuph6gDggAE74ZtjecsmyyWpW768hOyiONa8zwWGbIWYfa2Xp4tRTrpQQ00CQ==",
"dev": true,
"funding": [
{
@@ -507,9 +506,9 @@
}
},
"node_modules/@csstools/media-query-list-parser": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.7.tgz",
- "integrity": "sha512-lHPKJDkPUECsyAvD60joYfDmp8UERYxHGkFfyLJFTVK/ERJe0sVlIFLXU5XFxdjNDTerp5L4KeaKG+Z5S94qxQ==",
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.8.tgz",
+ "integrity": "sha512-DiD3vG5ciNzeuTEoh74S+JMjQDs50R3zlxHnBnfd04YYfA/kh2KiBCGhzqLxlJcNq+7yNQ3stuZZYLX6wK/U2g==",
"dev": true,
"funding": [
{
@@ -525,14 +524,14 @@
"node": "^14 || ^16 || >=18"
},
"peerDependencies": {
- "@csstools/css-parser-algorithms": "^2.5.0",
+ "@csstools/css-parser-algorithms": "^2.6.0",
"@csstools/css-tokenizer": "^2.2.3"
}
},
"node_modules/@csstools/selector-specificity": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.1.tgz",
- "integrity": "sha512-NPljRHkq4a14YzZ3YD406uaxh7s0g6eAq3L9aLOWywoqe8PkYamAvtsh7KNX6c++ihDrJ0RiU+/z7rGnhlZ5ww==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.2.tgz",
+ "integrity": "sha512-RpHaZ1h9LE7aALeQXmXrJkRG84ZxIsctEN2biEUmFyKpzFM3zZ35eUMcIzZFsw/2olQE6v69+esEqU2f1MKycg==",
"dev": true,
"funding": [
{
@@ -1012,9 +1011,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
- "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
+ "version": "8.57.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
+ "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1032,9 +1031,9 @@
"integrity": "sha512-gwxPzLw8XKecy1nP63i9lOBritS3bWmxl02UX6G0TwMQZbMem1BCS1tEZgYd3mkrkiDrUMWaX+DbFCuDFo3K+A=="
},
"node_modules/@github/markdown-toolbar-element": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-2.2.1.tgz",
- "integrity": "sha512-ap+ulyqzG3aVqwKsKjbDdYwM75TQXZpPtmIuPwm+54OTgcC96267oX3cEqd1wSqGsH7O5PonZ//fE9jH7Q4JkA=="
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-2.2.3.tgz",
+ "integrity": "sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A=="
},
"node_modules/@github/relative-time-element": {
"version": "4.3.1",
@@ -1142,11 +1141,6 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
- },
"node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@@ -1206,13 +1200,13 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
- "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dependencies": {
- "@jridgewell/set-array": "^1.0.1",
+ "@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.9"
+ "@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
@@ -1227,9 +1221,9 @@
}
},
"node_modules/@jridgewell/set-array": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
- "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"engines": {
"node": ">=6.0.0"
}
@@ -1249,9 +1243,9 @@
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.22",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
- "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
@@ -1389,12 +1383,12 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.41.2",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz",
- "integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==",
+ "version": "1.42.1",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz",
+ "integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==",
"dev": true,
"dependencies": {
- "playwright": "1.41.2"
+ "playwright": "1.42.1"
},
"bin": {
"playwright": "cli.js"
@@ -1465,9 +1459,9 @@
"dev": true
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.11.0.tgz",
- "integrity": "sha512-BV+u2QSfK3i1o6FucqJh5IK9cjAU6icjFFhvknzFgu472jzl0bBojfDAkJLBEsHFMo+YZg6rthBvBBt8z12IBQ==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
+ "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==",
"cpu": [
"arm"
],
@@ -1478,9 +1472,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.11.0.tgz",
- "integrity": "sha512-0ij3iw7sT5jbcdXofWO2NqDNjSVVsf6itcAkV2I6Xsq4+6wjW1A8rViVB67TfBEan7PV2kbLzT8rhOVWLI2YXw==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz",
+ "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==",
"cpu": [
"arm64"
],
@@ -1491,9 +1485,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.11.0.tgz",
- "integrity": "sha512-yPLs6RbbBMupArf6qv1UDk6dzZvlH66z6NLYEwqTU0VHtss1wkI4UYeeMS7TVj5QRVvaNAWYKP0TD/MOeZ76Zg==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz",
+ "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==",
"cpu": [
"arm64"
],
@@ -1504,9 +1498,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.11.0.tgz",
- "integrity": "sha512-OvqIgwaGAwnASzXaZEeoJY3RltOFg+WUbdkdfoluh2iqatd090UeOG3A/h0wNZmE93dDew9tAtXgm3/+U/B6bw==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz",
+ "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==",
"cpu": [
"x64"
],
@@ -1517,9 +1511,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.11.0.tgz",
- "integrity": "sha512-X17s4hZK3QbRmdAuLd2EE+qwwxL8JxyVupEqAkxKPa/IgX49ZO+vf0ka69gIKsaYeo6c1CuwY3k8trfDtZ9dFg==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz",
+ "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==",
"cpu": [
"arm"
],
@@ -1530,9 +1524,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.11.0.tgz",
- "integrity": "sha512-673Lu9EJwxVB9NfYeA4AdNu0FOHz7g9t6N1DmT7bZPn1u6bTF+oZjj+fuxUcrfxWXE0r2jxl5QYMa9cUOj9NFg==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz",
+ "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==",
"cpu": [
"arm64"
],
@@ -1543,9 +1537,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.11.0.tgz",
- "integrity": "sha512-yFW2msTAQNpPJaMmh2NpRalr1KXI7ZUjlN6dY/FhWlOclMrZezm5GIhy3cP4Ts2rIAC+IPLAjNibjp1BsxCVGg==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz",
+ "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==",
"cpu": [
"arm64"
],
@@ -1556,9 +1550,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.11.0.tgz",
- "integrity": "sha512-kKT9XIuhbvYgiA3cPAGntvrBgzhWkGpBMzuk1V12Xuoqg7CI41chye4HU0vLJnGf9MiZzfNh4I7StPeOzOWJfA==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz",
+ "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==",
"cpu": [
"riscv64"
],
@@ -1569,9 +1563,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.11.0.tgz",
- "integrity": "sha512-6q4ESWlyTO+erp1PSCmASac+ixaDv11dBk1fqyIuvIUc/CmRAX2Zk+2qK1FGo5q7kyDcjHCFVwgGFCGIZGVwCA==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz",
+ "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==",
"cpu": [
"x64"
],
@@ -1582,9 +1576,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.11.0.tgz",
- "integrity": "sha512-vIAQUmXeMLmaDN78HSE4Kh6xqof2e3TJUKr+LPqXWU4NYNON0MDN9h2+t4KHrPAQNmU3w1GxBQ/n01PaWFwa5w==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz",
+ "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==",
"cpu": [
"x64"
],
@@ -1595,9 +1589,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.11.0.tgz",
- "integrity": "sha512-LVXo9dDTGPr0nezMdqa1hK4JeoMZ02nstUxGYY/sMIDtTYlli1ZxTXBYAz3vzuuvKO4X6NBETciIh7N9+abT1g==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz",
+ "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==",
"cpu": [
"arm64"
],
@@ -1608,9 +1602,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.11.0.tgz",
- "integrity": "sha512-xZVt6K70Gr3I7nUhug2dN6VRR1ibot3rXqXS3wo+8JP64t7djc3lBFyqO4GiVrhNaAIhUCJtwQ/20dr0h0thmQ==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz",
+ "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==",
"cpu": [
"ia32"
],
@@ -1621,9 +1615,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.11.0.tgz",
- "integrity": "sha512-f3I7h9oTg79UitEco9/2bzwdciYkWr8pITs3meSDSlr1TdvQ7IxkQaaYN2YqZXX5uZhiYL+VuYDmHwNzhx+HOg==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz",
+ "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==",
"cpu": [
"x64"
],
@@ -2091,9 +2085,9 @@
"dev": true
},
"node_modules/@stylistic/eslint-plugin-js": {
- "version": "1.6.2",
- "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.6.2.tgz",
- "integrity": "sha512-ndT6X2KgWGxv8101pdMOxL8pihlYIHcOv3ICd70cgaJ9exwkPn8hJj4YQwslxoAlre1TFHnXd/G1/hYXgDrjIA==",
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.6.3.tgz",
+ "integrity": "sha512-ckdz51oHxD2FaxgY2piJWJVJiwgp8Uu96s+as2yB3RMwavn3nHBrpliVukXY9S/DmMicPRB2+H8nBk23GDG+qA==",
"dev": true,
"dependencies": {
"@types/eslint": "^8.56.2",
@@ -2110,19 +2104,19 @@
}
},
"node_modules/@stylistic/stylelint-plugin": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-2.0.0.tgz",
- "integrity": "sha512-dHKuT6PGd1WGZLOTuozAM7GdQzdmlmnFXYzvV1jYJXXpcCpV/OJ3+n8TXpMkoOeKHpJydY43EOoZTO1W/FOA4Q==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-2.1.0.tgz",
+ "integrity": "sha512-mUZEW9uImHSbXeyzbFmHb8WPBv56UTaEnWL/3dGdAiJ54C+8GTfDwDVdI6gbqT9wV7zynkPu7tCXc5746H9mZQ==",
"dev": true,
"dependencies": {
- "@csstools/css-parser-algorithms": "^2.3.2",
- "@csstools/css-tokenizer": "^2.2.1",
- "@csstools/media-query-list-parser": "^2.1.5",
+ "@csstools/css-parser-algorithms": "^2.5.0",
+ "@csstools/css-tokenizer": "^2.2.3",
+ "@csstools/media-query-list-parser": "^2.1.7",
"is-plain-object": "^5.0.0",
- "postcss-selector-parser": "^6.0.13",
+ "postcss-selector-parser": "^6.0.15",
"postcss-value-parser": "^4.2.0",
"style-search": "^0.1.0",
- "stylelint": "^16.0.2"
+ "stylelint": "^16.2.1"
},
"engines": {
"node": "^18.12 || >=20.9"
@@ -2189,9 +2183,9 @@
}
},
"node_modules/@types/eslint": {
- "version": "8.56.2",
- "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz",
- "integrity": "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==",
+ "version": "8.56.5",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.5.tgz",
+ "integrity": "sha512-u5/YPJHo1tvkSF2CE0USEkxon82Z5DBy2xR+qfyYNszpX9qcs4sT6uq2kBbj4BXY1+DBGDPnrhMZV3pKWGNukw==",
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
@@ -2241,9 +2235,9 @@
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
},
"node_modules/@types/node": {
- "version": "20.11.19",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz",
- "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==",
+ "version": "20.11.24",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz",
+ "integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==",
"dependencies": {
"undici-types": "~5.26.4"
}
@@ -2261,9 +2255,9 @@
"dev": true
},
"node_modules/@types/semver": {
- "version": "7.5.7",
- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz",
- "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==",
+ "version": "7.5.8",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
+ "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
"dev": true
},
"node_modules/@types/tern": {
@@ -2286,16 +2280,16 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
- "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.0.tgz",
+ "integrity": "sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
- "@typescript-eslint/scope-manager": "6.21.0",
- "@typescript-eslint/type-utils": "6.21.0",
- "@typescript-eslint/utils": "6.21.0",
- "@typescript-eslint/visitor-keys": "6.21.0",
+ "@typescript-eslint/scope-manager": "7.1.0",
+ "@typescript-eslint/type-utils": "7.1.0",
+ "@typescript-eslint/utils": "7.1.0",
+ "@typescript-eslint/visitor-keys": "7.1.0",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
@@ -2311,8 +2305,8 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
- "eslint": "^7.0.0 || ^8.0.0"
+ "@typescript-eslint/parser": "^7.0.0",
+ "eslint": "^8.56.0"
},
"peerDependenciesMeta": {
"typescript": {
@@ -2321,15 +2315,15 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
- "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.1.0.tgz",
+ "integrity": "sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==",
"dev": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "6.21.0",
- "@typescript-eslint/types": "6.21.0",
- "@typescript-eslint/typescript-estree": "6.21.0",
- "@typescript-eslint/visitor-keys": "6.21.0",
+ "@typescript-eslint/scope-manager": "7.1.0",
+ "@typescript-eslint/types": "7.1.0",
+ "@typescript-eslint/typescript-estree": "7.1.0",
+ "@typescript-eslint/visitor-keys": "7.1.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2340,7 +2334,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^7.0.0 || ^8.0.0"
+ "eslint": "^8.56.0"
},
"peerDependenciesMeta": {
"typescript": {
@@ -2349,13 +2343,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
- "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.0.tgz",
+ "integrity": "sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.21.0",
- "@typescript-eslint/visitor-keys": "6.21.0"
+ "@typescript-eslint/types": "7.1.0",
+ "@typescript-eslint/visitor-keys": "7.1.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -2366,13 +2360,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz",
- "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.1.0.tgz",
+ "integrity": "sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==",
"dev": true,
"dependencies": {
- "@typescript-eslint/typescript-estree": "6.21.0",
- "@typescript-eslint/utils": "6.21.0",
+ "@typescript-eslint/typescript-estree": "7.1.0",
+ "@typescript-eslint/utils": "7.1.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
@@ -2384,7 +2378,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^7.0.0 || ^8.0.0"
+ "eslint": "^8.56.0"
},
"peerDependenciesMeta": {
"typescript": {
@@ -2393,9 +2387,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
- "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz",
+ "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -2406,13 +2400,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
- "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.0.tgz",
+ "integrity": "sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.21.0",
- "@typescript-eslint/visitor-keys": "6.21.0",
+ "@typescript-eslint/types": "7.1.0",
+ "@typescript-eslint/visitor-keys": "7.1.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -2434,17 +2428,17 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
- "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.0.tgz",
+ "integrity": "sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
- "@typescript-eslint/scope-manager": "6.21.0",
- "@typescript-eslint/types": "6.21.0",
- "@typescript-eslint/typescript-estree": "6.21.0",
+ "@typescript-eslint/scope-manager": "7.1.0",
+ "@typescript-eslint/types": "7.1.0",
+ "@typescript-eslint/typescript-estree": "7.1.0",
"semver": "^7.5.4"
},
"engines": {
@@ -2455,16 +2449,16 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^7.0.0 || ^8.0.0"
+ "eslint": "^8.56.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
- "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz",
+ "integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/types": "7.1.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -2495,13 +2489,13 @@
}
},
"node_modules/@vitest/expect": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.2.tgz",
- "integrity": "sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz",
+ "integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==",
"dev": true,
"dependencies": {
- "@vitest/spy": "1.2.2",
- "@vitest/utils": "1.2.2",
+ "@vitest/spy": "1.3.1",
+ "@vitest/utils": "1.3.1",
"chai": "^4.3.10"
},
"funding": {
@@ -2509,12 +2503,12 @@
}
},
"node_modules/@vitest/runner": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.2.tgz",
- "integrity": "sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz",
+ "integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==",
"dev": true,
"dependencies": {
- "@vitest/utils": "1.2.2",
+ "@vitest/utils": "1.3.1",
"p-limit": "^5.0.0",
"pathe": "^1.1.1"
},
@@ -2550,9 +2544,9 @@
}
},
"node_modules/@vitest/snapshot": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.2.tgz",
- "integrity": "sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz",
+ "integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==",
"dev": true,
"dependencies": {
"magic-string": "^0.30.5",
@@ -2576,9 +2570,9 @@
}
},
"node_modules/@vitest/spy": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.2.tgz",
- "integrity": "sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz",
+ "integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==",
"dev": true,
"dependencies": {
"tinyspy": "^2.2.0"
@@ -2588,9 +2582,9 @@
}
},
"node_modules/@vitest/utils": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.2.tgz",
- "integrity": "sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz",
+ "integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==",
"dev": true,
"dependencies": {
"diff-sequences": "^29.6.3",
@@ -2618,39 +2612,39 @@
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz",
- "integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz",
+ "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==",
"dependencies": {
"@babel/parser": "^7.23.9",
- "@vue/shared": "3.4.19",
+ "@vue/shared": "3.4.21",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-dom": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz",
- "integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz",
+ "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==",
"dependencies": {
- "@vue/compiler-core": "3.4.19",
- "@vue/shared": "3.4.19"
+ "@vue/compiler-core": "3.4.21",
+ "@vue/shared": "3.4.21"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.19.tgz",
- "integrity": "sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz",
+ "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==",
"dependencies": {
"@babel/parser": "^7.23.9",
- "@vue/compiler-core": "3.4.19",
- "@vue/compiler-dom": "3.4.19",
- "@vue/compiler-ssr": "3.4.19",
- "@vue/shared": "3.4.19",
+ "@vue/compiler-core": "3.4.21",
+ "@vue/compiler-dom": "3.4.21",
+ "@vue/compiler-ssr": "3.4.21",
+ "@vue/shared": "3.4.21",
"estree-walker": "^2.0.2",
- "magic-string": "^0.30.6",
- "postcss": "^8.4.33",
+ "magic-string": "^0.30.7",
+ "postcss": "^8.4.35",
"source-map-js": "^1.0.2"
}
},
@@ -2666,57 +2660,57 @@
}
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.19.tgz",
- "integrity": "sha512-P0PLKC4+u4OMJ8sinba/5Z/iDT84uMRRlrWzadgLA69opCpI1gG4N55qDSC+dedwq2fJtzmGald05LWR5TFfLw==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz",
+ "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==",
"dependencies": {
- "@vue/compiler-dom": "3.4.19",
- "@vue/shared": "3.4.19"
+ "@vue/compiler-dom": "3.4.21",
+ "@vue/shared": "3.4.21"
}
},
"node_modules/@vue/reactivity": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.19.tgz",
- "integrity": "sha512-+VcwrQvLZgEclGZRHx4O2XhyEEcKaBi50WbxdVItEezUf4fqRh838Ix6amWTdX0CNb/b6t3Gkz3eOebfcSt+UA==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz",
+ "integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==",
"dependencies": {
- "@vue/shared": "3.4.19"
+ "@vue/shared": "3.4.21"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.19.tgz",
- "integrity": "sha512-/Z3tFwOrerJB/oyutmJGoYbuoadphDcJAd5jOuJE86THNZji9pYjZroQ2NFsZkTxOq0GJbb+s2kxTYToDiyZzw==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz",
+ "integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==",
"dependencies": {
- "@vue/reactivity": "3.4.19",
- "@vue/shared": "3.4.19"
+ "@vue/reactivity": "3.4.21",
+ "@vue/shared": "3.4.21"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.19.tgz",
- "integrity": "sha512-IyZzIDqfNCF0OyZOauL+F4yzjMPN2rPd8nhqPP2N1lBn3kYqJpPHHru+83Rkvo2lHz5mW+rEeIMEF9qY3PB94g==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz",
+ "integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==",
"dependencies": {
- "@vue/runtime-core": "3.4.19",
- "@vue/shared": "3.4.19",
+ "@vue/runtime-core": "3.4.21",
+ "@vue/shared": "3.4.21",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.19.tgz",
- "integrity": "sha512-eAj2p0c429RZyyhtMRnttjcSToch+kTWxFPHlzGMkR28ZbF1PDlTcmGmlDxccBuqNd9iOQ7xPRPAGgPVj+YpQw==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz",
+ "integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==",
"dependencies": {
- "@vue/compiler-ssr": "3.4.19",
- "@vue/shared": "3.4.19"
+ "@vue/compiler-ssr": "3.4.21",
+ "@vue/shared": "3.4.21"
},
"peerDependencies": {
- "vue": "3.4.19"
+ "vue": "3.4.21"
}
},
"node_modules/@vue/shared": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
- "integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw=="
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
+ "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
},
"node_modules/@webassemblyjs/ast": {
"version": "1.11.6",
@@ -3269,9 +3263,9 @@
}
},
"node_modules/asciinema-player": {
- "version": "3.6.4",
- "resolved": "https://registry.npmjs.org/asciinema-player/-/asciinema-player-3.6.4.tgz",
- "integrity": "sha512-yyMHTjoDuz82/BYPrc3J5GjOtlNI5t2VHTZWss8BmRcY/6nXv+Vilip+XzwIyRBa3/2SSn9FJIEg8bJXBc9o4w==",
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/asciinema-player/-/asciinema-player-3.7.0.tgz",
+ "integrity": "sha512-0RDc4j7TkjyhAwxkDe3vNqjAcizc7tubYW2VZi/06csY8iAoSC2uRvSyfNzh9ONDZu8pdf0bZJ91A84Gexb3tg==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"solid-js": "^1.3.0"
@@ -3350,10 +3344,13 @@
}
},
"node_modules/available-typed-arrays": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz",
- "integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+ "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
"dev": true,
+ "dependencies": {
+ "possible-typed-array-names": "^1.0.0"
+ },
"engines": {
"node": ">= 0.4"
},
@@ -3566,9 +3563,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001587",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz",
- "integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==",
+ "version": "1.0.30001591",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz",
+ "integrity": "sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==",
"funding": [
{
"type": "opencollective",
@@ -3627,14 +3624,14 @@
}
},
"node_modules/chart.js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz",
- "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==",
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz",
+ "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
- "pnpm": ">=7"
+ "pnpm": ">=8"
}
},
"node_modules/chartjs-adapter-dayjs-4": {
@@ -3756,9 +3753,9 @@
}
},
"node_modules/clippie": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/clippie/-/clippie-4.0.6.tgz",
- "integrity": "sha512-E5EtOw8iMH0enuL3kBZJ+Po1nPnBD7O+HHpIaWpfWgHbHmdoOQoERrlNOcEEn2yMJQ98WqeKacouAcnRXn7oWA=="
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/clippie/-/clippie-4.0.7.tgz",
+ "integrity": "sha512-xmIARCRFQUoCR0kNNu4uIv5f/IFqM1fUts0vQwt1hQEdCPEqs3/dTaG38WenlWOgs3Fcn73PBYXbPIVSlOgFRw=="
},
"node_modules/cliui": {
"version": "7.0.4",
@@ -4034,35 +4031,6 @@
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
- "node_modules/css-variables-parser": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/css-variables-parser/-/css-variables-parser-1.0.1.tgz",
- "integrity": "sha512-GWaqrwGtAWVr/yjjE17iyvbcy+W3voe0vko1/xLCwFeYd3kTLstzUdVH+g5TTXejrtlsb1FS4L9rP6PmeTa8wQ==",
- "dependencies": {
- "postcss": "^7.0.36"
- }
- },
- "node_modules/css-variables-parser/node_modules/picocolors": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
- "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
- },
- "node_modules/css-variables-parser/node_modules/postcss": {
- "version": "7.0.39",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
- "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
- "dependencies": {
- "picocolors": "^0.2.1",
- "source-map": "^0.6.1"
- },
- "engines": {
- "node": ">=6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- }
- },
"node_modules/css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
@@ -4842,9 +4810,9 @@
}
},
"node_modules/dompurify": {
- "version": "3.0.8",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.8.tgz",
- "integrity": "sha512-b7uwreMYL2eZhrSCRC4ahLTeZcPZxSmYfmcQGXGkXiZSNW1X85v+SDM5KsWcpivIiUBH47Ji7NtyUdpLeF5JZQ=="
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.9.tgz",
+ "integrity": "sha512-uyb4NDIvQ3hRn6NiC+SIFaP4mJ/MdXlvtunaqK9Bn6dD3RuB/1S/gasEjDHD8eiaqdSael2vBv+hOs7Y+jhYOQ=="
},
"node_modules/domutils": {
"version": "3.1.0",
@@ -4887,19 +4855,19 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.4.671",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.671.tgz",
- "integrity": "sha512-UUlE+/rWbydmp+FW8xlnnTA5WNA0ZZd2XL8CuMS72rh+k4y1f8+z6yk3UQhEwqHQWj6IBdL78DwWOdGMvYfQyA=="
+ "version": "1.4.690",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.690.tgz",
+ "integrity": "sha512-+2OAGjUx68xElQhydpcbqH50hE8Vs2K6TkAeLhICYfndb67CVH0UsZaijmRUE3rHlIxU1u0jxwhgVe6fK3YANA=="
},
"node_modules/elkjs": {
- "version": "0.9.1",
- "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.1.tgz",
- "integrity": "sha512-JWKDyqAdltuUcyxaECtYG6H4sqysXSLeoXuGUBfRNESMTkj+w+qdb0jya8Z/WI0jVd03WQtCGhS6FOFtlhD5FQ=="
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.2.tgz",
+ "integrity": "sha512-2Y/RaA1pdgSHpY0YG4TYuYCD2wh97CRvu22eLG3Kz0pgQ/6KbIFTxsTnDc4MH/6hFlg2L/9qXrDMG0nMjP63iw=="
},
"node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"node_modules/emojis-list": {
"version": "3.0.0",
@@ -4910,9 +4878,9 @@
}
},
"node_modules/enhanced-resolve": {
- "version": "5.15.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
- "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
+ "version": "5.15.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.1.tgz",
+ "integrity": "sha512-3d3JRbwsCLJsYgvb6NuWEG44jjPSOMuS73L/6+7BZuoKm3W+qXnSoIYVHi8dG7Qcg4inAY4jbzkZ7MnskePeDg==",
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
@@ -4960,18 +4928,18 @@
}
},
"node_modules/es-abstract": {
- "version": "1.22.4",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.4.tgz",
- "integrity": "sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==",
+ "version": "1.22.5",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz",
+ "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==",
"dev": true,
"dependencies": {
"array-buffer-byte-length": "^1.0.1",
"arraybuffer.prototype.slice": "^1.0.3",
- "available-typed-arrays": "^1.0.6",
+ "available-typed-arrays": "^1.0.7",
"call-bind": "^1.0.7",
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
- "es-set-tostringtag": "^2.0.2",
+ "es-set-tostringtag": "^2.0.3",
"es-to-primitive": "^1.2.1",
"function.prototype.name": "^1.1.6",
"get-intrinsic": "^1.2.4",
@@ -4979,15 +4947,15 @@
"globalthis": "^1.0.3",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2",
- "has-proto": "^1.0.1",
+ "has-proto": "^1.0.3",
"has-symbols": "^1.0.3",
"hasown": "^2.0.1",
"internal-slot": "^1.0.7",
"is-array-buffer": "^3.0.4",
"is-callable": "^1.2.7",
- "is-negative-zero": "^2.0.2",
+ "is-negative-zero": "^2.0.3",
"is-regex": "^1.1.4",
- "is-shared-array-buffer": "^1.0.2",
+ "is-shared-array-buffer": "^1.0.3",
"is-string": "^1.0.7",
"is-typed-array": "^1.1.13",
"is-weakref": "^1.0.2",
@@ -5000,10 +4968,10 @@
"string.prototype.trim": "^1.2.8",
"string.prototype.trimend": "^1.0.7",
"string.prototype.trimstart": "^1.0.7",
- "typed-array-buffer": "^1.0.1",
- "typed-array-byte-length": "^1.0.0",
- "typed-array-byte-offset": "^1.0.0",
- "typed-array-length": "^1.0.4",
+ "typed-array-buffer": "^1.0.2",
+ "typed-array-byte-length": "^1.0.1",
+ "typed-array-byte-offset": "^1.0.2",
+ "typed-array-length": "^1.0.5",
"unbox-primitive": "^1.0.2",
"which-typed-array": "^1.1.14"
},
@@ -5095,14 +5063,14 @@
"integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="
},
"node_modules/es-set-tostringtag": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
- "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
+ "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
"dev": true,
"dependencies": {
- "get-intrinsic": "^1.2.2",
- "has-tostringtag": "^1.0.0",
- "hasown": "^2.0.0"
+ "get-intrinsic": "^1.2.4",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.1"
},
"engines": {
"node": ">= 0.4"
@@ -5220,16 +5188,16 @@
}
},
"node_modules/eslint": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
- "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
+ "version": "8.57.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
+ "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4",
- "@eslint/js": "8.56.0",
- "@humanwhocodes/config-array": "^0.11.13",
+ "@eslint/js": "8.57.0",
+ "@humanwhocodes/config-array": "^0.11.14",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
@@ -5322,9 +5290,9 @@
}
},
"node_modules/eslint-module-utils": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz",
- "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==",
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz",
+ "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==",
"dev": true,
"dependencies": {
"debug": "^3.2.7"
@@ -5415,14 +5383,14 @@
}
},
"node_modules/eslint-plugin-github": {
- "version": "4.10.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-github/-/eslint-plugin-github-4.10.1.tgz",
- "integrity": "sha512-1AqQBockOM+m0ZUpwfjWtX0lWdX5cRi/hwJnSNvXoOmz/Hh+ULH6QFz6ENWueTWjoWpgPv0af3bj+snps6o4og==",
+ "version": "4.10.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-github/-/eslint-plugin-github-4.10.2.tgz",
+ "integrity": "sha512-F1F5aAFgi1Y5hYoTFzGQACBkw5W1hu2Fu5FSTrMlXqrojJnKl1S2pWO/rprlowRQpt+hzHhqSpsfnodJEVd5QA==",
"dev": true,
"dependencies": {
"@github/browserslist-config": "^1.0.0",
- "@typescript-eslint/eslint-plugin": "^6.0.0",
- "@typescript-eslint/parser": "^6.0.0",
+ "@typescript-eslint/eslint-plugin": "^7.0.1",
+ "@typescript-eslint/parser": "^7.0.1",
"aria-query": "^5.3.0",
"eslint-config-prettier": ">=8.0.0",
"eslint-plugin-escompat": "^3.3.3",
@@ -5633,12 +5601,6 @@
"concat-map": "0.0.1"
}
},
- "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true
- },
"node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -5810,17 +5772,117 @@
"integrity": "sha512-WE+YlK9X9s4vf5EaYRU0Scw7WItDZStm+PapFSYlg2ABNtaQ4zIG7wEqpoUB3SlfM+SgkhgmzR0TeJOO5k3/Nw==",
"dev": true
},
+ "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/scope-manager": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
+ "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/visitor-keys": "6.21.0"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
+ "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
+ "dev": true,
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/typescript-estree": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
+ "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/visitor-keys": "6.21.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "minimatch": "9.0.3",
+ "semver": "^7.5.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/utils": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
+ "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@types/json-schema": "^7.0.12",
+ "@types/semver": "^7.5.0",
+ "@typescript-eslint/scope-manager": "6.21.0",
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/typescript-estree": "6.21.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/visitor-keys": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
+ "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "6.21.0",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
"node_modules/eslint-plugin-vue": {
- "version": "9.21.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.21.1.tgz",
- "integrity": "sha512-XVtI7z39yOVBFJyi8Ljbn7kY9yHzznKXL02qQYn+ta63Iy4A9JFBw6o4OSB9hyD2++tVT+su9kQqetUyCCwhjw==",
+ "version": "9.22.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.22.0.tgz",
+ "integrity": "sha512-7wCXv5zuVnBtZE/74z4yZ0CM8AjH6bk4MQGm7hZjUC2DBppKU5ioeOk5LGSg/s9a1ZJnIsdPLJpXnu1Rc+cVHg==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"natural-compare": "^1.4.0",
"nth-check": "^2.1.1",
- "postcss-selector-parser": "^6.0.13",
- "semver": "^7.5.4",
+ "postcss-selector-parser": "^6.0.15",
+ "semver": "^7.6.0",
"vue-eslint-parser": "^9.4.2",
"xml-name-validator": "^4.0.0"
},
@@ -6231,9 +6293,9 @@
}
},
"node_modules/flatted": {
- "version": "3.2.9",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz",
- "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
+ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
"dev": true
},
"node_modules/for-each": {
@@ -6681,9 +6743,9 @@
}
},
"node_modules/has-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
- "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+ "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
"dev": true,
"engines": {
"node": ">= 0.4"
@@ -7218,9 +7280,9 @@
}
},
"node_modules/is-negative-zero": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
- "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+ "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
"dev": true,
"engines": {
"node": ">= 0.4"
@@ -7331,12 +7393,15 @@
}
},
"node_modules/is-shared-array-buffer": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
- "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.2"
+ "call-bind": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -8777,9 +8842,9 @@
}
},
"node_modules/mini-css-extract-plugin": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.0.tgz",
- "integrity": "sha512-CxmUYPFcTgET1zImteG/LZOy/4T5rTojesQXkSNBiquhydn78tfbCE9sjIjnJ/UcjNjOC1bphTCCW5rrS7cXAg==",
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz",
+ "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==",
"dependencies": {
"schema-utils": "^4.0.0",
"tapable": "^2.2.1"
@@ -8827,9 +8892,9 @@
}
},
"node_modules/mlly": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz",
- "integrity": "sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==",
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz",
+ "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==",
"dev": true,
"dependencies": {
"acorn": "^8.11.3",
@@ -9031,9 +9096,9 @@
}
},
"node_modules/npm-run-path": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz",
- "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
+ "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
"dev": true,
"dependencies": {
"path-key": "^4.0.0"
@@ -9516,12 +9581,12 @@
"dev": true
},
"node_modules/playwright": {
- "version": "1.41.2",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz",
- "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==",
+ "version": "1.42.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz",
+ "integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==",
"dev": true,
"dependencies": {
- "playwright-core": "1.41.2"
+ "playwright-core": "1.42.1"
},
"bin": {
"playwright": "cli.js"
@@ -9534,9 +9599,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.41.2",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz",
- "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==",
+ "version": "1.42.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz",
+ "integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
@@ -9563,6 +9628,15 @@
"node": ">=12.0.0"
}
},
+ "node_modules/possible-typed-array-names": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
+ "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/postcss": {
"version": "8.4.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
@@ -9640,9 +9714,9 @@
}
},
"node_modules/postcss-loader": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.0.tgz",
- "integrity": "sha512-AbperNcX3rlob7Ay7A/HQcrofug1caABBkopoFeOQMspZBqcqj6giYn1Bwey/0uiOPAcR+NQD0I2HC7rXzk91w==",
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz",
+ "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==",
"dependencies": {
"cosmiconfig": "^9.0.0",
"jiti": "^1.20.0",
@@ -10570,14 +10644,15 @@
}
},
"node_modules/set-function-name": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz",
- "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
"dev": true,
"dependencies": {
- "define-data-property": "^1.0.1",
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
"functions-have-names": "^1.2.3",
- "has-property-descriptors": "^1.0.0"
+ "has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -10614,12 +10689,12 @@
}
},
"node_modules/side-channel": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
- "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+ "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.6",
+ "call-bind": "^1.0.7",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.4",
"object-inspect": "^1.13.1"
@@ -10860,6 +10935,16 @@
"node": ">=8"
}
},
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/string-width/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
"node_modules/string.prototype.trim": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz",
@@ -10974,12 +11059,12 @@
}
},
"node_modules/strip-literal": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz",
- "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz",
+ "integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==",
"dev": true,
"dependencies": {
- "acorn": "^8.10.0"
+ "js-tokens": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
@@ -11102,41 +11187,18 @@
}
},
"node_modules/stylelint/node_modules/flat-cache": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.0.tgz",
- "integrity": "sha512-EryKbCE/wxpxKniQlyas6PY1I9vwtF3uCBweX+N8KYTCn3Y12RTGtQAJ/bd5pl7kxUAc8v/R3Ake/N17OZiFqA==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
"dev": true,
"dependencies": {
"flatted": "^3.2.9",
- "keyv": "^4.5.4",
- "rimraf": "^5.0.5"
+ "keyv": "^4.5.4"
},
"engines": {
"node": ">=16"
}
},
- "node_modules/stylelint/node_modules/glob": {
- "version": "10.3.10",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
- "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
- "dev": true,
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^2.3.5",
- "minimatch": "^9.0.1",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
- "path-scurry": "^1.10.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/stylelint/node_modules/postcss-safe-parser": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.0.tgz",
@@ -11172,24 +11234,6 @@
"node": ">=8"
}
},
- "node_modules/stylelint/node_modules/rimraf": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz",
- "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==",
- "dev": true,
- "dependencies": {
- "glob": "^10.3.7"
- },
- "bin": {
- "rimraf": "dist/esm/bin.mjs"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/stylelint/node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
@@ -11380,9 +11424,9 @@
}
},
"node_modules/swagger-ui-dist": {
- "version": "5.11.6",
- "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.6.tgz",
- "integrity": "sha512-K5BpYuMoPpJY7NwCHIWohH6tU9o0fs1+plNT5KJ+3BBlVEh4H1CpeKJV8o91lpscVY9oqb2jmaAassnW3wVoTg=="
+ "version": "5.11.8",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.8.tgz",
+ "integrity": "sha512-IfPtCPdf6opT5HXrzHO4kjL1eco0/8xJCtcs7ilhKuzatrpF2j9s+3QbOag6G3mVFKf+g+Ca5UG9DquVUs2obA=="
},
"node_modules/symbol-tree": {
"version": "3.2.4",
@@ -11524,9 +11568,9 @@
}
},
"node_modules/terser": {
- "version": "5.27.1",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.1.tgz",
- "integrity": "sha512-29wAr6UU/oQpnTw5HoadwjUZnFQXGdOfj0LjZ4sVxzqwHh/QVkvr7m8y9WoR4iN3FRitVduTc6KdjcW38Npsug==",
+ "version": "5.28.1",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.28.1.tgz",
+ "integrity": "sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
@@ -11839,12 +11883,12 @@
}
},
"node_modules/typed-array-buffer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.1.tgz",
- "integrity": "sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.6",
+ "call-bind": "^1.0.7",
"es-errors": "^1.3.0",
"is-typed-array": "^1.1.13"
},
@@ -11853,15 +11897,16 @@
}
},
"node_modules/typed-array-byte-length": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz",
- "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz",
+ "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.2",
+ "call-bind": "^1.0.7",
"for-each": "^0.3.3",
- "has-proto": "^1.0.1",
- "is-typed-array": "^1.1.10"
+ "gopd": "^1.0.1",
+ "has-proto": "^1.0.3",
+ "is-typed-array": "^1.1.13"
},
"engines": {
"node": ">= 0.4"
@@ -11871,16 +11916,17 @@
}
},
"node_modules/typed-array-byte-offset": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz",
- "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz",
+ "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==",
"dev": true,
"dependencies": {
- "available-typed-arrays": "^1.0.5",
- "call-bind": "^1.0.2",
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.7",
"for-each": "^0.3.3",
- "has-proto": "^1.0.1",
- "is-typed-array": "^1.1.10"
+ "gopd": "^1.0.1",
+ "has-proto": "^1.0.3",
+ "is-typed-array": "^1.1.13"
},
"engines": {
"node": ">= 0.4"
@@ -11890,14 +11936,20 @@
}
},
"node_modules/typed-array-length": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
- "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz",
+ "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.2",
+ "call-bind": "^1.0.7",
"for-each": "^0.3.3",
- "is-typed-array": "^1.1.9"
+ "gopd": "^1.0.1",
+ "has-proto": "^1.0.3",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -11923,9 +11975,9 @@
"integrity": "sha512-Oy/k+tFle5NAA3J/yrrYGfvEnPVrDZ8s8/WCwjUE75k331QyKIsFss7byQ/PzBmXLY6h1moRnZbnaxWBe3I3CA=="
},
"node_modules/uc.micro": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.0.0.tgz",
- "integrity": "sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"dev": true
},
"node_modules/ufo": {
@@ -12108,9 +12160,9 @@
}
},
"node_modules/vite": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz",
- "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==",
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.4.tgz",
+ "integrity": "sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==",
"dev": true,
"dependencies": {
"esbuild": "^0.19.3",
@@ -12163,9 +12215,9 @@
}
},
"node_modules/vite-node": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.2.tgz",
- "integrity": "sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz",
+ "integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==",
"dev": true,
"dependencies": {
"cac": "^6.7.14",
@@ -12211,9 +12263,9 @@
}
},
"node_modules/vite/node_modules/rollup": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.11.0.tgz",
- "integrity": "sha512-2xIbaXDXjf3u2tajvA5xROpib7eegJ9Y/uPlSFhXLNpK9ampCczXAhLEb5yLzJyG3LAdI1NWtNjDXiLyniNdjQ==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz",
+ "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
@@ -12226,35 +12278,34 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.11.0",
- "@rollup/rollup-android-arm64": "4.11.0",
- "@rollup/rollup-darwin-arm64": "4.11.0",
- "@rollup/rollup-darwin-x64": "4.11.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.11.0",
- "@rollup/rollup-linux-arm64-gnu": "4.11.0",
- "@rollup/rollup-linux-arm64-musl": "4.11.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.11.0",
- "@rollup/rollup-linux-x64-gnu": "4.11.0",
- "@rollup/rollup-linux-x64-musl": "4.11.0",
- "@rollup/rollup-win32-arm64-msvc": "4.11.0",
- "@rollup/rollup-win32-ia32-msvc": "4.11.0",
- "@rollup/rollup-win32-x64-msvc": "4.11.0",
+ "@rollup/rollup-android-arm-eabi": "4.12.0",
+ "@rollup/rollup-android-arm64": "4.12.0",
+ "@rollup/rollup-darwin-arm64": "4.12.0",
+ "@rollup/rollup-darwin-x64": "4.12.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.12.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.12.0",
+ "@rollup/rollup-linux-arm64-musl": "4.12.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.12.0",
+ "@rollup/rollup-linux-x64-gnu": "4.12.0",
+ "@rollup/rollup-linux-x64-musl": "4.12.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.12.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.12.0",
+ "@rollup/rollup-win32-x64-msvc": "4.12.0",
"fsevents": "~2.3.2"
}
},
"node_modules/vitest": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.2.tgz",
- "integrity": "sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz",
+ "integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==",
"dev": true,
"dependencies": {
- "@vitest/expect": "1.2.2",
- "@vitest/runner": "1.2.2",
- "@vitest/snapshot": "1.2.2",
- "@vitest/spy": "1.2.2",
- "@vitest/utils": "1.2.2",
+ "@vitest/expect": "1.3.1",
+ "@vitest/runner": "1.3.1",
+ "@vitest/snapshot": "1.3.1",
+ "@vitest/spy": "1.3.1",
+ "@vitest/utils": "1.3.1",
"acorn-walk": "^8.3.2",
- "cac": "^6.7.14",
"chai": "^4.3.10",
"debug": "^4.3.4",
"execa": "^8.0.1",
@@ -12263,11 +12314,11 @@
"pathe": "^1.1.1",
"picocolors": "^1.0.0",
"std-env": "^3.5.0",
- "strip-literal": "^1.3.0",
+ "strip-literal": "^2.0.0",
"tinybench": "^2.5.1",
"tinypool": "^0.8.2",
"vite": "^5.0.0",
- "vite-node": "1.2.2",
+ "vite-node": "1.3.1",
"why-is-node-running": "^2.2.2"
},
"bin": {
@@ -12282,8 +12333,8 @@
"peerDependencies": {
"@edge-runtime/vm": "*",
"@types/node": "^18.0.0 || >=20.0.0",
- "@vitest/browser": "^1.0.0",
- "@vitest/ui": "^1.0.0",
+ "@vitest/browser": "1.3.1",
+ "@vitest/ui": "1.3.1",
"happy-dom": "*",
"jsdom": "*"
},
@@ -12321,15 +12372,15 @@
}
},
"node_modules/vue": {
- "version": "3.4.19",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.19.tgz",
- "integrity": "sha512-W/7Fc9KUkajFU8dBeDluM4sRGc/aa4YJnOYck8dkjgZoXtVsn3OeTGni66FV1l3+nvPA7VBFYtPioaGKUmEADw==",
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz",
+ "integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==",
"dependencies": {
- "@vue/compiler-dom": "3.4.19",
- "@vue/compiler-sfc": "3.4.19",
- "@vue/runtime-dom": "3.4.19",
- "@vue/server-renderer": "3.4.19",
- "@vue/shared": "3.4.19"
+ "@vue/compiler-dom": "3.4.21",
+ "@vue/compiler-sfc": "3.4.21",
+ "@vue/runtime-dom": "3.4.21",
+ "@vue/server-renderer": "3.4.21",
+ "@vue/shared": "3.4.21"
},
"peerDependencies": {
"typescript": "*"
@@ -12463,9 +12514,9 @@
}
},
"node_modules/webpack": {
- "version": "5.90.2",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.2.tgz",
- "integrity": "sha512-ziXu8ABGr0InCMEYFnHrYweinHK2PWrMqnwdHk2oK3rRhv/1B+2FnfwYv5oD+RrknK/Pp/Hmyvu+eAsaMYhzCw==",
+ "version": "5.90.3",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz",
+ "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==",
"dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.5",
@@ -12964,9 +13015,12 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yaml": {
- "version": "2.3.4",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
- "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.0.tgz",
+ "integrity": "sha512-j9iR8g+/t0lArF4V6NE/QCfT+CO7iLqrXAHZbJdo+LfjqP1vR8Fg5bSiaq6Q2lOD1AUEVrEVIgABvBFYojJVYQ==",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
"engines": {
"node": ">= 14"
}
diff --git a/package.json b/package.json
index 3f0f9103cf..d5e8170228 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
"@citation-js/plugin-csl": "0.7.6",
"@citation-js/plugin-software-formats": "0.6.1",
"@claviska/jquery-minicolors": "2.3.6",
- "@github/markdown-toolbar-element": "2.2.1",
+ "@github/markdown-toolbar-element": "2.2.3",
"@github/relative-time-element": "4.3.1",
"@github/text-expander-element": "2.6.1",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
@@ -17,13 +17,12 @@
"@webcomponents/custom-elements": "1.6.0",
"add-asset-webpack-plugin": "2.0.1",
"ansi_up": "6.0.2",
- "asciinema-player": "3.6.4",
- "chart.js": "4.4.1",
+ "asciinema-player": "3.7.0",
+ "chart.js": "4.4.2",
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.0.1",
- "clippie": "4.0.6",
+ "clippie": "4.0.7",
"css-loader": "6.10.0",
- "css-variables-parser": "1.0.1",
"dayjs": "1.11.10",
"dropzone": "6.0.0-beta.2",
"easymde": "2.18.0",
@@ -36,16 +35,16 @@
"katex": "0.16.9",
"license-checker-webpack-plugin": "0.2.1",
"mermaid": "10.8.0",
- "mini-css-extract-plugin": "2.8.0",
+ "mini-css-extract-plugin": "2.8.1",
"minimatch": "9.0.3",
"monaco-editor": "0.46.0",
"monaco-editor-webpack-plugin": "7.1.0",
"pdfobject": "2.3.0",
"postcss": "8.4.35",
- "postcss-loader": "8.1.0",
+ "postcss-loader": "8.1.1",
"pretty-ms": "9.0.0",
"sortablejs": "1.15.2",
- "swagger-ui-dist": "5.11.6",
+ "swagger-ui-dist": "5.11.8",
"tailwindcss": "3.4.1",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0",
@@ -53,25 +52,25 @@
"toastify-js": "1.12.0",
"tributejs": "5.1.3",
"uint8-to-base64": "0.2.0",
- "vue": "3.4.19",
+ "vue": "3.4.21",
"vue-bar-graph": "2.0.0",
"vue-chartjs": "5.3.0",
"vue-loader": "17.4.2",
"vue3-calendar-heatmap": "2.0.5",
- "webpack": "5.90.2",
+ "webpack": "5.90.3",
"webpack-cli": "5.1.4",
"wrap-ansi": "9.0.0"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.1.0",
- "@playwright/test": "1.41.2",
+ "@playwright/test": "1.42.1",
"@stoplight/spectral-cli": "6.11.0",
- "@stylistic/eslint-plugin-js": "1.6.2",
- "@stylistic/stylelint-plugin": "2.0.0",
+ "@stylistic/eslint-plugin-js": "1.6.3",
+ "@stylistic/stylelint-plugin": "2.1.0",
"@vitejs/plugin-vue": "5.0.4",
- "eslint": "8.56.0",
+ "eslint": "8.57.0",
"eslint-plugin-array-func": "4.0.0",
- "eslint-plugin-github": "4.10.1",
+ "eslint-plugin-github": "4.10.2",
"eslint-plugin-i": "2.29.1",
"eslint-plugin-jquery": "1.5.1",
"eslint-plugin-no-jquery": "2.7.0",
@@ -81,7 +80,7 @@
"eslint-plugin-unicorn": "51.0.1",
"eslint-plugin-vitest": "0.3.22",
"eslint-plugin-vitest-globals": "1.4.0",
- "eslint-plugin-vue": "9.21.1",
+ "eslint-plugin-vue": "9.22.0",
"eslint-plugin-vue-scoped-css": "2.7.2",
"eslint-plugin-wc": "2.0.4",
"jsdom": "24.0.0",
@@ -93,7 +92,7 @@
"svgo": "3.2.0",
"updates": "15.1.2",
"vite-string-plugin": "1.1.5",
- "vitest": "1.2.2"
+ "vitest": "1.3.1"
},
"browserslist": [
"defaults"
diff --git a/poetry.lock b/poetry.lock
index 4cb58c6ef2..46520fba3c 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
[[package]]
name = "click"
@@ -27,12 +27,12 @@ files = [
[[package]]
name = "cssbeautifier"
-version = "1.14.11"
+version = "1.15.1"
description = "CSS unobfuscator and beautifier."
optional = false
python-versions = "*"
files = [
- {file = "cssbeautifier-1.14.11.tar.gz", hash = "sha256:40544c2b62bbcb64caa5e7f37a02df95654e5ce1bcacadac4ca1f3dc89c31513"},
+ {file = "cssbeautifier-1.15.1.tar.gz", hash = "sha256:9f7064362aedd559c55eeecf6b6bed65e05f33488dcbe39044f0403c26e1c006"},
]
[package.dependencies]
@@ -67,13 +67,12 @@ tqdm = ">=4.62.2,<5.0.0"
[[package]]
name = "editorconfig"
-version = "0.12.3"
+version = "0.12.4"
description = "EditorConfig File Locator and Interpreter for Python"
optional = false
python-versions = "*"
files = [
- {file = "EditorConfig-0.12.3-py3-none-any.whl", hash = "sha256:6b0851425aa875b08b16789ee0eeadbd4ab59666e9ebe728e526314c4a2e52c1"},
- {file = "EditorConfig-0.12.3.tar.gz", hash = "sha256:57f8ce78afcba15c8b18d46b5170848c88d56fd38f05c2ec60dbbfcb8996e89e"},
+ {file = "EditorConfig-0.12.4.tar.gz", hash = "sha256:24857fa1793917dd9ccf0c7810a07e05404ce9b823521c7dce22a4fb5d125f80"},
]
[[package]]
@@ -100,12 +99,12 @@ files = [
[[package]]
name = "jsbeautifier"
-version = "1.14.11"
+version = "1.15.1"
description = "JavaScript unobfuscator and beautifier."
optional = false
python-versions = "*"
files = [
- {file = "jsbeautifier-1.14.11.tar.gz", hash = "sha256:6b632581ea60dd1c133cd25a48ad187b4b91f526623c4b0fb5443ef805250505"},
+ {file = "jsbeautifier-1.15.1.tar.gz", hash = "sha256:ebd733b560704c602d744eafc839db60a1ee9326e30a2a80c4adb8718adc1b24"},
]
[package.dependencies]
@@ -114,13 +113,13 @@ six = ">=1.13.0"
[[package]]
name = "json5"
-version = "0.9.14"
+version = "0.9.18"
description = "A Python implementation of the JSON5 data format."
optional = false
-python-versions = "*"
+python-versions = ">=3.8"
files = [
- {file = "json5-0.9.14-py2.py3-none-any.whl", hash = "sha256:740c7f1b9e584a468dbb2939d8d458db3427f2c93ae2139d05f47e453eae964f"},
- {file = "json5-0.9.14.tar.gz", hash = "sha256:9ed66c3a6ca3510a976a9ef9b8c0787de24802724ab1860bc0153c7fdd589b02"},
+ {file = "json5-0.9.18-py2.py3-none-any.whl", hash = "sha256:3f20193ff8dfdec6ab114b344e7ac5d76fac453c8bab9bdfe1460d1d528ec393"},
+ {file = "json5-0.9.18.tar.gz", hash = "sha256:ecb8ac357004e3522fb989da1bf08b146011edbd14fdffae6caad3bd68493467"},
]
[package.extras]
@@ -322,13 +321,13 @@ files = [
[[package]]
name = "tqdm"
-version = "4.66.1"
+version = "4.66.2"
description = "Fast, Extensible Progress Meter"
optional = false
python-versions = ">=3.7"
files = [
- {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"},
- {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"},
+ {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"},
+ {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"},
]
[package.dependencies]
@@ -342,13 +341,13 @@ telegram = ["requests"]
[[package]]
name = "yamllint"
-version = "1.35.0"
+version = "1.35.1"
description = "A linter for YAML files."
optional = false
python-versions = ">=3.8"
files = [
- {file = "yamllint-1.35.0-py3-none-any.whl", hash = "sha256:601b0adaaac6d9bacb16a2e612e7ee8d23caf941ceebf9bfe2cff0f196266004"},
- {file = "yamllint-1.35.0.tar.gz", hash = "sha256:9bc99c3e9fe89b4c6ee26e17aa817cf2d14390de6577cb6e2e6ed5f72120c835"},
+ {file = "yamllint-1.35.1-py3-none-any.whl", hash = "sha256:2e16e504bb129ff515b37823b472750b36b6de07963bd74b307341ef5ad8bdc3"},
+ {file = "yamllint-1.35.1.tar.gz", hash = "sha256:7a003809f88324fd2c877734f2d575ee7881dd9043360657cc8049c809eba6cd"},
]
[package.dependencies]
@@ -360,5 +359,5 @@ dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"]
[metadata]
lock-version = "2.0"
-python-versions = "^3.8"
-content-hash = "ba1c2c4235872f67354b5f52aa5bf0cd616354961530d9dc907f9fba28cc1ece"
+python-versions = "^3.10"
+content-hash = "cd2ff218e9f27a464dfbc8ec2387824a90f4360e04c3f2e58cc375796b7df33a"
diff --git a/public/assets/img/svg/gitea-bitbucket.svg b/public/assets/img/svg/gitea-bitbucket.svg
index b900335ea1..83e4c5c6e7 100644
--- a/public/assets/img/svg/gitea-bitbucket.svg
+++ b/public/assets/img/svg/gitea-bitbucket.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-facebook.svg b/public/assets/img/svg/gitea-facebook.svg
index cbeb76b127..6101becad2 100644
--- a/public/assets/img/svg/gitea-facebook.svg
+++ b/public/assets/img/svg/gitea-facebook.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-jetbrains.svg b/public/assets/img/svg/gitea-jetbrains.svg
new file mode 100644
index 0000000000..5821736225
--- /dev/null
+++ b/public/assets/img/svg/gitea-jetbrains.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-microsoftonline.svg b/public/assets/img/svg/gitea-microsoftonline.svg
index ce4f1a5c8f..f2ce13ac22 100644
--- a/public/assets/img/svg/gitea-microsoftonline.svg
+++ b/public/assets/img/svg/gitea-microsoftonline.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-twitter.svg b/public/assets/img/svg/gitea-twitter.svg
index 5d11c6eaec..5ed1e264ca 100644
--- a/public/assets/img/svg/gitea-twitter.svg
+++ b/public/assets/img/svg/gitea-twitter.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-vscodium.svg b/public/assets/img/svg/gitea-vscodium.svg
new file mode 100644
index 0000000000..6aad3d3a64
--- /dev/null
+++ b/public/assets/img/svg/gitea-vscodium.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index ef763da24d..323307c1dc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,11 +5,11 @@ description = ""
authors = []
[tool.poetry.dependencies]
-python = "^3.8"
+python = "^3.10"
[tool.poetry.group.dev.dependencies]
djlint = "1.34.1"
-yamllint = "1.35.0"
+yamllint = "1.35.1"
[tool.djlint]
profile="golang"
diff --git a/routers/api/actions/artifact.pb.go b/routers/api/actions/artifact.pb.go
new file mode 100644
index 0000000000..590eda9fb9
--- /dev/null
+++ b/routers/api/actions/artifact.pb.go
@@ -0,0 +1,1058 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.32.0
+// protoc v4.25.2
+// source: artifact.proto
+
+package actions
+
+import (
+ reflect "reflect"
+ sync "sync"
+
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ timestamppb "google.golang.org/protobuf/types/known/timestamppb"
+ wrapperspb "google.golang.org/protobuf/types/known/wrapperspb"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type CreateArtifactRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ WorkflowRunBackendId string `protobuf:"bytes,1,opt,name=workflow_run_backend_id,json=workflowRunBackendId,proto3" json:"workflow_run_backend_id,omitempty"`
+ WorkflowJobRunBackendId string `protobuf:"bytes,2,opt,name=workflow_job_run_backend_id,json=workflowJobRunBackendId,proto3" json:"workflow_job_run_backend_id,omitempty"`
+ Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
+ ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"`
+ Version int32 `protobuf:"varint,5,opt,name=version,proto3" json:"version,omitempty"`
+}
+
+func (x *CreateArtifactRequest) Reset() {
+ *x = CreateArtifactRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *CreateArtifactRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateArtifactRequest) ProtoMessage() {}
+
+func (x *CreateArtifactRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateArtifactRequest.ProtoReflect.Descriptor instead.
+func (*CreateArtifactRequest) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *CreateArtifactRequest) GetWorkflowRunBackendId() string {
+ if x != nil {
+ return x.WorkflowRunBackendId
+ }
+ return ""
+}
+
+func (x *CreateArtifactRequest) GetWorkflowJobRunBackendId() string {
+ if x != nil {
+ return x.WorkflowJobRunBackendId
+ }
+ return ""
+}
+
+func (x *CreateArtifactRequest) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *CreateArtifactRequest) GetExpiresAt() *timestamppb.Timestamp {
+ if x != nil {
+ return x.ExpiresAt
+ }
+ return nil
+}
+
+func (x *CreateArtifactRequest) GetVersion() int32 {
+ if x != nil {
+ return x.Version
+ }
+ return 0
+}
+
+type CreateArtifactResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"`
+ SignedUploadUrl string `protobuf:"bytes,2,opt,name=signed_upload_url,json=signedUploadUrl,proto3" json:"signed_upload_url,omitempty"`
+}
+
+func (x *CreateArtifactResponse) Reset() {
+ *x = CreateArtifactResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *CreateArtifactResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateArtifactResponse) ProtoMessage() {}
+
+func (x *CreateArtifactResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateArtifactResponse.ProtoReflect.Descriptor instead.
+func (*CreateArtifactResponse) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *CreateArtifactResponse) GetOk() bool {
+ if x != nil {
+ return x.Ok
+ }
+ return false
+}
+
+func (x *CreateArtifactResponse) GetSignedUploadUrl() string {
+ if x != nil {
+ return x.SignedUploadUrl
+ }
+ return ""
+}
+
+type FinalizeArtifactRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ WorkflowRunBackendId string `protobuf:"bytes,1,opt,name=workflow_run_backend_id,json=workflowRunBackendId,proto3" json:"workflow_run_backend_id,omitempty"`
+ WorkflowJobRunBackendId string `protobuf:"bytes,2,opt,name=workflow_job_run_backend_id,json=workflowJobRunBackendId,proto3" json:"workflow_job_run_backend_id,omitempty"`
+ Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
+ Size int64 `protobuf:"varint,4,opt,name=size,proto3" json:"size,omitempty"`
+ Hash *wrapperspb.StringValue `protobuf:"bytes,5,opt,name=hash,proto3" json:"hash,omitempty"`
+}
+
+func (x *FinalizeArtifactRequest) Reset() {
+ *x = FinalizeArtifactRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *FinalizeArtifactRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FinalizeArtifactRequest) ProtoMessage() {}
+
+func (x *FinalizeArtifactRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use FinalizeArtifactRequest.ProtoReflect.Descriptor instead.
+func (*FinalizeArtifactRequest) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *FinalizeArtifactRequest) GetWorkflowRunBackendId() string {
+ if x != nil {
+ return x.WorkflowRunBackendId
+ }
+ return ""
+}
+
+func (x *FinalizeArtifactRequest) GetWorkflowJobRunBackendId() string {
+ if x != nil {
+ return x.WorkflowJobRunBackendId
+ }
+ return ""
+}
+
+func (x *FinalizeArtifactRequest) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *FinalizeArtifactRequest) GetSize() int64 {
+ if x != nil {
+ return x.Size
+ }
+ return 0
+}
+
+func (x *FinalizeArtifactRequest) GetHash() *wrapperspb.StringValue {
+ if x != nil {
+ return x.Hash
+ }
+ return nil
+}
+
+type FinalizeArtifactResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"`
+ ArtifactId int64 `protobuf:"varint,2,opt,name=artifact_id,json=artifactId,proto3" json:"artifact_id,omitempty"`
+}
+
+func (x *FinalizeArtifactResponse) Reset() {
+ *x = FinalizeArtifactResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *FinalizeArtifactResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FinalizeArtifactResponse) ProtoMessage() {}
+
+func (x *FinalizeArtifactResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use FinalizeArtifactResponse.ProtoReflect.Descriptor instead.
+func (*FinalizeArtifactResponse) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *FinalizeArtifactResponse) GetOk() bool {
+ if x != nil {
+ return x.Ok
+ }
+ return false
+}
+
+func (x *FinalizeArtifactResponse) GetArtifactId() int64 {
+ if x != nil {
+ return x.ArtifactId
+ }
+ return 0
+}
+
+type ListArtifactsRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ WorkflowRunBackendId string `protobuf:"bytes,1,opt,name=workflow_run_backend_id,json=workflowRunBackendId,proto3" json:"workflow_run_backend_id,omitempty"`
+ WorkflowJobRunBackendId string `protobuf:"bytes,2,opt,name=workflow_job_run_backend_id,json=workflowJobRunBackendId,proto3" json:"workflow_job_run_backend_id,omitempty"`
+ NameFilter *wrapperspb.StringValue `protobuf:"bytes,3,opt,name=name_filter,json=nameFilter,proto3" json:"name_filter,omitempty"`
+ IdFilter *wrapperspb.Int64Value `protobuf:"bytes,4,opt,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"`
+}
+
+func (x *ListArtifactsRequest) Reset() {
+ *x = ListArtifactsRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ListArtifactsRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListArtifactsRequest) ProtoMessage() {}
+
+func (x *ListArtifactsRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[4]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListArtifactsRequest.ProtoReflect.Descriptor instead.
+func (*ListArtifactsRequest) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *ListArtifactsRequest) GetWorkflowRunBackendId() string {
+ if x != nil {
+ return x.WorkflowRunBackendId
+ }
+ return ""
+}
+
+func (x *ListArtifactsRequest) GetWorkflowJobRunBackendId() string {
+ if x != nil {
+ return x.WorkflowJobRunBackendId
+ }
+ return ""
+}
+
+func (x *ListArtifactsRequest) GetNameFilter() *wrapperspb.StringValue {
+ if x != nil {
+ return x.NameFilter
+ }
+ return nil
+}
+
+func (x *ListArtifactsRequest) GetIdFilter() *wrapperspb.Int64Value {
+ if x != nil {
+ return x.IdFilter
+ }
+ return nil
+}
+
+type ListArtifactsResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Artifacts []*ListArtifactsResponse_MonolithArtifact `protobuf:"bytes,1,rep,name=artifacts,proto3" json:"artifacts,omitempty"`
+}
+
+func (x *ListArtifactsResponse) Reset() {
+ *x = ListArtifactsResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ListArtifactsResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListArtifactsResponse) ProtoMessage() {}
+
+func (x *ListArtifactsResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[5]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListArtifactsResponse.ProtoReflect.Descriptor instead.
+func (*ListArtifactsResponse) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *ListArtifactsResponse) GetArtifacts() []*ListArtifactsResponse_MonolithArtifact {
+ if x != nil {
+ return x.Artifacts
+ }
+ return nil
+}
+
+type ListArtifactsResponse_MonolithArtifact struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ WorkflowRunBackendId string `protobuf:"bytes,1,opt,name=workflow_run_backend_id,json=workflowRunBackendId,proto3" json:"workflow_run_backend_id,omitempty"`
+ WorkflowJobRunBackendId string `protobuf:"bytes,2,opt,name=workflow_job_run_backend_id,json=workflowJobRunBackendId,proto3" json:"workflow_job_run_backend_id,omitempty"`
+ DatabaseId int64 `protobuf:"varint,3,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"`
+ Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"`
+ Size int64 `protobuf:"varint,5,opt,name=size,proto3" json:"size,omitempty"`
+ CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) Reset() {
+ *x = ListArtifactsResponse_MonolithArtifact{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListArtifactsResponse_MonolithArtifact) ProtoMessage() {}
+
+func (x *ListArtifactsResponse_MonolithArtifact) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[6]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListArtifactsResponse_MonolithArtifact.ProtoReflect.Descriptor instead.
+func (*ListArtifactsResponse_MonolithArtifact) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) GetWorkflowRunBackendId() string {
+ if x != nil {
+ return x.WorkflowRunBackendId
+ }
+ return ""
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) GetWorkflowJobRunBackendId() string {
+ if x != nil {
+ return x.WorkflowJobRunBackendId
+ }
+ return ""
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) GetDatabaseId() int64 {
+ if x != nil {
+ return x.DatabaseId
+ }
+ return 0
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) GetSize() int64 {
+ if x != nil {
+ return x.Size
+ }
+ return 0
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) GetCreatedAt() *timestamppb.Timestamp {
+ if x != nil {
+ return x.CreatedAt
+ }
+ return nil
+}
+
+type GetSignedArtifactURLRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ WorkflowRunBackendId string `protobuf:"bytes,1,opt,name=workflow_run_backend_id,json=workflowRunBackendId,proto3" json:"workflow_run_backend_id,omitempty"`
+ WorkflowJobRunBackendId string `protobuf:"bytes,2,opt,name=workflow_job_run_backend_id,json=workflowJobRunBackendId,proto3" json:"workflow_job_run_backend_id,omitempty"`
+ Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
+}
+
+func (x *GetSignedArtifactURLRequest) Reset() {
+ *x = GetSignedArtifactURLRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GetSignedArtifactURLRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetSignedArtifactURLRequest) ProtoMessage() {}
+
+func (x *GetSignedArtifactURLRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[7]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetSignedArtifactURLRequest.ProtoReflect.Descriptor instead.
+func (*GetSignedArtifactURLRequest) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *GetSignedArtifactURLRequest) GetWorkflowRunBackendId() string {
+ if x != nil {
+ return x.WorkflowRunBackendId
+ }
+ return ""
+}
+
+func (x *GetSignedArtifactURLRequest) GetWorkflowJobRunBackendId() string {
+ if x != nil {
+ return x.WorkflowJobRunBackendId
+ }
+ return ""
+}
+
+func (x *GetSignedArtifactURLRequest) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+type GetSignedArtifactURLResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ SignedUrl string `protobuf:"bytes,1,opt,name=signed_url,json=signedUrl,proto3" json:"signed_url,omitempty"`
+}
+
+func (x *GetSignedArtifactURLResponse) Reset() {
+ *x = GetSignedArtifactURLResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GetSignedArtifactURLResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetSignedArtifactURLResponse) ProtoMessage() {}
+
+func (x *GetSignedArtifactURLResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[8]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetSignedArtifactURLResponse.ProtoReflect.Descriptor instead.
+func (*GetSignedArtifactURLResponse) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *GetSignedArtifactURLResponse) GetSignedUrl() string {
+ if x != nil {
+ return x.SignedUrl
+ }
+ return ""
+}
+
+type DeleteArtifactRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ WorkflowRunBackendId string `protobuf:"bytes,1,opt,name=workflow_run_backend_id,json=workflowRunBackendId,proto3" json:"workflow_run_backend_id,omitempty"`
+ WorkflowJobRunBackendId string `protobuf:"bytes,2,opt,name=workflow_job_run_backend_id,json=workflowJobRunBackendId,proto3" json:"workflow_job_run_backend_id,omitempty"`
+ Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
+}
+
+func (x *DeleteArtifactRequest) Reset() {
+ *x = DeleteArtifactRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[9]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *DeleteArtifactRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteArtifactRequest) ProtoMessage() {}
+
+func (x *DeleteArtifactRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[9]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteArtifactRequest.ProtoReflect.Descriptor instead.
+func (*DeleteArtifactRequest) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *DeleteArtifactRequest) GetWorkflowRunBackendId() string {
+ if x != nil {
+ return x.WorkflowRunBackendId
+ }
+ return ""
+}
+
+func (x *DeleteArtifactRequest) GetWorkflowJobRunBackendId() string {
+ if x != nil {
+ return x.WorkflowJobRunBackendId
+ }
+ return ""
+}
+
+func (x *DeleteArtifactRequest) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+type DeleteArtifactResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"`
+ ArtifactId int64 `protobuf:"varint,2,opt,name=artifact_id,json=artifactId,proto3" json:"artifact_id,omitempty"`
+}
+
+func (x *DeleteArtifactResponse) Reset() {
+ *x = DeleteArtifactResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[10]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *DeleteArtifactResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteArtifactResponse) ProtoMessage() {}
+
+func (x *DeleteArtifactResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[10]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteArtifactResponse.ProtoReflect.Descriptor instead.
+func (*DeleteArtifactResponse) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *DeleteArtifactResponse) GetOk() bool {
+ if x != nil {
+ return x.Ok
+ }
+ return false
+}
+
+func (x *DeleteArtifactResponse) GetArtifactId() int64 {
+ if x != nil {
+ return x.ArtifactId
+ }
+ return 0
+}
+
+var File_artifact_proto protoreflect.FileDescriptor
+
+var file_artifact_proto_rawDesc = []byte{
+ 0x0a, 0x0e, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x12, 0x1d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73,
+ 0x2e, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x1a,
+ 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
+ 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
+ 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x22, 0xf5, 0x01, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x72, 0x74, 0x69, 0x66,
+ 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x77, 0x6f,
+ 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65,
+ 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72,
+ 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49,
+ 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6a, 0x6f,
+ 0x62, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x64,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77,
+ 0x4a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12,
+ 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
+ 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61,
+ 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
+ 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
+ 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x12, 0x18,
+ 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52,
+ 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x54, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61,
+ 0x74, 0x65, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+ 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02,
+ 0x6f, 0x6b, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x75, 0x70, 0x6c,
+ 0x6f, 0x61, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73,
+ 0x69, 0x67, 0x6e, 0x65, 0x64, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x72, 0x6c, 0x22, 0xe8,
+ 0x01, 0x0a, 0x17, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x41, 0x72, 0x74, 0x69, 0x66,
+ 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x77, 0x6f,
+ 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65,
+ 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72,
+ 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49,
+ 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6a, 0x6f,
+ 0x62, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x64,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77,
+ 0x4a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12,
+ 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
+ 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
+ 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x30, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18,
+ 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
+ 0x6c, 0x75, 0x65, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0x4b, 0x0a, 0x18, 0x46, 0x69, 0x6e,
+ 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63,
+ 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x61, 0x72, 0x74, 0x69,
+ 0x66, 0x61, 0x63, 0x74, 0x49, 0x64, 0x22, 0x84, 0x02, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41,
+ 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+ 0x35, 0x0a, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x75, 0x6e, 0x5f,
+ 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63,
+ 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c,
+ 0x6f, 0x77, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65,
+ 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x6f, 0x72,
+ 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65,
+ 0x6e, 0x64, 0x49, 0x64, 0x12, 0x3d, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x66, 0x69, 0x6c,
+ 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
+ 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69,
+ 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x46, 0x69, 0x6c,
+ 0x74, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72,
+ 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61,
+ 0x6c, 0x75, 0x65, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x7c, 0x0a,
+ 0x15, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65,
+ 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x09, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61,
+ 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x45, 0x2e, 0x67, 0x69, 0x74, 0x68,
+ 0x75, 0x62, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x72, 0x65, 0x73, 0x75, 0x6c,
+ 0x74, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x72,
+ 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f,
+ 0x4d, 0x6f, 0x6e, 0x6f, 0x6c, 0x69, 0x74, 0x68, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74,
+ 0x52, 0x09, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x22, 0xa1, 0x02, 0x0a, 0x26,
+ 0x4c, 0x69, 0x73, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x6f, 0x6c, 0x69, 0x74, 0x68, 0x41, 0x72,
+ 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c,
+ 0x6f, 0x77, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69,
+ 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f,
+ 0x77, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x3c, 0x0a,
+ 0x1b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x75,
+ 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4a, 0x6f, 0x62, 0x52,
+ 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x64,
+ 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
+ 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04,
+ 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
+ 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04,
+ 0x73, 0x69, 0x7a, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f,
+ 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
+ 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
+ 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22,
+ 0xa6, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x72, 0x74,
+ 0x69, 0x66, 0x61, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+ 0x35, 0x0a, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x75, 0x6e, 0x5f,
+ 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63,
+ 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c,
+ 0x6f, 0x77, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65,
+ 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x6f, 0x72,
+ 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65,
+ 0x6e, 0x64, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3d, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x53,
+ 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x55, 0x52, 0x4c,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x69, 0x67, 0x6e,
+ 0x65, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69,
+ 0x67, 0x6e, 0x65, 0x64, 0x55, 0x72, 0x6c, 0x22, 0xa0, 0x01, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65,
+ 0x74, 0x65, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x12, 0x35, 0x0a, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x75,
+ 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x42,
+ 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x77, 0x6f, 0x72, 0x6b,
+ 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63,
+ 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77,
+ 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63,
+ 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x49, 0x0a, 0x16, 0x44, 0x65,
+ 0x6c, 0x65, 0x74, 0x65, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70,
+ 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
+ 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74,
+ 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x61, 0x72, 0x74, 0x69, 0x66,
+ 0x61, 0x63, 0x74, 0x49, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_artifact_proto_rawDescOnce sync.Once
+ file_artifact_proto_rawDescData = file_artifact_proto_rawDesc
+)
+
+func file_artifact_proto_rawDescGZIP() []byte {
+ file_artifact_proto_rawDescOnce.Do(func() {
+ file_artifact_proto_rawDescData = protoimpl.X.CompressGZIP(file_artifact_proto_rawDescData)
+ })
+ return file_artifact_proto_rawDescData
+}
+
+var (
+ file_artifact_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
+ file_artifact_proto_goTypes = []interface{}{
+ (*CreateArtifactRequest)(nil), // 0: github.actions.results.api.v1.CreateArtifactRequest
+ (*CreateArtifactResponse)(nil), // 1: github.actions.results.api.v1.CreateArtifactResponse
+ (*FinalizeArtifactRequest)(nil), // 2: github.actions.results.api.v1.FinalizeArtifactRequest
+ (*FinalizeArtifactResponse)(nil), // 3: github.actions.results.api.v1.FinalizeArtifactResponse
+ (*ListArtifactsRequest)(nil), // 4: github.actions.results.api.v1.ListArtifactsRequest
+ (*ListArtifactsResponse)(nil), // 5: github.actions.results.api.v1.ListArtifactsResponse
+ (*ListArtifactsResponse_MonolithArtifact)(nil), // 6: github.actions.results.api.v1.ListArtifactsResponse_MonolithArtifact
+ (*GetSignedArtifactURLRequest)(nil), // 7: github.actions.results.api.v1.GetSignedArtifactURLRequest
+ (*GetSignedArtifactURLResponse)(nil), // 8: github.actions.results.api.v1.GetSignedArtifactURLResponse
+ (*DeleteArtifactRequest)(nil), // 9: github.actions.results.api.v1.DeleteArtifactRequest
+ (*DeleteArtifactResponse)(nil), // 10: github.actions.results.api.v1.DeleteArtifactResponse
+ (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp
+ (*wrapperspb.StringValue)(nil), // 12: google.protobuf.StringValue
+ (*wrapperspb.Int64Value)(nil), // 13: google.protobuf.Int64Value
+ }
+)
+
+var file_artifact_proto_depIdxs = []int32{
+ 11, // 0: github.actions.results.api.v1.CreateArtifactRequest.expires_at:type_name -> google.protobuf.Timestamp
+ 12, // 1: github.actions.results.api.v1.FinalizeArtifactRequest.hash:type_name -> google.protobuf.StringValue
+ 12, // 2: github.actions.results.api.v1.ListArtifactsRequest.name_filter:type_name -> google.protobuf.StringValue
+ 13, // 3: github.actions.results.api.v1.ListArtifactsRequest.id_filter:type_name -> google.protobuf.Int64Value
+ 6, // 4: github.actions.results.api.v1.ListArtifactsResponse.artifacts:type_name -> github.actions.results.api.v1.ListArtifactsResponse_MonolithArtifact
+ 11, // 5: github.actions.results.api.v1.ListArtifactsResponse_MonolithArtifact.created_at:type_name -> google.protobuf.Timestamp
+ 6, // [6:6] is the sub-list for method output_type
+ 6, // [6:6] is the sub-list for method input_type
+ 6, // [6:6] is the sub-list for extension type_name
+ 6, // [6:6] is the sub-list for extension extendee
+ 0, // [0:6] is the sub-list for field type_name
+}
+
+func init() { file_artifact_proto_init() }
+func file_artifact_proto_init() {
+ if File_artifact_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_artifact_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*CreateArtifactRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*CreateArtifactResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*FinalizeArtifactRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*FinalizeArtifactResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ListArtifactsRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ListArtifactsResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ListArtifactsResponse_MonolithArtifact); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GetSignedArtifactURLRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GetSignedArtifactURLResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*DeleteArtifactRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*DeleteArtifactResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_artifact_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 11,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_artifact_proto_goTypes,
+ DependencyIndexes: file_artifact_proto_depIdxs,
+ MessageInfos: file_artifact_proto_msgTypes,
+ }.Build()
+ File_artifact_proto = out.File
+ file_artifact_proto_rawDesc = nil
+ file_artifact_proto_goTypes = nil
+ file_artifact_proto_depIdxs = nil
+}
diff --git a/routers/api/actions/artifact.proto b/routers/api/actions/artifact.proto
new file mode 100644
index 0000000000..c68e5d030d
--- /dev/null
+++ b/routers/api/actions/artifact.proto
@@ -0,0 +1,73 @@
+syntax = "proto3";
+
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/wrappers.proto";
+
+package github.actions.results.api.v1;
+
+message CreateArtifactRequest {
+ string workflow_run_backend_id = 1;
+ string workflow_job_run_backend_id = 2;
+ string name = 3;
+ google.protobuf.Timestamp expires_at = 4;
+ int32 version = 5;
+}
+
+message CreateArtifactResponse {
+ bool ok = 1;
+ string signed_upload_url = 2;
+}
+
+message FinalizeArtifactRequest {
+ string workflow_run_backend_id = 1;
+ string workflow_job_run_backend_id = 2;
+ string name = 3;
+ int64 size = 4;
+ google.protobuf.StringValue hash = 5;
+}
+
+message FinalizeArtifactResponse {
+ bool ok = 1;
+ int64 artifact_id = 2;
+}
+
+message ListArtifactsRequest {
+ string workflow_run_backend_id = 1;
+ string workflow_job_run_backend_id = 2;
+ google.protobuf.StringValue name_filter = 3;
+ google.protobuf.Int64Value id_filter = 4;
+}
+
+message ListArtifactsResponse {
+ repeated ListArtifactsResponse_MonolithArtifact artifacts = 1;
+}
+
+message ListArtifactsResponse_MonolithArtifact {
+ string workflow_run_backend_id = 1;
+ string workflow_job_run_backend_id = 2;
+ int64 database_id = 3;
+ string name = 4;
+ int64 size = 5;
+ google.protobuf.Timestamp created_at = 6;
+}
+
+message GetSignedArtifactURLRequest {
+ string workflow_run_backend_id = 1;
+ string workflow_job_run_backend_id = 2;
+ string name = 3;
+}
+
+message GetSignedArtifactURLResponse {
+ string signed_url = 1;
+}
+
+message DeleteArtifactRequest {
+ string workflow_run_backend_id = 1;
+ string workflow_job_run_backend_id = 2;
+ string name = 3;
+}
+
+message DeleteArtifactResponse {
+ bool ok = 1;
+ int64 artifact_id = 2;
+}
diff --git a/routers/api/actions/artifacts.go b/routers/api/actions/artifacts.go
index 9fbd3f045d..d530e9cee5 100644
--- a/routers/api/actions/artifacts.go
+++ b/routers/api/actions/artifacts.go
@@ -71,7 +71,6 @@ import (
"code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -80,6 +79,7 @@ import (
"code.gitea.io/gitea/modules/web"
web_types "code.gitea.io/gitea/modules/web/types"
actions_service "code.gitea.io/gitea/services/actions"
+ "code.gitea.io/gitea/services/context"
)
const artifactRouteBase = "/_apis/pipelines/workflows/{run_id}/artifacts"
diff --git a/routers/api/actions/artifacts_chunks.go b/routers/api/actions/artifacts_chunks.go
index 0713c8bba8..3a81724b3a 100644
--- a/routers/api/actions/artifacts_chunks.go
+++ b/routers/api/actions/artifacts_chunks.go
@@ -5,11 +5,16 @@ package actions
import (
"crypto/md5"
+ "crypto/sha256"
"encoding/base64"
+ "encoding/hex"
+ "errors"
"fmt"
+ "hash"
"io"
"path/filepath"
"sort"
+ "strings"
"time"
"code.gitea.io/gitea/models/actions"
@@ -18,6 +23,52 @@ import (
"code.gitea.io/gitea/modules/storage"
)
+func saveUploadChunkBase(st storage.ObjectStorage, ctx *ArtifactContext,
+ artifact *actions.ActionArtifact,
+ contentSize, runID, start, end, length int64, checkMd5 bool,
+) (int64, error) {
+ // build chunk store path
+ storagePath := fmt.Sprintf("tmp%d/%d-%d-%d-%d.chunk", runID, runID, artifact.ID, start, end)
+ var r io.Reader = ctx.Req.Body
+ var hasher hash.Hash
+ if checkMd5 {
+ // use io.TeeReader to avoid reading all body to md5 sum.
+ // it writes data to hasher after reading end
+ // if hash is not matched, delete the read-end result
+ hasher = md5.New()
+ r = io.TeeReader(r, hasher)
+ }
+ // save chunk to storage
+ writtenSize, err := st.Save(storagePath, r, -1)
+ if err != nil {
+ return -1, fmt.Errorf("save chunk to storage error: %v", err)
+ }
+ var checkErr error
+ if checkMd5 {
+ // check md5
+ reqMd5String := ctx.Req.Header.Get(artifactXActionsResultsMD5Header)
+ chunkMd5String := base64.StdEncoding.EncodeToString(hasher.Sum(nil))
+ log.Info("[artifact] check chunk md5, sum: %s, header: %s", chunkMd5String, reqMd5String)
+ // if md5 not match, delete the chunk
+ if reqMd5String != chunkMd5String {
+ checkErr = fmt.Errorf("md5 not match")
+ }
+ }
+ if writtenSize != contentSize {
+ checkErr = errors.Join(checkErr, fmt.Errorf("contentSize not match body size"))
+ }
+ if checkErr != nil {
+ if err := st.Delete(storagePath); err != nil {
+ log.Error("Error deleting chunk: %s, %v", storagePath, err)
+ }
+ return -1, checkErr
+ }
+ log.Info("[artifact] save chunk %s, size: %d, artifact id: %d, start: %d, end: %d",
+ storagePath, contentSize, artifact.ID, start, end)
+ // return chunk total size
+ return length, nil
+}
+
func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
artifact *actions.ActionArtifact,
contentSize, runID int64,
@@ -29,33 +80,15 @@ func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
log.Warn("parse content range error: %v, content-range: %s", err, contentRange)
return -1, fmt.Errorf("parse content range error: %v", err)
}
- // build chunk store path
- storagePath := fmt.Sprintf("tmp%d/%d-%d-%d-%d.chunk", runID, runID, artifact.ID, start, end)
- // use io.TeeReader to avoid reading all body to md5 sum.
- // it writes data to hasher after reading end
- // if hash is not matched, delete the read-end result
- hasher := md5.New()
- r := io.TeeReader(ctx.Req.Body, hasher)
- // save chunk to storage
- writtenSize, err := st.Save(storagePath, r, -1)
- if err != nil {
- return -1, fmt.Errorf("save chunk to storage error: %v", err)
- }
- // check md5
- reqMd5String := ctx.Req.Header.Get(artifactXActionsResultsMD5Header)
- chunkMd5String := base64.StdEncoding.EncodeToString(hasher.Sum(nil))
- log.Info("[artifact] check chunk md5, sum: %s, header: %s", chunkMd5String, reqMd5String)
- // if md5 not match, delete the chunk
- if reqMd5String != chunkMd5String || writtenSize != contentSize {
- if err := st.Delete(storagePath); err != nil {
- log.Error("Error deleting chunk: %s, %v", storagePath, err)
- }
- return -1, fmt.Errorf("md5 not match")
- }
- log.Info("[artifact] save chunk %s, size: %d, artifact id: %d, start: %d, end: %d",
- storagePath, contentSize, artifact.ID, start, end)
- // return chunk total size
- return length, nil
+ return saveUploadChunkBase(st, ctx, artifact, contentSize, runID, start, end, length, true)
+}
+
+func appendUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
+ artifact *actions.ActionArtifact,
+ start, contentSize, runID int64,
+) (int64, error) {
+ end := start + contentSize - 1
+ return saveUploadChunkBase(st, ctx, artifact, contentSize, runID, start, end, contentSize, false)
}
type chunkFileItem struct {
@@ -111,14 +144,14 @@ func mergeChunksForRun(ctx *ArtifactContext, st storage.ObjectStorage, runID int
log.Debug("artifact %d chunks not found", art.ID)
continue
}
- if err := mergeChunksForArtifact(ctx, chunks, st, art); err != nil {
+ if err := mergeChunksForArtifact(ctx, chunks, st, art, ""); err != nil {
return err
}
}
return nil
}
-func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st storage.ObjectStorage, artifact *actions.ActionArtifact) error {
+func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st storage.ObjectStorage, artifact *actions.ActionArtifact, checksum string) error {
sort.Slice(chunks, func(i, j int) bool {
return chunks[i].Start < chunks[j].Start
})
@@ -157,6 +190,14 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st
readers = append(readers, readCloser)
}
mergedReader := io.MultiReader(readers...)
+ shaPrefix := "sha256:"
+ var hash hash.Hash
+ if strings.HasPrefix(checksum, shaPrefix) {
+ hash = sha256.New()
+ }
+ if hash != nil {
+ mergedReader = io.TeeReader(mergedReader, hash)
+ }
// if chunk is gzip, use gz as extension
// download-artifact action will use content-encoding header to decide if it should decompress the file
@@ -185,6 +226,14 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st
}
}()
+ if hash != nil {
+ rawChecksum := hash.Sum(nil)
+ actualChecksum := hex.EncodeToString(rawChecksum)
+ if !strings.HasSuffix(checksum, actualChecksum) {
+ return fmt.Errorf("update artifact error checksum is invalid")
+ }
+ }
+
// save storage path to artifact
log.Debug("[artifact] merge chunks to artifact: %d, %s, old:%s", artifact.ID, storagePath, artifact.StoragePath)
// if artifact is already uploaded, delete the old file
diff --git a/routers/api/actions/artifacts_utils.go b/routers/api/actions/artifacts_utils.go
index 381e7eb16e..aaf89ef40e 100644
--- a/routers/api/actions/artifacts_utils.go
+++ b/routers/api/actions/artifacts_utils.go
@@ -43,6 +43,17 @@ func validateRunID(ctx *ArtifactContext) (*actions.ActionTask, int64, bool) {
return task, runID, true
}
+func validateRunIDV4(ctx *ArtifactContext, rawRunID string) (*actions.ActionTask, int64, bool) {
+ task := ctx.ActionTask
+ runID, err := strconv.ParseInt(rawRunID, 10, 64)
+ if err != nil || task.Job.RunID != runID {
+ log.Error("Error runID not match")
+ ctx.Error(http.StatusBadRequest, "run-id does not match")
+ return nil, 0, false
+ }
+ return task, runID, true
+}
+
func validateArtifactHash(ctx *ArtifactContext, artifactName string) bool {
paramHash := ctx.Params("artifact_hash")
// use artifact name to create upload url
diff --git a/routers/api/actions/artifactsv4.go b/routers/api/actions/artifactsv4.go
new file mode 100644
index 0000000000..8300989c75
--- /dev/null
+++ b/routers/api/actions/artifactsv4.go
@@ -0,0 +1,512 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package actions
+
+// GitHub Actions Artifacts V4 API Simple Description
+//
+// 1. Upload artifact
+// 1.1. CreateArtifact
+// Post: /twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact
+// Request:
+// {
+// "workflow_run_backend_id": "21",
+// "workflow_job_run_backend_id": "49",
+// "name": "test",
+// "version": 4
+// }
+// Response:
+// {
+// "ok": true,
+// "signedUploadUrl": "http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75"
+// }
+// 1.2. Upload Zip Content to Blobstorage (unauthenticated request)
+// PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=block
+// 1.3. Continue Upload Zip Content to Blobstorage (unauthenticated request), repeat until everything is uploaded
+// PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=appendBlock
+// 1.4. Unknown xml payload to Blobstorage (unauthenticated request), ignored for now
+// PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=blockList
+// 1.5. FinalizeArtifact
+// Post: /twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact
+// Request
+// {
+// "workflow_run_backend_id": "21",
+// "workflow_job_run_backend_id": "49",
+// "name": "test",
+// "size": "2097",
+// "hash": "sha256:b6325614d5649338b87215d9536b3c0477729b8638994c74cdefacb020a2cad4"
+// }
+// Response
+// {
+// "ok": true,
+// "artifactId": "4"
+// }
+// 2. Download artifact
+// 2.1. ListArtifacts and optionally filter by artifact exact name or id
+// Post: /twirp/github.actions.results.api.v1.ArtifactService/ListArtifacts
+// Request
+// {
+// "workflow_run_backend_id": "21",
+// "workflow_job_run_backend_id": "49",
+// "name_filter": "test"
+// }
+// Response
+// {
+// "artifacts": [
+// {
+// "workflowRunBackendId": "21",
+// "workflowJobRunBackendId": "49",
+// "databaseId": "4",
+// "name": "test",
+// "size": "2093",
+// "createdAt": "2024-01-23T00:13:28Z"
+// }
+// ]
+// }
+// 2.2. GetSignedArtifactURL get the URL to download the artifact zip file of a specific artifact
+// Post: /twirp/github.actions.results.api.v1.ArtifactService/GetSignedArtifactURL
+// Request
+// {
+// "workflow_run_backend_id": "21",
+// "workflow_job_run_backend_id": "49",
+// "name": "test"
+// }
+// Response
+// {
+// "signedUrl": "http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/DownloadArtifact?sig=wHzFOwpF-6220-5CA0CIRmAX9VbiTC2Mji89UOqo1E8=&expires=2024-01-23+21%3A51%3A56.872846295+%2B0100+CET&artifactName=test&taskID=76"
+// }
+// 2.3. Download Zip from Blobstorage (unauthenticated request)
+// GET: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/DownloadArtifact?sig=wHzFOwpF-6220-5CA0CIRmAX9VbiTC2Mji89UOqo1E8=&expires=2024-01-23+21%3A51%3A56.872846295+%2B0100+CET&artifactName=test&taskID=76
+
+import (
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/base64"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/storage"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
+
+ "google.golang.org/protobuf/encoding/protojson"
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ "google.golang.org/protobuf/types/known/timestamppb"
+)
+
+const (
+ ArtifactV4RouteBase = "/twirp/github.actions.results.api.v1.ArtifactService"
+ ArtifactV4ContentEncoding = "application/zip"
+)
+
+type artifactV4Routes struct {
+ prefix string
+ fs storage.ObjectStorage
+}
+
+func ArtifactV4Contexter() func(next http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ base, baseCleanUp := context.NewBaseContext(resp, req)
+ defer baseCleanUp()
+
+ ctx := &ArtifactContext{Base: base}
+ ctx.AppendContextValue(artifactContextKey, ctx)
+
+ next.ServeHTTP(ctx.Resp, ctx.Req)
+ })
+ }
+}
+
+func ArtifactsV4Routes(prefix string) *web.Route {
+ m := web.NewRoute()
+
+ r := artifactV4Routes{
+ prefix: prefix,
+ fs: storage.ActionsArtifacts,
+ }
+
+ m.Group("", func() {
+ m.Post("CreateArtifact", r.createArtifact)
+ m.Post("FinalizeArtifact", r.finalizeArtifact)
+ m.Post("ListArtifacts", r.listArtifacts)
+ m.Post("GetSignedArtifactURL", r.getSignedArtifactURL)
+ m.Post("DeleteArtifact", r.deleteArtifact)
+ }, ArtifactContexter())
+ m.Group("", func() {
+ m.Put("UploadArtifact", r.uploadArtifact)
+ m.Get("DownloadArtifact", r.downloadArtifact)
+ }, ArtifactV4Contexter())
+
+ return m
+}
+
+func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, taskID int64) []byte {
+ mac := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret())
+ mac.Write([]byte(endp))
+ mac.Write([]byte(expires))
+ mac.Write([]byte(artifactName))
+ mac.Write([]byte(fmt.Sprint(taskID)))
+ return mac.Sum(nil)
+}
+
+func (r artifactV4Routes) buildArtifactURL(endp, artifactName string, taskID int64) string {
+ expires := time.Now().Add(60 * time.Minute).Format("2006-01-02 15:04:05.999999999 -0700 MST")
+ uploadURL := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(r.prefix, "/") +
+ "/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + fmt.Sprint(taskID)
+ return uploadURL
+}
+
+func (r artifactV4Routes) verifySignature(ctx *ArtifactContext, endp string) (*actions.ActionTask, string, bool) {
+ rawTaskID := ctx.Req.URL.Query().Get("taskID")
+ sig := ctx.Req.URL.Query().Get("sig")
+ expires := ctx.Req.URL.Query().Get("expires")
+ artifactName := ctx.Req.URL.Query().Get("artifactName")
+ dsig, _ := base64.URLEncoding.DecodeString(sig)
+ taskID, _ := strconv.ParseInt(rawTaskID, 10, 64)
+
+ expecedsig := r.buildSignature(endp, expires, artifactName, taskID)
+ if !hmac.Equal(dsig, expecedsig) {
+ log.Error("Error unauthorized")
+ ctx.Error(http.StatusUnauthorized, "Error unauthorized")
+ return nil, "", false
+ }
+ t, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", expires)
+ if err != nil || t.Before(time.Now()) {
+ log.Error("Error link expired")
+ ctx.Error(http.StatusUnauthorized, "Error link expired")
+ return nil, "", false
+ }
+ task, err := actions.GetTaskByID(ctx, taskID)
+ if err != nil {
+ log.Error("Error runner api getting task by ID: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error runner api getting task by ID")
+ return nil, "", false
+ }
+ if task.Status != actions.StatusRunning {
+ log.Error("Error runner api getting task: task is not running")
+ ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running")
+ return nil, "", false
+ }
+ if err := task.LoadJob(ctx); err != nil {
+ log.Error("Error runner api getting job: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error runner api getting job")
+ return nil, "", false
+ }
+ return task, artifactName, true
+}
+
+func (r *artifactV4Routes) getArtifactByName(ctx *ArtifactContext, runID int64, name string) (*actions.ActionArtifact, error) {
+ var art actions.ActionArtifact
+ has, err := db.GetEngine(ctx).Where("run_id = ? AND artifact_name = ? AND artifact_path = ? AND content_encoding = ?", runID, name, name+".zip", ArtifactV4ContentEncoding).Get(&art)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, util.ErrNotExist
+ }
+ return &art, nil
+}
+
+func (r *artifactV4Routes) parseProtbufBody(ctx *ArtifactContext, req protoreflect.ProtoMessage) bool {
+ body, err := io.ReadAll(ctx.Req.Body)
+ if err != nil {
+ log.Error("Error decode request body: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error decode request body")
+ return false
+ }
+ err = protojson.Unmarshal(body, req)
+ if err != nil {
+ log.Error("Error decode request body: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error decode request body")
+ return false
+ }
+ return true
+}
+
+func (r *artifactV4Routes) sendProtbufBody(ctx *ArtifactContext, req protoreflect.ProtoMessage) {
+ resp, err := protojson.Marshal(req)
+ if err != nil {
+ log.Error("Error encode response body: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error encode response body")
+ return
+ }
+ ctx.Resp.Header().Set("Content-Type", "application/json;charset=utf-8")
+ ctx.Resp.WriteHeader(http.StatusOK)
+ _, _ = ctx.Resp.Write(resp)
+}
+
+func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
+ var req CreateArtifactRequest
+
+ if ok := r.parseProtbufBody(ctx, &req); !ok {
+ return
+ }
+ _, _, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
+ if !ok {
+ return
+ }
+
+ artifactName := req.Name
+
+ rententionDays := setting.Actions.ArtifactRetentionDays
+ if req.ExpiresAt != nil {
+ rententionDays = int64(time.Until(req.ExpiresAt.AsTime()).Hours() / 24)
+ }
+ // create or get artifact with name and path
+ artifact, err := actions.CreateArtifact(ctx, ctx.ActionTask, artifactName, artifactName+".zip", rententionDays)
+ if err != nil {
+ log.Error("Error create or get artifact: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error create or get artifact")
+ return
+ }
+ artifact.ContentEncoding = ArtifactV4ContentEncoding
+ if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
+ log.Error("Error UpdateArtifactByID: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID")
+ return
+ }
+
+ respData := CreateArtifactResponse{
+ Ok: true,
+ SignedUploadUrl: r.buildArtifactURL("UploadArtifact", artifactName, ctx.ActionTask.ID),
+ }
+ r.sendProtbufBody(ctx, &respData)
+}
+
+func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) {
+ task, artifactName, ok := r.verifySignature(ctx, "UploadArtifact")
+ if !ok {
+ return
+ }
+
+ comp := ctx.Req.URL.Query().Get("comp")
+ switch comp {
+ case "block", "appendBlock":
+ // get artifact by name
+ artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName)
+ if err != nil {
+ log.Error("Error artifact not found: %v", err)
+ ctx.Error(http.StatusNotFound, "Error artifact not found")
+ return
+ }
+
+ if comp == "block" {
+ artifact.FileSize = 0
+ artifact.FileCompressedSize = 0
+ }
+
+ _, err = appendUploadChunk(r.fs, ctx, artifact, artifact.FileSize, ctx.Req.ContentLength, artifact.RunID)
+ if err != nil {
+ log.Error("Error runner api getting task: task is not running")
+ ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running")
+ return
+ }
+ artifact.FileCompressedSize += ctx.Req.ContentLength
+ artifact.FileSize += ctx.Req.ContentLength
+ if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
+ log.Error("Error UpdateArtifactByID: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID")
+ return
+ }
+ ctx.JSON(http.StatusCreated, "appended")
+ case "blocklist":
+ ctx.JSON(http.StatusCreated, "created")
+ }
+}
+
+func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) {
+ var req FinalizeArtifactRequest
+
+ if ok := r.parseProtbufBody(ctx, &req); !ok {
+ return
+ }
+ _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
+ if !ok {
+ return
+ }
+
+ // get artifact by name
+ artifact, err := r.getArtifactByName(ctx, runID, req.Name)
+ if err != nil {
+ log.Error("Error artifact not found: %v", err)
+ ctx.Error(http.StatusNotFound, "Error artifact not found")
+ return
+ }
+ chunkMap, err := listChunksByRunID(r.fs, runID)
+ if err != nil {
+ log.Error("Error merge chunks: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error merge chunks")
+ return
+ }
+ chunks, ok := chunkMap[artifact.ID]
+ if !ok {
+ log.Error("Error merge chunks")
+ ctx.Error(http.StatusInternalServerError, "Error merge chunks")
+ return
+ }
+ checksum := ""
+ if req.Hash != nil {
+ checksum = req.Hash.Value
+ }
+ if err := mergeChunksForArtifact(ctx, chunks, r.fs, artifact, checksum); err != nil {
+ log.Error("Error merge chunks: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error merge chunks")
+ return
+ }
+
+ respData := FinalizeArtifactResponse{
+ Ok: true,
+ ArtifactId: artifact.ID,
+ }
+ r.sendProtbufBody(ctx, &respData)
+}
+
+func (r *artifactV4Routes) listArtifacts(ctx *ArtifactContext) {
+ var req ListArtifactsRequest
+
+ if ok := r.parseProtbufBody(ctx, &req); !ok {
+ return
+ }
+ _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
+ if !ok {
+ return
+ }
+
+ artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{RunID: runID})
+ if err != nil {
+ log.Error("Error getting artifacts: %v", err)
+ ctx.Error(http.StatusInternalServerError, err.Error())
+ return
+ }
+ if len(artifacts) == 0 {
+ log.Debug("[artifact] handleListArtifacts, no artifacts")
+ ctx.Error(http.StatusNotFound)
+ return
+ }
+
+ list := []*ListArtifactsResponse_MonolithArtifact{}
+
+ table := map[string]*ListArtifactsResponse_MonolithArtifact{}
+ for _, artifact := range artifacts {
+ if _, ok := table[artifact.ArtifactName]; ok || req.IdFilter != nil && artifact.ID != req.IdFilter.Value || req.NameFilter != nil && artifact.ArtifactName != req.NameFilter.Value || artifact.ArtifactName+".zip" != artifact.ArtifactPath || artifact.ContentEncoding != ArtifactV4ContentEncoding {
+ table[artifact.ArtifactName] = nil
+ continue
+ }
+
+ table[artifact.ArtifactName] = &ListArtifactsResponse_MonolithArtifact{
+ Name: artifact.ArtifactName,
+ CreatedAt: timestamppb.New(artifact.CreatedUnix.AsTime()),
+ DatabaseId: artifact.ID,
+ WorkflowRunBackendId: req.WorkflowRunBackendId,
+ WorkflowJobRunBackendId: req.WorkflowJobRunBackendId,
+ Size: artifact.FileSize,
+ }
+ }
+ for _, artifact := range table {
+ if artifact != nil {
+ list = append(list, artifact)
+ }
+ }
+
+ respData := ListArtifactsResponse{
+ Artifacts: list,
+ }
+ r.sendProtbufBody(ctx, &respData)
+}
+
+func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) {
+ var req GetSignedArtifactURLRequest
+
+ if ok := r.parseProtbufBody(ctx, &req); !ok {
+ return
+ }
+ _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
+ if !ok {
+ return
+ }
+
+ artifactName := req.Name
+
+ // get artifact by name
+ artifact, err := r.getArtifactByName(ctx, runID, artifactName)
+ if err != nil {
+ log.Error("Error artifact not found: %v", err)
+ ctx.Error(http.StatusNotFound, "Error artifact not found")
+ return
+ }
+
+ respData := GetSignedArtifactURLResponse{}
+
+ if setting.Actions.ArtifactStorage.MinioConfig.ServeDirect {
+ u, err := storage.ActionsArtifacts.URL(artifact.StoragePath, artifact.ArtifactPath)
+ if u != nil && err == nil {
+ respData.SignedUrl = u.String()
+ }
+ }
+ if respData.SignedUrl == "" {
+ respData.SignedUrl = r.buildArtifactURL("DownloadArtifact", artifactName, ctx.ActionTask.ID)
+ }
+ r.sendProtbufBody(ctx, &respData)
+}
+
+func (r *artifactV4Routes) downloadArtifact(ctx *ArtifactContext) {
+ task, artifactName, ok := r.verifySignature(ctx, "DownloadArtifact")
+ if !ok {
+ return
+ }
+
+ // get artifact by name
+ artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName)
+ if err != nil {
+ log.Error("Error artifact not found: %v", err)
+ ctx.Error(http.StatusNotFound, "Error artifact not found")
+ return
+ }
+
+ file, _ := r.fs.Open(artifact.StoragePath)
+
+ _, _ = io.Copy(ctx.Resp, file)
+}
+
+func (r *artifactV4Routes) deleteArtifact(ctx *ArtifactContext) {
+ var req DeleteArtifactRequest
+
+ if ok := r.parseProtbufBody(ctx, &req); !ok {
+ return
+ }
+ _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
+ if !ok {
+ return
+ }
+
+ // get artifact by name
+ artifact, err := r.getArtifactByName(ctx, runID, req.Name)
+ if err != nil {
+ log.Error("Error artifact not found: %v", err)
+ ctx.Error(http.StatusNotFound, "Error artifact not found")
+ return
+ }
+
+ err = actions.SetArtifactNeedDelete(ctx, runID, req.Name)
+ if err != nil {
+ log.Error("Error deleting artifacts: %v", err)
+ ctx.Error(http.StatusInternalServerError, err.Error())
+ return
+ }
+
+ respData := DeleteArtifactResponse{
+ Ok: true,
+ ArtifactId: artifact.ID,
+ }
+ r.sendProtbufBody(ctx, &respData)
+}
diff --git a/routers/api/actions/runner/utils.go b/routers/api/actions/runner/utils.go
index a7cb31288c..ff6ec5bd54 100644
--- a/routers/api/actions/runner/utils.go
+++ b/routers/api/actions/runner/utils.go
@@ -15,7 +15,6 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
- secret_module "code.gitea.io/gitea/modules/secret"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/actions"
@@ -32,14 +31,24 @@ func pickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv
return nil, false, nil
}
+ secrets, err := secret_model.GetSecretsOfTask(ctx, t)
+ if err != nil {
+ return nil, false, fmt.Errorf("GetSecretsOfTask: %w", err)
+ }
+
+ vars, err := actions_model.GetVariablesOfRun(ctx, t.Job.Run)
+ if err != nil {
+ return nil, false, fmt.Errorf("GetVariablesOfRun: %w", err)
+ }
+
actions.CreateCommitStatus(ctx, t.Job)
task := &runnerv1.Task{
Id: t.ID,
WorkflowPayload: t.Job.WorkflowPayload,
Context: generateTaskContext(t),
- Secrets: getSecretsOfTask(ctx, t),
- Vars: getVariablesOfTask(ctx, t),
+ Secrets: secrets,
+ Vars: vars,
}
if needs, err := findTaskNeeds(ctx, t); err != nil {
@@ -55,71 +64,6 @@ func pickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv
return task, true, nil
}
-func getSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) map[string]string {
- 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
- }
-
- ownerSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{OwnerID: task.Job.Run.Repo.OwnerID})
- if err != nil {
- log.Error("find secrets of owner %v: %v", task.Job.Run.Repo.OwnerID, err)
- // go on
- }
- repoSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{RepoID: task.Job.Run.RepoID})
- if err != nil {
- log.Error("find secrets of repo %v: %v", task.Job.Run.RepoID, err)
- // go on
- }
-
- for _, secret := range append(ownerSecrets, repoSecrets...) {
- if v, err := secret_module.DecryptSecret(setting.SecretKey, secret.Data); err != nil {
- log.Error("decrypt secret %v %q: %v", secret.ID, secret.Name, err)
- // go on
- } else {
- secrets[secret.Name] = v
- }
- }
-
- return secrets
-}
-
-func getVariablesOfTask(ctx context.Context, task *actions_model.ActionTask) map[string]string {
- variables := map[string]string{}
-
- // Global
- globalVariables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{})
- if err != nil {
- log.Error("find global variables: %v", err)
- }
-
- // Org / User level
- ownerVariables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{OwnerID: task.Job.Run.Repo.OwnerID})
- if err != nil {
- log.Error("find variables of org: %d, error: %v", task.Job.Run.Repo.OwnerID, err)
- }
-
- // Repo level
- repoVariables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{RepoID: task.Job.Run.RepoID})
- if err != nil {
- log.Error("find variables of repo: %d, error: %v", task.Job.Run.RepoID, err)
- }
-
- // Level precedence: Repo > Org / User > Global
- for _, v := range append(globalVariables, append(ownerVariables, repoVariables...)...) {
- variables[v.Name] = v.Data
- }
-
- return variables
-}
-
func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct {
event := map[string]any{}
_ = json.Unmarshal([]byte(t.Job.Run.EventPayload), &event)
diff --git a/routers/api/forgejo/v1/api.go b/routers/api/forgejo/v1/api.go
index 33e9eb1967..88c7502e66 100644
--- a/routers/api/forgejo/v1/api.go
+++ b/routers/api/forgejo/v1/api.go
@@ -5,10 +5,14 @@ package v1
import (
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/routers/api/shared"
)
func Routes() *web.Route {
m := web.NewRoute()
+
+ m.Use(shared.Middlewares()...)
+
forgejo := NewForgejo()
m.Get("", Root)
m.Get("/version", forgejo.GetVersion)
diff --git a/routers/api/packages/alpine/alpine.go b/routers/api/packages/alpine/alpine.go
index 3701fc97c9..cf0fe6c07c 100644
--- a/routers/api/packages/alpine/alpine.go
+++ b/routers/api/packages/alpine/alpine.go
@@ -15,12 +15,12 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
alpine_model "code.gitea.io/gitea/models/packages/alpine"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
packages_module "code.gitea.io/gitea/modules/packages"
alpine_module "code.gitea.io/gitea/modules/packages/alpine"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
alpine_service "code.gitea.io/gitea/services/packages/alpine"
)
diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go
index d990ebb56a..5e3cbac8f9 100644
--- a/routers/api/packages/api.go
+++ b/routers/api/packages/api.go
@@ -10,7 +10,6 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/perm"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
@@ -36,7 +35,7 @@ import (
"code.gitea.io/gitea/routers/api/packages/swift"
"code.gitea.io/gitea/routers/api/packages/vagrant"
"code.gitea.io/gitea/services/auth"
- context_service "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context"
)
func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
@@ -642,7 +641,7 @@ func CommonRoutes() *web.Route {
})
})
}, reqPackageAccess(perm.AccessModeRead))
- }, context_service.UserAssignmentWeb(), context.PackageAssignment())
+ }, context.UserAssignmentWeb(), context.PackageAssignment())
return r
}
@@ -812,7 +811,7 @@ func ContainerRoutes() *web.Route {
ctx.Status(http.StatusNotFound)
})
- }, container.ReqContainerAccess, context_service.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead))
+ }, container.ReqContainerAccess, context.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead))
return r
}
diff --git a/routers/api/packages/cargo/cargo.go b/routers/api/packages/cargo/cargo.go
index 8f1e965c9a..140e532efd 100644
--- a/routers/api/packages/cargo/cargo.go
+++ b/routers/api/packages/cargo/cargo.go
@@ -12,14 +12,15 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
cargo_module "code.gitea.io/gitea/modules/packages/cargo"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
packages_service "code.gitea.io/gitea/services/packages"
cargo_service "code.gitea.io/gitea/services/packages/cargo"
@@ -110,7 +111,7 @@ func SearchPackages(ctx *context.Context) {
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeCargo,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: &paginator,
},
)
diff --git a/routers/api/packages/chef/chef.go b/routers/api/packages/chef/chef.go
index f1e9ae12d8..b49f4e9d0a 100644
--- a/routers/api/packages/chef/chef.go
+++ b/routers/api/packages/chef/chef.go
@@ -15,12 +15,13 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
chef_module "code.gitea.io/gitea/modules/packages/chef"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
@@ -40,7 +41,7 @@ func PackagesUniverse(ctx *context.Context) {
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeChef,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -85,7 +86,7 @@ func EnumeratePackages(ctx *context.Context) {
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeChef,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: db.NewAbsoluteListOptions(
ctx.FormInt("start"),
ctx.FormInt("items"),
diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go
index 0551093cd1..a045da40de 100644
--- a/routers/api/packages/composer/composer.go
+++ b/routers/api/packages/composer/composer.go
@@ -14,12 +14,13 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
composer_module "code.gitea.io/gitea/modules/packages/composer"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
packages_service "code.gitea.io/gitea/services/packages"
@@ -66,7 +67,7 @@ func SearchPackages(ctx *context.Context) {
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeComposer,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: &paginator,
}
if ctx.FormTrim("type") != "" {
diff --git a/routers/api/packages/conan/conan.go b/routers/api/packages/conan/conan.go
index 4bf13222dc..c45e085a4d 100644
--- a/routers/api/packages/conan/conan.go
+++ b/routers/api/packages/conan/conan.go
@@ -15,13 +15,13 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
conan_model "code.gitea.io/gitea/models/packages/conan"
"code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
conan_module "code.gitea.io/gitea/modules/packages/conan"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
notify_service "code.gitea.io/gitea/services/notify"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/conan/search.go b/routers/api/packages/conan/search.go
index 2bcf9df162..7370c702cd 100644
--- a/routers/api/packages/conan/search.go
+++ b/routers/api/packages/conan/search.go
@@ -9,9 +9,9 @@ import (
conan_model "code.gitea.io/gitea/models/packages/conan"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
conan_module "code.gitea.io/gitea/modules/packages/conan"
+ "code.gitea.io/gitea/services/context"
)
// SearchResult contains the found recipe names
diff --git a/routers/api/packages/conda/conda.go b/routers/api/packages/conda/conda.go
index 0bee7baa96..30c80fc15e 100644
--- a/routers/api/packages/conda/conda.go
+++ b/routers/api/packages/conda/conda.go
@@ -12,13 +12,13 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
conda_model "code.gitea.io/gitea/models/packages/conda"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
conda_module "code.gitea.io/gitea/modules/packages/conda"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/dsnet/compress/bzip2"
diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go
index 8621242da4..e519766142 100644
--- a/routers/api/packages/container/container.go
+++ b/routers/api/packages/container/container.go
@@ -17,7 +17,6 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
@@ -25,6 +24,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
container_service "code.gitea.io/gitea/services/packages/container"
diff --git a/routers/api/packages/cran/cran.go b/routers/api/packages/cran/cran.go
index ae43df7c9a..2cec75294f 100644
--- a/routers/api/packages/cran/cran.go
+++ b/routers/api/packages/cran/cran.go
@@ -13,11 +13,11 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
cran_model "code.gitea.io/gitea/models/packages/cran"
- "code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages"
cran_module "code.gitea.io/gitea/modules/packages/cran"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go
index 379137e87e..241de3ac5d 100644
--- a/routers/api/packages/debian/debian.go
+++ b/routers/api/packages/debian/debian.go
@@ -13,11 +13,11 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages"
debian_module "code.gitea.io/gitea/modules/packages/debian"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
notify_service "code.gitea.io/gitea/services/notify"
packages_service "code.gitea.io/gitea/services/packages"
debian_service "code.gitea.io/gitea/services/packages/debian"
diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go
index 30854335c0..b65870a8d0 100644
--- a/routers/api/packages/generic/generic.go
+++ b/routers/api/packages/generic/generic.go
@@ -10,10 +10,10 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/goproxy/goproxy.go b/routers/api/packages/goproxy/goproxy.go
index 18e0074ab4..d658066bb4 100644
--- a/routers/api/packages/goproxy/goproxy.go
+++ b/routers/api/packages/goproxy/goproxy.go
@@ -12,11 +12,12 @@ import (
"time"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
goproxy_module "code.gitea.io/gitea/modules/packages/goproxy"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
@@ -129,7 +130,7 @@ func resolvePackage(ctx *context.Context, ownerID int64, name, version string) (
Value: name,
ExactMatch: true,
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Sort: packages_model.SortCreatedDesc,
})
if err != nil {
diff --git a/routers/api/packages/helm/helm.go b/routers/api/packages/helm/helm.go
index a8daa69dc3..efdb83ec0e 100644
--- a/routers/api/packages/helm/helm.go
+++ b/routers/api/packages/helm/helm.go
@@ -13,14 +13,15 @@ import (
"time"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
helm_module "code.gitea.io/gitea/modules/packages/helm"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
"gopkg.in/yaml.v3"
@@ -42,7 +43,7 @@ func Index(ctx *context.Context) {
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeHelm,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -110,7 +111,7 @@ func DownloadPackageFile(ctx *context.Context) {
Value: ctx.Params("package"),
},
HasFileWithName: filename,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
diff --git a/routers/api/packages/helper/helper.go b/routers/api/packages/helper/helper.go
index aadb10376c..cdb64109ad 100644
--- a/routers/api/packages/helper/helper.go
+++ b/routers/api/packages/helper/helper.go
@@ -10,9 +10,9 @@ import (
"net/url"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
// LogAndProcessError logs an error and calls a custom callback with the processed error message.
diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go
index 5106395eb1..27f0578db7 100644
--- a/routers/api/packages/maven/maven.go
+++ b/routers/api/packages/maven/maven.go
@@ -20,12 +20,12 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
maven_module "code.gitea.io/gitea/modules/packages/maven"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/npm/api.go b/routers/api/packages/npm/api.go
index 8470874884..f8e839c424 100644
--- a/routers/api/packages/npm/api.go
+++ b/routers/api/packages/npm/api.go
@@ -12,6 +12,7 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
npm_module "code.gitea.io/gitea/modules/packages/npm"
+ "code.gitea.io/gitea/modules/setting"
)
func createPackageMetadataResponse(registryURL string, pds []*packages_model.PackageDescriptor) *npm_module.PackageMetadata {
@@ -98,7 +99,7 @@ func createPackageSearchResponse(pds []*packages_model.PackageDescriptor, total
Maintainers: []npm_module.User{}, // npm cli needs this field
Keywords: metadata.Keywords,
Links: &npm_module.PackageSearchPackageLinks{
- Registry: pd.FullWebLink(),
+ Registry: setting.AppURL + "api/packages/" + pd.Owner.Name + "/npm",
Homepage: metadata.ProjectURL,
},
},
diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go
index 170edfbe11..84acfffae2 100644
--- a/routers/api/packages/npm/npm.go
+++ b/routers/api/packages/npm/npm.go
@@ -17,12 +17,13 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
npm_module "code.gitea.io/gitea/modules/packages/npm"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/hashicorp/go-version"
@@ -120,7 +121,7 @@ func DownloadPackageFileByName(ctx *context.Context) {
Value: packageNameFromParams(ctx),
},
HasFileWithName: filename,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -395,7 +396,7 @@ func setPackageTag(ctx std_ctx.Context, tag string, pv *packages_model.PackageVe
Properties: map[string]string{
npm_module.TagProperty: tag,
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
return err
@@ -431,7 +432,7 @@ func PackageSearch(ctx *context.Context) {
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNpm,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Name: packages_model.SearchValue{
ExactMatch: false,
Value: ctx.FormTrim("text"),
diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go
index a63df2a1fc..0eb817c1a3 100644
--- a/routers/api/packages/nuget/nuget.go
+++ b/routers/api/packages/nuget/nuget.go
@@ -17,13 +17,14 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
nuget_model "code.gitea.io/gitea/models/packages/nuget"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
nuget_module "code.gitea.io/gitea/modules/packages/nuget"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
@@ -122,7 +123,7 @@ func SearchServiceV2(ctx *context.Context) {
Name: packages_model.SearchValue{
Value: getSearchTerm(ctx),
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: paginator,
})
if err != nil {
@@ -172,7 +173,7 @@ func SearchServiceV2Count(ctx *context.Context) {
Name: packages_model.SearchValue{
Value: getSearchTerm(ctx),
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -187,7 +188,7 @@ func SearchServiceV3(ctx *context.Context) {
pvs, count, err := nuget_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: db.NewAbsoluteListOptions(
ctx.FormInt("skip"),
ctx.FormInt("take"),
@@ -313,7 +314,7 @@ func EnumeratePackageVersionsV2(ctx *context.Context) {
ExactMatch: true,
Value: packageName,
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: paginator,
})
if err != nil {
@@ -358,7 +359,7 @@ func EnumeratePackageVersionsV2Count(ctx *context.Context) {
ExactMatch: true,
Value: strings.Trim(ctx.FormTrim("id"), "'"),
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
diff --git a/routers/api/packages/pub/pub.go b/routers/api/packages/pub/pub.go
index 1f605c6c9f..f87df52a29 100644
--- a/routers/api/packages/pub/pub.go
+++ b/routers/api/packages/pub/pub.go
@@ -14,7 +14,6 @@ import (
"time"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
@@ -22,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/pypi/pypi.go b/routers/api/packages/pypi/pypi.go
index 5718b1203b..7824db1823 100644
--- a/routers/api/packages/pypi/pypi.go
+++ b/routers/api/packages/pypi/pypi.go
@@ -12,12 +12,12 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages"
pypi_module "code.gitea.io/gitea/modules/packages/pypi"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/validation"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go
index 5d06680552..4de361c214 100644
--- a/routers/api/packages/rpm/rpm.go
+++ b/routers/api/packages/rpm/rpm.go
@@ -13,13 +13,13 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
packages_module "code.gitea.io/gitea/modules/packages"
rpm_module "code.gitea.io/gitea/modules/packages/rpm"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
notify_service "code.gitea.io/gitea/services/notify"
packages_service "code.gitea.io/gitea/services/packages"
rpm_service "code.gitea.io/gitea/services/packages/rpm"
diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go
index 01fd4dad66..d2fbcd01f0 100644
--- a/routers/api/packages/rubygems/rubygems.go
+++ b/routers/api/packages/rubygems/rubygems.go
@@ -13,11 +13,12 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
rubygems_module "code.gitea.io/gitea/modules/packages/rubygems"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
@@ -43,7 +44,7 @@ func EnumeratePackagesLatest(ctx *context.Context) {
pvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeRubyGems,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -304,7 +305,7 @@ func getVersionsByFilename(ctx *context.Context, filename string) ([]*packages_m
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeRubyGems,
HasFileWithName: filename,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
return pvs, err
}
diff --git a/routers/api/packages/swift/swift.go b/routers/api/packages/swift/swift.go
index 6ad289e51e..a9da3ea9c2 100644
--- a/routers/api/packages/swift/swift.go
+++ b/routers/api/packages/swift/swift.go
@@ -13,14 +13,15 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
swift_module "code.gitea.io/gitea/modules/packages/swift"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/hashicorp/go-version"
@@ -433,7 +434,7 @@ func LookupPackageIdentifiers(ctx *context.Context) {
Properties: map[string]string{
swift_module.PropertyRepositoryURL: url,
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
diff --git a/routers/api/packages/vagrant/vagrant.go b/routers/api/packages/vagrant/vagrant.go
index af9cd08a62..98a81da368 100644
--- a/routers/api/packages/vagrant/vagrant.go
+++ b/routers/api/packages/vagrant/vagrant.go
@@ -12,11 +12,11 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages"
vagrant_module "code.gitea.io/gitea/modules/packages/vagrant"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/hashicorp/go-version"
diff --git a/routers/api/shared/middleware.go b/routers/api/shared/middleware.go
new file mode 100644
index 0000000000..e2ff004024
--- /dev/null
+++ b/routers/api/shared/middleware.go
@@ -0,0 +1,152 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package shared
+
+import (
+ "net/http"
+
+ auth_model "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/routers/common"
+ "code.gitea.io/gitea/services/auth"
+ "code.gitea.io/gitea/services/context"
+
+ "github.com/go-chi/cors"
+)
+
+func Middlewares() (stack []any) {
+ stack = append(stack, securityHeaders())
+
+ if setting.CORSConfig.Enabled {
+ stack = append(stack, cors.Handler(cors.Options{
+ AllowedOrigins: setting.CORSConfig.AllowDomain,
+ AllowedMethods: setting.CORSConfig.Methods,
+ AllowCredentials: setting.CORSConfig.AllowCredentials,
+ AllowedHeaders: append([]string{"Authorization", "X-Gitea-OTP", "X-Forgejo-OTP"}, setting.CORSConfig.Headers...),
+ MaxAge: int(setting.CORSConfig.MaxAge.Seconds()),
+ }))
+ }
+ return append(stack,
+ context.APIContexter(),
+
+ checkDeprecatedAuthMethods,
+ // Get user from session if logged in.
+ apiAuth(buildAuthGroup()),
+ verifyAuthWithOptions(&common.VerifyOptions{
+ SignInRequired: setting.Service.RequireSignInView,
+ }),
+ )
+}
+
+func buildAuthGroup() *auth.Group {
+ group := auth.NewGroup(
+ &auth.OAuth2{},
+ &auth.HTTPSign{},
+ &auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
+ )
+ if setting.Service.EnableReverseProxyAuthAPI {
+ group.Add(&auth.ReverseProxy{})
+ }
+
+ if setting.IsWindows && auth_model.IsSSPIEnabled(db.DefaultContext) {
+ group.Add(&auth.SSPI{}) // it MUST be the last, see the comment of SSPI
+ }
+
+ return group
+}
+
+func apiAuth(authMethod auth.Method) func(*context.APIContext) {
+ return func(ctx *context.APIContext) {
+ ar, err := common.AuthShared(ctx.Base, nil, authMethod)
+ if err != nil {
+ ctx.Error(http.StatusUnauthorized, "APIAuth", err)
+ return
+ }
+ ctx.Doer = ar.Doer
+ ctx.IsSigned = ar.Doer != nil
+ ctx.IsBasicAuth = ar.IsBasicAuth
+ }
+}
+
+// verifyAuthWithOptions checks authentication according to options
+func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIContext) {
+ return func(ctx *context.APIContext) {
+ // Check prohibit login users.
+ if ctx.IsSigned {
+ if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
+ ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
+ ctx.JSON(http.StatusForbidden, map[string]string{
+ "message": "This account is not activated.",
+ })
+ return
+ }
+ if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
+ log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr())
+ ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
+ ctx.JSON(http.StatusForbidden, map[string]string{
+ "message": "This account is prohibited from signing in, please contact your site administrator.",
+ })
+ return
+ }
+
+ if ctx.Doer.MustChangePassword {
+ ctx.JSON(http.StatusForbidden, map[string]string{
+ "message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password",
+ })
+ return
+ }
+ }
+
+ // Redirect to dashboard if user tries to visit any non-login page.
+ if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" {
+ ctx.Redirect(setting.AppSubURL + "/")
+ return
+ }
+
+ if options.SignInRequired {
+ if !ctx.IsSigned {
+ // Restrict API calls with error message.
+ ctx.JSON(http.StatusForbidden, map[string]string{
+ "message": "Only signed in user is allowed to call APIs.",
+ })
+ return
+ } else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
+ ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
+ ctx.JSON(http.StatusForbidden, map[string]string{
+ "message": "This account is not activated.",
+ })
+ return
+ }
+ }
+
+ if options.AdminRequired {
+ if !ctx.Doer.IsAdmin {
+ ctx.JSON(http.StatusForbidden, map[string]string{
+ "message": "You have no permission to request for this.",
+ })
+ return
+ }
+ }
+ }
+}
+
+// check for and warn against deprecated authentication options
+func checkDeprecatedAuthMethods(ctx *context.APIContext) {
+ if ctx.FormString("token") != "" || ctx.FormString("access_token") != "" {
+ ctx.Resp.Header().Set("Warning", "token and access_token API authentication is deprecated and will be removed in gitea 1.23. Please use AuthorizationHeaderToken instead. Existing queries will continue to work but without authorization.")
+ }
+}
+
+func securityHeaders() func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ // CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers
+ // http://stackoverflow.com/a/3146618/244009
+ resp.Header().Set("x-content-type-options", "nosniff")
+ next.ServeHTTP(resp, req)
+ })
+ }
+}
diff --git a/routers/api/v1/activitypub/person.go b/routers/api/v1/activitypub/person.go
index cad5032d10..995a148f0b 100644
--- a/routers/api/v1/activitypub/person.go
+++ b/routers/api/v1/activitypub/person.go
@@ -9,9 +9,9 @@ import (
"strings"
"code.gitea.io/gitea/modules/activitypub"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
ap "github.com/go-ap/activitypub"
"github.com/go-ap/jsonld"
diff --git a/routers/api/v1/activitypub/reqsignature.go b/routers/api/v1/activitypub/reqsignature.go
index 3f60ed7776..59ebc74b89 100644
--- a/routers/api/v1/activitypub/reqsignature.go
+++ b/routers/api/v1/activitypub/reqsignature.go
@@ -13,9 +13,9 @@ import (
"net/url"
"code.gitea.io/gitea/modules/activitypub"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/setting"
+ gitea_context "code.gitea.io/gitea/services/context"
ap "github.com/go-ap/activitypub"
"github.com/go-fed/httpsig"
diff --git a/routers/api/v1/admin/adopt.go b/routers/api/v1/admin/adopt.go
index bf030eb222..a4708fe032 100644
--- a/routers/api/v1/admin/adopt.go
+++ b/routers/api/v1/admin/adopt.go
@@ -8,9 +8,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/api/v1/admin/cron.go b/routers/api/v1/admin/cron.go
index cc8c6c9e23..e1ca6048c9 100644
--- a/routers/api/v1/admin/cron.go
+++ b/routers/api/v1/admin/cron.go
@@ -6,11 +6,11 @@ package admin
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/cron"
)
diff --git a/routers/api/v1/admin/email.go b/routers/api/v1/admin/email.go
index 5914215bc2..ba963e9f69 100644
--- a/routers/api/v1/admin/email.go
+++ b/routers/api/v1/admin/email.go
@@ -7,9 +7,9 @@ import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/admin/hooks.go b/routers/api/v1/admin/hooks.go
index 8a095a7def..4c168b55bf 100644
--- a/routers/api/v1/admin/hooks.go
+++ b/routers/api/v1/admin/hooks.go
@@ -8,12 +8,13 @@ import (
"net/http"
"code.gitea.io/gitea/models/webhook"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
webhook_service "code.gitea.io/gitea/services/webhook"
)
@@ -37,7 +38,7 @@ func ListHooks(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/HookList"
- sysHooks, err := webhook.GetSystemWebhooks(ctx, util.OptionalBoolNone)
+ sysHooks, err := webhook.GetSystemWebhooks(ctx, optional.None[bool]())
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetSystemWebhooks", err)
return
diff --git a/routers/api/v1/admin/org.go b/routers/api/v1/admin/org.go
index bf68942a9c..a5c299bbf0 100644
--- a/routers/api/v1/admin/org.go
+++ b/routers/api/v1/admin/org.go
@@ -10,10 +10,10 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/admin/repo.go b/routers/api/v1/admin/repo.go
index a4895f260b..c119d5390a 100644
--- a/routers/api/v1/admin/repo.go
+++ b/routers/api/v1/admin/repo.go
@@ -4,10 +4,10 @@
package admin
import (
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/repo"
+ "code.gitea.io/gitea/services/context"
)
// CreateRepo api for creating a repository
diff --git a/routers/api/v1/admin/runners.go b/routers/api/v1/admin/runners.go
index c0d9364435..329242d9f6 100644
--- a/routers/api/v1/admin/runners.go
+++ b/routers/api/v1/admin/runners.go
@@ -4,8 +4,8 @@
package admin
import (
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/shared"
+ "code.gitea.io/gitea/services/context"
)
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index 2ce7651a09..986305d423 100644
--- a/routers/api/v1/admin/user.go
+++ b/routers/api/v1/admin/user.go
@@ -15,7 +15,6 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
@@ -25,6 +24,7 @@ import (
"code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/mailer"
user_service "code.gitea.io/gitea/services/user"
@@ -133,7 +133,7 @@ func CreateUser(ctx *context.APIContext) {
u.UpdatedUnix = u.CreatedUnix
}
- if err := user_model.CreateUser(ctx, u, overwriteDefault); err != nil {
+ if err := user_model.AdminCreateUser(ctx, u, overwriteDefault); err != nil {
if user_model.IsErrUserAlreadyExist(err) ||
user_model.IsErrEmailAlreadyUsed(err) ||
db.IsErrNameReserved(err) ||
@@ -209,7 +209,7 @@ func EditUser(ctx *context.APIContext) {
}
if form.Email != nil {
- if err := user_service.AddOrSetPrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil {
+ if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil {
switch {
case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err):
ctx.Error(http.StatusBadRequest, "EmailInvalid", err)
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 48ce494479..b202e32e4e 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -72,7 +72,6 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
- "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
@@ -80,11 +79,11 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/routers/api/shared"
"code.gitea.io/gitea/routers/api/v1/activitypub"
"code.gitea.io/gitea/routers/api/v1/admin"
"code.gitea.io/gitea/routers/api/v1/misc"
@@ -94,15 +93,13 @@ import (
"code.gitea.io/gitea/routers/api/v1/repo"
"code.gitea.io/gitea/routers/api/v1/settings"
"code.gitea.io/gitea/routers/api/v1/user"
- "code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/services/auth"
- context_service "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
"gitea.com/go-chi/binding"
- "github.com/go-chi/cors"
)
func sudo() func(ctx *context.APIContext) {
@@ -732,98 +729,6 @@ func bind[T any](_ T) any {
}
}
-func buildAuthGroup() *auth.Group {
- group := auth.NewGroup(
- &auth.OAuth2{},
- &auth.HTTPSign{},
- &auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
- )
- if setting.Service.EnableReverseProxyAuthAPI {
- group.Add(&auth.ReverseProxy{})
- }
-
- if setting.IsWindows && auth_model.IsSSPIEnabled(db.DefaultContext) {
- group.Add(&auth.SSPI{}) // it MUST be the last, see the comment of SSPI
- }
-
- return group
-}
-
-func apiAuth(authMethod auth.Method) func(*context.APIContext) {
- return func(ctx *context.APIContext) {
- ar, err := common.AuthShared(ctx.Base, nil, authMethod)
- if err != nil {
- ctx.Error(http.StatusUnauthorized, "APIAuth", err)
- return
- }
- ctx.Doer = ar.Doer
- ctx.IsSigned = ar.Doer != nil
- ctx.IsBasicAuth = ar.IsBasicAuth
- }
-}
-
-// verifyAuthWithOptions checks authentication according to options
-func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIContext) {
- return func(ctx *context.APIContext) {
- // Check prohibit login users.
- if ctx.IsSigned {
- if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
- ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
- ctx.JSON(http.StatusForbidden, map[string]string{
- "message": "This account is not activated.",
- })
- return
- }
- if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
- log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr())
- ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
- ctx.JSON(http.StatusForbidden, map[string]string{
- "message": "This account is prohibited from signing in, please contact your site administrator.",
- })
- return
- }
-
- if ctx.Doer.MustChangePassword {
- ctx.JSON(http.StatusForbidden, map[string]string{
- "message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password",
- })
- return
- }
- }
-
- // Redirect to dashboard if user tries to visit any non-login page.
- if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" {
- ctx.Redirect(setting.AppSubURL + "/")
- return
- }
-
- if options.SignInRequired {
- if !ctx.IsSigned {
- // Restrict API calls with error message.
- ctx.JSON(http.StatusForbidden, map[string]string{
- "message": "Only signed in user is allowed to call APIs.",
- })
- return
- } else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
- ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
- ctx.JSON(http.StatusForbidden, map[string]string{
- "message": "This account is not activated.",
- })
- return
- }
- }
-
- if options.AdminRequired {
- if !ctx.Doer.IsAdmin {
- ctx.JSON(http.StatusForbidden, map[string]string{
- "message": "You have no permission to request for this.",
- })
- return
- }
- }
- }
-}
-
func individualPermsChecker(ctx *context.APIContext) {
// org permissions have been checked in context.OrgAssignment(), but individual permissions haven't been checked.
if ctx.ContextUser.IsIndividual() {
@@ -842,37 +747,11 @@ func individualPermsChecker(ctx *context.APIContext) {
}
}
-// check for and warn against deprecated authentication options
-func checkDeprecatedAuthMethods(ctx *context.APIContext) {
- if ctx.FormString("token") != "" || ctx.FormString("access_token") != "" {
- ctx.Resp.Header().Set("Warning", "token and access_token API authentication is deprecated and will be removed in gitea 1.23. Please use AuthorizationHeaderToken instead. Existing queries will continue to work but without authorization.")
- }
-}
-
// Routes registers all v1 APIs routes to web application.
func Routes() *web.Route {
m := web.NewRoute()
- m.Use(securityHeaders())
- if setting.CORSConfig.Enabled {
- m.Use(cors.Handler(cors.Options{
- AllowedOrigins: setting.CORSConfig.AllowDomain,
- AllowedMethods: setting.CORSConfig.Methods,
- AllowCredentials: setting.CORSConfig.AllowCredentials,
- AllowedHeaders: append([]string{"Authorization", "X-Gitea-OTP", "X-Forgejo-OTP"}, setting.CORSConfig.Headers...),
- MaxAge: int(setting.CORSConfig.MaxAge.Seconds()),
- }))
- }
- m.Use(context.APIContexter())
-
- m.Use(checkDeprecatedAuthMethods)
-
- // Get user from session if logged in.
- m.Use(apiAuth(buildAuthGroup()))
-
- m.Use(verifyAuthWithOptions(&common.VerifyOptions{
- SignInRequired: setting.Service.RequireSignInView,
- }))
+ m.Use(shared.Middlewares()...)
m.Group("", func() {
// Miscellaneous (no scope required)
@@ -889,11 +768,11 @@ func Routes() *web.Route {
m.Group("/user/{username}", func() {
m.Get("", activitypub.Person)
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
- }, context_service.UserAssignmentAPI())
+ }, context.UserAssignmentAPI())
m.Group("/user-id/{user-id}", func() {
m.Get("", activitypub.Person)
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
- }, context_service.UserIDAssignmentAPI())
+ }, context.UserIDAssignmentAPI())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub))
}
@@ -949,7 +828,7 @@ func Routes() *web.Route {
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
m.Get("/activities/feeds", user.ListUserActivityFeeds)
- }, context_service.UserAssignmentAPI(), individualPermsChecker)
+ }, context.UserAssignmentAPI(), individualPermsChecker)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
// Users (requires user scope)
@@ -969,7 +848,7 @@ func Routes() *web.Route {
}
m.Get("/subscriptions", user.GetWatchedRepos)
- }, context_service.UserAssignmentAPI())
+ }, context.UserAssignmentAPI())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
// Users (requires user scope)
@@ -1004,7 +883,7 @@ func Routes() *web.Route {
m.Get("", user.CheckMyFollowing)
m.Put("", user.Follow)
m.Delete("", user.Unfollow)
- }, context_service.UserAssignmentAPI())
+ }, context.UserAssignmentAPI())
})
// (admin:public_key scope)
@@ -1068,7 +947,7 @@ func Routes() *web.Route {
m.Group("", func() {
m.Put("/block/{username}", user.BlockUser)
m.Put("/unblock/{username}", user.UnblockUser)
- }, context_service.UserAssignmentAPI())
+ }, context.UserAssignmentAPI())
})
m.Group("/avatar", func() {
@@ -1485,14 +1364,14 @@ func Routes() *web.Route {
m.Get("/files", reqToken(), packages.ListPackageFiles)
})
m.Get("/", reqToken(), packages.ListPackages)
- }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context_service.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead))
+ }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead))
// Organizations
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
m.Group("/users/{username}/orgs", func() {
m.Get("", reqToken(), org.ListUserOrgs)
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
- }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context_service.UserAssignmentAPI())
+ }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI())
m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create)
m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
m.Group("/orgs/{org}", func() {
@@ -1554,7 +1433,7 @@ func Routes() *web.Route {
m.Group("", func() {
m.Put("/block/{username}", org.BlockUser)
m.Put("/unblock/{username}", org.UnblockUser)
- }, context_service.UserAssignmentAPI())
+ }, context.UserAssignmentAPI())
}, reqToken(), reqOrgOwnership())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
m.Group("/teams/{teamid}", func() {
@@ -1598,7 +1477,7 @@ func Routes() *web.Route {
m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
m.Post("/rename", bind(api.RenameUserOption{}), admin.RenameUser)
- }, context_service.UserAssignmentAPI())
+ }, context.UserAssignmentAPI())
})
m.Group("/emails", func() {
m.Get("", admin.GetAllEmails)
@@ -1628,14 +1507,3 @@ func Routes() *web.Route {
return m
}
-
-func securityHeaders() func(http.Handler) http.Handler {
- return func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
- // CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers
- // http://stackoverflow.com/a/3146618/244009
- resp.Header().Set("x-content-type-options", "nosniff")
- next.ServeHTTP(resp, req)
- })
- }
-}
diff --git a/routers/api/v1/misc/gitignore.go b/routers/api/v1/misc/gitignore.go
index 7c7fe4b125..dffd771752 100644
--- a/routers/api/v1/misc/gitignore.go
+++ b/routers/api/v1/misc/gitignore.go
@@ -6,11 +6,11 @@ package misc
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/options"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
// Shows a list of all Gitignore templates
diff --git a/routers/api/v1/misc/label_templates.go b/routers/api/v1/misc/label_templates.go
index 0e0ca39fc5..cc11f37626 100644
--- a/routers/api/v1/misc/label_templates.go
+++ b/routers/api/v1/misc/label_templates.go
@@ -6,9 +6,9 @@ package misc
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/misc/licenses.go b/routers/api/v1/misc/licenses.go
index 65f63468cf..2a980f5084 100644
--- a/routers/api/v1/misc/licenses.go
+++ b/routers/api/v1/misc/licenses.go
@@ -8,12 +8,12 @@ import (
"net/http"
"net/url"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/options"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
// Returns a list of all License templates
diff --git a/routers/api/v1/misc/markup.go b/routers/api/v1/misc/markup.go
index 7b24b353b6..9699c79368 100644
--- a/routers/api/v1/misc/markup.go
+++ b/routers/api/v1/misc/markup.go
@@ -6,12 +6,12 @@ package misc
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/common"
+ "code.gitea.io/gitea/services/context"
)
// Markup render markup document to HTML
diff --git a/routers/api/v1/misc/markup_test.go b/routers/api/v1/misc/markup_test.go
index ec8f8f47b7..5236fd06ae 100644
--- a/routers/api/v1/misc/markup_test.go
+++ b/routers/api/v1/misc/markup_test.go
@@ -10,19 +10,19 @@ import (
"strings"
"testing"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
const (
- AppURL = "http://localhost:3000/"
- Repo = "gogits/gogs"
- AppSubURL = AppURL + Repo + "/"
+ AppURL = "http://localhost:3000/"
+ Repo = "gogits/gogs"
+ FullURL = AppURL + Repo + "/"
)
func testRenderMarkup(t *testing.T, mode, filePath, text, responseBody string, responseCode int) {
@@ -74,20 +74,20 @@ func TestAPI_RenderGFM(t *testing.T) {
// rendered
`Wiki! Enjoy :)
`,
// Guard wiki sidebar: special syntax
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
// rendered
- `Guardfile-DSL / Configuring-Guard
+ `Guardfile-DSL / Configuring-Guard
`,
// special syntax
`[[Name|Link]]`,
// rendered
- `Name
+ `Name
`,
// empty
``,
@@ -111,8 +111,8 @@ Here are some links to the most important topics. You can find the full list of
Wine Staging on website wine-staging.com .
Quick Links
Here are some links to the most important topics. You can find the full list of pages at the sidebar.
-Configuration
-
+Configuration
+
`,
}
diff --git a/routers/api/v1/misc/nodeinfo.go b/routers/api/v1/misc/nodeinfo.go
index 6045208f24..9c2a0db8d2 100644
--- a/routers/api/v1/misc/nodeinfo.go
+++ b/routers/api/v1/misc/nodeinfo.go
@@ -9,9 +9,9 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
)
const cacheKeyNodeInfoUsage = "API_NodeInfoUsage"
diff --git a/routers/api/v1/misc/signing.go b/routers/api/v1/misc/signing.go
index 2ca9813e15..24a46c1e70 100644
--- a/routers/api/v1/misc/signing.go
+++ b/routers/api/v1/misc/signing.go
@@ -7,8 +7,8 @@ import (
"fmt"
"net/http"
- "code.gitea.io/gitea/modules/context"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ "code.gitea.io/gitea/services/context"
)
// SigningKey returns the public key of the default signing key if it exists
diff --git a/routers/api/v1/misc/version.go b/routers/api/v1/misc/version.go
index 83fa35219a..e3b43a0e6b 100644
--- a/routers/api/v1/misc/version.go
+++ b/routers/api/v1/misc/version.go
@@ -6,9 +6,9 @@ package misc
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
)
// Version shows the version of the Gitea server
diff --git a/routers/api/v1/notify/notifications.go b/routers/api/v1/notify/notifications.go
index c87da9399f..46b3c7f5e7 100644
--- a/routers/api/v1/notify/notifications.go
+++ b/routers/api/v1/notify/notifications.go
@@ -9,9 +9,9 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
)
// NewAvailable check if unread notifications exist
diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go
index 55ca6ad1fd..8d97e8a3f8 100644
--- a/routers/api/v1/notify/repo.go
+++ b/routers/api/v1/notify/repo.go
@@ -10,9 +10,9 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/notify/threads.go b/routers/api/v1/notify/threads.go
index 919e52952d..8e12d359cb 100644
--- a/routers/api/v1/notify/threads.go
+++ b/routers/api/v1/notify/threads.go
@@ -10,7 +10,7 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/notify/user.go b/routers/api/v1/notify/user.go
index 4abdfb2e92..879f484cce 100644
--- a/routers/api/v1/notify/user.go
+++ b/routers/api/v1/notify/user.go
@@ -9,8 +9,8 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/org/avatar.go b/routers/api/v1/org/avatar.go
index 7b621a50c3..e34c68dfc9 100644
--- a/routers/api/v1/org/avatar.go
+++ b/routers/api/v1/org/avatar.go
@@ -7,9 +7,9 @@ import (
"encoding/base64"
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
user_service "code.gitea.io/gitea/services/user"
)
diff --git a/routers/api/v1/org/hook.go b/routers/api/v1/org/hook.go
index 3c3f058b5d..c1dc0519ea 100644
--- a/routers/api/v1/org/hook.go
+++ b/routers/api/v1/org/hook.go
@@ -6,10 +6,10 @@ package org
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
webhook_service "code.gitea.io/gitea/services/webhook"
)
diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go
index 5a03059ded..b5ec54ccf4 100644
--- a/routers/api/v1/org/label.go
+++ b/routers/api/v1/org/label.go
@@ -9,11 +9,11 @@ import (
"strings"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go
index 422b7cecfe..fb66d4c3f5 100644
--- a/routers/api/v1/org/member.go
+++ b/routers/api/v1/org/member.go
@@ -9,11 +9,11 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/organization"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go
index 2eefad6a98..9f9483d4ff 100644
--- a/routers/api/v1/org/org.go
+++ b/routers/api/v1/org/org.go
@@ -13,12 +13,12 @@ import (
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/org"
user_service "code.gitea.io/gitea/services/user"
diff --git a/routers/api/v1/org/runners.go b/routers/api/v1/org/runners.go
index 05bce8daef..2a52bd8778 100644
--- a/routers/api/v1/org/runners.go
+++ b/routers/api/v1/org/runners.go
@@ -4,8 +4,8 @@
package org
import (
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/shared"
+ "code.gitea.io/gitea/services/context"
)
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
diff --git a/routers/api/v1/org/secrets.go b/routers/api/v1/org/secrets.go
index ddc74d865b..abb6bb26c4 100644
--- a/routers/api/v1/org/secrets.go
+++ b/routers/api/v1/org/secrets.go
@@ -9,11 +9,11 @@ import (
"code.gitea.io/gitea/models/db"
secret_model "code.gitea.io/gitea/models/secret"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
secret_service "code.gitea.io/gitea/services/secrets"
)
diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go
index f129c66230..b62a386fd7 100644
--- a/routers/api/v1/org/team.go
+++ b/routers/api/v1/org/team.go
@@ -15,12 +15,12 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
org_service "code.gitea.io/gitea/services/org"
repo_service "code.gitea.io/gitea/services/repository"
diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go
index a79ba315be..b38aa13167 100644
--- a/routers/api/v1/packages/package.go
+++ b/routers/api/v1/packages/package.go
@@ -7,10 +7,10 @@ import (
"net/http"
"code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
packages_service "code.gitea.io/gitea/services/packages"
)
@@ -60,7 +60,7 @@ func ListPackages(ctx *context.APIContext) {
OwnerID: ctx.Package.Owner.ID,
Type: packages.Type(packageType),
Name: packages.SearchValue{Value: query},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: &listOptions,
})
if err != nil {
diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go
index 039cdadac9..e0af276c71 100644
--- a/routers/api/v1/repo/action.go
+++ b/routers/api/v1/repo/action.go
@@ -7,10 +7,10 @@ import (
"errors"
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
secret_service "code.gitea.io/gitea/services/secrets"
)
diff --git a/routers/api/v1/repo/avatar.go b/routers/api/v1/repo/avatar.go
index 1b661955f0..698337ffd2 100644
--- a/routers/api/v1/repo/avatar.go
+++ b/routers/api/v1/repo/avatar.go
@@ -7,9 +7,9 @@ import (
"encoding/base64"
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/api/v1/repo/blob.go b/routers/api/v1/repo/blob.go
index 26605bba03..3b116666ea 100644
--- a/routers/api/v1/repo/blob.go
+++ b/routers/api/v1/repo/blob.go
@@ -6,7 +6,7 @@ package repo
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
files_service "code.gitea.io/gitea/services/repository/files"
)
diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index 2cdbcd25a2..5e6b6a8658 100644
--- a/routers/api/v1/repo/branch.go
+++ b/routers/api/v1/repo/branch.go
@@ -14,7 +14,6 @@ import (
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/optional"
@@ -22,6 +21,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository"
diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go
index 2c85657b93..a43a21a88e 100644
--- a/routers/api/v1/repo/collaborators.go
+++ b/routers/api/v1/repo/collaborators.go
@@ -13,11 +13,11 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
repo_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go
index d01cf6b8bc..d06a3b4e49 100644
--- a/routers/api/v1/repo/commits.go
+++ b/routers/api/v1/repo/commits.go
@@ -12,11 +12,11 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go
index 94a6381e25..72ada25ec7 100644
--- a/routers/api/v1/repo/file.go
+++ b/routers/api/v1/repo/file.go
@@ -19,7 +19,6 @@ import (
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httpcache"
@@ -30,6 +29,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/common"
+ "code.gitea.io/gitea/services/context"
archiver_service "code.gitea.io/gitea/services/repository/archiver"
files_service "code.gitea.io/gitea/services/repository/files"
)
@@ -672,6 +672,7 @@ func UpdateFile(ctx *context.APIContext) {
apiOpts := web.GetForm(ctx).(*api.UpdateFileOptions)
if ctx.Repo.Repository.IsEmpty {
ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
+ return
}
if apiOpts.BranchName == "" {
diff --git a/routers/api/v1/repo/flags.go b/routers/api/v1/repo/flags.go
index cbb2c95914..ac5cb2e6d6 100644
--- a/routers/api/v1/repo/flags.go
+++ b/routers/api/v1/repo/flags.go
@@ -6,9 +6,9 @@ package repo
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
)
func ListFlags(ctx *context.APIContext) {
diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go
index 69433bf4cc..212cc7a93b 100644
--- a/routers/api/v1/repo/fork.go
+++ b/routers/api/v1/repo/fork.go
@@ -14,11 +14,11 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
repo_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/api/v1/repo/git_hook.go b/routers/api/v1/repo/git_hook.go
index 7e471e263b..26ae84d08d 100644
--- a/routers/api/v1/repo/git_hook.go
+++ b/routers/api/v1/repo/git_hook.go
@@ -6,10 +6,10 @@ package repo
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/git_ref.go b/routers/api/v1/repo/git_ref.go
index 34d2dcfcc8..0fa58425b8 100644
--- a/routers/api/v1/repo/git_ref.go
+++ b/routers/api/v1/repo/git_ref.go
@@ -7,10 +7,10 @@ import (
"net/http"
"net/url"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
)
// GetGitAllRefs get ref or an list all the refs of a repository
diff --git a/routers/api/v1/repo/hook.go b/routers/api/v1/repo/hook.go
index 8859e3ae23..ffd2313591 100644
--- a/routers/api/v1/repo/hook.go
+++ b/routers/api/v1/repo/hook.go
@@ -11,13 +11,13 @@ import (
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/models/webhook"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
webhook_service "code.gitea.io/gitea/services/webhook"
)
diff --git a/routers/api/v1/repo/hook_test.go b/routers/api/v1/repo/hook_test.go
index 94a71e20ad..37cf61c1ed 100644
--- a/routers/api/v1/repo/hook_test.go
+++ b/routers/api/v1/repo/hook_test.go
@@ -9,7 +9,7 @@ import (
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/models/webhook"
- "code.gitea.io/gitea/modules/contexttest"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index ae35bf2eef..af3f890f88 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -19,14 +19,14 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue"
notify_service "code.gitea.io/gitea/services/notify"
@@ -123,14 +123,14 @@ func SearchIssues(ctx *context.APIContext) {
return
}
- var isClosed util.OptionalBool
+ var isClosed optional.Option[bool]
switch ctx.FormString("state") {
case "closed":
- isClosed = util.OptionalBoolTrue
+ isClosed = optional.Some(true)
case "all":
- isClosed = util.OptionalBoolNone
+ isClosed = optional.None[bool]()
default:
- isClosed = util.OptionalBoolFalse
+ isClosed = optional.Some(false)
}
var (
@@ -143,7 +143,7 @@ func SearchIssues(ctx *context.APIContext) {
Private: false,
AllPublic: true,
TopicOnly: false,
- Collaborate: util.OptionalBoolNone,
+ Collaborate: optional.None[bool](),
// This needs to be a column that is not nil in fixtures or
// MySQL will return different results when sorting by null in some cases
OrderBy: db.SearchOrderByAlphabetically,
@@ -166,7 +166,7 @@ func SearchIssues(ctx *context.APIContext) {
opts.OwnerID = owner.ID
opts.AllLimited = false
opts.AllPublic = false
- opts.Collaborate = util.OptionalBoolFalse
+ opts.Collaborate = optional.Some(false)
}
if ctx.FormString("team") != "" {
if ctx.FormString("owner") == "" {
@@ -205,14 +205,14 @@ func SearchIssues(ctx *context.APIContext) {
keyword = ""
}
- var isPull util.OptionalBool
+ var isPull optional.Option[bool]
switch ctx.FormString("type") {
case "pulls":
- isPull = util.OptionalBoolTrue
+ isPull = optional.Some(true)
case "issues":
- isPull = util.OptionalBoolFalse
+ isPull = optional.Some(false)
default:
- isPull = util.OptionalBoolNone
+ isPull = optional.None[bool]()
}
var includedAnyLabels []int64
@@ -397,14 +397,14 @@ func ListIssues(ctx *context.APIContext) {
return
}
- var isClosed util.OptionalBool
+ var isClosed optional.Option[bool]
switch ctx.FormString("state") {
case "closed":
- isClosed = util.OptionalBoolTrue
+ isClosed = optional.Some(true)
case "all":
- isClosed = util.OptionalBoolNone
+ isClosed = optional.None[bool]()
default:
- isClosed = util.OptionalBoolFalse
+ isClosed = optional.Some(false)
}
keyword := ctx.FormTrim("q")
@@ -453,31 +453,29 @@ func ListIssues(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
- var isPull util.OptionalBool
+ isPull := optional.None[bool]()
switch ctx.FormString("type") {
case "pulls":
- isPull = util.OptionalBoolTrue
+ isPull = optional.Some(true)
case "issues":
- isPull = util.OptionalBoolFalse
- default:
- isPull = util.OptionalBoolNone
+ isPull = optional.Some(false)
}
- if isPull != util.OptionalBoolNone && !ctx.Repo.CanReadIssuesOrPulls(isPull.IsTrue()) {
+ if isPull.Has() && !ctx.Repo.CanReadIssuesOrPulls(isPull.Value()) {
ctx.NotFound()
return
}
- if isPull == util.OptionalBoolNone {
+ if !isPull.Has() {
canReadIssues := ctx.Repo.CanRead(unit.TypeIssues)
canReadPulls := ctx.Repo.CanRead(unit.TypePullRequests)
if !canReadIssues && !canReadPulls {
ctx.NotFound()
return
} else if !canReadIssues {
- isPull = util.OptionalBoolTrue
+ isPull = optional.Some(true)
} else if !canReadPulls {
- isPull = util.OptionalBoolFalse
+ isPull = optional.Some(false)
}
}
diff --git a/routers/api/v1/repo/issue_attachment.go b/routers/api/v1/repo/issue_attachment.go
index eaaaeaa9f9..fc9e88d63d 100644
--- a/routers/api/v1/repo/issue_attachment.go
+++ b/routers/api/v1/repo/issue_attachment.go
@@ -9,12 +9,12 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/attachment"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue"
)
diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go
index 35d7658b6c..c3005dee3b 100644
--- a/routers/api/v1/repo/issue_comment.go
+++ b/routers/api/v1/repo/issue_comment.go
@@ -14,11 +14,11 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue"
)
@@ -278,15 +278,15 @@ func ListRepoIssueComments(ctx *context.APIContext) {
return
}
- var isPull util.OptionalBool
+ var isPull optional.Option[bool]
canReadIssue := ctx.Repo.CanRead(unit.TypeIssues)
canReadPull := ctx.Repo.CanRead(unit.TypePullRequests)
if canReadIssue && canReadPull {
- isPull = util.OptionalBoolNone
+ isPull = optional.None[bool]()
} else if canReadIssue {
- isPull = util.OptionalBoolFalse
+ isPull = optional.Some(false)
} else if canReadPull {
- isPull = util.OptionalBoolTrue
+ isPull = optional.Some(true)
} else {
ctx.NotFound()
return
diff --git a/routers/api/v1/repo/issue_comment_attachment.go b/routers/api/v1/repo/issue_comment_attachment.go
index 5622a9292a..0f8fc96f08 100644
--- a/routers/api/v1/repo/issue_comment_attachment.go
+++ b/routers/api/v1/repo/issue_comment_attachment.go
@@ -9,12 +9,12 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/attachment"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue"
)
diff --git a/routers/api/v1/repo/issue_dependency.go b/routers/api/v1/repo/issue_dependency.go
index 62d1057cdf..a42920d4fd 100644
--- a/routers/api/v1/repo/issue_dependency.go
+++ b/routers/api/v1/repo/issue_dependency.go
@@ -11,10 +11,10 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/issue_label.go b/routers/api/v1/repo/issue_label.go
index 008ff5c699..fd9625c0fb 100644
--- a/routers/api/v1/repo/issue_label.go
+++ b/routers/api/v1/repo/issue_label.go
@@ -8,9 +8,9 @@ import (
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue"
)
diff --git a/routers/api/v1/repo/issue_pin.go b/routers/api/v1/repo/issue_pin.go
index 61f88de34e..ff1135862b 100644
--- a/routers/api/v1/repo/issue_pin.go
+++ b/routers/api/v1/repo/issue_pin.go
@@ -7,8 +7,8 @@ import (
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/issue_reaction.go b/routers/api/v1/repo/issue_reaction.go
index 27edc1abad..c395255c13 100644
--- a/routers/api/v1/repo/issue_reaction.go
+++ b/routers/api/v1/repo/issue_reaction.go
@@ -9,10 +9,10 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue"
)
diff --git a/routers/api/v1/repo/issue_stopwatch.go b/routers/api/v1/repo/issue_stopwatch.go
index 52bf8b5c7b..d9054e8f77 100644
--- a/routers/api/v1/repo/issue_stopwatch.go
+++ b/routers/api/v1/repo/issue_stopwatch.go
@@ -8,8 +8,8 @@ import (
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/issue_subscription.go b/routers/api/v1/repo/issue_subscription.go
index ece880c03e..a535172462 100644
--- a/routers/api/v1/repo/issue_subscription.go
+++ b/routers/api/v1/repo/issue_subscription.go
@@ -9,9 +9,9 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go
index cf03e72aa0..c640515881 100644
--- a/routers/api/v1/repo/issue_tracked_time.go
+++ b/routers/api/v1/repo/issue_tracked_time.go
@@ -12,10 +12,10 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/key.go b/routers/api/v1/repo/key.go
index af48c40885..88444a2625 100644
--- a/routers/api/v1/repo/key.go
+++ b/routers/api/v1/repo/key.go
@@ -15,12 +15,12 @@ import (
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go
index 420d3ab5b4..b6eb51fd20 100644
--- a/routers/api/v1/repo/label.go
+++ b/routers/api/v1/repo/label.go
@@ -9,11 +9,11 @@ import (
"strconv"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/language.go b/routers/api/v1/repo/language.go
index 12f1761ad0..f1d5bbe45f 100644
--- a/routers/api/v1/repo/language.go
+++ b/routers/api/v1/repo/language.go
@@ -9,8 +9,8 @@ import (
"strconv"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/context"
)
type languageResponse []*repo_model.LanguageStat
diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go
index 839fbfe8a1..2caaa130e8 100644
--- a/routers/api/v1/repo/migrate.go
+++ b/routers/api/v1/repo/migrate.go
@@ -17,7 +17,6 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
@@ -26,6 +25,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/migrations"
diff --git a/routers/api/v1/repo/milestone.go b/routers/api/v1/repo/milestone.go
index 9c2ed16d93..b9534016e4 100644
--- a/routers/api/v1/repo/milestone.go
+++ b/routers/api/v1/repo/milestone.go
@@ -11,12 +11,12 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
@@ -61,10 +61,10 @@ func ListMilestones(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
state := api.StateType(ctx.FormString("state"))
- var isClosed util.OptionalBool
+ var isClosed optional.Option[bool]
switch state {
case api.StateClosed, api.StateOpen:
- isClosed = util.OptionalBoolOf(state == api.StateClosed)
+ isClosed = optional.Some(state == api.StateClosed)
}
milestones, total, err := db.FindAndCount[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go
index 26e0be301c..864644e1ef 100644
--- a/routers/api/v1/repo/mirror.go
+++ b/routers/api/v1/repo/mirror.go
@@ -13,12 +13,12 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/migrations"
diff --git a/routers/api/v1/repo/notes.go b/routers/api/v1/repo/notes.go
index e7e00dae41..a4a1d4eab7 100644
--- a/routers/api/v1/repo/notes.go
+++ b/routers/api/v1/repo/notes.go
@@ -7,9 +7,9 @@ import (
"fmt"
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/patch.go b/routers/api/v1/repo/patch.go
index 9b5635d245..0e0601b7d9 100644
--- a/routers/api/v1/repo/patch.go
+++ b/routers/api/v1/repo/patch.go
@@ -10,10 +10,10 @@ import (
"code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/repository/files"
)
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index ef9b4893fb..f78e34d7b3 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -21,7 +21,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
@@ -32,6 +31,7 @@ import (
"code.gitea.io/gitea/routers/api/v1/utils"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/automerge"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/gitdiff"
diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go
index e39a096add..6860d6e773 100644
--- a/routers/api/v1/repo/pull_review.go
+++ b/routers/api/v1/repo/pull_review.go
@@ -12,11 +12,11 @@ import (
"code.gitea.io/gitea/models/organization"
access_model "code.gitea.io/gitea/models/perm/access"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/gitrepo"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go
index a41c5ba7d8..f0f3c0bbc7 100644
--- a/routers/api/v1/repo/release.go
+++ b/routers/api/v1/repo/release.go
@@ -4,6 +4,7 @@
package repo
import (
+ "fmt"
"net/http"
"code.gitea.io/gitea/models"
@@ -11,10 +12,10 @@ import (
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
release_service "code.gitea.io/gitea/services/release"
)
@@ -215,6 +216,10 @@ func CreateRelease(ctx *context.APIContext) {
// "409":
// "$ref": "#/responses/error"
form := web.GetForm(ctx).(*api.CreateReleaseOption)
+ if ctx.Repo.Repository.IsEmpty {
+ ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
+ return
+ }
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, form.TagName)
if err != nil {
if !repo_model.IsErrReleaseNotExist(err) {
diff --git a/routers/api/v1/repo/release_attachment.go b/routers/api/v1/repo/release_attachment.go
index c36bf12e6d..59fd83e3a2 100644
--- a/routers/api/v1/repo/release_attachment.go
+++ b/routers/api/v1/repo/release_attachment.go
@@ -4,16 +4,18 @@
package repo
import (
+ "io"
"net/http"
+ "strings"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/attachment"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/convert"
)
@@ -154,6 +156,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
// - application/json
// consumes:
// - multipart/form-data
+ // - application/octet-stream
// parameters:
// - name: owner
// in: path
@@ -180,7 +183,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
// in: formData
// description: attachment to upload
// type: file
- // required: true
+ // required: false
// responses:
// "201":
// "$ref": "#/responses/Attachment"
@@ -202,20 +205,36 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
}
// Get uploaded file from request
- file, header, err := ctx.Req.FormFile("attachment")
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetFile", err)
- return
- }
- defer file.Close()
+ var content io.ReadCloser
+ var filename string
+ var size int64 = -1
- filename := header.Filename
- if query := ctx.FormString("name"); query != "" {
- filename = query
+ if strings.HasPrefix(strings.ToLower(ctx.Req.Header.Get("Content-Type")), "multipart/form-data") {
+ file, header, err := ctx.Req.FormFile("attachment")
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetFile", err)
+ return
+ }
+ defer file.Close()
+
+ content = file
+ size = header.Size
+ filename = header.Filename
+ if name := ctx.FormString("name"); name != "" {
+ filename = name
+ }
+ } else {
+ content = ctx.Req.Body
+ filename = ctx.FormString("name")
+ }
+
+ if filename == "" {
+ ctx.Error(http.StatusBadRequest, "CreateReleaseAttachment", "Could not determine name of attachment.")
+ return
}
// Create a new attachment and save the file
- attach, err := attachment.UploadAttachment(ctx, file, setting.Repository.Release.AllowedTypes, header.Size, &repo_model.Attachment{
+ attach, err := attachment.UploadAttachment(ctx, content, setting.Repository.Release.AllowedTypes, size, &repo_model.Attachment{
Name: filename,
UploaderID: ctx.Doer.ID,
RepoID: ctx.Repo.Repository.ID,
diff --git a/routers/api/v1/repo/release_tags.go b/routers/api/v1/repo/release_tags.go
index 9f2098df06..fec91164a2 100644
--- a/routers/api/v1/repo/release_tags.go
+++ b/routers/api/v1/repo/release_tags.go
@@ -8,7 +8,7 @@ import (
"code.gitea.io/gitea/models"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
releaseservice "code.gitea.io/gitea/services/release"
)
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index de105f474f..316a1161da 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -19,18 +19,18 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/issue"
repo_service "code.gitea.io/gitea/services/repository"
@@ -135,33 +135,33 @@ func Search(ctx *context.APIContext) {
PriorityOwnerID: ctx.FormInt64("priority_owner_id"),
TeamID: ctx.FormInt64("team_id"),
TopicOnly: ctx.FormBool("topic"),
- Collaborate: util.OptionalBoolNone,
+ Collaborate: optional.None[bool](),
Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")),
- Template: util.OptionalBoolNone,
+ Template: optional.None[bool](),
StarredByID: ctx.FormInt64("starredBy"),
IncludeDescription: ctx.FormBool("includeDesc"),
}
if ctx.FormString("template") != "" {
- opts.Template = util.OptionalBoolOf(ctx.FormBool("template"))
+ opts.Template = optional.Some(ctx.FormBool("template"))
}
if ctx.FormBool("exclusive") {
- opts.Collaborate = util.OptionalBoolFalse
+ opts.Collaborate = optional.Some(false)
}
mode := ctx.FormString("mode")
switch mode {
case "source":
- opts.Fork = util.OptionalBoolFalse
- opts.Mirror = util.OptionalBoolFalse
+ opts.Fork = optional.Some(false)
+ opts.Mirror = optional.Some(false)
case "fork":
- opts.Fork = util.OptionalBoolTrue
+ opts.Fork = optional.Some(true)
case "mirror":
- opts.Mirror = util.OptionalBoolTrue
+ opts.Mirror = optional.Some(true)
case "collaborative":
- opts.Mirror = util.OptionalBoolFalse
- opts.Collaborate = util.OptionalBoolTrue
+ opts.Mirror = optional.Some(false)
+ opts.Collaborate = optional.Some(true)
case "":
default:
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid search mode: \"%s\"", mode))
@@ -169,11 +169,11 @@ func Search(ctx *context.APIContext) {
}
if ctx.FormString("archived") != "" {
- opts.Archived = util.OptionalBoolOf(ctx.FormBool("archived"))
+ opts.Archived = optional.Some(ctx.FormBool("archived"))
}
if ctx.FormString("is_private") != "" {
- opts.IsPrivate = util.OptionalBoolOf(ctx.FormBool("is_private"))
+ opts.IsPrivate = optional.Some(ctx.FormBool("is_private"))
}
sortMode := ctx.FormString("sort")
@@ -358,7 +358,7 @@ func Generate(ctx *context.APIContext) {
return
}
- opts := repo_module.GenerateRepoOptions{
+ opts := repo_service.GenerateRepoOptions{
Name: form.Name,
DefaultBranch: form.DefaultBranch,
Description: form.Description,
@@ -720,7 +720,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
if ctx.Repo.GitRepo == nil && !repo.IsEmpty {
var err error
- ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
+ ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, repo)
if err != nil {
ctx.Error(http.StatusInternalServerError, "Unable to OpenRepository", err)
return err
@@ -731,7 +731,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
// Default branch only updated if changed and exist or the repository is empty
if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && (repo.IsEmpty || ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch)) {
if !repo.IsEmpty {
- if err := ctx.Repo.GitRepo.SetDefaultBranch(*opts.DefaultBranch); err != nil {
+ if err := gitrepo.SetDefaultBranch(ctx, ctx.Repo.Repository, *opts.DefaultBranch); err != nil {
if !git.IsErrUnsupportedVersion(err) {
ctx.Error(http.StatusInternalServerError, "SetDefaultBranch", err)
return err
diff --git a/routers/api/v1/repo/repo_test.go b/routers/api/v1/repo/repo_test.go
index 08ba7fabac..8d6ca9e3b5 100644
--- a/routers/api/v1/repo/repo_test.go
+++ b/routers/api/v1/repo/repo_test.go
@@ -9,9 +9,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
diff --git a/routers/api/v1/repo/runners.go b/routers/api/v1/repo/runners.go
index 0a2bbf8117..fe133b311d 100644
--- a/routers/api/v1/repo/runners.go
+++ b/routers/api/v1/repo/runners.go
@@ -4,8 +4,8 @@
package repo
import (
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/shared"
+ "code.gitea.io/gitea/services/context"
)
// GetRegistrationToken returns the token to register repo runners
diff --git a/routers/api/v1/repo/star.go b/routers/api/v1/repo/star.go
index 05227e33a0..99676de119 100644
--- a/routers/api/v1/repo/star.go
+++ b/routers/api/v1/repo/star.go
@@ -7,9 +7,9 @@ import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/status.go b/routers/api/v1/repo/status.go
index b4edf0608c..9e36ea0aed 100644
--- a/routers/api/v1/repo/status.go
+++ b/routers/api/v1/repo/status.go
@@ -9,12 +9,12 @@ import (
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
- files_service "code.gitea.io/gitea/services/repository/files"
+ commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus"
)
// NewCommitStatus creates a new CommitStatus
@@ -64,7 +64,7 @@ func NewCommitStatus(ctx *context.APIContext) {
Description: form.Description,
Context: form.Context,
}
- if err := files_service.CreateCommitStatus(ctx, ctx.Repo.Repository, ctx.Doer, sha, status); err != nil {
+ if err := commitstatus_service.CreateCommitStatus(ctx, ctx.Repo.Repository, ctx.Doer, sha, status); err != nil {
ctx.Error(http.StatusInternalServerError, "CreateCommitStatus", err)
return
}
diff --git a/routers/api/v1/repo/subscriber.go b/routers/api/v1/repo/subscriber.go
index 05509fc443..8584182857 100644
--- a/routers/api/v1/repo/subscriber.go
+++ b/routers/api/v1/repo/subscriber.go
@@ -7,9 +7,9 @@ import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go
index ad812ace56..84ec3dd91c 100644
--- a/routers/api/v1/repo/tag.go
+++ b/routers/api/v1/repo/tag.go
@@ -10,10 +10,10 @@ import (
"code.gitea.io/gitea/models"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
releaseservice "code.gitea.io/gitea/services/release"
)
diff --git a/routers/api/v1/repo/teams.go b/routers/api/v1/repo/teams.go
index 1bacc71211..0ecf3a39d8 100644
--- a/routers/api/v1/repo/teams.go
+++ b/routers/api/v1/repo/teams.go
@@ -8,7 +8,7 @@ import (
"net/http"
"code.gitea.io/gitea/models/organization"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
org_service "code.gitea.io/gitea/services/org"
repo_service "code.gitea.io/gitea/services/repository"
diff --git a/routers/api/v1/repo/topic.go b/routers/api/v1/repo/topic.go
index d662b9b583..1d8e675bde 100644
--- a/routers/api/v1/repo/topic.go
+++ b/routers/api/v1/repo/topic.go
@@ -8,11 +8,11 @@ import (
"strings"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go
index a00bbcc17c..94c6bc6ded 100644
--- a/routers/api/v1/repo/transfer.go
+++ b/routers/api/v1/repo/transfer.go
@@ -14,10 +14,10 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
repo_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/api/v1/repo/tree.go b/routers/api/v1/repo/tree.go
index f63100b6ea..353a996d5b 100644
--- a/routers/api/v1/repo/tree.go
+++ b/routers/api/v1/repo/tree.go
@@ -6,7 +6,7 @@ package repo
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
files_service "code.gitea.io/gitea/services/repository/files"
)
diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go
index ba3e978a83..347b7539b9 100644
--- a/routers/api/v1/repo/wiki.go
+++ b/routers/api/v1/repo/wiki.go
@@ -10,13 +10,13 @@ import (
"net/url"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
notify_service "code.gitea.io/gitea/services/notify"
wiki_service "code.gitea.io/gitea/services/wiki"
diff --git a/routers/api/v1/settings/settings.go b/routers/api/v1/settings/settings.go
index 957b839e66..c422315b22 100644
--- a/routers/api/v1/settings/settings.go
+++ b/routers/api/v1/settings/settings.go
@@ -6,9 +6,9 @@ package settings
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
)
// GetGeneralUISettings returns instance's global settings for ui
diff --git a/routers/api/v1/shared/runners.go b/routers/api/v1/shared/runners.go
index fe1584d2e7..f184786d7d 100644
--- a/routers/api/v1/shared/runners.go
+++ b/routers/api/v1/shared/runners.go
@@ -8,8 +8,8 @@ import (
"net/http"
actions_model "code.gitea.io/gitea/models/actions"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
// RegistrationToken is a string used to register a runner with a server
diff --git a/routers/api/v1/user/action.go b/routers/api/v1/user/action.go
index cbe332a779..babb8c0cf7 100644
--- a/routers/api/v1/user/action.go
+++ b/routers/api/v1/user/action.go
@@ -7,10 +7,10 @@ import (
"errors"
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
secret_service "code.gitea.io/gitea/services/secrets"
)
diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go
index eb35d8031a..b61ebac7d0 100644
--- a/routers/api/v1/user/app.go
+++ b/routers/api/v1/user/app.go
@@ -13,10 +13,10 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/user/avatar.go b/routers/api/v1/user/avatar.go
index 1c1bb6181a..f912296228 100644
--- a/routers/api/v1/user/avatar.go
+++ b/routers/api/v1/user/avatar.go
@@ -7,9 +7,9 @@ import (
"encoding/base64"
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
user_service "code.gitea.io/gitea/services/user"
)
diff --git a/routers/api/v1/user/email.go b/routers/api/v1/user/email.go
index 3dcea9083c..33aa851a80 100644
--- a/routers/api/v1/user/email.go
+++ b/routers/api/v1/user/email.go
@@ -8,9 +8,9 @@ import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
user_service "code.gitea.io/gitea/services/user"
)
diff --git a/routers/api/v1/user/follower.go b/routers/api/v1/user/follower.go
index 783cee8584..1ba7346b8c 100644
--- a/routers/api/v1/user/follower.go
+++ b/routers/api/v1/user/follower.go
@@ -9,9 +9,9 @@ import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go
index 234da5dfdc..dcf5da0b2e 100644
--- a/routers/api/v1/user/gpg_key.go
+++ b/routers/api/v1/user/gpg_key.go
@@ -10,10 +10,11 @@ import (
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
@@ -132,6 +133,11 @@ func GetGPGKey(ctx *context.APIContext) {
// CreateUserGPGKey creates new GPG key to given user by ID.
func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) {
+ if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
+ ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
+ return
+ }
+
token := asymkey_model.VerificationToken(ctx.Doer, 1)
lastToken := asymkey_model.VerificationToken(ctx.Doer, 0)
@@ -268,6 +274,11 @@ func DeleteGPGKey(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
+ if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
+ ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
+ return
+ }
+
if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.ParamsInt64(":id")); err != nil {
if asymkey_model.IsErrGPGKeyAccessDenied(err) {
ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
diff --git a/routers/api/v1/user/helper.go b/routers/api/v1/user/helper.go
index 392b266ebd..8b5c64e291 100644
--- a/routers/api/v1/user/helper.go
+++ b/routers/api/v1/user/helper.go
@@ -7,7 +7,7 @@ import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
)
// GetUserByParamsName get user by name
diff --git a/routers/api/v1/user/hook.go b/routers/api/v1/user/hook.go
index e87385e4a2..9d9ca5bf01 100644
--- a/routers/api/v1/user/hook.go
+++ b/routers/api/v1/user/hook.go
@@ -6,10 +6,10 @@ package user
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
webhook_service "code.gitea.io/gitea/services/webhook"
)
diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go
index dd185aa7d6..bcbfd93bd3 100644
--- a/routers/api/v1/user/key.go
+++ b/routers/api/v1/user/key.go
@@ -5,19 +5,20 @@ package user
import (
std_ctx "context"
+ "fmt"
"net/http"
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/repo"
"code.gitea.io/gitea/routers/api/v1/utils"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
@@ -198,6 +199,11 @@ func GetPublicKey(ctx *context.APIContext) {
// CreateUserPublicKey creates new public key to given user by ID.
func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) {
+ if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
+ ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
+ return
+ }
+
content, err := asymkey_model.CheckPublicKeyString(form.Key)
if err != nil {
repo.HandleCheckKeyStringError(ctx, err)
@@ -263,6 +269,11 @@ func DeletePublicKey(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
+ if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
+ ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
+ return
+ }
+
id := ctx.ParamsInt64(":id")
externallyManaged, err := asymkey_model.PublicKeyIsExternallyManaged(ctx, id)
if err != nil {
diff --git a/routers/api/v1/user/repo.go b/routers/api/v1/user/repo.go
index b8b2d265bf..81f8e0f3fe 100644
--- a/routers/api/v1/user/repo.go
+++ b/routers/api/v1/user/repo.go
@@ -11,9 +11,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/user/runners.go b/routers/api/v1/user/runners.go
index 51556ae0fb..899218473e 100644
--- a/routers/api/v1/user/runners.go
+++ b/routers/api/v1/user/runners.go
@@ -4,8 +4,8 @@
package user
import (
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/shared"
+ "code.gitea.io/gitea/services/context"
)
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
diff --git a/routers/api/v1/user/settings.go b/routers/api/v1/user/settings.go
index 062df1ca43..d0a8daaa85 100644
--- a/routers/api/v1/user/settings.go
+++ b/routers/api/v1/user/settings.go
@@ -6,10 +6,10 @@ package user
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
user_service "code.gitea.io/gitea/services/user"
)
diff --git a/routers/api/v1/user/star.go b/routers/api/v1/user/star.go
index 2659789ddd..e624884db3 100644
--- a/routers/api/v1/user/star.go
+++ b/routers/api/v1/user/star.go
@@ -12,9 +12,9 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go
index 67651062c7..831c1436da 100644
--- a/routers/api/v1/user/user.go
+++ b/routers/api/v1/user/user.go
@@ -10,8 +10,8 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/user/watch.go b/routers/api/v1/user/watch.go
index 7f531eafaa..706f4cc66b 100644
--- a/routers/api/v1/user/watch.go
+++ b/routers/api/v1/user/watch.go
@@ -11,9 +11,9 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/utils/block.go b/routers/api/v1/utils/block.go
index 187d69044e..34fad96034 100644
--- a/routers/api/v1/utils/block.go
+++ b/routers/api/v1/utils/block.go
@@ -7,8 +7,8 @@ import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
user_service "code.gitea.io/gitea/services/user"
)
diff --git a/routers/api/v1/utils/git.go b/routers/api/v1/utils/git.go
index 5e80190017..4e25137817 100644
--- a/routers/api/v1/utils/git.go
+++ b/routers/api/v1/utils/git.go
@@ -8,10 +8,10 @@ import (
"fmt"
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/context"
)
// ResolveRefOrSha resolve ref to sha if exist
diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go
index 28b21ab8db..f1abd49a7d 100644
--- a/routers/api/v1/utils/hook.go
+++ b/routers/api/v1/utils/hook.go
@@ -12,12 +12,12 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
+ "code.gitea.io/gitea/services/context"
webhook_service "code.gitea.io/gitea/services/webhook"
)
diff --git a/routers/api/v1/utils/page.go b/routers/api/v1/utils/page.go
index 6910b82931..024ba7b8d9 100644
--- a/routers/api/v1/utils/page.go
+++ b/routers/api/v1/utils/page.go
@@ -5,7 +5,7 @@ package utils
import (
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/common/auth.go b/routers/common/auth.go
index 8904785d51..115d65ed10 100644
--- a/routers/common/auth.go
+++ b/routers/common/auth.go
@@ -5,9 +5,9 @@ package common
import (
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/web/middleware"
auth_service "code.gitea.io/gitea/services/auth"
+ "code.gitea.io/gitea/services/context"
)
type AuthResult struct {
diff --git a/routers/common/errpage.go b/routers/common/errpage.go
index 923421a29c..402ca44c12 100644
--- a/routers/common/errpage.go
+++ b/routers/common/errpage.go
@@ -9,13 +9,13 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/modules/web/routing"
+ "code.gitea.io/gitea/services/context"
)
const tplStatus500 base.TplName = "status/500"
diff --git a/routers/common/markup.go b/routers/common/markup.go
index a1c2c37ac0..7819ee7227 100644
--- a/routers/common/markup.go
+++ b/routers/common/markup.go
@@ -9,11 +9,11 @@ import (
"net/http"
"strings"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
"mvdan.cc/xurls/v2"
)
diff --git a/routers/common/middleware.go b/routers/common/middleware.go
index 8a39dda179..c7c75fb099 100644
--- a/routers/common/middleware.go
+++ b/routers/common/middleware.go
@@ -9,11 +9,11 @@ import (
"strings"
"code.gitea.io/gitea/modules/cache"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/modules/web/routing"
+ "code.gitea.io/gitea/services/context"
"gitea.com/go-chi/session"
"github.com/chi-middleware/proxy"
@@ -38,6 +38,7 @@ func ProtocolMiddlewares() (handlers []any) {
})
})
+ // wrap the request and response, use the process context and add it to the process manager
handlers = append(handlers, func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
ctx, _, finished := process.GetManager().AddTypedContext(req.Context(), fmt.Sprintf("%s: %s", req.Method, req.RequestURI), process.RequestProcessType, true)
diff --git a/routers/common/serve.go b/routers/common/serve.go
index 8a7f8b3332..446908db75 100644
--- a/routers/common/serve.go
+++ b/routers/common/serve.go
@@ -7,11 +7,11 @@ import (
"io"
"time"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/context"
)
// ServeBlob download a git.Blob
diff --git a/routers/init.go b/routers/init.go
index 5ae6a551f9..28e1c49801 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -200,6 +200,8 @@ func NormalRoutes() *web.Route {
// TODO: this prefix should be generated with a token string with runner ?
prefix = "/api/actions_pipeline"
r.Mount(prefix, actions_router.ArtifactsRoutes(prefix))
+ prefix = actions_router.ArtifactV4RouteBase
+ r.Mount(prefix, actions_router.ArtifactsV4Routes(prefix))
}
return r
diff --git a/routers/install/install.go b/routers/install/install.go
index 13504953ce..5030306d89 100644
--- a/routers/install/install.go
+++ b/routers/install/install.go
@@ -22,7 +22,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
@@ -34,6 +33,7 @@ import (
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers/common"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"gitea.com/go-chi/session"
diff --git a/routers/private/actions.go b/routers/private/actions.go
index 1325913e1b..397f20a091 100644
--- a/routers/private/actions.go
+++ b/routers/private/actions.go
@@ -13,11 +13,11 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
// GenerateActionsRunnerToken generates a new runner token for a given scope
diff --git a/routers/private/default_branch.go b/routers/private/default_branch.go
index a23e101e9d..33890be6a9 100644
--- a/routers/private/default_branch.go
+++ b/routers/private/default_branch.go
@@ -8,9 +8,10 @@ import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/private"
+ gitea_context "code.gitea.io/gitea/services/context"
)
// SetDefaultBranch updates the default branch
@@ -20,7 +21,7 @@ func SetDefaultBranch(ctx *gitea_context.PrivateContext) {
branch := ctx.Params(":branch")
ctx.Repo.Repository.DefaultBranch = branch
- if err := ctx.Repo.GitRepo.SetDefaultBranch(ctx.Repo.Repository.DefaultBranch); err != nil {
+ if err := gitrepo.SetDefaultBranch(ctx, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch); err != nil {
if !git.IsErrUnsupportedVersion(err) {
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to set default branch on repository: %s/%s Error: %v", ownerName, repoName, err),
diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go
index 1b274ae154..f5527cb15b 100644
--- a/routers/private/hook_post_receive.go
+++ b/routers/private/hook_post_receive.go
@@ -8,16 +8,18 @@ import (
"net/http"
"strconv"
+ git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ gitea_context "code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
)
@@ -27,6 +29,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
// We don't rely on RepoAssignment here because:
// a) we don't need the git repo in this function
+ // OUT OF DATE: we do need the git repo to sync the branch to the db now.
// b) our update function will likely change the repository in the db so we will need to refresh it
// c) we don't always need the repo
@@ -34,7 +37,11 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
repoName := ctx.Params(":repo")
// defer getting the repository at this point - as we should only retrieve it if we're going to call update
- var repo *repo_model.Repository
+ var (
+ repo *repo_model.Repository
+ gitRepo *git.Repository
+ )
+ defer gitRepo.Close() // it's safe to call Close on a nil pointer
updates := make([]*repo_module.PushUpdateOptions, 0, len(opts.OldCommitIDs))
wasEmpty := false
@@ -75,6 +82,61 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
}
if repo != nil && len(updates) > 0 {
+ branchesToSync := make([]*repo_module.PushUpdateOptions, 0, len(updates))
+ for _, update := range updates {
+ if !update.RefFullName.IsBranch() {
+ continue
+ }
+ if repo == nil {
+ repo = loadRepository(ctx, ownerName, repoName)
+ if ctx.Written() {
+ return
+ }
+ wasEmpty = repo.IsEmpty
+ }
+
+ if update.IsDelRef() {
+ if err := git_model.AddDeletedBranch(ctx, repo.ID, update.RefFullName.BranchName(), update.PusherID); err != nil {
+ log.Error("Failed to add deleted branch: %s/%s Error: %v", ownerName, repoName, err)
+ ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
+ Err: fmt.Sprintf("Failed to add deleted branch: %s/%s Error: %v", ownerName, repoName, err),
+ })
+ return
+ }
+ } else {
+ branchesToSync = append(branchesToSync, update)
+ }
+ }
+ if len(branchesToSync) > 0 {
+ if gitRepo == nil {
+ var err error
+ gitRepo, err = gitrepo.OpenRepository(ctx, repo)
+ if err != nil {
+ log.Error("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err)
+ ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
+ Err: fmt.Sprintf("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err),
+ })
+ return
+ }
+ }
+
+ var (
+ branchNames = make([]string, 0, len(branchesToSync))
+ commitIDs = make([]string, 0, len(branchesToSync))
+ )
+ for _, update := range branchesToSync {
+ branchNames = append(branchNames, update.RefFullName.BranchName())
+ commitIDs = append(commitIDs, update.NewCommitID)
+ }
+
+ if err := repo_service.SyncBranchesToDB(ctx, repo.ID, opts.UserID, branchNames, commitIDs, gitRepo.GetCommit); err != nil {
+ ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
+ Err: fmt.Sprintf("Failed to sync branch to DB in repository: %s/%s Error: %v", ownerName, repoName, err),
+ })
+ return
+ }
+ }
+
if err := repo_service.PushUpdates(updates); err != nil {
log.Error("Failed to Update: %s/%s Total Updates: %d", ownerName, repoName, len(updates))
for i, update := range updates {
@@ -159,7 +221,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
}
// If we've pushed a branch (and not deleted it)
- if git.IsEmptyCommitID(newCommitID) && refFullName.IsBranch() {
+ if !git.IsEmptyCommitID(newCommitID) && refFullName.IsBranch() {
// First ensure we have the repository loaded, we're allowed pulls requests and we can get the base repo
if repo == nil {
repo = loadRepository(ctx, ownerName, repoName)
diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go
index f28ae4c0eb..f45e57b9e3 100644
--- a/routers/private/hook_pre_receive.go
+++ b/routers/private/hook_pre_receive.go
@@ -16,11 +16,11 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/web"
+ gitea_context "code.gitea.io/gitea/services/context"
pull_service "code.gitea.io/gitea/services/pull"
)
diff --git a/routers/private/hook_proc_receive.go b/routers/private/hook_proc_receive.go
index 5577120770..e4aabd858c 100644
--- a/routers/private/hook_proc_receive.go
+++ b/routers/private/hook_proc_receive.go
@@ -7,12 +7,12 @@ import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/agit"
+ gitea_context "code.gitea.io/gitea/services/context"
)
// HookProcReceive proc-receive hook - only handles agit Proc-Receive requests at present
diff --git a/routers/private/internal.go b/routers/private/internal.go
index 407edebeed..ede310113c 100644
--- a/routers/private/internal.go
+++ b/routers/private/internal.go
@@ -8,11 +8,11 @@ import (
"net/http"
"strings"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"gitea.com/go-chi/binding"
chi_middleware "github.com/go-chi/chi/v5/middleware"
diff --git a/routers/private/internal_repo.go b/routers/private/internal_repo.go
index 615239d479..e8ee8ba8ac 100644
--- a/routers/private/internal_repo.go
+++ b/routers/private/internal_repo.go
@@ -9,10 +9,10 @@ import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
+ gitea_context "code.gitea.io/gitea/services/context"
)
// This file contains common functions relating to setting the Repository for the internal routes
diff --git a/routers/private/key.go b/routers/private/key.go
index 0096480d6a..5b8f238a83 100644
--- a/routers/private/key.go
+++ b/routers/private/key.go
@@ -7,9 +7,9 @@ import (
"net/http"
asymkey_model "code.gitea.io/gitea/models/asymkey"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/services/context"
)
// UpdatePublicKeyInRepo update public key and deploy key updates
diff --git a/routers/private/mail.go b/routers/private/mail.go
index e5e162c880..c19ee67896 100644
--- a/routers/private/mail.go
+++ b/routers/private/mail.go
@@ -11,11 +11,11 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/mailer"
)
diff --git a/routers/private/manager.go b/routers/private/manager.go
index 397e6fac7b..a6aa03e4ec 100644
--- a/routers/private/manager.go
+++ b/routers/private/manager.go
@@ -8,7 +8,6 @@ import (
"net/http"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/graceful/releasereopen"
"code.gitea.io/gitea/modules/log"
@@ -17,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
)
// ReloadTemplates reloads all the templates
diff --git a/routers/private/manager_process.go b/routers/private/manager_process.go
index 68e4a21805..9a0298a37c 100644
--- a/routers/private/manager_process.go
+++ b/routers/private/manager_process.go
@@ -11,10 +11,10 @@ import (
"runtime"
"time"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
process_module "code.gitea.io/gitea/modules/process"
+ "code.gitea.io/gitea/services/context"
)
// Processes prints out the processes
diff --git a/routers/private/manager_unix.go b/routers/private/manager_unix.go
index 09ced33b8d..0c63ebc918 100644
--- a/routers/private/manager_unix.go
+++ b/routers/private/manager_unix.go
@@ -8,8 +8,8 @@ package private
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
+ "code.gitea.io/gitea/services/context"
)
// Restart causes the server to perform a graceful restart
diff --git a/routers/private/manager_windows.go b/routers/private/manager_windows.go
index bd3c3c30d0..f1b9365f52 100644
--- a/routers/private/manager_windows.go
+++ b/routers/private/manager_windows.go
@@ -8,9 +8,9 @@ package private
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/private"
+ "code.gitea.io/gitea/services/context"
)
// Restart is not implemented for Windows based servers as they can't fork
diff --git a/routers/private/restore_repo.go b/routers/private/restore_repo.go
index 7efc22a3d9..4e95d3071d 100644
--- a/routers/private/restore_repo.go
+++ b/routers/private/restore_repo.go
@@ -7,9 +7,9 @@ import (
"io"
"net/http"
- myCtx "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/private"
+ myCtx "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/migrations"
)
diff --git a/routers/private/serv.go b/routers/private/serv.go
index 00731947a5..ef3920d359 100644
--- a/routers/private/serv.go
+++ b/routers/private/serv.go
@@ -14,11 +14,11 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
wiki_service "code.gitea.io/gitea/services/wiki"
)
diff --git a/routers/private/ssh_log.go b/routers/private/ssh_log.go
index eacfa18f05..5bec632ead 100644
--- a/routers/private/ssh_log.go
+++ b/routers/private/ssh_log.go
@@ -6,11 +6,11 @@ package private
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
)
// SSHLog hook to response ssh log
diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go
index 58bb281731..fe98178ac3 100644
--- a/routers/web/admin/admin.go
+++ b/routers/web/admin/admin.go
@@ -14,12 +14,12 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/updatechecker"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/cron"
"code.gitea.io/gitea/services/forms"
release_service "code.gitea.io/gitea/services/release"
diff --git a/routers/web/admin/admin_test.go b/routers/web/admin/admin_test.go
index 452291e179..3518869ede 100644
--- a/routers/web/admin/admin_test.go
+++ b/routers/web/admin/admin_test.go
@@ -8,9 +8,10 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
+ "code.gitea.io/gitea/services/contexttest"
+
"github.com/stretchr/testify/assert"
)
diff --git a/routers/web/admin/applications.go b/routers/web/admin/applications.go
index b6f7bcd2a5..8583398074 100644
--- a/routers/web/admin/applications.go
+++ b/routers/web/admin/applications.go
@@ -10,9 +10,9 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
+ "code.gitea.io/gitea/services/context"
)
var (
diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go
index 7fdd18dfae..ba487d1045 100644
--- a/routers/web/admin/auths.go
+++ b/routers/web/admin/auths.go
@@ -16,7 +16,6 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/auth/pam"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -27,6 +26,7 @@ import (
pam_service "code.gitea.io/gitea/services/auth/source/pam"
"code.gitea.io/gitea/services/auth/source/smtp"
"code.gitea.io/gitea/services/auth/source/sspi"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"xorm.io/xorm/convert"
diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go
index c827f2a4f5..d9b1973332 100644
--- a/routers/web/admin/config.go
+++ b/routers/web/admin/config.go
@@ -12,13 +12,13 @@ import (
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/setting/config"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/mailer"
"gitea.com/go-chi/session"
diff --git a/routers/web/admin/diagnosis.go b/routers/web/admin/diagnosis.go
index 2d550125d5..020554a35a 100644
--- a/routers/web/admin/diagnosis.go
+++ b/routers/web/admin/diagnosis.go
@@ -9,8 +9,8 @@ import (
"runtime/pprof"
"time"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/httplib"
+ "code.gitea.io/gitea/services/context"
)
func MonitorDiagnosis(ctx *context.Context) {
diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go
index 59f80035d8..2cf4035c6a 100644
--- a/routers/web/admin/emails.go
+++ b/routers/web/admin/emails.go
@@ -11,10 +11,10 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
const (
@@ -68,10 +68,10 @@ func Emails(ctx *context.Context) {
opts.Keyword = ctx.FormTrim("q")
opts.SortType = orderBy
if len(ctx.FormString("is_activated")) != 0 {
- opts.IsActivated = util.OptionalBoolOf(ctx.FormBool("activated"))
+ opts.IsActivated = optional.Some(ctx.FormBool("activated"))
}
if len(ctx.FormString("is_primary")) != 0 {
- opts.IsPrimary = util.OptionalBoolOf(ctx.FormBool("primary"))
+ opts.IsPrimary = optional.Some(ctx.FormBool("primary"))
}
if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) {
diff --git a/routers/web/admin/hooks.go b/routers/web/admin/hooks.go
index cd8cc29cdf..8d59fbb858 100644
--- a/routers/web/admin/hooks.go
+++ b/routers/web/admin/hooks.go
@@ -8,9 +8,9 @@ import (
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
const (
@@ -35,7 +35,7 @@ func DefaultOrSystemWebhooks(ctx *context.Context) {
sys["Title"] = ctx.Tr("admin.systemhooks")
sys["Description"] = ctx.Tr("admin.systemhooks.desc")
- sys["Webhooks"], err = webhook.GetSystemWebhooks(ctx, util.OptionalBoolNone)
+ sys["Webhooks"], err = webhook.GetSystemWebhooks(ctx, optional.None[bool]())
sys["BaseLink"] = setting.AppSubURL + "/admin/hooks"
sys["BaseLinkNew"] = setting.AppSubURL + "/admin/system-hooks"
if err != nil {
diff --git a/routers/web/admin/notice.go b/routers/web/admin/notice.go
index e1cb578d05..36303cbc06 100644
--- a/routers/web/admin/notice.go
+++ b/routers/web/admin/notice.go
@@ -11,9 +11,9 @@ import (
"code.gitea.io/gitea/models/db"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/admin/orgs.go b/routers/web/admin/orgs.go
index 00131c9e2f..c5454db71e 100644
--- a/routers/web/admin/orgs.go
+++ b/routers/web/admin/orgs.go
@@ -8,10 +8,10 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/web/explore"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go
index 35ce215be4..39f064a1be 100644
--- a/routers/web/admin/packages.go
+++ b/routers/web/admin/packages.go
@@ -11,9 +11,9 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
packages_cleanup_service "code.gitea.io/gitea/services/packages/cleanup"
)
@@ -36,7 +36,7 @@ func Packages(ctx *context.Context) {
Type: packages_model.Type(packageType),
Name: packages_model.SearchValue{Value: query},
Sort: sort,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: &db.ListOptions{
PageSize: setting.UI.PackagesPagingNum,
Page: page,
diff --git a/routers/web/admin/queue.go b/routers/web/admin/queue.go
index 18a8d7d3e6..d8c50730b1 100644
--- a/routers/web/admin/queue.go
+++ b/routers/web/admin/queue.go
@@ -7,9 +7,9 @@ import (
"net/http"
"strconv"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
func Queues(ctx *context.Context) {
diff --git a/routers/web/admin/repos.go b/routers/web/admin/repos.go
index 45c280ef73..ddf4440167 100644
--- a/routers/web/admin/repos.go
+++ b/routers/web/admin/repos.go
@@ -12,11 +12,11 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/explore"
+ "code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/web/admin/runners.go b/routers/web/admin/runners.go
index eaa268b4f1..d73290a8db 100644
--- a/routers/web/admin/runners.go
+++ b/routers/web/admin/runners.go
@@ -4,8 +4,8 @@
package admin
import (
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
func RedirectToDefaultSetting(ctx *context.Context) {
diff --git a/routers/web/admin/stacktrace.go b/routers/web/admin/stacktrace.go
index b603fb59a2..d6def94bb4 100644
--- a/routers/web/admin/stacktrace.go
+++ b/routers/web/admin/stacktrace.go
@@ -7,9 +7,9 @@ import (
"net/http"
"runtime"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
// Stacktrace show admin monitor goroutines page
diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go
index adb9799c01..671a0d8885 100644
--- a/routers/web/admin/users.go
+++ b/routers/web/admin/users.go
@@ -19,7 +19,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
@@ -27,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/explore"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/mailer"
user_service "code.gitea.io/gitea/services/user"
@@ -96,7 +96,7 @@ func NewUser(ctx *context.Context) {
ctx.Data["login_type"] = "0-0"
sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
})
if err != nil {
ctx.ServerError("auth.Sources", err)
@@ -117,7 +117,7 @@ func NewUserPost(ctx *context.Context) {
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
})
if err != nil {
ctx.ServerError("auth.Sources", err)
@@ -177,7 +177,7 @@ func NewUserPost(ctx *context.Context) {
u.MustChangePassword = form.MustChangePassword
}
- if err := user_model.CreateUser(ctx, u, overwriteDefault); err != nil {
+ if err := user_model.AdminCreateUser(ctx, u, overwriteDefault); err != nil {
switch {
case user_model.IsErrUserAlreadyExist(err):
ctx.Data["Err_UserName"] = true
@@ -276,7 +276,7 @@ func ViewUser(ctx *context.Context) {
OwnerID: u.ID,
OrderBy: db.SearchOrderByAlphabetically,
Private: true,
- Collaborate: util.OptionalBoolFalse,
+ Collaborate: optional.Some(false),
})
if err != nil {
ctx.ServerError("SearchRepository", err)
@@ -412,7 +412,7 @@ func EditUserPost(ctx *context.Context) {
}
if form.Email != "" {
- if err := user_service.AddOrSetPrimaryEmailAddress(ctx, u, form.Email); err != nil {
+ if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, u, form.Email); err != nil {
switch {
case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err):
ctx.Data["Err_Email"] = true
@@ -439,6 +439,7 @@ func EditUserPost(ctx *context.Context) {
AllowCreateOrganization: optional.Some(form.AllowCreateOrganization),
IsRestricted: optional.Some(form.Restricted),
Visibility: optional.Some(form.Visibility),
+ Language: optional.Some(form.Language),
}
if err := user_service.UpdateUser(ctx, u, opts); err != nil {
diff --git a/routers/web/admin/users_test.go b/routers/web/admin/users_test.go
index 560ee70ea0..f6f9237858 100644
--- a/routers/web/admin/users_test.go
+++ b/routers/web/admin/users_test.go
@@ -8,10 +8,10 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/contexttest"
"code.gitea.io/gitea/services/forms"
"github.com/stretchr/testify/assert"
diff --git a/routers/web/auth/2fa.go b/routers/web/auth/2fa.go
index dc0062ebaa..f93177bf96 100644
--- a/routers/web/auth/2fa.go
+++ b/routers/web/auth/2fa.go
@@ -10,9 +10,9 @@ import (
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go
index 91f631acac..1c55256db4 100644
--- a/routers/web/auth/auth.go
+++ b/routers/web/auth/auth.go
@@ -17,7 +17,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/eventsource"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
@@ -30,6 +29,7 @@ import (
"code.gitea.io/gitea/routers/utils"
auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/mailer"
@@ -135,9 +135,21 @@ func resetLocale(ctx *context.Context, u *user_model.User) error {
return nil
}
+func RedirectAfterLogin(ctx *context.Context) {
+ redirectTo := ctx.FormString("redirect_to")
+ if redirectTo == "" {
+ redirectTo = ctx.GetSiteCookie("redirect_to")
+ }
+ middleware.DeleteRedirectToCookie(ctx.Resp)
+ nextRedirectTo := setting.AppSubURL + string(setting.LandingPageURL)
+ if setting.LandingPageURL == setting.LandingPageLogin {
+ nextRedirectTo = setting.AppSubURL + "/" // do not cycle-redirect to the login page
+ }
+ ctx.RedirectToFirst(redirectTo, nextRedirectTo)
+}
+
func CheckAutoLogin(ctx *context.Context) bool {
- // Check auto-login
- isSucceed, err := autoSignIn(ctx)
+ isSucceed, err := autoSignIn(ctx) // try to auto-login
if err != nil {
ctx.ServerError("autoSignIn", err)
return true
@@ -146,17 +158,10 @@ func CheckAutoLogin(ctx *context.Context) bool {
redirectTo := ctx.FormString("redirect_to")
if len(redirectTo) > 0 {
middleware.SetRedirectToCookie(ctx.Resp, redirectTo)
- } else {
- redirectTo = ctx.GetSiteCookie("redirect_to")
}
if isSucceed {
- middleware.DeleteRedirectToCookie(ctx.Resp)
- nextRedirectTo := setting.AppSubURL + string(setting.LandingPageURL)
- if setting.LandingPageURL == setting.LandingPageLogin {
- nextRedirectTo = setting.AppSubURL + "/" // do not cycle-redirect to the login page
- }
- ctx.RedirectToFirst(redirectTo, nextRedirectTo)
+ RedirectAfterLogin(ctx)
return true
}
@@ -171,7 +176,12 @@ func SignIn(ctx *context.Context) {
return
}
- oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue)
+ if ctx.IsSigned {
+ RedirectAfterLogin(ctx)
+ return
+ }
+
+ oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
if err != nil {
ctx.ServerError("UserSignIn", err)
return
@@ -194,7 +204,7 @@ func SignIn(ctx *context.Context) {
func SignInPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("sign_in")
- oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue)
+ oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
if err != nil {
ctx.ServerError("UserSignIn", err)
return
@@ -414,7 +424,7 @@ func SignUp(ctx *context.Context) {
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"
- oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue)
+ oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
if err != nil {
ctx.ServerError("UserSignUp", err)
return
@@ -443,7 +453,7 @@ func SignUpPost(ctx *context.Context) {
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"
- oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue)
+ oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
if err != nil {
ctx.ServerError("UserSignUp", err)
return
diff --git a/routers/web/auth/auth_test.go b/routers/web/auth/auth_test.go
new file mode 100644
index 0000000000..c6afbf877c
--- /dev/null
+++ b/routers/web/auth/auth_test.go
@@ -0,0 +1,43 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package auth
+
+import (
+ "net/http"
+ "net/url"
+ "testing"
+
+ "code.gitea.io/gitea/modules/test"
+ "code.gitea.io/gitea/services/contexttest"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestUserLogin(t *testing.T) {
+ ctx, resp := contexttest.MockContext(t, "/user/login")
+ SignIn(ctx)
+ assert.Equal(t, http.StatusOK, resp.Code)
+
+ ctx, resp = contexttest.MockContext(t, "/user/login")
+ ctx.IsSigned = true
+ SignIn(ctx)
+ assert.Equal(t, http.StatusSeeOther, resp.Code)
+ assert.Equal(t, "/", test.RedirectURL(resp))
+
+ ctx, resp = contexttest.MockContext(t, "/user/login?redirect_to=/other")
+ ctx.IsSigned = true
+ SignIn(ctx)
+ assert.Equal(t, "/other", test.RedirectURL(resp))
+
+ ctx, resp = contexttest.MockContext(t, "/user/login")
+ ctx.Req.AddCookie(&http.Cookie{Name: "redirect_to", Value: "/other-cookie"})
+ ctx.IsSigned = true
+ SignIn(ctx)
+ assert.Equal(t, "/other-cookie", test.RedirectURL(resp))
+
+ ctx, resp = contexttest.MockContext(t, "/user/login?redirect_to="+url.QueryEscape("https://example.com"))
+ ctx.IsSigned = true
+ SignIn(ctx)
+ assert.Equal(t, "/", test.RedirectURL(resp))
+}
diff --git a/routers/web/auth/linkaccount.go b/routers/web/auth/linkaccount.go
index 1d94e52fe3..f744a57a43 100644
--- a/routers/web/auth/linkaccount.go
+++ b/routers/web/auth/linkaccount.go
@@ -12,13 +12,13 @@ import (
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index 4e4079d8ff..f5ca0bda5e 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -21,7 +21,6 @@ import (
auth_module "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
@@ -33,6 +32,7 @@ import (
auth_service "code.gitea.io/gitea/services/auth"
source_service "code.gitea.io/gitea/services/auth/source"
"code.gitea.io/gitea/services/auth/source/oauth2"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
user_service "code.gitea.io/gitea/services/user"
diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go
index 29ef772b1c..2143b8096a 100644
--- a/routers/web/auth/openid.go
+++ b/routers/web/auth/openid.go
@@ -11,12 +11,12 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/auth"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go
index 1f2d133282..c9e0386041 100644
--- a/routers/web/auth/password.go
+++ b/routers/web/auth/password.go
@@ -12,7 +12,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
@@ -20,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/mailer"
user_service "code.gitea.io/gitea/services/user"
diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go
index 95c8d262a5..1079f44a08 100644
--- a/routers/web/auth/webauthn.go
+++ b/routers/web/auth/webauthn.go
@@ -11,9 +11,9 @@ import (
user_model "code.gitea.io/gitea/models/user"
wa "code.gitea.io/gitea/modules/auth/webauthn"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
"github.com/go-webauthn/webauthn/protocol"
diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go
index 525ca9be53..dd20663f94 100644
--- a/routers/web/devtest/devtest.go
+++ b/routers/web/devtest/devtest.go
@@ -10,8 +10,8 @@ import (
"time"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/services/context"
)
// List all devtest templates, they will be used for e2e tests for the UI components
diff --git a/routers/web/events/events.go b/routers/web/events/events.go
index 1a5a162c1a..52f20e07dc 100644
--- a/routers/web/events/events.go
+++ b/routers/web/events/events.go
@@ -7,11 +7,11 @@ import (
"net/http"
"time"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/eventsource"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/routers/web/auth"
+ "code.gitea.io/gitea/services/context"
)
// Events listens for events
diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go
index d81884ec62..a6bc71ac9c 100644
--- a/routers/web/explore/code.go
+++ b/routers/web/explore/code.go
@@ -8,9 +8,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
code_indexer "code.gitea.io/gitea/modules/indexer/code"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
const (
@@ -35,7 +35,7 @@ func Code(ctx *context.Context) {
keyword := ctx.FormTrim("q")
queryType := ctx.FormTrim("t")
- isMatch := queryType == "match"
+ isFuzzy := queryType != "match"
ctx.Data["Keyword"] = keyword
ctx.Data["Language"] = language
@@ -77,7 +77,7 @@ func Code(ctx *context.Context) {
)
if (len(repoIDs) > 0) || isAdmin {
- total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch)
+ total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isFuzzy)
if err != nil {
if code_indexer.IsAvailable(ctx) {
ctx.ServerError("SearchResults", err)
diff --git a/routers/web/explore/org.go b/routers/web/explore/org.go
index dc1318beef..f8fd6ec38e 100644
--- a/routers/web/explore/org.go
+++ b/routers/web/explore/org.go
@@ -6,9 +6,10 @@ package explore
import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
)
// Organizations render explore organizations page
@@ -24,8 +25,16 @@ func Organizations(ctx *context.Context) {
visibleTypes = append(visibleTypes, structs.VisibleTypeLimited, structs.VisibleTypePrivate)
}
- if ctx.FormString("sort") == "" {
- ctx.SetFormString("sort", setting.UI.ExploreDefaultSort)
+ supportedSortOrders := container.SetOf(
+ "newest",
+ "oldest",
+ "alphabetically",
+ "reversealphabetically",
+ )
+ sortOrder := ctx.FormString("sort")
+ if sortOrder == "" {
+ sortOrder = "newest"
+ ctx.SetFormString("sort", sortOrder)
}
RenderUserSearch(ctx, &user_model.SearchUserOptions{
@@ -33,5 +42,7 @@ func Organizations(ctx *context.Context) {
Type: user_model.UserTypeOrganization,
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
Visible: visibleTypes,
+
+ SupportedSortOrders: supportedSortOrders,
}, tplExploreUsers)
}
diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go
index 0446edebe6..cf7381512b 100644
--- a/routers/web/explore/repo.go
+++ b/routers/web/explore/repo.go
@@ -10,10 +10,10 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sitemap"
+ "code.gitea.io/gitea/services/context"
)
const (
@@ -109,6 +109,21 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
language := ctx.FormTrim("language")
ctx.Data["Language"] = language
+ archived := ctx.FormOptionalBool("archived")
+ ctx.Data["IsArchived"] = archived
+
+ fork := ctx.FormOptionalBool("fork")
+ ctx.Data["IsFork"] = fork
+
+ mirror := ctx.FormOptionalBool("mirror")
+ ctx.Data["IsMirror"] = mirror
+
+ template := ctx.FormOptionalBool("template")
+ ctx.Data["IsTemplate"] = template
+
+ private := ctx.FormOptionalBool("private")
+ ctx.Data["IsPrivate"] = private
+
repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
Page: page,
@@ -125,6 +140,11 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
Language: language,
IncludeDescription: setting.UI.SearchRepoDescription,
OnlyShowRelevant: opts.OnlyShowRelevant,
+ Archived: archived,
+ Fork: fork,
+ Mirror: mirror,
+ Template: template,
+ IsPrivate: private,
})
if err != nil {
ctx.ServerError("SearchRepository", err)
diff --git a/routers/web/explore/topic.go b/routers/web/explore/topic.go
index bb1be310de..95fecfe2b8 100644
--- a/routers/web/explore/topic.go
+++ b/routers/web/explore/topic.go
@@ -8,8 +8,8 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/web/explore/user.go b/routers/web/explore/user.go
index 09d31f95ef..b79a79fb2c 100644
--- a/routers/web/explore/user.go
+++ b/routers/web/explore/user.go
@@ -10,12 +10,13 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sitemap"
"code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
const (
@@ -79,10 +80,16 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions,
fallthrough
default:
// in case the sortType is not valid, we set it to recentupdate
+ sortOrder = "recentupdate"
ctx.Data["SortType"] = "recentupdate"
orderBy = "`user`.updated_unix DESC"
}
+ if opts.SupportedSortOrders != nil && !opts.SupportedSortOrders.Contains(sortOrder) {
+ ctx.NotFound("unsupported sort order", nil)
+ return
+ }
+
opts.Keyword = ctx.FormTrim("q")
opts.OrderBy = orderBy
if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) {
@@ -132,15 +139,25 @@ func Users(ctx *context.Context) {
ctx.Data["PageIsExploreUsers"] = true
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
- if ctx.FormString("sort") == "" {
- ctx.SetFormString("sort", setting.UI.ExploreDefaultSort)
+ supportedSortOrders := container.SetOf(
+ "newest",
+ "oldest",
+ "alphabetically",
+ "reversealphabetically",
+ )
+ sortOrder := ctx.FormString("sort")
+ if sortOrder == "" {
+ sortOrder = "newest"
+ ctx.SetFormString("sort", sortOrder)
}
RenderUserSearch(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeIndividual,
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
+
+ SupportedSortOrders: supportedSortOrders,
}, tplExploreUsers)
}
diff --git a/routers/web/feed/branch.go b/routers/web/feed/branch.go
index f13038ff9b..80ce2ad198 100644
--- a/routers/web/feed/branch.go
+++ b/routers/web/feed/branch.go
@@ -9,7 +9,7 @@ import (
"time"
"code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"github.com/gorilla/feeds"
)
diff --git a/routers/web/feed/convert.go b/routers/web/feed/convert.go
index 5186d1a524..743465082e 100644
--- a/routers/web/feed/convert.go
+++ b/routers/web/feed/convert.go
@@ -14,12 +14,12 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
"github.com/gorilla/feeds"
"github.com/jaytaylor/html2text"
@@ -51,7 +51,7 @@ func toReleaseLink(ctx *context.Context, act *activities_model.Action) string {
// renderMarkdown creates a minimal markdown render context from an action.
// If rendering fails, the original markdown text is returned
-func renderMarkdown(ctx *context.Context, act *activities_model.Action, content string) string {
+func renderMarkdown(ctx *context.Context, act *activities_model.Action, content string) template.HTML {
markdownCtx := &markup.RenderContext{
Ctx: ctx,
Links: markup.Links{
@@ -65,7 +65,7 @@ func renderMarkdown(ctx *context.Context, act *activities_model.Action, content
}
markdown, err := markdown.RenderString(markdownCtx, content)
if err != nil {
- return content
+ return templates.SanitizeHTML(content) // old code did so: use SanitizeHTML to render in tmpl
}
return markdown
}
@@ -75,7 +75,11 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
for _, act := range actions {
act.LoadActUser(ctx)
- var content, desc, title string
+ // TODO: the code seems quite strange (maybe not right)
+ // sometimes it uses text content but sometimes it uses HTML content
+ // it should clearly defines which kind of content it should use for the feed items: plan text or rich HTML
+ var title, desc string
+ var content template.HTML
link := &feeds.Link{Href: act.GetCommentHTMLURL(ctx)}
@@ -229,7 +233,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
desc = act.GetIssueTitle(ctx)
comment := act.GetIssueInfos()[1]
if len(comment) != 0 {
- desc += "\n\n" + renderMarkdown(ctx, act, comment)
+ desc += "\n\n" + string(renderMarkdown(ctx, act, comment))
}
case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest:
desc = act.GetIssueInfos()[1]
@@ -240,7 +244,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
}
}
if len(content) == 0 {
- content = desc
+ content = templates.SanitizeHTML(desc)
}
// It's a common practice for feed generators to use plain text titles.
@@ -261,7 +265,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
},
Id: fmt.Sprintf("%v: %v", strconv.FormatInt(act.ID, 10), link.Href),
Created: act.CreatedUnix.AsTime(),
- Content: content,
+ Content: string(content),
})
}
return items, err
@@ -290,7 +294,8 @@ func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release, i
return nil, err
}
- var title, content string
+ var title string
+ var content template.HTML
if rel.IsTag {
title = rel.TagName
@@ -319,7 +324,7 @@ func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release, i
Email: rel.Publisher.GetEmail(),
},
Id: fmt.Sprintf("%v: %v", strconv.FormatInt(rel.ID, 10), link.Href),
- Content: content,
+ Content: string(content),
})
}
diff --git a/routers/web/feed/file.go b/routers/web/feed/file.go
index 56a9c54ddc..1ab768ff27 100644
--- a/routers/web/feed/file.go
+++ b/routers/web/feed/file.go
@@ -9,9 +9,9 @@ import (
"time"
"code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
"github.com/gorilla/feeds"
)
diff --git a/routers/web/feed/profile.go b/routers/web/feed/profile.go
index 3feca68d61..08cbcd9e12 100644
--- a/routers/web/feed/profile.go
+++ b/routers/web/feed/profile.go
@@ -7,9 +7,9 @@ import (
"time"
activities_model "code.gitea.io/gitea/models/activities"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
+ "code.gitea.io/gitea/services/context"
"github.com/gorilla/feeds"
)
@@ -58,7 +58,7 @@ func showUserFeed(ctx *context.Context, formatType string) {
feed := &feeds.Feed{
Title: ctx.Locale.TrString("home.feed_of", ctx.ContextUser.DisplayName()),
Link: &feeds.Link{Href: ctx.ContextUser.HTMLURL()},
- Description: ctxUserDescription,
+ Description: string(ctxUserDescription),
Created: time.Now(),
}
diff --git a/routers/web/feed/release.go b/routers/web/feed/release.go
index 558c03dba7..273f47e3b4 100644
--- a/routers/web/feed/release.go
+++ b/routers/web/feed/release.go
@@ -8,7 +8,7 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"github.com/gorilla/feeds"
)
diff --git a/routers/web/feed/render.go b/routers/web/feed/render.go
index 41f9af1c8c..dc99fb49ed 100644
--- a/routers/web/feed/render.go
+++ b/routers/web/feed/render.go
@@ -4,7 +4,7 @@
package feed
import (
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
)
// RenderBranchFeed render format for branch or file
diff --git a/routers/web/feed/repo.go b/routers/web/feed/repo.go
index 51c24510c7..bfcc3a37d6 100644
--- a/routers/web/feed/repo.go
+++ b/routers/web/feed/repo.go
@@ -8,7 +8,7 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"github.com/gorilla/feeds"
)
diff --git a/routers/web/githttp.go b/routers/web/githttp.go
index ab74e9a333..5f1dedce76 100644
--- a/routers/web/githttp.go
+++ b/routers/web/githttp.go
@@ -6,11 +6,10 @@ package web
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/repo"
- context_service "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context"
)
func requireSignIn(ctx *context.Context) {
@@ -39,5 +38,5 @@ func gitHTTPRouters(m *web.Route) {
m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38,62}}", repo.GetLooseObject)
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.pack", repo.GetPackFile)
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.idx", repo.GetIdxFile)
- }, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb())
+ }, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb())
}
diff --git a/routers/web/goget.go b/routers/web/goget.go
index c5b8b6cbc0..8d5612ebfe 100644
--- a/routers/web/goget.go
+++ b/routers/web/goget.go
@@ -12,9 +12,9 @@ import (
"strings"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
func goGet(ctx *context.Context) {
diff --git a/routers/web/home.go b/routers/web/home.go
index 2321b00efe..d4be0931e8 100644
--- a/routers/web/home.go
+++ b/routers/web/home.go
@@ -12,15 +12,15 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sitemap"
"code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers/web/auth"
"code.gitea.io/gitea/routers/web/user"
+ "code.gitea.io/gitea/services/context"
)
const (
@@ -71,7 +71,7 @@ func HomeSitemap(ctx *context.Context) {
_, cnt, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
Type: user_model.UserTypeIndividual,
ListOptions: db.ListOptions{PageSize: 1},
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
Visible: []structs.VisibleType{structs.VisibleTypePublic},
})
if err != nil {
diff --git a/routers/web/misc/markup.go b/routers/web/misc/markup.go
index c91da9a7f1..2dbbd6fc09 100644
--- a/routers/web/misc/markup.go
+++ b/routers/web/misc/markup.go
@@ -5,10 +5,10 @@
package misc
import (
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/common"
+ "code.gitea.io/gitea/services/context"
)
// Markup render markup document to HTML
diff --git a/routers/web/misc/swagger-forgejo.go b/routers/web/misc/swagger-forgejo.go
index 2f539e955c..e3aff02c5f 100644
--- a/routers/web/misc/swagger-forgejo.go
+++ b/routers/web/misc/swagger-forgejo.go
@@ -7,7 +7,7 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
)
// tplSwagger swagger page template
diff --git a/routers/web/misc/swagger.go b/routers/web/misc/swagger.go
index 72c09a3780..5fddfa8885 100644
--- a/routers/web/misc/swagger.go
+++ b/routers/web/misc/swagger.go
@@ -7,7 +7,7 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
)
// tplSwagger swagger page template
diff --git a/routers/web/nodeinfo.go b/routers/web/nodeinfo.go
index 01b71e7086..f1cc7bf530 100644
--- a/routers/web/nodeinfo.go
+++ b/routers/web/nodeinfo.go
@@ -7,8 +7,8 @@ import (
"fmt"
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
type nodeInfoLinks struct {
diff --git a/routers/web/org/home.go b/routers/web/org/home.go
index 36f543dc45..71d10f3a43 100644
--- a/routers/web/org/home.go
+++ b/routers/web/org/home.go
@@ -12,7 +12,6 @@ import (
"code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
@@ -20,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
)
const (
@@ -85,6 +85,21 @@ func Home(ctx *context.Context) {
page = 1
}
+ archived := ctx.FormOptionalBool("archived")
+ ctx.Data["IsArchived"] = archived
+
+ fork := ctx.FormOptionalBool("fork")
+ ctx.Data["IsFork"] = fork
+
+ mirror := ctx.FormOptionalBool("mirror")
+ ctx.Data["IsMirror"] = mirror
+
+ template := ctx.FormOptionalBool("template")
+ ctx.Data["IsTemplate"] = template
+
+ private := ctx.FormOptionalBool("private")
+ ctx.Data["IsPrivate"] = private
+
var (
repos []*repo_model.Repository
count int64
@@ -102,6 +117,11 @@ func Home(ctx *context.Context) {
Actor: ctx.Doer,
Language: language,
IncludeDescription: setting.UI.SearchRepoDescription,
+ Archived: archived,
+ Fork: fork,
+ Mirror: mirror,
+ Template: template,
+ IsPrivate: private,
})
if err != nil {
ctx.ServerError("SearchRepository", err)
diff --git a/routers/web/org/members.go b/routers/web/org/members.go
index 15a615c706..9a3d60e122 100644
--- a/routers/web/org/members.go
+++ b/routers/web/org/members.go
@@ -10,10 +10,10 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/org/org.go b/routers/web/org/org.go
index 1e4544730e..f94dd16eae 100644
--- a/routers/web/org/org.go
+++ b/routers/web/org/org.go
@@ -12,10 +12,10 @@ import (
"code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/org/org_labels.go b/routers/web/org/org_labels.go
index f78bd00274..02eae8052e 100644
--- a/routers/web/org/org_labels.go
+++ b/routers/web/org/org_labels.go
@@ -8,10 +8,10 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go
index f062127d24..ad8bb90d9e 100644
--- a/routers/web/org/projects.go
+++ b/routers/web/org/projects.go
@@ -17,12 +17,13 @@ import (
attachment_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
@@ -66,7 +67,7 @@ func Projects(ctx *context.Context) {
PageSize: setting.UI.IssuePagingNum,
},
OwnerID: ctx.ContextUser.ID,
- IsClosed: util.OptionalBoolOf(isShowClosed),
+ IsClosed: optional.Some(isShowClosed),
OrderBy: project_model.GetSearchOrderByBySortType(sortType),
Type: projectType,
Title: keyword,
@@ -78,7 +79,7 @@ func Projects(ctx *context.Context) {
opTotal, err := db.Count[project_model.Project](ctx, project_model.SearchOptions{
OwnerID: ctx.ContextUser.ID,
- IsClosed: util.OptionalBoolOf(!isShowClosed),
+ IsClosed: optional.Some(!isShowClosed),
Type: projectType,
})
if err != nil {
@@ -104,7 +105,7 @@ func Projects(ctx *context.Context) {
}
for _, project := range projects {
- project.RenderedContent = project.Description
+ project.RenderedContent = templates.SanitizeHTML(project.Description) // FIXME: is it right? why not render?
}
err = shared_user.LoadHeaderCount(ctx)
@@ -387,7 +388,7 @@ func ViewProject(ctx *context.Context) {
if len(referencedIDs) > 0 {
if linkedPrs, err := issues_model.Issues(ctx, &issues_model.IssuesOptions{
IssueIDs: referencedIDs,
- IsPull: util.OptionalBoolTrue,
+ IsPull: optional.Some(true),
}); err == nil {
linkedPrsMap[issue.ID] = linkedPrs
}
@@ -395,7 +396,7 @@ func ViewProject(ctx *context.Context) {
}
}
- project.RenderedContent = project.Description
+ project.RenderedContent = templates.SanitizeHTML(project.Description) // FIXME: is it right? why not render?
ctx.Data["LinkedPRs"] = linkedPrsMap
ctx.Data["PageIsViewProjects"] = true
ctx.Data["CanWriteProjects"] = canWriteProjects(ctx)
diff --git a/routers/web/org/projects_test.go b/routers/web/org/projects_test.go
index 8053ab4cf9..f4ccfe1c06 100644
--- a/routers/web/org/projects_test.go
+++ b/routers/web/org/projects_test.go
@@ -7,8 +7,8 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/routers/web/org"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go
index 47d0063f76..494ada4323 100644
--- a/routers/web/org/setting.go
+++ b/routers/web/org/setting.go
@@ -14,7 +14,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
repo_module "code.gitea.io/gitea/modules/repository"
@@ -22,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
org_service "code.gitea.io/gitea/services/org"
repo_service "code.gitea.io/gitea/services/repository"
diff --git a/routers/web/org/setting/blocked_users.go b/routers/web/org/setting/blocked_users.go
index d872dabd83..b23f5ba596 100644
--- a/routers/web/org/setting/blocked_users.go
+++ b/routers/web/org/setting/blocked_users.go
@@ -10,7 +10,7 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
user_service "code.gitea.io/gitea/services/user"
)
diff --git a/routers/web/org/setting/runners.go b/routers/web/org/setting/runners.go
index c3c771036a..fe05709237 100644
--- a/routers/web/org/setting/runners.go
+++ b/routers/web/org/setting/runners.go
@@ -4,7 +4,7 @@
package setting
import (
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
)
func RedirectToDefaultSetting(ctx *context.Context) {
diff --git a/routers/web/org/setting_oauth2.go b/routers/web/org/setting_oauth2.go
index ca4fe09f38..7f855795d3 100644
--- a/routers/web/org/setting_oauth2.go
+++ b/routers/web/org/setting_oauth2.go
@@ -10,10 +10,10 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/org/setting_packages.go b/routers/web/org/setting_packages.go
index 796829d34e..af9836e42c 100644
--- a/routers/web/org/setting_packages.go
+++ b/routers/web/org/setting_packages.go
@@ -8,10 +8,10 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
shared "code.gitea.io/gitea/routers/web/shared/packages"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go
index 71fe99c97c..fd7486cacd 100644
--- a/routers/web/org/teams.go
+++ b/routers/web/org/teams.go
@@ -20,11 +20,11 @@ import (
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms"
org_service "code.gitea.io/gitea/services/org"
diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go
index 19aca26711..f27329aa0f 100644
--- a/routers/web/repo/actions/actions.go
+++ b/routers/web/repo/actions/actions.go
@@ -15,11 +15,11 @@ import (
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/repo"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"github.com/nektos/act/pkg/model"
@@ -78,7 +78,7 @@ func List(ctx *context.Context) {
// Get all runner labels
runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{
RepoID: ctx.Repo.Repository.ID,
- IsOnline: util.OptionalBoolTrue,
+ IsOnline: optional.Some(true),
WithAvailable: true,
})
if err != nil {
diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
index 05675d73c0..23ce70a153 100644
--- a/routers/web/repo/actions/view.go
+++ b/routers/web/repo/actions/view.go
@@ -13,6 +13,7 @@ import (
"io"
"net/http"
"net/url"
+ "strconv"
"strings"
"time"
@@ -22,12 +23,13 @@ import (
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/base"
- context_module "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
actions_service "code.gitea.io/gitea/services/actions"
+ context_module "code.gitea.io/gitea/services/context"
"xorm.io/builder"
)
@@ -312,10 +314,14 @@ func ViewPost(ctx *context_module.Context) {
}
// Rerun will rerun jobs in the given run
-// jobIndex = 0 means rerun all jobs
+// If jobIndexStr is a blank string, it means rerun all jobs
func Rerun(ctx *context_module.Context) {
runIndex := ctx.ParamsInt64("run")
- jobIndex := ctx.ParamsInt64("job")
+ jobIndexStr := ctx.Params("job")
+ var jobIndex int64
+ if jobIndexStr != "" {
+ jobIndex, _ = strconv.ParseInt(jobIndexStr, 10, 64)
+ }
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
if err != nil {
@@ -347,7 +353,7 @@ func Rerun(ctx *context_module.Context) {
return
}
- if jobIndex != 0 {
+ if jobIndexStr != "" {
jobs = []*actions_model.ActionRunJob{job}
}
@@ -642,6 +648,28 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip; filename*=UTF-8''%s.zip", url.PathEscape(artifactName), artifactName))
+ // Artifacts using the v4 backend are stored as a single combined zip file per artifact on the backend
+ // The v4 backend enshures ContentEncoding is set to "application/zip", which is not the case for the old backend
+ if len(artifacts) == 1 && artifacts[0].ArtifactName+".zip" == artifacts[0].ArtifactPath && artifacts[0].ContentEncoding == "application/zip" {
+ art := artifacts[0]
+ if setting.Actions.ArtifactStorage.MinioConfig.ServeDirect {
+ u, err := storage.ActionsArtifacts.URL(art.StoragePath, art.ArtifactPath)
+ if u != nil && err == nil {
+ ctx.Redirect(u.String())
+ return
+ }
+ }
+ f, err := storage.ActionsArtifacts.Open(art.StoragePath)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, err.Error())
+ return
+ }
+ _, _ = io.Copy(ctx.Resp, f)
+ return
+ }
+
+ // Artifacts using the v1-v3 backend are stored as multiple individual files per artifact on the backend
+ // Those need to be zipped for download
writer := zip.NewWriter(ctx.Resp)
defer writer.Close()
for _, art := range artifacts {
diff --git a/routers/web/repo/activity.go b/routers/web/repo/activity.go
index af99c4ed98..6f6641cc65 100644
--- a/routers/web/repo/activity.go
+++ b/routers/web/repo/activity.go
@@ -10,7 +10,7 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/repo/attachment.go b/routers/web/repo/attachment.go
index 8c322b45e5..f0c5622aec 100644
--- a/routers/web/repo/attachment.go
+++ b/routers/web/repo/attachment.go
@@ -9,15 +9,15 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/services/attachment"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
repo_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/web/repo/badges/badges.go b/routers/web/repo/badges/badges.go
index 8fe99c7fc1..7f4549d606 100644
--- a/routers/web/repo/badges/badges.go
+++ b/routers/web/repo/badges/badges.go
@@ -11,8 +11,8 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- context_module "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
+ context_module "code.gitea.io/gitea/services/context"
)
func getBadgeURL(ctx *context_module.Context, label, text, color string) string {
diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go
index 2c938dc966..0f464ad076 100644
--- a/routers/web/repo/blame.go
+++ b/routers/web/repo/blame.go
@@ -12,7 +12,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/charset"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/log"
@@ -20,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
files_service "code.gitea.io/gitea/services/repository/files"
)
diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go
index c543160f42..ae51f0596b 100644
--- a/routers/web/repo/branch.go
+++ b/routers/web/repo/branch.go
@@ -16,14 +16,15 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
release_service "code.gitea.io/gitea/services/release"
repo_service "code.gitea.io/gitea/services/repository"
@@ -53,7 +54,7 @@ func Branches(ctx *context.Context) {
kw := ctx.FormString("q")
- defaultBranch, branches, branchesCount, err := repo_service.LoadBranches(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, util.OptionalBoolNone, kw, page, pageSize)
+ defaultBranch, branches, branchesCount, err := repo_service.LoadBranches(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, optional.None[bool](), kw, page, pageSize)
if err != nil {
ctx.ServerError("LoadBranches", err)
return
@@ -231,7 +232,7 @@ func CreateBranch(ctx *context.Context) {
if len(e.Message) == 0 {
ctx.Flash.Error(ctx.Tr("repo.editor.push_rejected_no_message"))
} else {
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.push_rejected"),
"Summary": ctx.Tr("repo.editor.push_rejected_summary"),
"Details": utils.SanitizeFlashErrorString(e.Message),
diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go
index 63516bb4d9..90dae704f4 100644
--- a/routers/web/repo/cherry_pick.go
+++ b/routers/web/repo/cherry_pick.go
@@ -12,11 +12,11 @@ import (
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/repository/files"
)
diff --git a/routers/web/repo/code_frequency.go b/routers/web/repo/code_frequency.go
index 48ade655b7..c76f492da0 100644
--- a/routers/web/repo/code_frequency.go
+++ b/routers/web/repo/code_frequency.go
@@ -8,7 +8,7 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
contributors_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index 3b6e482281..7c89ce5a38 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -19,7 +19,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitgraph"
"code.gitea.io/gitea/modules/gitrepo"
@@ -27,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/gitdiff"
git_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index 535487d5fd..b0570f97c3 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -25,7 +25,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
- "code.gitea.io/gitea/modules/context"
csv_module "code.gitea.io/gitea/modules/csv"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
@@ -35,8 +34,9 @@ import (
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/typesniffer"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/gitdiff"
)
diff --git a/routers/web/repo/contributors.go b/routers/web/repo/contributors.go
index bcfef7580a..5fda17469e 100644
--- a/routers/web/repo/contributors.go
+++ b/routers/web/repo/contributors.go
@@ -8,7 +8,7 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
contributors_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/web/repo/download.go b/routers/web/repo/download.go
index a9e2e2b2fa..c4a8baecca 100644
--- a/routers/web/repo/download.go
+++ b/routers/web/repo/download.go
@@ -9,7 +9,6 @@ import (
"time"
git_model "code.gitea.io/gitea/models/git"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/lfs"
@@ -17,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/routers/common"
+ "code.gitea.io/gitea/services/context"
)
// ServeBlobOrLFS download a git.Blob redirecting to LFS if necessary
diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go
index 075477e5f0..4f26c0c521 100644
--- a/routers/web/repo/editor.go
+++ b/routers/web/repo/editor.go
@@ -17,17 +17,17 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/typesniffer"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/forms"
files_service "code.gitea.io/gitea/services/repository/files"
)
@@ -382,7 +382,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
if len(errPushRej.Message) == 0 {
ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplEditFile, &form)
} else {
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.push_rejected"),
"Summary": ctx.Tr("repo.editor.push_rejected_summary"),
"Details": utils.SanitizeFlashErrorString(errPushRej.Message),
@@ -394,7 +394,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
ctx.RenderWithErr(flashError, tplEditFile, &form)
}
} else {
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.fail_to_update_file", form.TreePath),
"Summary": ctx.Tr("repo.editor.fail_to_update_file_summary"),
"Details": utils.SanitizeFlashErrorString(err.Error()),
@@ -590,7 +590,7 @@ func DeleteFilePost(ctx *context.Context) {
if len(errPushRej.Message) == 0 {
ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplDeleteFile, &form)
} else {
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.push_rejected"),
"Summary": ctx.Tr("repo.editor.push_rejected_summary"),
"Details": utils.SanitizeFlashErrorString(errPushRej.Message),
@@ -797,7 +797,7 @@ func UploadFilePost(ctx *context.Context) {
if len(errPushRej.Message) == 0 {
ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplUploadFile, &form)
} else {
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.push_rejected"),
"Summary": ctx.Tr("repo.editor.push_rejected_summary"),
"Details": utils.SanitizeFlashErrorString(errPushRej.Message),
diff --git a/routers/web/repo/editor_test.go b/routers/web/repo/editor_test.go
index c28c3ef1d6..313fcfe33a 100644
--- a/routers/web/repo/editor_test.go
+++ b/routers/web/repo/editor_test.go
@@ -7,9 +7,9 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
diff --git a/routers/web/repo/find.go b/routers/web/repo/find.go
index daefe59c8f..07b3722798 100644
--- a/routers/web/repo/find.go
+++ b/routers/web/repo/find.go
@@ -7,7 +7,7 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/repo/flags/manage.go b/routers/web/repo/flags/manage.go
index 840f6c3773..377a5c20f8 100644
--- a/routers/web/repo/flags/manage.go
+++ b/routers/web/repo/flags/manage.go
@@ -7,9 +7,9 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go
index f52abbfb02..9f3b63698a 100644
--- a/routers/web/repo/githttp.go
+++ b/routers/web/repo/githttp.go
@@ -24,13 +24,13 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
"github.com/go-chi/cors"
diff --git a/routers/web/repo/helper.go b/routers/web/repo/helper.go
index a98abe566f..5e1e116018 100644
--- a/routers/web/repo/helper.go
+++ b/routers/web/repo/helper.go
@@ -8,8 +8,8 @@ import (
"sort"
"code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/services/context"
)
func MakeSelfOnTop(doer *user.User, users []*user.User) []*user.User {
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 963c6289b2..2e2d12672e 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -9,6 +9,7 @@ import (
stdCtx "context"
"errors"
"fmt"
+ "html/template"
"math/big"
"net/http"
"net/url"
@@ -31,7 +32,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/git"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
@@ -39,16 +39,19 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
+ "code.gitea.io/gitea/modules/optional"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/templates/vars"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms"
issue_service "code.gitea.io/gitea/services/issue"
@@ -143,7 +146,7 @@ func MustAllowPulls(ctx *context.Context) {
}
}
-func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption util.OptionalBool) {
+func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption optional.Option[bool]) {
var err error
viewType := ctx.FormString("type")
sortType := ctx.FormString("sort")
@@ -244,18 +247,18 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
}
}
- var isShowClosed util.OptionalBool
+ var isShowClosed optional.Option[bool]
switch ctx.FormString("state") {
case "closed":
- isShowClosed = util.OptionalBoolTrue
+ isShowClosed = optional.Some(true)
case "all":
- isShowClosed = util.OptionalBoolNone
+ isShowClosed = optional.None[bool]()
default:
- isShowClosed = util.OptionalBoolFalse
+ isShowClosed = optional.Some(false)
}
// if there are closed issues and no open issues, default to showing all issues
if len(ctx.FormString("state")) == 0 && issueStats.OpenCount == 0 && issueStats.ClosedCount != 0 {
- isShowClosed = util.OptionalBoolNone
+ isShowClosed = optional.None[bool]()
}
if repo.IsTimetrackerEnabled(ctx) {
@@ -275,10 +278,10 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
}
var total int
- switch isShowClosed {
- case util.OptionalBoolTrue:
+ switch {
+ case isShowClosed.Value():
total = int(issueStats.ClosedCount)
- case util.OptionalBoolNone:
+ case !isShowClosed.Has():
total = int(issueStats.OpenCount + issueStats.ClosedCount)
default:
total = int(issueStats.OpenCount)
@@ -434,7 +437,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
return
}
- pinned, err := issues_model.GetPinnedIssues(ctx, repo.ID, isPullOption.IsTrue())
+ pinned, err := issues_model.GetPinnedIssues(ctx, repo.ID, isPullOption.Value())
if err != nil {
ctx.ServerError("GetPinnedIssues", err)
return
@@ -464,10 +467,10 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
ctx.Data["AssigneeID"] = assigneeID
ctx.Data["PosterID"] = posterID
ctx.Data["Keyword"] = keyword
- switch isShowClosed {
- case util.OptionalBoolTrue:
+ switch {
+ case isShowClosed.Value():
ctx.Data["State"] = "closed"
- case util.OptionalBoolNone:
+ case !isShowClosed.Has():
ctx.Data["State"] = "all"
default:
ctx.Data["State"] = "open"
@@ -516,7 +519,7 @@ func Issues(ctx *context.Context) {
ctx.Data["NewIssueChooseTemplate"] = issue_service.HasTemplatesOrContactLinks(ctx.Repo.Repository, ctx.Repo.GitRepo)
}
- issues(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), util.OptionalBoolOf(isPullList))
+ issues(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), optional.Some(isPullList))
if ctx.Written() {
return
}
@@ -558,7 +561,7 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.R
var err error
ctx.Data["OpenMilestones"], err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
RepoID: repo.ID,
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
})
if err != nil {
ctx.ServerError("GetMilestones", err)
@@ -566,7 +569,7 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.R
}
ctx.Data["ClosedMilestones"], err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
RepoID: repo.ID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
})
if err != nil {
ctx.ServerError("GetMilestones", err)
@@ -594,7 +597,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
projects, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{
ListOptions: db.ListOptionsAll,
RepoID: repo.ID,
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
Type: project_model.TypeRepository,
})
if err != nil {
@@ -604,7 +607,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
projects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{
ListOptions: db.ListOptionsAll,
OwnerID: repo.OwnerID,
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
Type: repoOwnerType,
})
if err != nil {
@@ -617,7 +620,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
projects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{
ListOptions: db.ListOptionsAll,
RepoID: repo.ID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
Type: project_model.TypeRepository,
})
if err != nil {
@@ -627,7 +630,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
projects2, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{
ListOptions: db.ListOptionsAll,
OwnerID: repo.OwnerID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
Type: repoOwnerType,
})
if err != nil {
@@ -1020,7 +1023,7 @@ func NewIssue(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplIssueNew)
}
-func renderErrorOfTemplates(ctx *context.Context, errs map[string]error) string {
+func renderErrorOfTemplates(ctx *context.Context, errs map[string]error) template.HTML {
var files []string
for k := range errs {
files = append(files, k)
@@ -1032,14 +1035,14 @@ func renderErrorOfTemplates(ctx *context.Context, errs map[string]error) string
lines = append(lines, fmt.Sprintf("%s: %v", file, errs[file]))
}
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.issues.choose.ignore_invalid_templates"),
"Summary": ctx.Tr("repo.issues.choose.invalid_templates", len(errs)),
"Details": utils.SanitizeFlashErrorString(strings.Join(lines, "\n")),
})
if err != nil {
log.Debug("render flash error: %v", err)
- flashError = ctx.Locale.TrString("repo.issues.choose.ignore_invalid_templates")
+ flashError = ctx.Locale.Tr("repo.issues.choose.ignore_invalid_templates")
}
return flashError
}
@@ -1768,7 +1771,7 @@ func ViewIssue(ctx *context.Context) {
// so "|" is used as delimeter to mark the new format
if comment.Content[0] != '|' {
// handle old time comments that have formatted text stored
- comment.RenderedContent = comment.Content
+ comment.RenderedContent = templates.SanitizeHTML(comment.Content)
comment.Content = ""
} else {
// else it's just a duration in seconds to pass on to the frontend
@@ -2509,14 +2512,14 @@ func SearchIssues(ctx *context.Context) {
return
}
- var isClosed util.OptionalBool
+ var isClosed optional.Option[bool]
switch ctx.FormString("state") {
case "closed":
- isClosed = util.OptionalBoolTrue
+ isClosed = optional.Some(true)
case "all":
- isClosed = util.OptionalBoolNone
+ isClosed = optional.None[bool]()
default:
- isClosed = util.OptionalBoolFalse
+ isClosed = optional.Some(false)
}
var (
@@ -2529,7 +2532,7 @@ func SearchIssues(ctx *context.Context) {
Private: false,
AllPublic: true,
TopicOnly: false,
- Collaborate: util.OptionalBoolNone,
+ Collaborate: optional.None[bool](),
// This needs to be a column that is not nil in fixtures or
// MySQL will return different results when sorting by null in some cases
OrderBy: db.SearchOrderByAlphabetically,
@@ -2553,7 +2556,7 @@ func SearchIssues(ctx *context.Context) {
opts.OwnerID = owner.ID
opts.AllLimited = false
opts.AllPublic = false
- opts.Collaborate = util.OptionalBoolFalse
+ opts.Collaborate = optional.Some(false)
}
if ctx.FormString("team") != "" {
if ctx.FormString("owner") == "" {
@@ -2594,14 +2597,12 @@ func SearchIssues(ctx *context.Context) {
keyword = ""
}
- var isPull util.OptionalBool
+ isPull := optional.None[bool]()
switch ctx.FormString("type") {
case "pulls":
- isPull = util.OptionalBoolTrue
+ isPull = optional.Some(true)
case "issues":
- isPull = util.OptionalBoolFalse
- default:
- isPull = util.OptionalBoolNone
+ isPull = optional.Some(false)
}
var includedAnyLabels []int64
@@ -2740,14 +2741,14 @@ func ListIssues(ctx *context.Context) {
return
}
- var isClosed util.OptionalBool
+ var isClosed optional.Option[bool]
switch ctx.FormString("state") {
case "closed":
- isClosed = util.OptionalBoolTrue
+ isClosed = optional.Some(true)
case "all":
- isClosed = util.OptionalBoolNone
+ isClosed = optional.None[bool]()
default:
- isClosed = util.OptionalBoolFalse
+ isClosed = optional.Some(false)
}
keyword := ctx.FormTrim("q")
@@ -2799,14 +2800,12 @@ func ListIssues(ctx *context.Context) {
projectID = &v
}
- var isPull util.OptionalBool
+ isPull := optional.None[bool]()
switch ctx.FormString("type") {
case "pulls":
- isPull = util.OptionalBoolTrue
+ isPull = optional.Some(true)
case "issues":
- isPull = util.OptionalBoolFalse
- default:
- isPull = util.OptionalBoolNone
+ isPull = optional.Some(false)
}
// FIXME: we should be more efficient here
@@ -3322,7 +3321,7 @@ func ChangeIssueReaction(ctx *context.Context) {
return
}
- html, err := ctx.RenderToString(tplReactions, map[string]any{
+ html, err := ctx.RenderToHTML(tplReactions, map[string]any{
"ctxData": ctx.Data,
"ActionURL": fmt.Sprintf("%s/issues/%d/reactions", ctx.Repo.RepoLink, issue.Index),
"Reactions": issue.Reactions.GroupByType(),
@@ -3429,7 +3428,7 @@ func ChangeCommentReaction(ctx *context.Context) {
return
}
- html, err := ctx.RenderToString(tplReactions, map[string]any{
+ html, err := ctx.RenderToHTML(tplReactions, map[string]any{
"ctxData": ctx.Data,
"ActionURL": fmt.Sprintf("%s/comments/%d/reactions", ctx.Repo.RepoLink, comment.ID),
"Reactions": comment.Reactions.GroupByType(),
@@ -3572,8 +3571,8 @@ func updateAttachments(ctx *context.Context, item any, files []string) error {
return err
}
-func attachmentsHTML(ctx *context.Context, attachments []*repo_model.Attachment, content string) string {
- attachHTML, err := ctx.RenderToString(tplAttachment, map[string]any{
+func attachmentsHTML(ctx *context.Context, attachments []*repo_model.Attachment, content string) template.HTML {
+ attachHTML, err := ctx.RenderToHTML(tplAttachment, map[string]any{
"ctxData": ctx.Data,
"Attachments": attachments,
"Content": content,
diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go
index 4dc537a06e..dfee2863b5 100644
--- a/routers/web/repo/issue_content_history.go
+++ b/routers/web/repo/issue_content_history.go
@@ -11,11 +11,11 @@ import (
"code.gitea.io/gitea/models/avatars"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/services/context"
"github.com/sergi/go-diff/diffmatchpatch"
)
diff --git a/routers/web/repo/issue_dependency.go b/routers/web/repo/issue_dependency.go
index 022ec3ae3e..e3b85ee638 100644
--- a/routers/web/repo/issue_dependency.go
+++ b/routers/web/repo/issue_dependency.go
@@ -8,8 +8,8 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
// AddDependency adds new dependencies
diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go
index dd3e2803b4..9dedaefa4b 100644
--- a/routers/web/repo/issue_label.go
+++ b/routers/web/repo/issue_label.go
@@ -10,12 +10,12 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
issue_service "code.gitea.io/gitea/services/issue"
)
diff --git a/routers/web/repo/issue_label_test.go b/routers/web/repo/issue_label_test.go
index 742f12114d..93fc72300b 100644
--- a/routers/web/repo/issue_label_test.go
+++ b/routers/web/repo/issue_label_test.go
@@ -10,10 +10,10 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/contexttest"
"code.gitea.io/gitea/services/forms"
"github.com/stretchr/testify/assert"
diff --git a/routers/web/repo/issue_lock.go b/routers/web/repo/issue_lock.go
index f83109d9b3..1d5fc8a5f3 100644
--- a/routers/web/repo/issue_lock.go
+++ b/routers/web/repo/issue_lock.go
@@ -5,8 +5,8 @@ package repo
import (
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/repo/issue_pin.go b/routers/web/repo/issue_pin.go
index 9f334129f9..365c812681 100644
--- a/routers/web/repo/issue_pin.go
+++ b/routers/web/repo/issue_pin.go
@@ -7,9 +7,9 @@ import (
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/context"
)
// IssuePinOrUnpin pin or unpin a Issue
diff --git a/routers/web/repo/issue_stopwatch.go b/routers/web/repo/issue_stopwatch.go
index ab9fe3e69d..70d42b27c0 100644
--- a/routers/web/repo/issue_stopwatch.go
+++ b/routers/web/repo/issue_stopwatch.go
@@ -9,8 +9,8 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/eventsource"
+ "code.gitea.io/gitea/services/context"
)
// IssueStopwatch creates or stops a stopwatch for the given issue.
diff --git a/routers/web/repo/issue_timetrack.go b/routers/web/repo/issue_timetrack.go
index c9bf861b84..241e434049 100644
--- a/routers/web/repo/issue_timetrack.go
+++ b/routers/web/repo/issue_timetrack.go
@@ -9,9 +9,9 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/repo/issue_watch.go b/routers/web/repo/issue_watch.go
index 1f51ceba5e..8b033f3b17 100644
--- a/routers/web/repo/issue_watch.go
+++ b/routers/web/repo/issue_watch.go
@@ -9,8 +9,8 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/repo/middlewares.go b/routers/web/repo/middlewares.go
index ee49649654..ddda9f3ff2 100644
--- a/routers/web/repo/middlewares.go
+++ b/routers/web/repo/middlewares.go
@@ -9,9 +9,9 @@ import (
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/optional"
+ "code.gitea.io/gitea/services/context"
user_service "code.gitea.io/gitea/services/user"
)
diff --git a/routers/web/repo/migrate.go b/routers/web/repo/migrate.go
index b70901d5f2..97b0c425ea 100644
--- a/routers/web/repo/migrate.go
+++ b/routers/web/repo/migrate.go
@@ -15,13 +15,13 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/migrations"
"code.gitea.io/gitea/services/task"
diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go
index 19db2abd68..1c53f73fdb 100644
--- a/routers/web/repo/milestone.go
+++ b/routers/web/repo/milestone.go
@@ -12,13 +12,13 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/issue"
@@ -51,7 +51,7 @@ func Milestones(ctx *context.Context) {
PageSize: setting.UI.IssuePagingNum,
},
RepoID: ctx.Repo.Repository.ID,
- IsClosed: util.OptionalBoolOf(isShowClosed),
+ IsClosed: optional.Some(isShowClosed),
SortType: sortType,
Name: keyword,
})
@@ -292,7 +292,7 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
ctx.Data["Title"] = milestone.Name
ctx.Data["Milestone"] = milestone
- issues(ctx, milestoneID, projectID, util.OptionalBoolNone)
+ issues(ctx, milestoneID, projectID, optional.None[bool]())
ret, _ := issue.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
ctx.Data["NewIssueChooseTemplate"] = len(ret) > 0
diff --git a/routers/web/repo/packages.go b/routers/web/repo/packages.go
index ac9e64d774..11874ab0d0 100644
--- a/routers/web/repo/packages.go
+++ b/routers/web/repo/packages.go
@@ -10,9 +10,9 @@ import (
"code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
const (
@@ -37,7 +37,7 @@ func Packages(ctx *context.Context) {
RepoID: ctx.Repo.Repository.ID,
Type: packages.Type(packageType),
Name: packages.SearchValue{Value: query},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
ctx.ServerError("SearchLatestVersions", err)
diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go
index 03ea03467a..d234f6c964 100644
--- a/routers/web/repo/patch.go
+++ b/routers/web/repo/patch.go
@@ -10,10 +10,10 @@ import (
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/repository/files"
)
diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go
index cc0127e7e1..4c171defbd 100644
--- a/routers/web/repo/projects.go
+++ b/routers/web/repo/projects.go
@@ -17,13 +17,13 @@ import (
attachment_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
@@ -78,7 +78,7 @@ func Projects(ctx *context.Context) {
Page: page,
},
RepoID: repo.ID,
- IsClosed: util.OptionalBoolOf(isShowClosed),
+ IsClosed: optional.Some(isShowClosed),
OrderBy: project_model.GetSearchOrderByBySortType(sortType),
Type: project_model.TypeRepository,
Title: keyword,
@@ -349,7 +349,7 @@ func ViewProject(ctx *context.Context) {
if len(referencedIDs) > 0 {
if linkedPrs, err := issues_model.Issues(ctx, &issues_model.IssuesOptions{
IssueIDs: referencedIDs,
- IsPull: util.OptionalBoolTrue,
+ IsPull: optional.Some(true),
}); err == nil {
linkedPrsMap[issue.ID] = linkedPrs
}
diff --git a/routers/web/repo/projects_test.go b/routers/web/repo/projects_test.go
index 6698d47028..479f8c55a2 100644
--- a/routers/web/repo/projects_test.go
+++ b/routers/web/repo/projects_test.go
@@ -7,7 +7,7 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index 561039c413..8727f3d1e3 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -28,7 +28,6 @@ import (
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
@@ -37,12 +36,13 @@ import (
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/automerge"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/gitdiff"
notify_service "code.gitea.io/gitea/services/notify"
@@ -186,7 +186,7 @@ func updateForkRepositoryInContext(ctx *context.Context, forkRepo *repo_model.Re
ctx.Data["ContextUser"] = orgs[0]
} else {
ctx.Data["CanForkRepo"] = false
- ctx.Flash.Error(ctx.Tr("repo.fork_no_valid_owners"), true)
+ ctx.RenderWithErr(ctx.Tr("repo.fork_no_valid_owners"), tplFork, nil)
return false
}
@@ -1159,7 +1159,7 @@ func UpdatePullRequest(ctx *context.Context) {
if err = pull_service.Update(ctx, issue.PullRequest, ctx.Doer, message, rebase); err != nil {
if models.IsErrMergeConflicts(err) {
conflictError := err.(models.ErrMergeConflicts)
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.pulls.merge_conflict"),
"Summary": ctx.Tr("repo.pulls.merge_conflict_summary"),
"Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + " " + utils.SanitizeFlashErrorString(conflictError.StdOut),
@@ -1173,7 +1173,7 @@ func UpdatePullRequest(ctx *context.Context) {
return
} else if models.IsErrRebaseConflicts(err) {
conflictError := err.(models.ErrRebaseConflicts)
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)),
"Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"),
"Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + " " + utils.SanitizeFlashErrorString(conflictError.StdOut),
@@ -1305,7 +1305,7 @@ func MergePullRequest(ctx *context.Context) {
ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option"))
} else if models.IsErrMergeConflicts(err) {
conflictError := err.(models.ErrMergeConflicts)
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.merge_conflict"),
"Summary": ctx.Tr("repo.editor.merge_conflict_summary"),
"Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + " " + utils.SanitizeFlashErrorString(conflictError.StdOut),
@@ -1318,7 +1318,7 @@ func MergePullRequest(ctx *context.Context) {
ctx.JSONRedirect(issue.Link())
} else if models.IsErrRebaseConflicts(err) {
conflictError := err.(models.ErrRebaseConflicts)
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)),
"Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"),
"Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + " " + utils.SanitizeFlashErrorString(conflictError.StdOut),
@@ -1348,7 +1348,7 @@ func MergePullRequest(ctx *context.Context) {
if len(message) == 0 {
ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message"))
} else {
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.pulls.push_rejected"),
"Summary": ctx.Tr("repo.pulls.push_rejected_summary"),
"Details": utils.SanitizeFlashErrorString(pushrejErr.Message),
@@ -1525,7 +1525,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
ctx.JSONError(ctx.Tr("repo.pulls.push_rejected_no_message"))
return
}
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.pulls.push_rejected"),
"Summary": ctx.Tr("repo.pulls.push_rejected_summary"),
"Details": utils.SanitizeFlashErrorString(pushrejErr.Message),
@@ -1534,8 +1534,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
ctx.ServerError("CompareAndPullRequest.HTMLString", err)
return
}
- ctx.Flash.Error(flashError)
- ctx.JSONRedirect(pullIssue.Link()) // FIXME: it's unfriendly, and will make the content lost
+ ctx.JSONError(flashError)
return
}
ctx.ServerError("NewPullRequest", err)
diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go
index 798d3d5454..ead7592fd0 100644
--- a/routers/web/repo/pull_review.go
+++ b/routers/web/repo/pull_review.go
@@ -11,12 +11,12 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
pull_model "code.gitea.io/gitea/models/pull"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/forms"
pull_service "code.gitea.io/gitea/services/pull"
)
diff --git a/routers/web/repo/pull_review_test.go b/routers/web/repo/pull_review_test.go
index 68f68d7bb0..d87656f796 100644
--- a/routers/web/repo/pull_review_test.go
+++ b/routers/web/repo/pull_review_test.go
@@ -10,9 +10,9 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/contexttest"
"code.gitea.io/gitea/services/pull"
"github.com/stretchr/testify/assert"
diff --git a/routers/web/repo/recent_commits.go b/routers/web/repo/recent_commits.go
index 3507cb8752..c158fb30b6 100644
--- a/routers/web/repo/recent_commits.go
+++ b/routers/web/repo/recent_commits.go
@@ -8,7 +8,7 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
contributors_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go
index 1998bd8ccd..95447a8f54 100644
--- a/routers/web/repo/release.go
+++ b/routers/web/repo/release.go
@@ -17,16 +17,17 @@ import (
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/feed"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/forms"
releaseservice "code.gitea.io/gitea/services/release"
)
@@ -112,7 +113,7 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions)
cacheUsers[r.PublisherID] = r.Publisher
}
- r.Note, err = markdown.RenderString(&markup.RenderContext{
+ r.RenderedNote, err = markdown.RenderString(&markup.RenderContext{
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
@@ -184,6 +185,11 @@ func Releases(ctx *context.Context) {
ctx.ServerError("getReleaseInfos", err)
return
}
+ for _, rel := range releases {
+ if rel.Release.IsTag && rel.Release.Title == "" {
+ rel.Release.Title = rel.Release.TagName
+ }
+ }
ctx.Data["Releases"] = releases
@@ -223,7 +229,7 @@ func TagsList(ctx *context.Context) {
// the drafts should also be included because a real tag might be used as a draft.
IncludeDrafts: true,
IncludeTags: true,
- HasSha1: util.OptionalBoolTrue,
+ HasSha1: optional.Some(true),
RepoID: ctx.Repo.Repository.ID,
}
@@ -295,6 +301,9 @@ func SingleRelease(ctx *context.Context) {
}
release := releases[0].Release
+ if release.IsTag && release.Title == "" {
+ release.Title = release.TagName
+ }
ctx.Data["PageIsSingleTag"] = release.IsTag
if release.IsTag {
diff --git a/routers/web/repo/release_test.go b/routers/web/repo/release_test.go
index c4a2c1904e..7ebea4c3fb 100644
--- a/routers/web/repo/release_test.go
+++ b/routers/web/repo/release_test.go
@@ -10,8 +10,8 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/contexttest"
"code.gitea.io/gitea/services/forms"
"github.com/stretchr/testify/assert"
diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go
index 7eb5a42aa4..10fa21c60e 100644
--- a/routers/web/repo/render.go
+++ b/routers/web/repo/render.go
@@ -10,11 +10,11 @@ import (
"path"
"code.gitea.io/gitea/modules/charset"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
// RenderFile renders a file by repos path
diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go
index 17e5d9b582..57723e6a9a 100644
--- a/routers/web/repo/repo.go
+++ b/routers/web/repo/repo.go
@@ -21,7 +21,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/cache"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
@@ -31,10 +30,12 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms"
repo_service "code.gitea.io/gitea/services/repository"
archiver_service "code.gitea.io/gitea/services/repository/archiver"
+ commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus"
)
const (
@@ -244,7 +245,7 @@ func CreatePost(ctx *context.Context) {
var repo *repo_model.Repository
var err error
if form.RepoTemplate > 0 {
- opts := repo_module.GenerateRepoOptions{
+ opts := repo_service.GenerateRepoOptions{
Name: form.RepoName,
Description: form.Description,
Private: form.Private,
@@ -553,33 +554,33 @@ func SearchRepo(ctx *context.Context) {
PriorityOwnerID: ctx.FormInt64("priority_owner_id"),
TeamID: ctx.FormInt64("team_id"),
TopicOnly: ctx.FormBool("topic"),
- Collaborate: util.OptionalBoolNone,
+ Collaborate: optional.None[bool](),
Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")),
- Template: util.OptionalBoolNone,
+ Template: optional.None[bool](),
StarredByID: ctx.FormInt64("starredBy"),
IncludeDescription: ctx.FormBool("includeDesc"),
}
if ctx.FormString("template") != "" {
- opts.Template = util.OptionalBoolOf(ctx.FormBool("template"))
+ opts.Template = optional.Some(ctx.FormBool("template"))
}
if ctx.FormBool("exclusive") {
- opts.Collaborate = util.OptionalBoolFalse
+ opts.Collaborate = optional.Some(false)
}
mode := ctx.FormString("mode")
switch mode {
case "source":
- opts.Fork = util.OptionalBoolFalse
- opts.Mirror = util.OptionalBoolFalse
+ opts.Fork = optional.Some(false)
+ opts.Mirror = optional.Some(false)
case "fork":
- opts.Fork = util.OptionalBoolTrue
+ opts.Fork = optional.Some(true)
case "mirror":
- opts.Mirror = util.OptionalBoolTrue
+ opts.Mirror = optional.Some(true)
case "collaborative":
- opts.Mirror = util.OptionalBoolFalse
- opts.Collaborate = util.OptionalBoolTrue
+ opts.Mirror = optional.Some(false)
+ opts.Collaborate = optional.Some(true)
case "":
default:
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid search mode: \"%s\"", mode))
@@ -587,11 +588,11 @@ func SearchRepo(ctx *context.Context) {
}
if ctx.FormString("archived") != "" {
- opts.Archived = util.OptionalBoolOf(ctx.FormBool("archived"))
+ opts.Archived = optional.Some(ctx.FormBool("archived"))
}
if ctx.FormString("is_private") != "" {
- opts.IsPrivate = util.OptionalBoolOf(ctx.FormBool("is_private"))
+ opts.IsPrivate = optional.Some(ctx.FormBool("is_private"))
}
sortMode := ctx.FormString("sort")
@@ -630,30 +631,14 @@ func SearchRepo(ctx *context.Context) {
return
}
- // collect the latest commit of each repo
- // at most there are dozens of repos (limited by MaxResponseItems), so it's not a big problem at the moment
- repoBranchNames := make(map[int64]string, len(repos))
- for _, repo := range repos {
- repoBranchNames[repo.ID] = repo.DefaultBranch
- }
-
- repoIDsToLatestCommitSHAs, err := git_model.FindBranchesByRepoAndBranchName(ctx, repoBranchNames)
+ latestCommitStatuses, err := commitstatus_service.FindReposLastestCommitStatuses(ctx, repos)
if err != nil {
- log.Error("FindBranchesByRepoAndBranchName: %v", err)
- return
- }
-
- // call the database O(1) times to get the commit statuses for all repos
- repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoIDsToLatestCommitSHAs, db.ListOptionsAll)
- if err != nil {
- log.Error("GetLatestCommitStatusForPairs: %v", err)
+ log.Error("FindReposLastestCommitStatuses: %v", err)
return
}
results := make([]*repo_service.WebSearchRepository, len(repos))
for i, repo := range repos {
- latestCommitStatus := git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID])
-
results[i] = &repo_service.WebSearchRepository{
Repository: &api.Repository{
ID: repo.ID,
@@ -667,8 +652,11 @@ func SearchRepo(ctx *context.Context) {
Link: repo.Link(),
Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
},
- LatestCommitStatus: latestCommitStatus,
- LocaleLatestCommitStatus: latestCommitStatus.LocaleString(ctx.Locale),
+ }
+
+ if latestCommitStatuses[i] != nil {
+ results[i].LatestCommitStatus = latestCommitStatuses[i]
+ results[i].LocaleLatestCommitStatus = latestCommitStatuses[i].LocaleString(ctx.Locale)
}
}
diff --git a/routers/web/repo/search.go b/routers/web/repo/search.go
index 29b3b7b476..550a3dc8be 100644
--- a/routers/web/repo/search.go
+++ b/routers/web/repo/search.go
@@ -7,9 +7,9 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
code_indexer "code.gitea.io/gitea/modules/indexer/code"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/repository/files"
)
@@ -21,7 +21,7 @@ func Search(ctx *context.Context) {
keyword := ctx.FormTrim("q")
queryType := ctx.FormTrim("t")
- isMatch := queryType == "match"
+ isFuzzy := queryType != "match"
ctx.Data["Keyword"] = keyword
ctx.Data["Language"] = language
@@ -44,7 +44,7 @@ func Search(ctx *context.Context) {
ctx.Data["CodeIndexerEnabled"] = true
total, searchResults, searchResultLanguages, err := code_indexer.PerformSearch(ctx, []int64{ctx.Repo.Repository.ID},
- language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch)
+ language, keyword, page, setting.UI.RepoSearchPagingNum, isFuzzy)
if err != nil {
if code_indexer.IsAvailable(ctx) {
ctx.ServerError("SearchResults", err)
diff --git a/routers/web/repo/setting/avatar.go b/routers/web/repo/setting/avatar.go
index 44468d2666..504f57cfc2 100644
--- a/routers/web/repo/setting/avatar.go
+++ b/routers/web/repo/setting/avatar.go
@@ -8,11 +8,11 @@ import (
"fmt"
"io"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
repo_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/web/repo/setting/collaboration.go b/routers/web/repo/setting/collaboration.go
index a73a7e5a8a..75b55151e7 100644
--- a/routers/web/repo/setting/collaboration.go
+++ b/routers/web/repo/setting/collaboration.go
@@ -14,10 +14,10 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/mailer"
org_service "code.gitea.io/gitea/services/org"
repo_service "code.gitea.io/gitea/services/repository"
diff --git a/routers/web/repo/setting/default_branch.go b/routers/web/repo/setting/default_branch.go
index 9bf54e706a..d0b32ef079 100644
--- a/routers/web/repo/setting/default_branch.go
+++ b/routers/web/repo/setting/default_branch.go
@@ -7,11 +7,12 @@ import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/web/repo"
+ "code.gitea.io/gitea/services/context"
notify_service "code.gitea.io/gitea/services/notify"
)
@@ -40,7 +41,7 @@ func SetDefaultBranchPost(ctx *context.Context) {
return
} else if repo.DefaultBranch != branch {
repo.DefaultBranch = branch
- if err := ctx.Repo.GitRepo.SetDefaultBranch(branch); err != nil {
+ if err := gitrepo.SetDefaultBranch(ctx, repo, branch); err != nil {
if !git.IsErrUnsupportedVersion(err) {
ctx.ServerError("SetDefaultBranch", err)
return
diff --git a/routers/web/repo/setting/deploy_key.go b/routers/web/repo/setting/deploy_key.go
index 3d4420006c..abc3eb4af1 100644
--- a/routers/web/repo/setting/deploy_key.go
+++ b/routers/web/repo/setting/deploy_key.go
@@ -8,11 +8,11 @@ import (
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/repo/setting/git_hooks.go b/routers/web/repo/setting/git_hooks.go
index 551327d44b..217a01c90c 100644
--- a/routers/web/repo/setting/git_hooks.go
+++ b/routers/web/repo/setting/git_hooks.go
@@ -6,8 +6,8 @@ package setting
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/services/context"
)
// GitHooks hooks of a repository
diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go
index 53e7b22d1b..9b66af37b2 100644
--- a/routers/web/repo/setting/lfs.go
+++ b/routers/web/repo/setting/lfs.go
@@ -18,7 +18,6 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/pipeline"
"code.gitea.io/gitea/modules/lfs"
@@ -28,6 +27,7 @@ import (
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go
index 827ebea7f8..b37520a390 100644
--- a/routers/web/repo/setting/protected_branch.go
+++ b/routers/web/repo/setting/protected_branch.go
@@ -15,9 +15,9 @@ import (
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/repo"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
pull_service "code.gitea.io/gitea/services/pull"
"code.gitea.io/gitea/services/repository"
diff --git a/routers/web/repo/setting/protected_tag.go b/routers/web/repo/setting/protected_tag.go
index 46addb3f0a..2c25b650b9 100644
--- a/routers/web/repo/setting/protected_tag.go
+++ b/routers/web/repo/setting/protected_tag.go
@@ -13,9 +13,9 @@ import (
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/repo/setting/runners.go b/routers/web/repo/setting/runners.go
index 8d4112c157..a47d3b45e2 100644
--- a/routers/web/repo/setting/runners.go
+++ b/routers/web/repo/setting/runners.go
@@ -11,10 +11,10 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
actions_shared "code.gitea.io/gitea/routers/web/shared/actions"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/repo/setting/secrets.go b/routers/web/repo/setting/secrets.go
index cf427b2c44..d4d56bfc57 100644
--- a/routers/web/repo/setting/secrets.go
+++ b/routers/web/repo/setting/secrets.go
@@ -8,10 +8,10 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
shared "code.gitea.io/gitea/routers/web/shared/secrets"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go
index dcb4be7ef8..99c15b74f3 100644
--- a/routers/web/repo/setting/setting.go
+++ b/routers/web/repo/setting/setting.go
@@ -19,19 +19,18 @@ import (
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/indexer/code"
"code.gitea.io/gitea/modules/indexer/stats"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
- repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
"code.gitea.io/gitea/modules/web"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/migrations"
mirror_service "code.gitea.io/gitea/services/mirror"
@@ -715,7 +714,7 @@ func SettingsPost(ctx *context.Context) {
}
repo.IsMirror = false
- if _, err := repo_module.CleanUpMigrateInfo(ctx, repo); err != nil {
+ if _, err := repo_service.CleanUpMigrateInfo(ctx, repo); err != nil {
ctx.ServerError("CleanUpMigrateInfo", err)
return
} else if err = repo_model.DeleteMirrorByRepoID(ctx, ctx.Repo.Repository.ID); err != nil {
diff --git a/routers/web/repo/setting/settings_test.go b/routers/web/repo/setting/settings_test.go
index 1ed6858b99..b771113841 100644
--- a/routers/web/repo/setting/settings_test.go
+++ b/routers/web/repo/setting/settings_test.go
@@ -14,10 +14,10 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/contexttest"
"code.gitea.io/gitea/services/forms"
repo_service "code.gitea.io/gitea/services/repository"
diff --git a/routers/web/repo/setting/variables.go b/routers/web/repo/setting/variables.go
index 428aa0bd5c..45b6c0f39a 100644
--- a/routers/web/repo/setting/variables.go
+++ b/routers/web/repo/setting/variables.go
@@ -8,10 +8,10 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
shared "code.gitea.io/gitea/routers/web/shared/actions"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go
index 7bbe4d81a9..4e967c86d6 100644
--- a/routers/web/repo/setting/webhook.go
+++ b/routers/web/repo/setting/webhook.go
@@ -18,7 +18,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
@@ -26,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
webhook_module "code.gitea.io/gitea/modules/webhook"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms"
webhook_service "code.gitea.io/gitea/services/webhook"
diff --git a/routers/web/repo/topic.go b/routers/web/repo/topic.go
index d0e706c5bd..d81a695df9 100644
--- a/routers/web/repo/topic.go
+++ b/routers/web/repo/topic.go
@@ -8,8 +8,8 @@ import (
"strings"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/context"
)
// TopicsPost response for creating repository
diff --git a/routers/web/repo/treelist.go b/routers/web/repo/treelist.go
index c364e7090f..d11af4669f 100644
--- a/routers/web/repo/treelist.go
+++ b/routers/web/repo/treelist.go
@@ -7,8 +7,8 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/services/context"
"github.com/go-enry/go-enry/v2"
)
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index 32c09bb5f2..842d14232e 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -35,9 +35,8 @@ import (
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
- "code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
@@ -48,6 +47,7 @@ import (
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/feed"
+ "code.gitea.io/gitea/services/context"
issue_service "code.gitea.io/gitea/services/issue"
files_service "code.gitea.io/gitea/services/repository/files"
@@ -881,25 +881,18 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
defer cancel()
}
- selected := make(container.Set[string])
- selected.AddMultiple(ctx.FormStrings("f[]")...)
-
- entries := allEntries
- if len(selected) > 0 {
- entries = make(git.Entries, 0, len(selected))
- for _, entry := range allEntries {
- if selected.Contains(entry.Name()) {
- entries = append(entries, entry)
- }
- }
- }
-
- var latestCommit *git.Commit
- ctx.Data["Files"], latestCommit, err = entries.GetCommitsInfo(commitInfoCtx, ctx.Repo.Commit, ctx.Repo.TreePath)
+ files, latestCommit, err := allEntries.GetCommitsInfo(commitInfoCtx, ctx.Repo.Commit, ctx.Repo.TreePath)
if err != nil {
ctx.ServerError("GetCommitsInfo", err)
return nil
}
+ ctx.Data["Files"] = files
+ for _, f := range files {
+ if f.Commit == nil {
+ ctx.Data["HasFilesWithoutLatestCommit"] = true
+ break
+ }
+ }
if !loadLatestCommitData(ctx, latestCommit) {
return nil
@@ -1004,6 +997,8 @@ func renderCode(ctx *context.Context) {
return
}
+ checkOutdatedBranch(ctx)
+
checkCitationFile(ctx, entry)
if ctx.Written() {
return
@@ -1079,7 +1074,7 @@ func renderCode(ctx *context.Context) {
if err != nil {
continue
}
- defaultBranch, err := gitRepo.GetDefaultBranch()
+ defaultBranch, err := gitrepo.GetDefaultBranch(ctx, repo)
if err != nil {
continue
}
@@ -1129,18 +1124,43 @@ PostRecentBranchCheck:
ctx.HTML(http.StatusOK, tplRepoHome)
}
+func checkOutdatedBranch(ctx *context.Context) {
+ if !(ctx.Repo.IsAdmin() || ctx.Repo.IsOwner()) {
+ return
+ }
+
+ // get the head commit of the branch since ctx.Repo.CommitID is not always the head commit of `ctx.Repo.BranchName`
+ commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName)
+ if err != nil {
+ log.Error("GetBranchCommitID: %v", err)
+ // Don't return an error page, as it can be rechecked the next time the user opens the page.
+ return
+ }
+
+ dbBranch, err := git_model.GetBranch(ctx, ctx.Repo.Repository.ID, ctx.Repo.BranchName)
+ if err != nil {
+ log.Error("GetBranch: %v", err)
+ // Don't return an error page, as it can be rechecked the next time the user opens the page.
+ return
+ }
+
+ if dbBranch.CommitID != commit.ID.String() {
+ ctx.Flash.Warning(ctx.Tr("repo.error.broken_git_hook", "https://docs.gitea.com/help/faq#push-hook--webhook--actions-arent-running"), true)
+ }
+}
+
// RenderUserCards render a page show users according the input template
func RenderUserCards(ctx *context.Context, total int, getter func(opts db.ListOptions) ([]*user_model.User, error), tpl base.TplName) {
page := ctx.FormInt("page")
if page <= 0 {
page = 1
}
- pager := context.NewPagination(total, setting.ItemsPerPage, page, 5)
+ pager := context.NewPagination(total, setting.MaxUserCardsPerPage, page, 5)
ctx.Data["Page"] = pager
items, err := getter(db.ListOptions{
Page: pager.Paginater.Current(),
- PageSize: setting.ItemsPerPage,
+ PageSize: setting.MaxUserCardsPerPage,
})
if err != nil {
ctx.ServerError("getter", err)
@@ -1181,12 +1201,12 @@ func Forks(ctx *context.Context) {
page = 1
}
- pager := context.NewPagination(ctx.Repo.Repository.NumForks, setting.ItemsPerPage, page, 5)
+ pager := context.NewPagination(ctx.Repo.Repository.NumForks, setting.MaxForksPerPage, page, 5)
ctx.Data["Page"] = pager
forks, err := repo_model.GetForks(ctx, ctx.Repo.Repository, db.ListOptions{
Page: pager.Paginater.Current(),
- PageSize: setting.ItemsPerPage,
+ PageSize: setting.MaxForksPerPage,
})
if err != nil {
ctx.ServerError("GetForks", err)
diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go
index 157ebd4d5d..f0743cc89c 100644
--- a/routers/web/repo/wiki.go
+++ b/routers/web/repo/wiki.go
@@ -18,7 +18,6 @@ import (
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
@@ -29,6 +28,7 @@ import (
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/common"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
notify_service "code.gitea.io/gitea/services/notify"
wiki_service "code.gitea.io/gitea/services/wiki"
diff --git a/routers/web/repo/wiki_test.go b/routers/web/repo/wiki_test.go
index d3decdae2d..719cca3049 100644
--- a/routers/web/repo/wiki_test.go
+++ b/routers/web/repo/wiki_test.go
@@ -11,10 +11,10 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/contexttest"
"code.gitea.io/gitea/services/forms"
wiki_service "code.gitea.io/gitea/services/wiki"
@@ -79,7 +79,7 @@ func assertPagesMetas(t *testing.T, expectedNames []string, metas any) {
func TestWiki(t *testing.T) {
unittest.PrepareTestEnv(t)
- ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki/?action=_pages")
+ ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki")
ctx.SetParams("*", "Home")
contexttest.LoadRepo(t, ctx, 1)
Wiki(ctx)
diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go
index ae9a376724..34b7969442 100644
--- a/routers/web/shared/actions/runners.go
+++ b/routers/web/shared/actions/runners.go
@@ -8,10 +8,10 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/shared/actions/variables.go b/routers/web/shared/actions/variables.go
index 07a0575207..0f705399c9 100644
--- a/routers/web/shared/actions/variables.go
+++ b/routers/web/shared/actions/variables.go
@@ -10,9 +10,9 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
secret_service "code.gitea.io/gitea/services/secrets"
)
diff --git a/routers/web/shared/packages/packages.go b/routers/web/shared/packages/packages.go
index 30c25374d1..57671ad8f1 100644
--- a/routers/web/shared/packages/packages.go
+++ b/routers/web/shared/packages/packages.go
@@ -12,10 +12,10 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
cargo_service "code.gitea.io/gitea/services/packages/cargo"
container_service "code.gitea.io/gitea/services/packages/container"
@@ -157,7 +157,7 @@ func SetRulePreviewContext(ctx *context.Context, owner *user_model.User) {
for _, p := range packages {
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
PackageID: p.ID,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Sort: packages_model.SortCreatedDesc,
Paginator: db.NewAbsoluteListOptions(pcr.KeepCount, 200),
})
diff --git a/routers/web/shared/secrets/secrets.go b/routers/web/shared/secrets/secrets.go
index c805da734a..73505ec372 100644
--- a/routers/web/shared/secrets/secrets.go
+++ b/routers/web/shared/secrets/secrets.go
@@ -6,10 +6,10 @@ package secrets
import (
"code.gitea.io/gitea/models/db"
secret_model "code.gitea.io/gitea/models/secret"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/shared/actions"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
secret_service "code.gitea.io/gitea/services/secrets"
)
diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go
index 07026e484f..f007772d6c 100644
--- a/routers/web/shared/user/header.go
+++ b/routers/web/shared/user/header.go
@@ -13,12 +13,12 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
// prepareContextForCommonProfile store some common data into context data for user's profile related pages (including the nav menu)
@@ -36,8 +36,9 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
ctx.Data["IsBlocked"] = ctx.Doer != nil && user_model.IsBlocked(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate
- ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location)
-
+ if setting.Service.UserLocationMapURL != "" {
+ ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location)
+ }
// Show OpenID URIs
openIDs, err := user_model.GetUserOpenIDs(ctx, ctx.ContextUser.ID)
if err != nil {
@@ -114,7 +115,7 @@ func LoadHeaderCount(ctx *context.Context) error {
Actor: ctx.Doer,
OwnerID: ctx.ContextUser.ID,
Private: ctx.IsSigned,
- Collaborate: util.OptionalBoolFalse,
+ Collaborate: optional.Some(false),
IncludeDescription: setting.UI.SearchRepoDescription,
})
if err != nil {
@@ -130,7 +131,7 @@ func LoadHeaderCount(ctx *context.Context) error {
}
projectCount, err := db.Count[project_model.Project](ctx, project_model.SearchOptions{
OwnerID: ctx.ContextUser.ID,
- IsClosed: util.OptionalBoolOf(false),
+ IsClosed: optional.Some(false),
Type: projectType,
})
if err != nil {
diff --git a/routers/web/swagger_json.go b/routers/web/swagger_json.go
index 42e9dbe967..fc39b504a9 100644
--- a/routers/web/swagger_json.go
+++ b/routers/web/swagger_json.go
@@ -4,7 +4,7 @@
package web
import (
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
)
// SwaggerV1Json render swagger v1 json
diff --git a/routers/web/user/avatar.go b/routers/web/user/avatar.go
index 772cc38bea..04f510161d 100644
--- a/routers/web/user/avatar.go
+++ b/routers/web/user/avatar.go
@@ -9,8 +9,8 @@ import (
"code.gitea.io/gitea/models/avatars"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/httpcache"
+ "code.gitea.io/gitea/services/context"
)
func cacheableRedirect(ctx *context.Context, location string) {
diff --git a/routers/web/user/code.go b/routers/web/user/code.go
index ee514a7cfe..8613d38b65 100644
--- a/routers/web/user/code.go
+++ b/routers/web/user/code.go
@@ -8,10 +8,10 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
code_indexer "code.gitea.io/gitea/modules/indexer/code"
"code.gitea.io/gitea/modules/setting"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
)
const (
@@ -40,7 +40,7 @@ func CodeSearch(ctx *context.Context) {
keyword := ctx.FormTrim("q")
queryType := ctx.FormTrim("t")
- isMatch := queryType == "match"
+ isFuzzy := queryType != "match"
ctx.Data["Keyword"] = keyword
ctx.Data["Language"] = language
@@ -75,7 +75,7 @@ func CodeSearch(ctx *context.Context) {
)
if len(repoIDs) > 0 {
- total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch)
+ total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isFuzzy)
if err != nil {
if code_indexer.IsAvailable(ctx) {
ctx.ServerError("SearchResults", err)
diff --git a/routers/web/user/home.go b/routers/web/user/home.go
index 8759ba32b1..31ee0e40df 100644
--- a/routers/web/user/home.go
+++ b/routers/web/user/home.go
@@ -24,15 +24,14 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/context"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/feed"
- context_service "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context"
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
@@ -162,8 +161,8 @@ func Milestones(ctx *context.Context) {
Private: true,
AllPublic: false, // Include also all public repositories of users and public organisations
AllLimited: false, // Include also all public repositories of limited organisations
- Archived: util.OptionalBoolFalse,
- HasMilestones: util.OptionalBoolTrue, // Just needs display repos has milestones
+ Archived: optional.Some(false),
+ HasMilestones: optional.Some(true), // Just needs display repos has milestones
}
if ctxUser.IsOrganization() && ctx.Org.Team != nil {
@@ -215,7 +214,7 @@ func Milestones(ctx *context.Context) {
counts, err := issues_model.CountMilestonesMap(ctx, issues_model.FindMilestoneOptions{
RepoCond: userRepoCond,
Name: keyword,
- IsClosed: util.OptionalBoolOf(isShowClosed),
+ IsClosed: optional.Some(isShowClosed),
})
if err != nil {
ctx.ServerError("CountMilestonesByRepoIDs", err)
@@ -228,7 +227,7 @@ func Milestones(ctx *context.Context) {
PageSize: setting.UI.IssuePagingNum,
},
RepoCond: repoCond,
- IsClosed: util.OptionalBoolOf(isShowClosed),
+ IsClosed: optional.Some(isShowClosed),
SortType: sortType,
Name: keyword,
})
@@ -440,9 +439,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
isPullList := unitType == unit.TypePullRequests
opts := &issues_model.IssuesOptions{
- IsPull: util.OptionalBoolOf(isPullList),
+ IsPull: optional.Some(isPullList),
SortType: sortType,
- IsArchived: util.OptionalBoolFalse,
+ IsArchived: optional.Some(false),
Org: org,
Team: team,
User: ctx.Doer,
@@ -466,9 +465,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
Private: true,
AllPublic: false,
AllLimited: false,
- Collaborate: util.OptionalBoolNone,
+ Collaborate: optional.None[bool](),
UnitType: unitType,
- Archived: util.OptionalBoolFalse,
+ Archived: optional.Some(false),
}
if team != nil {
repoOpts.TeamID = team.ID
@@ -516,7 +515,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
// Educated guess: Do or don't show closed issues.
isShowClosed := ctx.FormString("state") == "closed"
- opts.IsClosed = util.OptionalBoolOf(isShowClosed)
+ opts.IsClosed = optional.Some(isShowClosed)
// Make sure page number is at least 1. Will be posted to ctx.Data.
page := ctx.FormInt("page")
@@ -714,7 +713,7 @@ func UsernameSubRoute(ctx *context.Context) {
username := ctx.Params("username")
reloadParam := func(suffix string) (success bool) {
ctx.SetParams("username", strings.TrimSuffix(username, suffix))
- context_service.UserAssignmentWeb()(ctx)
+ context.UserAssignmentWeb()(ctx)
if ctx.Written() {
return false
}
@@ -744,7 +743,6 @@ func UsernameSubRoute(ctx *context.Context) {
return
}
if reloadParam(".rss") {
- context_service.UserAssignmentWeb()(ctx)
feed.ShowUserFeedRSS(ctx)
}
case strings.HasSuffix(username, ".atom"):
@@ -756,7 +754,7 @@ func UsernameSubRoute(ctx *context.Context) {
feed.ShowUserFeedAtom(ctx)
}
default:
- context_service.UserAssignmentWeb()(ctx)
+ context.UserAssignmentWeb()(ctx)
if !ctx.Written() {
ctx.Data["EnableFeed"] = setting.Other.EnableFeed
OwnerProfile(ctx)
@@ -803,12 +801,12 @@ func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMod
case issues_model.FilterModeReviewed:
openClosedOpts.ReviewedID = &doerID
}
- openClosedOpts.IsClosed = util.OptionalBoolFalse
+ openClosedOpts.IsClosed = optional.Some(false)
ret.OpenCount, err = issue_indexer.CountIssues(ctx, openClosedOpts)
if err != nil {
return nil, err
}
- openClosedOpts.IsClosed = util.OptionalBoolTrue
+ openClosedOpts.IsClosed = optional.Some(true)
ret.ClosedCount, err = issue_indexer.CountIssues(ctx, openClosedOpts)
if err != nil {
return nil, err
diff --git a/routers/web/user/home_test.go b/routers/web/user/home_test.go
index a32b015cd1..1cc9886308 100644
--- a/routers/web/user/home_test.go
+++ b/routers/web/user/home_test.go
@@ -10,8 +10,10 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
@@ -113,3 +115,18 @@ func TestMilestonesForSpecificRepo(t *testing.T) {
assert.Len(t, ctx.Data["Milestones"], 1)
assert.Len(t, ctx.Data["Repos"], 2) // both repo 42 and 1 have milestones and both are owned by user 2
}
+
+func TestDashboardPagination(t *testing.T) {
+ ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
+ page := context.NewPagination(10, 3, 1, 3)
+
+ setting.AppSubURL = "/SubPath"
+ out, err := ctx.RenderToHTML("base/paginate", map[string]any{"Link": setting.AppSubURL, "Page": page})
+ assert.NoError(t, err)
+ assert.Contains(t, out, ``)
+
+ setting.AppSubURL = ""
+ out, err = ctx.RenderToHTML("base/paginate", map[string]any{"Link": setting.AppSubURL, "Page": page})
+ assert.NoError(t, err)
+ assert.Contains(t, out, ` `)
+}
diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go
index 26f77cfc3a..324205ed91 100644
--- a/routers/web/user/notification.go
+++ b/routers/web/user/notification.go
@@ -16,11 +16,12 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
)
@@ -232,26 +233,25 @@ func NotificationSubscriptions(ctx *context.Context) {
if !util.SliceContainsString([]string{"all", "open", "closed"}, state, true) {
state = "all"
}
+
ctx.Data["State"] = state
- var showClosed util.OptionalBool
+ // default state filter is "all"
+ showClosed := optional.None[bool]()
switch state {
- case "all":
- showClosed = util.OptionalBoolNone
case "closed":
- showClosed = util.OptionalBoolTrue
+ showClosed = optional.Some(true)
case "open":
- showClosed = util.OptionalBoolFalse
+ showClosed = optional.Some(false)
}
- var issueTypeBool util.OptionalBool
issueType := ctx.FormString("issueType")
+ // default issue type is no filter
+ issueTypeBool := optional.None[bool]()
switch issueType {
case "issues":
- issueTypeBool = util.OptionalBoolFalse
+ issueTypeBool = optional.Some(false)
case "pulls":
- issueTypeBool = util.OptionalBoolTrue
- default:
- issueTypeBool = util.OptionalBoolNone
+ issueTypeBool = optional.Some(true)
}
ctx.Data["IssueType"] = issueType
@@ -389,6 +389,21 @@ func NotificationWatching(ctx *context.Context) {
orderBy = db.SearchOrderByRecentUpdated
}
+ archived := ctx.FormOptionalBool("archived")
+ ctx.Data["IsArchived"] = archived
+
+ fork := ctx.FormOptionalBool("fork")
+ ctx.Data["IsFork"] = fork
+
+ mirror := ctx.FormOptionalBool("mirror")
+ ctx.Data["IsMirror"] = mirror
+
+ template := ctx.FormOptionalBool("template")
+ ctx.Data["IsTemplate"] = template
+
+ private := ctx.FormOptionalBool("private")
+ ctx.Data["IsPrivate"] = private
+
repos, count, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: setting.UI.User.RepoPagingNum,
@@ -399,9 +414,14 @@ func NotificationWatching(ctx *context.Context) {
OrderBy: orderBy,
Private: ctx.IsSigned,
WatchedByID: ctx.Doer.ID,
- Collaborate: util.OptionalBoolFalse,
+ Collaborate: optional.Some(false),
TopicOnly: ctx.FormBool("topic"),
IncludeDescription: setting.UI.SearchRepoDescription,
+ Archived: archived,
+ Fork: fork,
+ Mirror: mirror,
+ Template: template,
+ IsPrivate: private,
})
if err != nil {
ctx.ServerError("SearchRepository", err)
diff --git a/routers/web/user/package.go b/routers/web/user/package.go
index 708af3e43c..3ecc59a2ab 100644
--- a/routers/web/user/package.go
+++ b/routers/web/user/package.go
@@ -15,8 +15,8 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
alpine_module "code.gitea.io/gitea/modules/packages/alpine"
debian_module "code.gitea.io/gitea/modules/packages/debian"
rpm_module "code.gitea.io/gitea/modules/packages/rpm"
@@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/web"
packages_helper "code.gitea.io/gitea/routers/api/packages/helper"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
packages_service "code.gitea.io/gitea/services/packages"
)
@@ -54,7 +55,7 @@ func ListPackages(ctx *context.Context) {
OwnerID: ctx.ContextUser.ID,
Type: packages_model.Type(packageType),
Name: packages_model.SearchValue{Value: query},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
ctx.ServerError("SearchLatestVersions", err)
@@ -145,7 +146,7 @@ func RedirectToLastVersion(ctx *context.Context) {
pvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
PackageID: p.ID,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
ctx.ServerError("GetPackageByName", err)
@@ -162,7 +163,7 @@ func RedirectToLastVersion(ctx *context.Context) {
return
}
- ctx.Redirect(pd.FullWebLink())
+ ctx.Redirect(pd.VersionWebLink())
}
// ViewPackageVersion displays a single package version
@@ -255,7 +256,7 @@ func ViewPackageVersion(ctx *context.Context) {
pvs, total, err = packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
Paginator: db.NewAbsoluteListOptions(0, 5),
PackageID: pd.Package.ID,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
}
if err != nil {
@@ -359,7 +360,7 @@ func ListPackageVersions(ctx *context.Context) {
ExactMatch: false,
Value: query,
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Sort: sort,
})
if err != nil {
@@ -467,7 +468,7 @@ func PackageSettingsPost(ctx *context.Context) {
redirectURL := ctx.Package.Owner.HomeLink() + "/-/packages"
// redirect to the package if there are still versions available
- if has, _ := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{PackageID: ctx.Package.Descriptor.Package.ID, IsInternal: util.OptionalBoolFalse}); has {
+ if has, _ := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{PackageID: ctx.Package.Descriptor.Package.ID, IsInternal: optional.Some(false)}); has {
redirectURL = ctx.Package.Descriptor.PackageWebLink()
}
diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go
index 4eec0e9905..89d22d3ef4 100644
--- a/routers/web/user/profile.go
+++ b/routers/web/user/profile.go
@@ -16,16 +16,17 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/feed"
"code.gitea.io/gitea/routers/web/org"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
user_service "code.gitea.io/gitea/services/user"
)
@@ -163,6 +164,21 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
}
ctx.Data["NumFollowing"] = numFollowing
+ archived := ctx.FormOptionalBool("archived")
+ ctx.Data["IsArchived"] = archived
+
+ fork := ctx.FormOptionalBool("fork")
+ ctx.Data["IsFork"] = fork
+
+ mirror := ctx.FormOptionalBool("mirror")
+ ctx.Data["IsMirror"] = mirror
+
+ template := ctx.FormOptionalBool("template")
+ ctx.Data["IsTemplate"] = template
+
+ private := ctx.FormOptionalBool("private")
+ ctx.Data["IsPrivate"] = private
+
switch tab {
case "followers":
ctx.Data["Cards"] = followers
@@ -205,10 +221,15 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
OrderBy: orderBy,
Private: ctx.IsSigned,
StarredByID: ctx.ContextUser.ID,
- Collaborate: util.OptionalBoolFalse,
+ Collaborate: optional.Some(false),
TopicOnly: topicOnly,
Language: language,
IncludeDescription: setting.UI.SearchRepoDescription,
+ Archived: archived,
+ Fork: fork,
+ Mirror: mirror,
+ Template: template,
+ IsPrivate: private,
})
if err != nil {
ctx.ServerError("SearchRepository", err)
@@ -227,10 +248,15 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
OrderBy: orderBy,
Private: ctx.IsSigned,
WatchedByID: ctx.ContextUser.ID,
- Collaborate: util.OptionalBoolFalse,
+ Collaborate: optional.Some(false),
TopicOnly: topicOnly,
Language: language,
IncludeDescription: setting.UI.SearchRepoDescription,
+ Archived: archived,
+ Fork: fork,
+ Mirror: mirror,
+ Template: template,
+ IsPrivate: private,
})
if err != nil {
ctx.ServerError("SearchRepository", err)
@@ -272,10 +298,15 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
OwnerID: ctx.ContextUser.ID,
OrderBy: orderBy,
Private: ctx.IsSigned,
- Collaborate: util.OptionalBoolFalse,
+ Collaborate: optional.Some(false),
TopicOnly: topicOnly,
Language: language,
IncludeDescription: setting.UI.SearchRepoDescription,
+ Archived: archived,
+ Fork: fork,
+ Mirror: mirror,
+ Template: template,
+ IsPrivate: private,
})
if err != nil {
ctx.ServerError("SearchRepository", err)
diff --git a/routers/web/user/search.go b/routers/web/user/search.go
index 4d090a3784..fb7729bbe1 100644
--- a/routers/web/user/search.go
+++ b/routers/web/user/search.go
@@ -8,7 +8,7 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go
index 6042e0b6cd..386309aa71 100644
--- a/routers/web/user/setting/account.go
+++ b/routers/web/user/setting/account.go
@@ -13,13 +13,15 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/auth"
+ "code.gitea.io/gitea/services/auth/source/db"
+ "code.gitea.io/gitea/services/auth/source/smtp"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/mailer"
"code.gitea.io/gitea/services/user"
@@ -251,11 +253,24 @@ func DeleteAccount(ctx *context.Context) {
ctx.Data["PageIsSettingsAccount"] = true
if _, _, err := auth.UserSignIn(ctx, ctx.Doer.Name, ctx.FormString("password")); err != nil {
- if user_model.IsErrUserNotExist(err) {
+ switch {
+ case user_model.IsErrUserNotExist(err):
+ loadAccountData(ctx)
+
+ ctx.RenderWithErr(ctx.Tr("form.user_not_exist"), tplSettingsAccount, nil)
+ case errors.Is(err, smtp.ErrUnsupportedLoginType):
+ loadAccountData(ctx)
+
+ ctx.RenderWithErr(ctx.Tr("form.unsupported_login_type"), tplSettingsAccount, nil)
+ case errors.As(err, &db.ErrUserPasswordNotSet{}):
+ loadAccountData(ctx)
+
+ ctx.RenderWithErr(ctx.Tr("form.unset_password"), tplSettingsAccount, nil)
+ case errors.As(err, &db.ErrUserPasswordInvalid{}):
loadAccountData(ctx)
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_password"), tplSettingsAccount, nil)
- } else {
+ default:
ctx.ServerError("UserSignIn", err)
}
return
diff --git a/routers/web/user/setting/account_test.go b/routers/web/user/setting/account_test.go
index 6742c382e9..9fdc5e4d53 100644
--- a/routers/web/user/setting/account_test.go
+++ b/routers/web/user/setting/account_test.go
@@ -8,9 +8,9 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/contexttest"
"code.gitea.io/gitea/services/forms"
"github.com/stretchr/testify/assert"
diff --git a/routers/web/user/setting/adopt.go b/routers/web/user/setting/adopt.go
index decb35c1e1..171c1933d4 100644
--- a/routers/web/user/setting/adopt.go
+++ b/routers/web/user/setting/adopt.go
@@ -8,9 +8,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go
index a7e31fd505..e3822ca988 100644
--- a/routers/web/user/setting/applications.go
+++ b/routers/web/user/setting/applications.go
@@ -10,9 +10,9 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/user/setting/blocked_users.go b/routers/web/user/setting/blocked_users.go
index ed1c340fb9..3f35b2eadf 100644
--- a/routers/web/user/setting/blocked_users.go
+++ b/routers/web/user/setting/blocked_users.go
@@ -9,8 +9,8 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go
index 16410d06ff..d2b60fc809 100644
--- a/routers/web/user/setting/keys.go
+++ b/routers/web/user/setting/keys.go
@@ -5,15 +5,16 @@
package setting
import (
+ "fmt"
"net/http"
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
@@ -77,6 +78,11 @@ func KeysPost(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content))
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
case "gpg":
+ if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
+ ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
+ return
+ }
+
token := asymkey_model.VerificationToken(ctx.Doer, 1)
lastToken := asymkey_model.VerificationToken(ctx.Doer, 0)
@@ -153,6 +159,11 @@ func KeysPost(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("settings.verify_gpg_key_success", keyID))
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
case "ssh":
+ if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
+ ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
+ return
+ }
+
content, err := asymkey_model.CheckPublicKeyString(form.Content)
if err != nil {
if db.IsErrSSHDisabled(err) {
@@ -192,6 +203,11 @@ func KeysPost(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title))
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
case "verify_ssh":
+ if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
+ ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
+ return
+ }
+
token := asymkey_model.VerificationToken(ctx.Doer, 1)
lastToken := asymkey_model.VerificationToken(ctx.Doer, 0)
@@ -224,12 +240,21 @@ func KeysPost(ctx *context.Context) {
func DeleteKey(ctx *context.Context) {
switch ctx.FormString("type") {
case "gpg":
+ if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
+ ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
+ return
+ }
if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.FormInt64("id")); err != nil {
ctx.Flash.Error("DeleteGPGKey: " + err.Error())
} else {
ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success"))
}
case "ssh":
+ if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) {
+ ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited"))
+ return
+ }
+
keyID := ctx.FormInt64("id")
external, err := asymkey_model.PublicKeyIsExternallyManaged(ctx, keyID)
if err != nil {
@@ -308,4 +333,5 @@ func loadKeysData(ctx *context.Context) {
ctx.Data["VerifyingID"] = ctx.FormString("verify_gpg")
ctx.Data["VerifyingFingerprint"] = ctx.FormString("verify_ssh")
+ ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures
}
diff --git a/routers/web/user/setting/oauth2.go b/routers/web/user/setting/oauth2.go
index 93142c21fc..1f485e06c8 100644
--- a/routers/web/user/setting/oauth2.go
+++ b/routers/web/user/setting/oauth2.go
@@ -5,8 +5,8 @@ package setting
import (
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/user/setting/oauth2_common.go b/routers/web/user/setting/oauth2_common.go
index fecaa4b873..85d1e820a5 100644
--- a/routers/web/user/setting/oauth2_common.go
+++ b/routers/web/user/setting/oauth2_common.go
@@ -9,10 +9,10 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/user/setting/packages.go b/routers/web/user/setting/packages.go
index 34d18f999e..4132659495 100644
--- a/routers/web/user/setting/packages.go
+++ b/routers/web/user/setting/packages.go
@@ -9,11 +9,11 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
chef_module "code.gitea.io/gitea/modules/packages/chef"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
shared "code.gitea.io/gitea/routers/web/shared/packages"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go
index 24a807d518..49eb050dcb 100644
--- a/routers/web/user/setting/profile.go
+++ b/routers/web/user/setting/profile.go
@@ -20,7 +20,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
@@ -29,6 +28,7 @@ import (
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
user_service "code.gitea.io/gitea/services/user"
)
diff --git a/routers/web/user/setting/runner.go b/routers/web/user/setting/runner.go
index 451fd0ca97..2bb10cceb9 100644
--- a/routers/web/user/setting/runner.go
+++ b/routers/web/user/setting/runner.go
@@ -4,8 +4,8 @@
package setting
import (
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
func RedirectToDefaultSetting(ctx *context.Context) {
diff --git a/routers/web/user/setting/security/2fa.go b/routers/web/user/setting/security/2fa.go
index 7858b634ce..cd09102369 100644
--- a/routers/web/user/setting/security/2fa.go
+++ b/routers/web/user/setting/security/2fa.go
@@ -13,10 +13,10 @@ import (
"strings"
"code.gitea.io/gitea/models/auth"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"github.com/pquerna/otp"
diff --git a/routers/web/user/setting/security/openid.go b/routers/web/user/setting/security/openid.go
index 9a207e149d..8f788e1735 100644
--- a/routers/web/user/setting/security/openid.go
+++ b/routers/web/user/setting/security/openid.go
@@ -8,10 +8,10 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/openid"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go
index 3647d606ee..8d6859ab87 100644
--- a/routers/web/user/setting/security/security.go
+++ b/routers/web/user/setting/security/security.go
@@ -12,10 +12,10 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/oauth2"
+ "code.gitea.io/gitea/services/context"
)
const (
@@ -112,7 +112,7 @@ func loadSecurityData(ctx *context.Context) {
ctx.Data["AccountLinks"] = sources
authSources, err := db.Find[auth_model.Source](ctx, auth_model.FindSourcesOptions{
- IsActive: util.OptionalBoolNone,
+ IsActive: optional.None[bool](),
LoginType: auth_model.OAuth2,
})
if err != nil {
diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go
index ce103528c5..e382c8b9af 100644
--- a/routers/web/user/setting/security/webauthn.go
+++ b/routers/web/user/setting/security/webauthn.go
@@ -11,10 +11,10 @@ import (
"code.gitea.io/gitea/models/auth"
wa "code.gitea.io/gitea/modules/auth/webauthn"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"github.com/go-webauthn/webauthn/protocol"
diff --git a/routers/web/user/setting/webhooks.go b/routers/web/user/setting/webhooks.go
index 679b72e501..4423b62781 100644
--- a/routers/web/user/setting/webhooks.go
+++ b/routers/web/user/setting/webhooks.go
@@ -9,8 +9,8 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/user/stop_watch.go b/routers/web/user/stop_watch.go
index 86f66e64a6..38f74ea455 100644
--- a/routers/web/user/stop_watch.go
+++ b/routers/web/user/stop_watch.go
@@ -8,7 +8,7 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/web/user/task.go b/routers/web/user/task.go
index bec68c5f20..8476767e9e 100644
--- a/routers/web/user/task.go
+++ b/routers/web/user/task.go
@@ -8,8 +8,8 @@ import (
"strconv"
admin_model "code.gitea.io/gitea/models/admin"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/services/context"
)
// TaskStatus returns task's status
diff --git a/routers/web/web.go b/routers/web/web.go
index fc09ed2b6b..114a50cf08 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -12,7 +12,6 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/metrics"
"code.gitea.io/gitea/modules/public"
@@ -44,7 +43,7 @@ import (
user_setting "code.gitea.io/gitea/routers/web/user/setting"
"code.gitea.io/gitea/routers/web/user/setting/security"
auth_service "code.gitea.io/gitea/services/auth"
- context_service "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/lfs"
@@ -795,7 +794,7 @@ func registerRoutes(m *web.Route) {
m.Methods("GET, OPTIONS", "/attachments/{uuid}", optionsCorsHandler(), repo.GetAttachment)
}, ignSignIn)
- m.Post("/{username}", reqSignIn, context_service.UserAssignmentWeb(), user.Action)
+ m.Post("/{username}", reqSignIn, context.UserAssignmentWeb(), user.Action)
reqRepoAdmin := context.RequireRepoAdmin()
reqRepoCodeWriter := context.RequireRepoWriter(unit.TypeCode)
@@ -1029,7 +1028,7 @@ func registerRoutes(m *web.Route) {
m.Group("", func() {
m.Get("/code", user.CodeSearch)
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false), individualPermsChecker)
- }, ignSignIn, context_service.UserAssignmentWeb(), context.OrgAssignment()) // for "/{username}/-" (packages, projects, code)
+ }, ignSignIn, context.UserAssignmentWeb(), context.OrgAssignment()) // for "/{username}/-" (packages, projects, code)
m.Group("/{username}/{reponame}", func() {
m.Group("/settings", func() {
diff --git a/routers/web/webfinger.go b/routers/web/webfinger.go
index faa35b8d2f..a87c426b3b 100644
--- a/routers/web/webfinger.go
+++ b/routers/web/webfinger.go
@@ -10,9 +10,9 @@ import (
"strings"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
// https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-webfinger-14#section-4.4
diff --git a/services/actions/auth.go b/services/actions/auth.go
index e0f9a9015d..8e934d89a8 100644
--- a/services/actions/auth.go
+++ b/services/actions/auth.go
@@ -9,6 +9,7 @@ import (
"strings"
"time"
+ "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -21,17 +22,41 @@ type actionsClaims struct {
TaskID int64
RunID int64
JobID int64
+ Ac string `json:"ac"`
}
+type actionsCacheScope struct {
+ Scope string
+ Permission actionsCachePermission
+}
+
+type actionsCachePermission int
+
+const (
+ actionsCachePermissionRead = 1 << iota
+ actionsCachePermissionWrite
+)
+
func CreateAuthorizationToken(taskID, runID, jobID int64) (string, error) {
now := time.Now()
+ ac, err := json.Marshal(&[]actionsCacheScope{
+ {
+ Scope: "",
+ Permission: actionsCachePermissionWrite,
+ },
+ })
+ if err != nil {
+ return "", err
+ }
+
claims := actionsClaims{
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(24 * time.Hour)),
NotBefore: jwt.NewNumericDate(now),
},
Scp: fmt.Sprintf("Actions.Results:%d:%d", runID, jobID),
+ Ac: string(ac),
TaskID: taskID,
RunID: runID,
JobID: jobID,
diff --git a/services/actions/auth_test.go b/services/actions/auth_test.go
index 1f62f17f52..f73ae8ae4c 100644
--- a/services/actions/auth_test.go
+++ b/services/actions/auth_test.go
@@ -7,6 +7,7 @@ import (
"net/http"
"testing"
+ "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
"github.com/golang-jwt/jwt/v5"
@@ -29,6 +30,14 @@ func TestCreateAuthorizationToken(t *testing.T) {
taskIDClaim, ok := claims["TaskID"]
assert.True(t, ok, "Has TaskID claim in jwt token")
assert.Equal(t, float64(taskID), taskIDClaim, "Supplied taskid must match stored one")
+ acClaim, ok := claims["ac"]
+ assert.True(t, ok, "Has ac claim in jwt token")
+ ac, ok := acClaim.(string)
+ assert.True(t, ok, "ac claim is a string for buildx gha cache")
+ scopes := []actionsCacheScope{}
+ err = json.Unmarshal([]byte(ac), &scopes)
+ assert.NoError(t, err, "ac claim is a json list for buildx gha cache")
+ assert.GreaterOrEqual(t, len(scopes), 1, "Expected at least one action cache scope for buildx gha cache")
}
func TestParseAuthorizationToken(t *testing.T) {
diff --git a/services/actions/job_emitter.go b/services/actions/job_emitter.go
index fe39312386..d2bbbd9a7c 100644
--- a/services/actions/job_emitter.go
+++ b/services/actions/job_emitter.go
@@ -7,12 +7,14 @@ import (
"context"
"errors"
"fmt"
+ "strings"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/queue"
+ "github.com/nektos/act/pkg/jobparser"
"xorm.io/builder"
)
@@ -76,12 +78,15 @@ func checkJobsOfRun(ctx context.Context, runID int64) error {
type jobStatusResolver struct {
statuses map[int64]actions_model.Status
needs map[int64][]int64
+ jobMap map[int64]*actions_model.ActionRunJob
}
func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver {
idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs))
+ jobMap := make(map[int64]*actions_model.ActionRunJob)
for _, job := range jobs {
idToJobs[job.JobID] = append(idToJobs[job.JobID], job)
+ jobMap[job.ID] = job
}
statuses := make(map[int64]actions_model.Status, len(jobs))
@@ -97,6 +102,7 @@ func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver {
return &jobStatusResolver{
statuses: statuses,
needs: needs,
+ jobMap: jobMap,
}
}
@@ -135,7 +141,20 @@ func (r *jobStatusResolver) resolve() map[int64]actions_model.Status {
if allSucceed {
ret[id] = actions_model.StatusWaiting
} else {
- ret[id] = actions_model.StatusSkipped
+ // If a job's "if" condition is "always()", the job should always run even if some of its dependencies did not succeed.
+ // See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds
+ always := false
+ if wfJobs, _ := jobparser.Parse(r.jobMap[id].WorkflowPayload); len(wfJobs) == 1 {
+ _, wfJob := wfJobs[0].Job()
+ expr := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(wfJob.If.Value, "${{"), "}}"))
+ always = expr == "always()"
+ }
+
+ if always {
+ ret[id] = actions_model.StatusWaiting
+ } else {
+ ret[id] = actions_model.StatusSkipped
+ }
}
}
}
diff --git a/services/actions/job_emitter_test.go b/services/actions/job_emitter_test.go
index e81aa61d80..038df7d4f8 100644
--- a/services/actions/job_emitter_test.go
+++ b/services/actions/job_emitter_test.go
@@ -70,6 +70,62 @@ func Test_jobStatusResolver_Resolve(t *testing.T) {
},
want: map[int64]actions_model.Status{},
},
+ {
+ name: "with ${{ always() }} condition",
+ jobs: actions_model.ActionJobList{
+ {ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
+ {ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
+ `
+name: test
+on: push
+jobs:
+ job2:
+ runs-on: ubuntu-latest
+ needs: job1
+ if: ${{ always() }}
+ steps:
+ - run: echo "always run"
+`)},
+ },
+ want: map[int64]actions_model.Status{2: actions_model.StatusWaiting},
+ },
+ {
+ name: "with always() condition",
+ jobs: actions_model.ActionJobList{
+ {ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
+ {ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
+ `
+name: test
+on: push
+jobs:
+ job2:
+ runs-on: ubuntu-latest
+ needs: job1
+ if: always()
+ steps:
+ - run: echo "always run"
+`)},
+ },
+ want: map[int64]actions_model.Status{2: actions_model.StatusWaiting},
+ },
+ {
+ name: "without always() condition",
+ jobs: actions_model.ActionJobList{
+ {ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
+ {ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
+ `
+name: test
+on: push
+jobs:
+ job2:
+ runs-on: ubuntu-latest
+ needs: job1
+ steps:
+ - run: echo "not always run"
+`)},
+ },
+ want: map[int64]actions_model.Status{2: actions_model.StatusSkipped},
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
diff --git a/services/actions/notifier.go b/services/actions/notifier.go
index e144484dab..aa88d4e0d8 100644
--- a/services/actions/notifier.go
+++ b/services/actions/notifier.go
@@ -152,7 +152,13 @@ func (n *actionsNotifier) IssueChangeAssignee(ctx context.Context, doer *user_mo
} else {
action = api.HookIssueAssigned
}
- notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestAssign, action)
+
+ hookEvent := webhook_module.HookEventIssueAssign
+ if issue.IsPull {
+ hookEvent = webhook_module.HookEventPullRequestAssign
+ }
+
+ notifyIssueChange(ctx, doer, issue, hookEvent, action)
}
// IssueChangeMilestone notifies assignee to notifiers
@@ -165,14 +171,26 @@ func (n *actionsNotifier) IssueChangeMilestone(ctx context.Context, doer *user_m
} else {
action = api.HookIssueDemilestoned
}
- notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestMilestone, action)
+
+ hookEvent := webhook_module.HookEventIssueMilestone
+ if issue.IsPull {
+ hookEvent = webhook_module.HookEventPullRequestMilestone
+ }
+
+ notifyIssueChange(ctx, doer, issue, hookEvent, action)
}
func (n *actionsNotifier) IssueChangeLabels(ctx context.Context, doer *user_model.User, issue *issues_model.Issue,
_, _ []*issues_model.Label,
) {
ctx = withMethod(ctx, "IssueChangeLabels")
- notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestLabel, api.HookIssueLabelUpdated)
+
+ hookEvent := webhook_module.HookEventIssueLabel
+ if issue.IsPull {
+ hookEvent = webhook_module.HookEventPullRequestLabel
+ }
+
+ notifyIssueChange(ctx, doer, issue, hookEvent, api.HookIssueLabelUpdated)
}
func notifyIssueChange(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, event webhook_module.HookEventType, action api.HookIssueAction) {
diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go
index 2d138aaf33..6f0e8058a0 100644
--- a/services/actions/notifier_helper.go
+++ b/services/actions/notifier_helper.go
@@ -117,6 +117,9 @@ func notify(ctx context.Context, input *notifyInput) error {
log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
return nil
}
+ if input.Repo.IsEmpty {
+ return nil
+ }
if unit_model.TypeActions.UnitGlobalDisabled() {
return nil
}
@@ -302,7 +305,18 @@ func handleWorkflows(
run.NeedApproval = need
}
- jobs, err := jobparser.Parse(dwf.Content)
+ if err := run.LoadAttributes(ctx); err != nil {
+ log.Error("LoadAttributes: %v", err)
+ continue
+ }
+
+ vars, err := actions_model.GetVariablesOfRun(ctx, run)
+ if err != nil {
+ log.Error("GetVariablesOfRun: %v", err)
+ continue
+ }
+
+ jobs, err := jobparser.Parse(dwf.Content, jobparser.WithVars(vars))
if err != nil {
log.Error("jobparser.Parse: %v", err)
continue
diff --git a/services/attachment/attachment.go b/services/attachment/attachment.go
index 1bcd460e3c..4481966b4a 100644
--- a/services/attachment/attachment.go
+++ b/services/attachment/attachment.go
@@ -12,8 +12,8 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/storage"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context/upload"
"github.com/google/uuid"
)
@@ -44,14 +44,14 @@ func NewAttachment(ctx context.Context, attach *repo_model.Attachment, file io.R
}
// UploadAttachment upload new attachment into storage and update database
-func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string, fileSize int64, opts *repo_model.Attachment) (*repo_model.Attachment, error) {
+func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string, fileSize int64, attach *repo_model.Attachment) (*repo_model.Attachment, error) {
buf := make([]byte, 1024)
n, _ := util.ReadAtMost(file, buf)
buf = buf[:n]
- if err := upload.Verify(buf, opts.Name, allowedTypes); err != nil {
+ if err := upload.Verify(buf, attach.Name, allowedTypes); err != nil {
return nil, err
}
- return NewAttachment(ctx, opts, io.MultiReader(bytes.NewReader(buf), file), fileSize)
+ return NewAttachment(ctx, attach, io.MultiReader(bytes.NewReader(buf), file), fileSize)
}
diff --git a/services/auth/auth.go b/services/auth/auth.go
index e53ff02dcf..c10872313f 100644
--- a/services/auth/auth.go
+++ b/services/auth/auth.go
@@ -12,12 +12,12 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/webauthn"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
+ gitea_context "code.gitea.io/gitea/services/context"
user_service "code.gitea.io/gitea/services/user"
)
diff --git a/services/auth/signin.go b/services/auth/signin.go
index fafe3ef3c6..e116a088e0 100644
--- a/services/auth/signin.go
+++ b/services/auth/signin.go
@@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/auth/source/smtp"
@@ -87,7 +87,7 @@ func UserSignIn(ctx context.Context, username, password string) (*user_model.Use
}
sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
})
if err != nil {
return nil, nil, err
diff --git a/services/auth/source/oauth2/init.go b/services/auth/source/oauth2/init.go
index 3ad6e307f1..5c25681548 100644
--- a/services/auth/source/oauth2/init.go
+++ b/services/auth/source/oauth2/init.go
@@ -12,8 +12,8 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"github.com/google/uuid"
"github.com/gorilla/sessions"
@@ -66,7 +66,7 @@ func ResetOAuth2(ctx context.Context) error {
// initOAuth2Sources is used to load and register all active OAuth2 providers
func initOAuth2Sources(ctx context.Context) error {
authSources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
LoginType: auth.OAuth2,
})
if err != nil {
diff --git a/services/auth/source/oauth2/providers.go b/services/auth/source/oauth2/providers.go
index f4edb507f2..c3edae4ab6 100644
--- a/services/auth/source/oauth2/providers.go
+++ b/services/auth/source/oauth2/providers.go
@@ -15,8 +15,8 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"github.com/markbates/goth"
)
@@ -59,7 +59,7 @@ func (p *AuthSourceProvider) DisplayName() string {
func (p *AuthSourceProvider) IconHTML(size int) template.HTML {
if p.iconURL != "" {
- img := fmt.Sprintf(` `,
+ img := fmt.Sprintf(` `,
size,
size,
html.EscapeString(p.iconURL), html.EscapeString(p.DisplayName()),
@@ -107,7 +107,7 @@ func CreateProviderFromSource(source *auth.Source) (Provider, error) {
}
// GetOAuth2Providers returns the list of configured OAuth2 providers
-func GetOAuth2Providers(ctx context.Context, isActive util.OptionalBool) ([]Provider, error) {
+func GetOAuth2Providers(ctx context.Context, isActive optional.Option[bool]) ([]Provider, error) {
authSources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
IsActive: isActive,
LoginType: auth.OAuth2,
diff --git a/services/auth/sspi.go b/services/auth/sspi.go
index 8c0fc77a96..64a127e97a 100644
--- a/services/auth/sspi.go
+++ b/services/auth/sspi.go
@@ -14,13 +14,12 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/auth/source/sspi"
+ gitea_context "code.gitea.io/gitea/services/context"
gouuid "github.com/google/uuid"
)
@@ -132,7 +131,7 @@ func (s *SSPI) Verify(req *http.Request, w http.ResponseWriter, store DataStore,
// getConfig retrieves the SSPI configuration from login sources
func (s *SSPI) getConfig(ctx context.Context) (*sspi.Source, error) {
sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
LoginType: auth.SSPI,
})
if err != nil {
diff --git a/modules/context/access_log.go b/services/context/access_log.go
similarity index 100%
rename from modules/context/access_log.go
rename to services/context/access_log.go
diff --git a/modules/context/api.go b/services/context/api.go
similarity index 100%
rename from modules/context/api.go
rename to services/context/api.go
diff --git a/modules/context/api_org.go b/services/context/api_org.go
similarity index 100%
rename from modules/context/api_org.go
rename to services/context/api_org.go
diff --git a/modules/context/api_test.go b/services/context/api_test.go
similarity index 100%
rename from modules/context/api_test.go
rename to services/context/api_test.go
diff --git a/modules/context/base.go b/services/context/base.go
similarity index 91%
rename from modules/context/base.go
rename to services/context/base.go
index fa05850a16..25ff935055 100644
--- a/modules/context/base.go
+++ b/services/context/base.go
@@ -17,8 +17,8 @@ import (
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/translation"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/middleware"
"github.com/go-chi/chi/v5"
@@ -207,17 +207,17 @@ func (b *Base) FormBool(key string) bool {
return v
}
-// FormOptionalBool returns an OptionalBoolTrue or OptionalBoolFalse if the value
-// for the provided key exists in the form else it returns OptionalBoolNone
-func (b *Base) FormOptionalBool(key string) util.OptionalBool {
+// FormOptionalBool returns an optional.Some(true) or optional.Some(false) if the value
+// for the provided key exists in the form else it returns optional.None[bool]()
+func (b *Base) FormOptionalBool(key string) optional.Option[bool] {
value := b.Req.FormValue(key)
if len(value) == 0 {
- return util.OptionalBoolNone
+ return optional.None[bool]()
}
s := b.Req.FormValue(key)
v, _ := strconv.ParseBool(s)
v = v || strings.EqualFold(s, "on")
- return util.OptionalBoolOf(v)
+ return optional.Some(v)
}
func (b *Base) SetFormString(key, value string) {
@@ -256,7 +256,7 @@ func (b *Base) Redirect(location string, status ...int) {
code = status[0]
}
- if strings.Contains(location, "://") || strings.HasPrefix(location, "//") {
+ if httplib.IsRiskyRedirectURL(location) {
// Some browsers (Safari) have buggy behavior for Cookie + Cache + External Redirection, eg: /my-path => https://other/path
// 1. the first request to "/my-path" contains cookie
// 2. some time later, the request to "/my-path" doesn't contain cookie (caused by Prevent web tracking)
@@ -265,6 +265,14 @@ func (b *Base) Redirect(location string, status ...int) {
// So in this case, we should remove the session cookie from the response header
removeSessionCookieHeader(b.Resp)
}
+ // in case the request is made by htmx, have it redirect the browser instead of trying to follow the redirect inside htmx
+ if b.Req.Header.Get("HX-Request") == "true" {
+ b.Resp.Header().Set("HX-Redirect", location)
+ // we have to return a non-redirect status code so XMLHTTPRequest will not immediately follow the redirect
+ // so as to give htmx redirect logic a chance to run
+ b.Status(http.StatusNoContent)
+ return
+ }
http.Redirect(b.Resp, b.Req, location, code)
}
diff --git a/services/context/base_test.go b/services/context/base_test.go
new file mode 100644
index 0000000000..823f20e00b
--- /dev/null
+++ b/services/context/base_test.go
@@ -0,0 +1,47 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package context
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRedirect(t *testing.T) {
+ req, _ := http.NewRequest("GET", "/", nil)
+
+ cases := []struct {
+ url string
+ keep bool
+ }{
+ {"http://test", false},
+ {"https://test", false},
+ {"//test", false},
+ {"/://test", true},
+ {"/test", true},
+ }
+ for _, c := range cases {
+ resp := httptest.NewRecorder()
+ b, cleanup := NewBaseContext(resp, req)
+ resp.Header().Add("Set-Cookie", (&http.Cookie{Name: setting.SessionConfig.CookieName, Value: "dummy"}).String())
+ b.Redirect(c.url)
+ cleanup()
+ has := resp.Header().Get("Set-Cookie") == "i_like_gitea=dummy"
+ assert.Equal(t, c.keep, has, "url = %q", c.url)
+ }
+
+ req, _ = http.NewRequest("GET", "/", nil)
+ resp := httptest.NewRecorder()
+ req.Header.Add("HX-Request", "true")
+ b, cleanup := NewBaseContext(resp, req)
+ b.Redirect("/other")
+ cleanup()
+ assert.Equal(t, "/other", resp.Header().Get("HX-Redirect"))
+ assert.Equal(t, http.StatusNoContent, resp.Code)
+}
diff --git a/modules/context/captcha.go b/services/context/captcha.go
similarity index 100%
rename from modules/context/captcha.go
rename to services/context/captcha.go
diff --git a/modules/context/context.go b/services/context/context.go
similarity index 100%
rename from modules/context/context.go
rename to services/context/context.go
diff --git a/modules/context/context_cookie.go b/services/context/context_cookie.go
similarity index 100%
rename from modules/context/context_cookie.go
rename to services/context/context_cookie.go
diff --git a/modules/context/context_model.go b/services/context/context_model.go
similarity index 100%
rename from modules/context/context_model.go
rename to services/context/context_model.go
diff --git a/modules/context/context_request.go b/services/context/context_request.go
similarity index 100%
rename from modules/context/context_request.go
rename to services/context/context_request.go
diff --git a/modules/context/context_response.go b/services/context/context_response.go
similarity index 94%
rename from modules/context/context_response.go
rename to services/context/context_response.go
index 829bca1f59..372b4cb38b 100644
--- a/modules/context/context_response.go
+++ b/services/context/context_response.go
@@ -6,6 +6,7 @@ package context
import (
"errors"
"fmt"
+ "html/template"
"net"
"net/http"
"net/url"
@@ -104,11 +105,11 @@ func (ctx *Context) JSONTemplate(tmpl base.TplName) {
}
}
-// RenderToString renders the template content to a string
-func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (string, error) {
+// RenderToHTML renders the template content to a HTML string
+func (ctx *Context) RenderToHTML(name base.TplName, data map[string]any) (template.HTML, error) {
var buf strings.Builder
- err := ctx.Render.HTML(&buf, http.StatusOK, string(name), data, ctx.TemplateContext)
- return buf.String(), err
+ err := ctx.Render.HTML(&buf, 0, string(name), data, ctx.TemplateContext)
+ return template.HTML(buf.String()), err
}
// RenderWithErr used for page has form validation but need to prompt error to users.
diff --git a/modules/context/context_template.go b/services/context/context_template.go
similarity index 100%
rename from modules/context/context_template.go
rename to services/context/context_template.go
diff --git a/modules/context/context_test.go b/services/context/context_test.go
similarity index 100%
rename from modules/context/context_test.go
rename to services/context/context_test.go
diff --git a/modules/context/csrf.go b/services/context/csrf.go
similarity index 100%
rename from modules/context/csrf.go
rename to services/context/csrf.go
diff --git a/modules/context/org.go b/services/context/org.go
similarity index 100%
rename from modules/context/org.go
rename to services/context/org.go
diff --git a/modules/context/package.go b/services/context/package.go
similarity index 100%
rename from modules/context/package.go
rename to services/context/package.go
diff --git a/modules/context/pagination.go b/services/context/pagination.go
similarity index 100%
rename from modules/context/pagination.go
rename to services/context/pagination.go
diff --git a/modules/context/permission.go b/services/context/permission.go
similarity index 100%
rename from modules/context/permission.go
rename to services/context/permission.go
diff --git a/modules/context/private.go b/services/context/private.go
similarity index 100%
rename from modules/context/private.go
rename to services/context/private.go
diff --git a/modules/context/repo.go b/services/context/repo.go
similarity index 99%
rename from modules/context/repo.go
rename to services/context/repo.go
index e8ed1134fe..43eeab8098 100644
--- a/modules/context/repo.go
+++ b/services/context/repo.go
@@ -547,7 +547,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
ctx.Data["NumTags"], err = db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{
IncludeDrafts: true,
IncludeTags: true,
- HasSha1: util.OptionalBoolTrue, // only draft releases which are created with existing tags
+ HasSha1: optional.Some(true), // only draft releases which are created with existing tags
RepoID: ctx.Repo.Repository.ID,
})
if err != nil {
@@ -702,7 +702,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
} else {
- ctx.Repo.BranchName, _ = gitRepo.GetDefaultBranch()
+ ctx.Repo.BranchName, _ = gitrepo.GetDefaultBranch(ctx, ctx.Repo.Repository)
if ctx.Repo.BranchName == "" {
// If it still can't get a default branch, fall back to default branch from setting.
// Something might be wrong. Either site admin should fix the repo sync or Gitea should fix a potential bug.
diff --git a/modules/context/response.go b/services/context/response.go
similarity index 100%
rename from modules/context/response.go
rename to services/context/response.go
diff --git a/modules/upload/upload.go b/services/context/upload/upload.go
similarity index 98%
rename from modules/upload/upload.go
rename to services/context/upload/upload.go
index cd10715864..77a7eb9377 100644
--- a/modules/upload/upload.go
+++ b/services/context/upload/upload.go
@@ -11,9 +11,9 @@ import (
"regexp"
"strings"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
// ErrFileTypeForbidden not allowed file type error
diff --git a/modules/upload/upload_test.go b/services/context/upload/upload_test.go
similarity index 100%
rename from modules/upload/upload_test.go
rename to services/context/upload/upload_test.go
diff --git a/services/context/user.go b/services/context/user.go
index 8b2faf3369..4c9cd2928b 100644
--- a/services/context/user.go
+++ b/services/context/user.go
@@ -9,12 +9,11 @@ import (
"strings"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
)
// UserAssignmentWeb returns a middleware to handle context-user assignment for web routes
-func UserAssignmentWeb() func(ctx *context.Context) {
- return func(ctx *context.Context) {
+func UserAssignmentWeb() func(ctx *Context) {
+ return func(ctx *Context) {
errorFn := func(status int, title string, obj any) {
err, ok := obj.(error)
if !ok {
@@ -32,8 +31,8 @@ func UserAssignmentWeb() func(ctx *context.Context) {
}
// UserIDAssignmentAPI returns a middleware to handle context-user assignment for api routes
-func UserIDAssignmentAPI() func(ctx *context.APIContext) {
- return func(ctx *context.APIContext) {
+func UserIDAssignmentAPI() func(ctx *APIContext) {
+ return func(ctx *APIContext) {
userID := ctx.ParamsInt64(":user-id")
if ctx.IsSigned && ctx.Doer.ID == userID {
@@ -53,13 +52,13 @@ func UserIDAssignmentAPI() func(ctx *context.APIContext) {
}
// UserAssignmentAPI returns a middleware to handle context-user assignment for api routes
-func UserAssignmentAPI() func(ctx *context.APIContext) {
- return func(ctx *context.APIContext) {
+func UserAssignmentAPI() func(ctx *APIContext) {
+ return func(ctx *APIContext) {
ctx.ContextUser = userAssignment(ctx.Base, ctx.Doer, ctx.Error)
}
}
-func userAssignment(ctx *context.Base, doer *user_model.User, errCb func(int, string, any)) (contextUser *user_model.User) {
+func userAssignment(ctx *Base, doer *user_model.User, errCb func(int, string, any)) (contextUser *user_model.User) {
username := ctx.Params(":username")
if doer != nil && doer.LowerName == strings.ToLower(username) {
@@ -70,7 +69,7 @@ func userAssignment(ctx *context.Base, doer *user_model.User, errCb func(int, st
if err != nil {
if user_model.IsErrUserNotExist(err) {
if redirectUserID, err := user_model.LookupUserRedirect(ctx, username); err == nil {
- context.RedirectToUser(ctx, username, redirectUserID)
+ RedirectToUser(ctx, username, redirectUserID)
} else if user_model.IsErrUserRedirectNotExist(err) {
errCb(http.StatusNotFound, "GetUserByName", err)
} else {
diff --git a/modules/context/utils.go b/services/context/utils.go
similarity index 100%
rename from modules/context/utils.go
rename to services/context/utils.go
diff --git a/modules/context/xsrf.go b/services/context/xsrf.go
similarity index 100%
rename from modules/context/xsrf.go
rename to services/context/xsrf.go
diff --git a/modules/context/xsrf_test.go b/services/context/xsrf_test.go
similarity index 100%
rename from modules/context/xsrf_test.go
rename to services/context/xsrf_test.go
diff --git a/modules/contexttest/context_tests.go b/services/contexttest/context_tests.go
similarity index 88%
rename from modules/contexttest/context_tests.go
rename to services/contexttest/context_tests.go
index 9ca028bb6e..d3e6de7efe 100644
--- a/modules/contexttest/context_tests.go
+++ b/services/contexttest/context_tests.go
@@ -7,21 +7,23 @@ package contexttest
import (
gocontext "context"
"io"
+ "maps"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
+ "time"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/assert"
@@ -35,13 +37,24 @@ func mockRequest(t *testing.T, reqPath string) *http.Request {
}
requestURL, err := url.Parse(path)
assert.NoError(t, err)
- req := &http.Request{Method: method, URL: requestURL, Form: url.Values{}}
+ req := &http.Request{Method: method, URL: requestURL, Form: maps.Clone(requestURL.Query()), Header: http.Header{}}
req = req.WithContext(middleware.WithContextData(req.Context()))
return req
}
+type MockContextOption struct {
+ Render context.Render
+}
+
// MockContext mock context for unit tests
-func MockContext(t *testing.T, reqPath string) (*context.Context, *httptest.ResponseRecorder) {
+func MockContext(t *testing.T, reqPath string, opts ...MockContextOption) (*context.Context, *httptest.ResponseRecorder) {
+ var opt MockContextOption
+ if len(opts) > 0 {
+ opt = opts[0]
+ }
+ if opt.Render == nil {
+ opt.Render = &MockRender{}
+ }
resp := httptest.NewRecorder()
req := mockRequest(t, reqPath)
base, baseCleanUp := context.NewBaseContext(resp, req)
@@ -49,8 +62,9 @@ func MockContext(t *testing.T, reqPath string) (*context.Context, *httptest.Resp
base.Data = middleware.GetContextData(req.Context())
base.Locale = &translation.MockLocale{}
- ctx := context.NewWebContext(base, &MockRender{}, nil)
-
+ ctx := context.NewWebContext(base, opt.Render, nil)
+ ctx.PageData = map[string]any{}
+ ctx.Data["PageStartTime"] = time.Now()
chiCtx := chi.NewRouteContext()
ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx)
return ctx, resp
diff --git a/services/convert/git_commit.go b/services/convert/git_commit.go
index ed08691c8b..e0efcddbcb 100644
--- a/services/convert/git_commit.go
+++ b/services/convert/git_commit.go
@@ -10,11 +10,11 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- ctx "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
+ ctx "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/gitdiff"
)
diff --git a/services/convert/package.go b/services/convert/package.go
index e90ce8a00f..b5fca21a3c 100644
--- a/services/convert/package.go
+++ b/services/convert/package.go
@@ -35,7 +35,7 @@ func ToPackage(ctx context.Context, pd *packages.PackageDescriptor, doer *user_m
Name: pd.Package.Name,
Version: pd.Version.Version,
CreatedAt: pd.Version.CreatedUnix.AsTime(),
- HTMLURL: pd.FullWebLink(),
+ HTMLURL: pd.VersionHTMLURL(),
}, nil
}
diff --git a/services/forms/admin.go b/services/forms/admin.go
index 4b3cacc606..81276f8f46 100644
--- a/services/forms/admin.go
+++ b/services/forms/admin.go
@@ -6,9 +6,9 @@ package forms
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
"gitea.com/go-chi/binding"
)
@@ -41,6 +41,7 @@ type AdminEditUserForm struct {
Password string `binding:"MaxSize(255)"`
Website string `binding:"ValidUrl;MaxSize(255)"`
Location string `binding:"MaxSize(50)"`
+ Language string `binding:"MaxSize(5)"`
MaxRepoCreation int
Active bool
Admin bool
diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go
index 25acbbb99e..c9f3182b3a 100644
--- a/services/forms/auth_form.go
+++ b/services/forms/auth_form.go
@@ -6,8 +6,8 @@ package forms
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
"gitea.com/go-chi/binding"
)
diff --git a/services/forms/org.go b/services/forms/org.go
index 6e2d787516..3677fcf429 100644
--- a/services/forms/org.go
+++ b/services/forms/org.go
@@ -7,9 +7,9 @@ package forms
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
"gitea.com/go-chi/binding"
)
diff --git a/services/forms/package_form.go b/services/forms/package_form.go
index 2f08dfe9f4..cc940d42d3 100644
--- a/services/forms/package_form.go
+++ b/services/forms/package_form.go
@@ -6,8 +6,8 @@ package forms
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
"gitea.com/go-chi/binding"
)
diff --git a/services/forms/repo_branch_form.go b/services/forms/repo_branch_form.go
index 5deb0ae463..42e6c85c37 100644
--- a/services/forms/repo_branch_form.go
+++ b/services/forms/repo_branch_form.go
@@ -6,8 +6,8 @@ package forms
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
"gitea.com/go-chi/binding"
)
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index da54f4de61..f9ebb6ebaf 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -12,10 +12,10 @@ import (
"code.gitea.io/gitea/models"
issues_model "code.gitea.io/gitea/models/issues"
project_model "code.gitea.io/gitea/models/project"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/webhook"
"gitea.com/go-chi/binding"
diff --git a/services/forms/repo_tag_form.go b/services/forms/repo_tag_form.go
index 4dd99f9e32..0135684737 100644
--- a/services/forms/repo_tag_form.go
+++ b/services/forms/repo_tag_form.go
@@ -6,8 +6,8 @@ package forms
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
"gitea.com/go-chi/binding"
)
diff --git a/services/forms/runner.go b/services/forms/runner.go
index 6d16cfce49..6abfc66fc2 100644
--- a/services/forms/runner.go
+++ b/services/forms/runner.go
@@ -6,8 +6,8 @@ package forms
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
"gitea.com/go-chi/binding"
)
diff --git a/services/forms/user_form.go b/services/forms/user_form.go
index fd4649025f..77316fb13a 100644
--- a/services/forms/user_form.go
+++ b/services/forms/user_form.go
@@ -10,11 +10,11 @@ import (
"strings"
auth_model "code.gitea.io/gitea/models/auth"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/validation"
"code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
"gitea.com/go-chi/binding"
)
diff --git a/services/forms/user_form_auth_openid.go b/services/forms/user_form_auth_openid.go
index d8137a8d13..ca1c77e320 100644
--- a/services/forms/user_form_auth_openid.go
+++ b/services/forms/user_form_auth_openid.go
@@ -6,8 +6,8 @@ package forms
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
"gitea.com/go-chi/binding"
)
diff --git a/services/forms/user_form_hidden_comments.go b/services/forms/user_form_hidden_comments.go
index 03e629a553..c21fddf478 100644
--- a/services/forms/user_form_hidden_comments.go
+++ b/services/forms/user_form_hidden_comments.go
@@ -7,8 +7,8 @@ import (
"math/big"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/context"
)
type hiddenCommentTypeGroupsType map[string][]issues_model.CommentType
diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go
index 715c5bf9b8..2b796181d8 100644
--- a/services/gitdiff/gitdiff.go
+++ b/services/gitdiff/gitdiff.go
@@ -152,7 +152,7 @@ func (d *DiffLine) GetBlobExcerptQuery() string {
// GetExpandDirection gets DiffLineExpandDirection
func (d *DiffLine) GetExpandDirection() DiffLineExpandDirection {
- if d.Type != DiffLineSection || d.SectionInfo == nil || d.SectionInfo.RightIdx-d.SectionInfo.LastRightIdx <= 1 {
+ if d.Type != DiffLineSection || d.SectionInfo == nil || d.SectionInfo.LeftIdx-d.SectionInfo.LastLeftIdx <= 1 || d.SectionInfo.RightIdx-d.SectionInfo.LastRightIdx <= 1 {
return DiffLineExpandNone
}
if d.SectionInfo.LastLeftIdx <= 0 && d.SectionInfo.LastRightIdx <= 0 {
diff --git a/services/lfs/locks.go b/services/lfs/locks.go
index 08d7432656..2a362b1c0d 100644
--- a/services/lfs/locks.go
+++ b/services/lfs/locks.go
@@ -11,12 +11,12 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
lfs_module "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/services/lfs/server.go b/services/lfs/server.go
index 56714120ad..706be0d080 100644
--- a/services/lfs/server.go
+++ b/services/lfs/server.go
@@ -26,12 +26,12 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
lfs_module "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
+ "code.gitea.io/gitea/services/context"
"github.com/golang-jwt/jwt/v5"
)
diff --git a/services/mailer/incoming/incoming_handler.go b/services/mailer/incoming/incoming_handler.go
index 5ce2cd5fd5..dc0b539822 100644
--- a/services/mailer/incoming/incoming_handler.go
+++ b/services/mailer/incoming/incoming_handler.go
@@ -14,9 +14,9 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
attachment_service "code.gitea.io/gitea/services/attachment"
+ "code.gitea.io/gitea/services/context/upload"
issue_service "code.gitea.io/gitea/services/issue"
incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload"
"code.gitea.io/gitea/services/mailer/token"
diff --git a/services/mailer/mail_admin_new_user.go b/services/mailer/mail_admin_new_user.go
index ecf0ddf5fa..aa0571e57c 100644
--- a/services/mailer/mail_admin_new_user.go
+++ b/services/mailer/mail_admin_new_user.go
@@ -55,13 +55,13 @@ func mailNewUser(ctx context.Context, u *user_model.User, lang string, tos []str
subject := locale.TrString("mail.admin.new_user.subject", u.Name)
body := locale.TrString("mail.admin.new_user.text", manageUserURL)
mailMeta := map[string]any{
- "NewUser": u,
- "NewUserUrl": u.HTMLURL(),
- "Subject": subject,
- "Body": body,
- "Language": locale.Language(),
- "Locale": locale,
- "Str2html": templates.Str2html,
+ "NewUser": u,
+ "NewUserUrl": u.HTMLURL(),
+ "Subject": subject,
+ "Body": body,
+ "Language": locale.Language(),
+ "Locale": locale,
+ "SanitizeHTML": templates.SanitizeHTML,
}
var mailBody bytes.Buffer
diff --git a/services/markup/processorhelper.go b/services/markup/processorhelper.go
index 3551f85c46..a4378678a0 100644
--- a/services/markup/processorhelper.go
+++ b/services/markup/processorhelper.go
@@ -7,8 +7,8 @@ import (
"context"
"code.gitea.io/gitea/models/user"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup"
+ gitea_context "code.gitea.io/gitea/services/context"
)
func ProcessorHelper() *markup.ProcessorHelper {
diff --git a/services/markup/processorhelper_test.go b/services/markup/processorhelper_test.go
index ef8f562245..170edae0e0 100644
--- a/services/markup/processorhelper_test.go
+++ b/services/markup/processorhelper_test.go
@@ -12,8 +12,8 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/models/user"
- gitea_context "code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/contexttest"
+ gitea_context "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go
index 99a44dff0f..9baae6d31d 100644
--- a/services/migrations/gitea_uploader.go
+++ b/services/migrations/gitea_uploader.go
@@ -120,7 +120,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
r.DefaultBranch = repo.DefaultBranch
r.Description = repo.Description
- r, err = repo_module.MigrateRepositoryGitData(g.ctx, owner, r, base.MigrateOptions{
+ r, err = repo_service.MigrateRepositoryGitData(g.ctx, owner, r, base.MigrateOptions{
RepoName: g.repoName,
Description: repo.Description,
OriginalURL: repo.OriginalURL,
@@ -483,6 +483,10 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
}
switch cm.Type {
+ case issues_model.CommentTypeReopen:
+ cm.Content = ""
+ case issues_model.CommentTypeClose:
+ cm.Content = ""
case issues_model.CommentTypeAssignees:
if assigneeID, ok := comment.Meta["AssigneeID"].(int); ok {
cm.AssigneeID = int64(assigneeID)
@@ -503,6 +507,8 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
cm.NewRef = fmt.Sprint(comment.Meta["NewRef"])
cm.Content = ""
}
+ case issues_model.CommentTypeMergePull:
+ cm.Content = ""
case issues_model.CommentTypePRScheduledToAutoMerge, issues_model.CommentTypePRUnScheduledToAutoMerge:
cm.Content = ""
default:
diff --git a/services/migrations/gitea_uploader_test.go b/services/migrations/gitea_uploader_test.go
index 33b7c6808f..e98582f31a 100644
--- a/services/migrations/gitea_uploader_test.go
+++ b/services/migrations/gitea_uploader_test.go
@@ -23,9 +23,9 @@ import (
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
- "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
)
@@ -68,14 +68,14 @@ func TestGiteaUploadRepo(t *testing.T) {
milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
RepoID: repo.ID,
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
})
assert.NoError(t, err)
assert.Len(t, milestones, 1)
milestones, err = db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
RepoID: repo.ID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
})
assert.NoError(t, err)
assert.Empty(t, milestones)
@@ -108,7 +108,7 @@ func TestGiteaUploadRepo(t *testing.T) {
issues, err := issues_model.Issues(db.DefaultContext, &issues_model.IssuesOptions{
RepoIDs: []int64{repo.ID},
- IsPull: util.OptionalBoolFalse,
+ IsPull: optional.Some(false),
SortType: "oldest",
})
assert.NoError(t, err)
diff --git a/services/migrations/gitlab.go b/services/migrations/gitlab.go
index 5e49ae6d57..bbc44e958a 100644
--- a/services/migrations/gitlab.go
+++ b/services/migrations/gitlab.go
@@ -517,6 +517,60 @@ func (g *GitlabDownloader) GetComments(commentable base.Commentable) ([]*base.Co
}
page = resp.NextPage
}
+
+ page = 1
+ for {
+ var stateEvents []*gitlab.StateEvent
+ var resp *gitlab.Response
+ var err error
+ if context.IsMergeRequest {
+ stateEvents, resp, err = g.client.ResourceStateEvents.ListMergeStateEvents(g.repoID, int(commentable.GetForeignIndex()), &gitlab.ListStateEventsOptions{
+ ListOptions: gitlab.ListOptions{
+ Page: page,
+ PerPage: g.maxPerPage,
+ },
+ }, nil, gitlab.WithContext(g.ctx))
+ } else {
+ stateEvents, resp, err = g.client.ResourceStateEvents.ListIssueStateEvents(g.repoID, int(commentable.GetForeignIndex()), &gitlab.ListStateEventsOptions{
+ ListOptions: gitlab.ListOptions{
+ Page: page,
+ PerPage: g.maxPerPage,
+ },
+ }, nil, gitlab.WithContext(g.ctx))
+ }
+ if err != nil {
+ return nil, false, fmt.Errorf("error while listing state events: %v %w", g.repoID, err)
+ }
+
+ for _, stateEvent := range stateEvents {
+ comment := &base.Comment{
+ IssueIndex: commentable.GetLocalIndex(),
+ Index: int64(stateEvent.ID),
+ PosterID: int64(stateEvent.User.ID),
+ PosterName: stateEvent.User.Username,
+ Content: "",
+ Created: *stateEvent.CreatedAt,
+ }
+ switch stateEvent.State {
+ case gitlab.ClosedEventType:
+ comment.CommentType = issues_model.CommentTypeClose.String()
+ case gitlab.MergedEventType:
+ comment.CommentType = issues_model.CommentTypeMergePull.String()
+ case gitlab.ReopenedEventType:
+ comment.CommentType = issues_model.CommentTypeReopen.String()
+ default:
+ // Ignore other event types
+ continue
+ }
+ allComments = append(allComments, comment)
+ }
+
+ if resp.NextPage == 0 {
+ break
+ }
+ page = resp.NextPage
+ }
+
return allComments, true, nil
}
diff --git a/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026
new file mode 100644
index 0000000000..81fb1f9e01
--- /dev/null
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026
@@ -0,0 +1,17 @@
+Gitlab-Sv: api-gke-us-east1-c
+Content-Type: application/json
+Cache-Control: max-age=0, private, must-revalidate
+Content-Security-Policy: default-src 'none'
+Etag: W/"8db4917b3be5f4ca0d101a702179b75a"
+X-Content-Type-Options: nosniff
+X-Runtime: 0.150020
+Referrer-Policy: strict-origin-when-cross-origin
+Set-Cookie: _cfuvid=2JDVzeRhKxkwd0xbLccErO2vFlf0KnUzsvPv1ZY4.H4-1710504205506-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+X-Gitlab-Meta: {"correlation_id":"fc467ca540c06233f6a25d0deae604d2","version":"1"}
+Vary: Origin, Accept-Encoding
+X-Frame-Options: SAMEORIGIN
+Strict-Transport-Security: max-age=31536000
+Gitlab-Lb: haproxy-main-01-lb-gprd
+Cf-Cache-Status: MISS
+
+{"id":15578026,"description":"Test repository for testing migration from gitlab to gitea","name":"test_repo","name_with_namespace":"gitea / test_repo","path":"test_repo","path_with_namespace":"gitea/test_repo","created_at":"2019-11-28T08:20:33.019Z","default_branch":"master","tag_list":["migration","test"],"topics":["migration","test"],"ssh_url_to_repo":"git@gitlab.com:gitea/test_repo.git","http_url_to_repo":"https://gitlab.com/gitea/test_repo.git","web_url":"https://gitlab.com/gitea/test_repo","readme_url":"https://gitlab.com/gitea/test_repo/-/blob/master/README.md","forks_count":1,"avatar_url":null,"star_count":0,"last_activity_at":"2020-04-19T19:46:04.527Z","namespace":{"id":3181312,"name":"gitea","path":"gitea","kind":"group","full_path":"gitea","parent_id":null,"avatar_url":"/uploads/-/system/group/avatar/3181312/gitea.png","web_url":"https://gitlab.com/groups/gitea"},"container_registry_image_prefix":"registry.gitlab.com/gitea/test_repo","_links":{"self":"https://gitlab.com/api/v4/projects/15578026","issues":"https://gitlab.com/api/v4/projects/15578026/issues","merge_requests":"https://gitlab.com/api/v4/projects/15578026/merge_requests","repo_branches":"https://gitlab.com/api/v4/projects/15578026/repository/branches","labels":"https://gitlab.com/api/v4/projects/15578026/labels","events":"https://gitlab.com/api/v4/projects/15578026/events","members":"https://gitlab.com/api/v4/projects/15578026/members","cluster_agents":"https://gitlab.com/api/v4/projects/15578026/cluster_agents"},"packages_enabled":true,"empty_repo":false,"archived":false,"visibility":"public","resolve_outdated_diff_discussions":false,"repository_object_format":"sha1","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"container_registry_enabled":true,"service_desk_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"enabled","analytics_access_level":"enabled","container_registry_access_level":"enabled","security_and_compliance_access_level":"private","releases_access_level":"enabled","environments_access_level":"enabled","feature_flags_access_level":"enabled","infrastructure_access_level":"enabled","monitor_access_level":"enabled","model_experiments_access_level":"enabled","model_registry_access_level":"enabled","emails_disabled":false,"emails_enabled":true,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":1241334,"import_status":"none","open_issues_count":0,"description_html":"\u003cp data-sourcepos=\"1:1-1:58\" dir=\"auto\"\u003eTest repository for testing migration from gitlab to gitea\u003c/p\u003e","updated_at":"2024-01-11T01:23:21.057Z","ci_config_path":null,"public_jobs":true,"shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"allow_merge_on_skipped_pipeline":null,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"ff","squash_option":"default_off","enforce_auth_checks_on_uploads":true,"suggestion_commit_message":null,"merge_commit_template":null,"squash_commit_template":null,"issue_branch_template":null,"warn_about_potentially_unwanted_characters":true,"autoclose_referenced_issues":true,"external_authorization_classification_label":"","requirements_enabled":false,"requirements_access_level":"enabled","security_and_compliance_enabled":false,"compliance_frameworks":[],"permissions":{"project_access":null,"group_access":null}}
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_1_award_emoji!page=1&per_page=2 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F1%2Faward_emoji%3Fpage=1&per_page=2
similarity index 76%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_1_award_emoji!page=1&per_page=2
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F1%2Faward_emoji%3Fpage=1&per_page=2
index d8ab294ee1..cbdfdde527 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_1_award_emoji!page=1&per_page=2
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F1%2Faward_emoji%3Fpage=1&per_page=2
@@ -1,29 +1,24 @@
-X-Total-Pages: 1
-Referrer-Policy: strict-origin-when-cross-origin
-Cache-Control: max-age=0, private, must-revalidate
-X-Frame-Options: SAMEORIGIN
-X-Runtime: 0.052748
-X-Total: 2
-Gitlab-Sv: localhost
-Link: ; rel="first", ; rel="last"
-X-Content-Type-Options: nosniff
-X-Gitlab-Meta: {"correlation_id":"22fc215ac386644c9cb8736b652cf702","version":"1"}
-X-Per-Page: 2
-Strict-Transport-Security: max-age=31536000
-Ratelimit-Reset: 1701333947
-Content-Security-Policy: default-src 'none'
-Ratelimit-Remaining: 1991
-Ratelimit-Observed: 9
-X-Next-Page:
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:47 GMT
-Ratelimit-Limit: 2000
-Gitlab-Lb: haproxy-main-43-lb-gprd
-Cf-Cache-Status: MISS
-Etag: W/"69c922434ed11248c864d157eb8eabfc"
-Content-Type: application/json
Vary: Origin, Accept-Encoding
-X-Page: 1
+Gitlab-Sv: api-gke-us-east1-b
+X-Runtime: 0.203565
+Referrer-Policy: strict-origin-when-cross-origin
+X-Frame-Options: SAMEORIGIN
+X-Next-Page:
+X-Gitlab-Meta: {"correlation_id":"9ee8f715be2950b629eff875667dab37","version":"1"}
+X-Total-Pages: 1
+Gitlab-Lb: haproxy-main-57-lb-gprd
+Cf-Cache-Status: MISS
+Content-Type: application/json
+Cache-Control: max-age=0, private, must-revalidate
+X-Content-Type-Options: nosniff
+Strict-Transport-Security: max-age=31536000
+Etag: W/"69c922434ed11248c864d157eb8eabfc"
+X-Per-Page: 2
X-Prev-Page:
-Set-Cookie: _cfuvid=POpffkskz4lvLcv2Fhjp7lF3MsmIOugWDzFGtb3ZUig-1701333887995-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Set-Cookie: _cfuvid=lj07r.PfLt5YP9_Ms5dtsY_JOkTSmeFWB1sd2Z8SLuM-1710504207278-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Content-Security-Policy: default-src 'none'
+Link: ; rel="first", ; rel="last"
+X-Page: 1
+X-Total: 2
[{"id":3009580,"name":"thumbsup","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:43:40.322Z","updated_at":"2019-11-28T08:43:40.322Z","awardable_id":27687675,"awardable_type":"Issue","url":null},{"id":3009585,"name":"open_mouth","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:44:01.902Z","updated_at":"2019-11-28T08:44:01.902Z","awardable_id":27687675,"awardable_type":"Issue","url":null}]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_1_award_emoji!page=2&per_page=2 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F1%2Faward_emoji%3Fpage=2&per_page=2
similarity index 61%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_1_award_emoji!page=2&per_page=2
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F1%2Faward_emoji%3Fpage=2&per_page=2
index 248afeff8f..262bf891ee 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_1_award_emoji!page=2&per_page=2
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F1%2Faward_emoji%3Fpage=2&per_page=2
@@ -1,31 +1,26 @@
-Content-Type: application/json
-Content-Length: 2
-X-Next-Page:
-X-Gitlab-Meta: {"correlation_id":"6b9bc368e2cdc69a1b8e7d2dff7546c5","version":"1"}
-Referrer-Policy: strict-origin-when-cross-origin
-Ratelimit-Reset: 1701333948
-Strict-Transport-Security: max-age=31536000
-X-Per-Page: 2
-X-Total: 2
-Ratelimit-Limit: 2000
-Etag: W/"4f53cda18c2baa0c0354bb5f9a3ecbe5"
Link: ; rel="first", ; rel="last"
-X-Runtime: 0.069944
-Ratelimit-Remaining: 1990
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:48 GMT
-Gitlab-Sv: localhost
-X-Prev-Page:
-X-Total-Pages: 1
-Ratelimit-Observed: 10
-Gitlab-Lb: haproxy-main-17-lb-gprd
-Content-Security-Policy: default-src 'none'
X-Content-Type-Options: nosniff
-X-Frame-Options: SAMEORIGIN
-Accept-Ranges: bytes
-Cache-Control: max-age=0, private, must-revalidate
-Vary: Origin, Accept-Encoding
X-Page: 2
+X-Per-Page: 2
+Gitlab-Sv: api-gke-us-east1-b
+Content-Type: application/json
+Etag: W/"4f53cda18c2baa0c0354bb5f9a3ecbe5"
+X-Frame-Options: SAMEORIGIN
+X-Gitlab-Meta: {"correlation_id":"05db2c172a3be5ea9e65494882e77167","version":"1"}
+X-Next-Page:
+Set-Cookie: _cfuvid=UDvTcjnLBRvcY_axm9MwnCJ0PmPtOKE9vnIQ4uoOUGE-1710504207498-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+X-Runtime: 0.061429
+X-Total-Pages: 1
+Strict-Transport-Security: max-age=31536000
+Gitlab-Lb: haproxy-main-51-lb-gprd
+Accept-Ranges: bytes
+Content-Length: 2
Cf-Cache-Status: MISS
-Set-Cookie: _cfuvid=vcfsxezcg_2Kdh8xD5coOU_uxQIH1in.6BsRttrSIYg-1701333888217-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Cache-Control: max-age=0, private, must-revalidate
+Content-Security-Policy: default-src 'none'
+Vary: Origin, Accept-Encoding
+X-Prev-Page:
+X-Total: 2
+Referrer-Policy: strict-origin-when-cross-origin
[]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji!page=1&per_page=2 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Faward_emoji%3Fpage=1&per_page=2
similarity index 77%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji!page=1&per_page=2
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Faward_emoji%3Fpage=1&per_page=2
index 70c50c8fed..52822db98b 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji!page=1&per_page=2
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Faward_emoji%3Fpage=1&per_page=2
@@ -1,29 +1,24 @@
-Cf-Cache-Status: MISS
-Etag: W/"5fdbcbf64f34ba0e74ce9dd8d6e0efe3"
-X-Content-Type-Options: nosniff
-X-Per-Page: 2
-Ratelimit-Limit: 2000
-Set-Cookie: _cfuvid=NLtUZdQlWvWiXr4L8Zfc555FowZOCxJlA0pAOAEkNvg-1701333888445-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-X-Runtime: 0.075612
-Ratelimit-Reset: 1701333948
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:48 GMT
-X-Page: 1
-X-Prev-Page:
-X-Total-Pages: 3
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
-Content-Security-Policy: default-src 'none'
-Ratelimit-Observed: 11
-Gitlab-Lb: haproxy-main-09-lb-gprd
-Gitlab-Sv: localhost
-Ratelimit-Remaining: 1989
-Cache-Control: max-age=0, private, must-revalidate
-Link: ; rel="next", ; rel="first", ; rel="last"
-X-Gitlab-Meta: {"correlation_id":"a69709cf552479bc5f5f3e4e1f808790","version":"1"}
-X-Next-Page: 2
-Strict-Transport-Security: max-age=31536000
+Gitlab-Lb: haproxy-main-40-lb-gprd
Content-Type: application/json
+Strict-Transport-Security: max-age=31536000
Vary: Origin, Accept-Encoding
+X-Page: 1
+X-Runtime: 0.078016
+Link: ; rel="next", ; rel="first", ; rel="last"
+X-Prev-Page:
X-Total: 6
+X-Total-Pages: 3
+X-Content-Type-Options: nosniff
+X-Next-Page: 2
+Gitlab-Sv: api-gke-us-east1-c
+Cache-Control: max-age=0, private, must-revalidate
+Cf-Cache-Status: MISS
+Set-Cookie: _cfuvid=YByIjysnuUyVymulLPR72WWURJsjsdM2aiUwKWAGtZI-1710504207733-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Content-Security-Policy: default-src 'none'
+Etag: W/"5fdbcbf64f34ba0e74ce9dd8d6e0efe3"
+X-Gitlab-Meta: {"correlation_id":"2c82a2ec8ad8bdd3c0d2adb0e208f69a","version":"1"}
+X-Per-Page: 2
[{"id":3009627,"name":"thumbsup","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:46:42.657Z","updated_at":"2019-11-28T08:46:42.657Z","awardable_id":27687706,"awardable_type":"Issue","url":null},{"id":3009628,"name":"thumbsdown","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:46:43.471Z","updated_at":"2019-11-28T08:46:43.471Z","awardable_id":27687706,"awardable_type":"Issue","url":null}]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji!page=2&per_page=2 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Faward_emoji%3Fpage=2&per_page=2
similarity index 78%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji!page=2&per_page=2
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Faward_emoji%3Fpage=2&per_page=2
index 1014d9dbc5..2ebb34db88 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji!page=2&per_page=2
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Faward_emoji%3Fpage=2&per_page=2
@@ -1,29 +1,24 @@
-Ratelimit-Limit: 2000
-X-Content-Type-Options: nosniff
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:48 GMT
-X-Total: 6
-Set-Cookie: _cfuvid=E5GyZy0rB2zrRH0ewMyrJd1wBrt7A2sGNmOHTiWwbYk-1701333888703-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-X-Next-Page: 3
-X-Page: 2
-Link: ; rel="prev", ; rel="next", ; rel="first", ; rel="last"
-X-Gitlab-Meta: {"correlation_id":"41e59fb2e78f5e68518b81bd4ff3bb1b","version":"1"}
+X-Gitlab-Meta: {"correlation_id":"3f684de509b846cafc057f0e2982ad76","version":"1"}
X-Prev-Page: 1
-Cf-Cache-Status: MISS
-Referrer-Policy: strict-origin-when-cross-origin
-Gitlab-Lb: haproxy-main-26-lb-gprd
-Cache-Control: max-age=0, private, must-revalidate
-Etag: W/"d16c513b32212d9286fce6f53340c1cf"
+X-Total-Pages: 3
+Gitlab-Lb: haproxy-main-24-lb-gprd
+Gitlab-Sv: api-gke-us-east1-b
Vary: Origin, Accept-Encoding
-X-Per-Page: 2
+Set-Cookie: _cfuvid=Bs.X45qZvylPDZxkoXQ0YQS72rXFkViMP2IaqBS6C0s-1710504207991-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Etag: W/"d16c513b32212d9286fce6f53340c1cf"
+X-Content-Type-Options: nosniff
+Cache-Control: max-age=0, private, must-revalidate
+X-Next-Page: 3
Strict-Transport-Security: max-age=31536000
-Ratelimit-Reset: 1701333948
+Referrer-Policy: strict-origin-when-cross-origin
Content-Type: application/json
-X-Runtime: 0.105758
-Ratelimit-Remaining: 1988
Content-Security-Policy: default-src 'none'
X-Frame-Options: SAMEORIGIN
-Gitlab-Sv: localhost
-X-Total-Pages: 3
-Ratelimit-Observed: 12
+X-Runtime: 0.098833
+Cf-Cache-Status: MISS
+Link: ; rel="prev", ; rel="next", ; rel="first", ; rel="last"
+X-Page: 2
+X-Per-Page: 2
+X-Total: 6
[{"id":3009632,"name":"laughing","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:47:14.381Z","updated_at":"2019-11-28T08:47:14.381Z","awardable_id":27687706,"awardable_type":"Issue","url":null},{"id":3009634,"name":"tada","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:47:18.254Z","updated_at":"2019-11-28T08:47:18.254Z","awardable_id":27687706,"awardable_type":"Issue","url":null}]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji!page=3&per_page=2 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Faward_emoji%3Fpage=3&per_page=2
similarity index 77%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji!page=3&per_page=2
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Faward_emoji%3Fpage=3&per_page=2
index 9c42bfb9bf..23da417c01 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji!page=3&per_page=2
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Faward_emoji%3Fpage=3&per_page=2
@@ -1,29 +1,24 @@
-Content-Type: application/json
-X-Content-Type-Options: nosniff
+X-Total-Pages: 3
+Content-Security-Policy: default-src 'none'
+Vary: Origin, Accept-Encoding
+X-Page: 3
+Strict-Transport-Security: max-age=31536000
+Etag: W/"165d37bf09a54bb31f4619cca8722cb4"
+X-Next-Page:
X-Frame-Options: SAMEORIGIN
+X-Prev-Page: 2
+Cf-Cache-Status: MISS
+Set-Cookie: _cfuvid=HHUVNinfPq8fL7PXFgbDm8yTm6pwWCXctd6JjWwfzY4-1710504208221-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+X-Runtime: 0.071448
+X-Total: 6
+Cache-Control: max-age=0, private, must-revalidate
+X-Gitlab-Meta: {"correlation_id":"886dbf65fe0de14ba39622416ae0ca1b","version":"1"}
Referrer-Policy: strict-origin-when-cross-origin
Link: ; rel="prev", ; rel="first", ; rel="last"
-Cf-Cache-Status: MISS
-Etag: W/"165d37bf09a54bb31f4619cca8722cb4"
-Ratelimit-Observed: 13
-Ratelimit-Limit: 2000
-Set-Cookie: _cfuvid=W6F2uJSFkB3Iyl27_xtklVvP4Z_nSsjPqytClHPW9H8-1701333888913-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-X-Page: 3
-X-Total-Pages: 3
-Strict-Transport-Security: max-age=31536000
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:48 GMT
-Cache-Control: max-age=0, private, must-revalidate
-Vary: Origin, Accept-Encoding
-X-Gitlab-Meta: {"correlation_id":"b805751aeb6f562dff684cec34dfdc0b","version":"1"}
+X-Content-Type-Options: nosniff
+Content-Type: application/json
+Gitlab-Lb: haproxy-main-50-lb-gprd
+Gitlab-Sv: api-gke-us-east1-d
X-Per-Page: 2
-X-Prev-Page: 2
-X-Runtime: 0.061943
-Gitlab-Lb: haproxy-main-11-lb-gprd
-Gitlab-Sv: localhost
-X-Total: 6
-Ratelimit-Remaining: 1987
-Content-Security-Policy: default-src 'none'
-X-Next-Page:
-Ratelimit-Reset: 1701333948
[{"id":3009636,"name":"confused","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:47:27.248Z","updated_at":"2019-11-28T08:47:27.248Z","awardable_id":27687706,"awardable_type":"Issue","url":null},{"id":3009640,"name":"hearts","user":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:47:33.059Z","updated_at":"2019-11-28T08:47:33.059Z","awardable_id":27687706,"awardable_type":"Issue","url":null}]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji!page=4&per_page=2 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Faward_emoji%3Fpage=4&per_page=2
similarity index 61%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji!page=4&per_page=2
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Faward_emoji%3Fpage=4&per_page=2
index e550e4ad19..086cfcd3b5 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_award_emoji!page=4&per_page=2
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Faward_emoji%3Fpage=4&per_page=2
@@ -1,31 +1,26 @@
-Ratelimit-Remaining: 1986
-Accept-Ranges: bytes
-Set-Cookie: _cfuvid=DZQIMINjFohqKKjkWnojkq2xuUaqb42YEQg3BZXe68w-1701333889148-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-Strict-Transport-Security: max-age=31536000
-X-Prev-Page:
-Ratelimit-Reset: 1701333949
-Ratelimit-Limit: 2000
-Etag: W/"4f53cda18c2baa0c0354bb5f9a3ecbe5"
-Cache-Control: max-age=0, private, must-revalidate
-X-Next-Page:
-X-Page: 4
-Referrer-Policy: strict-origin-when-cross-origin
-Gitlab-Sv: localhost
-Content-Type: application/json
-X-Content-Type-Options: nosniff
-Link: ; rel="first", ; rel="last"
-Vary: Origin, Accept-Encoding
-Content-Security-Policy: default-src 'none'
-X-Runtime: 0.081728
-X-Total: 6
-Content-Length: 2
-X-Frame-Options: SAMEORIGIN
-X-Gitlab-Meta: {"correlation_id":"588184d2d9ad2c4a7e7c00a51575091c","version":"1"}
-Cf-Cache-Status: MISS
X-Total-Pages: 3
-Ratelimit-Observed: 14
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:49 GMT
-Gitlab-Lb: haproxy-main-34-lb-gprd
+Gitlab-Lb: haproxy-main-52-lb-gprd
+X-Next-Page:
+Content-Security-Policy: default-src 'none'
+Content-Type: application/json
+Content-Length: 2
+X-Content-Type-Options: nosniff
X-Per-Page: 2
+X-Runtime: 0.083061
+Referrer-Policy: strict-origin-when-cross-origin
+X-Gitlab-Meta: {"correlation_id":"561369f96102c1a6ab8acd558d16a4d0","version":"1"}
+X-Prev-Page:
+Vary: Origin, Accept-Encoding
+X-Frame-Options: SAMEORIGIN
+X-Total: 6
+Cache-Control: max-age=0, private, must-revalidate
+Link: ; rel="first", ; rel="last"
+Gitlab-Sv: api-gke-us-east1-c
+Accept-Ranges: bytes
+Strict-Transport-Security: max-age=31536000
+Cf-Cache-Status: MISS
+Set-Cookie: _cfuvid=GkrvFxTx5xbwrDM0Jz5hSAycmMJcwb02y6n04i5gv2s-1710504208464-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Etag: W/"4f53cda18c2baa0c0354bb5f9a3ecbe5"
+X-Page: 4
[]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_discussions!page=1&per_page=100 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Fdiscussions%3Fpage=1&per_page=100
similarity index 87%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_discussions!page=1&per_page=100
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Fdiscussions%3Fpage=1&per_page=100
index d74fc325a3..6b62a85016 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues_2_discussions!page=1&per_page=100
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Fdiscussions%3Fpage=1&per_page=100
@@ -1,29 +1,24 @@
-X-Runtime: 0.173849
-Content-Security-Policy: default-src 'none'
-X-Per-Page: 100
-X-Prev-Page:
-X-Total: 4
-Ratelimit-Observed: 15
-X-Page: 1
-Ratelimit-Remaining: 1985
-Cache-Control: max-age=0, private, must-revalidate
-X-Gitlab-Meta: {"correlation_id":"1d2b49b1b17e0c2d58c800a1b6c7eca3","version":"1"}
-X-Next-Page:
-Referrer-Policy: strict-origin-when-cross-origin
-Cf-Cache-Status: MISS
-Content-Type: application/json
-Etag: W/"bcc91e8a7b2eac98b4d96ae791e0649d"
-Vary: Origin, Accept-Encoding
-X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=31536000
-Ratelimit-Reset: 1701333949
-Gitlab-Lb: haproxy-main-08-lb-gprd
-Gitlab-Sv: localhost
+Gitlab-Lb: haproxy-main-32-lb-gprd
+Content-Type: application/json
Link: ; rel="first", ; rel="last"
-X-Content-Type-Options: nosniff
+Set-Cookie: _cfuvid=CZZEZqJQZ97MpqkqjenLKOUdtc5tMbwPjVBKat9VrFo-1710504208832-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Cache-Control: max-age=0, private, must-revalidate
+Referrer-Policy: strict-origin-when-cross-origin
+Gitlab-Sv: gke-cny-api
X-Total-Pages: 1
-Ratelimit-Limit: 2000
-Set-Cookie: _cfuvid=yj.PGr9ftsz1kNpgtsmQhAcGpdMnklLE.NQ9h71hm5Q-1701333889475-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:49 GMT
+X-Page: 1
+X-Per-Page: 100
+X-Total: 4
+X-Content-Type-Options: nosniff
+X-Runtime: 0.215193
+X-Frame-Options: SAMEORIGIN
+Vary: Origin, Accept-Encoding
+X-Gitlab-Meta: {"correlation_id":"d84b389e2f5604d766104c7236dbfdf8","version":"1"}
+X-Next-Page:
+Content-Security-Policy: default-src 'none'
+Etag: W/"bcc91e8a7b2eac98b4d96ae791e0649d"
+X-Prev-Page:
+Cf-Cache-Status: MISS
[{"id":"617967369d98d8b73b6105a40318fe839f931a24","individual_note":true,"notes":[{"id":251637434,"type":null,"body":"This is a comment","attachment":null,"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:44:52.501Z","updated_at":"2019-11-28T08:44:52.501Z","system":false,"noteable_id":27687706,"noteable_type":"Issue","project_id":15578026,"resolvable":false,"confidential":false,"internal":false,"noteable_iid":2,"commands_changes":{}}]},{"id":"b92d74daee411a17d844041bcd3c267ade58f680","individual_note":true,"notes":[{"id":251637528,"type":null,"body":"changed milestone to %2","attachment":null,"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:45:02.329Z","updated_at":"2019-11-28T08:45:02.335Z","system":true,"noteable_id":27687706,"noteable_type":"Issue","project_id":15578026,"resolvable":false,"confidential":false,"internal":false,"noteable_iid":2,"commands_changes":{}}]},{"id":"6010f567d2b58758ef618070372c97891ac75349","individual_note":true,"notes":[{"id":251637892,"type":null,"body":"closed","attachment":null,"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:45:45.007Z","updated_at":"2019-11-28T08:45:45.010Z","system":true,"noteable_id":27687706,"noteable_type":"Issue","project_id":15578026,"resolvable":false,"confidential":false,"internal":false,"noteable_iid":2,"commands_changes":{}}]},{"id":"632d0cbfd6a1a08f38aaf9ef7715116f4b188ebb","individual_note":true,"notes":[{"id":251637999,"type":null,"body":"A second comment","attachment":null,"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"created_at":"2019-11-28T08:45:53.501Z","updated_at":"2019-11-28T08:45:53.501Z","system":false,"noteable_id":27687706,"noteable_type":"Issue","project_id":15578026,"resolvable":false,"confidential":false,"internal":false,"noteable_iid":2,"commands_changes":{}}]}]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Fresource_state_events%3Fpage=1&per_page=100 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Fresource_state_events%3Fpage=1&per_page=100
new file mode 100644
index 0000000000..33dce623cf
--- /dev/null
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%2F2%2Fresource_state_events%3Fpage=1&per_page=100
@@ -0,0 +1,26 @@
+Cache-Control: max-age=0, private, must-revalidate
+X-Gitlab-Meta: {"correlation_id":"9564d6f62bbab2cb1f60ca66015e3840","version":"1"}
+X-Runtime: 0.091327
+X-Per-Page: 100
+X-Total: 0
+X-Total-Pages: 1
+Gitlab-Sv: api-gke-us-east1-d
+Cf-Cache-Status: MISS
+X-Prev-Page:
+Content-Length: 2
+Content-Security-Policy: default-src 'none'
+X-Content-Type-Options: nosniff
+X-Frame-Options: SAMEORIGIN
+X-Next-Page:
+Link: ; rel="first", ; rel="last"
+Vary: Origin, Accept-Encoding
+Set-Cookie: _cfuvid=JAECWgzRO1L40L3GhX4c7HSSpyYna2z1sybaZdKrJ18-1710504209104-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Strict-Transport-Security: max-age=31536000
+Referrer-Policy: strict-origin-when-cross-origin
+Gitlab-Lb: haproxy-main-11-lb-gprd
+Content-Type: application/json
+Etag: W/"4f53cda18c2baa0c0354bb5f9a3ecbe5"
+X-Page: 1
+Accept-Ranges: bytes
+
+[]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues!page=1&per_page=2&sort=asc&state=all b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%3Fpage=1&per_page=2&sort=asc&state=all
similarity index 91%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues!page=1&per_page=2&sort=asc&state=all
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%3Fpage=1&per_page=2&sort=asc&state=all
index 331a82720c..ff6128e8ac 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_issues!page=1&per_page=2&sort=asc&state=all
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fissues%3Fpage=1&per_page=2&sort=asc&state=all
@@ -1,29 +1,24 @@
-Cache-Control: max-age=0, private, must-revalidate
-Vary: Origin, Accept-Encoding
-Gitlab-Lb: haproxy-main-24-lb-gprd
-Content-Type: application/json
-X-Per-Page: 2
-X-Prev-Page:
-Set-Cookie: _cfuvid=N.nfy5eIdFH5lXhsnMyEbeBkoxabcl1SVeyyP0_NrdE-1701333887790-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
Content-Security-Policy: default-src 'none'
-X-Content-Type-Options: nosniff
-X-Next-Page:
-X-Total-Pages: 1
Etag: W/"4c0531a3595f741f229f5a105e013b95"
-X-Gitlab-Meta: {"correlation_id":"b2eca136986f016d946685fb99287f1c","version":"1"}
-Ratelimit-Observed: 8
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:47 GMT
-X-Frame-Options: SAMEORIGIN
-Referrer-Policy: strict-origin-when-cross-origin
-Ratelimit-Remaining: 1992
-Gitlab-Sv: localhost
+Link: ; rel="first", ; rel="last"
+X-Next-Page:
Cf-Cache-Status: MISS
Strict-Transport-Security: max-age=31536000
-Ratelimit-Limit: 2000
+X-Content-Type-Options: nosniff
X-Total: 2
+X-Total-Pages: 1
+Gitlab-Lb: haproxy-main-16-lb-gprd
+Cache-Control: max-age=0, private, must-revalidate
+Vary: Origin, Accept-Encoding
+X-Runtime: 0.143514
+Gitlab-Sv: api-gke-us-east1-c
+X-Gitlab-Meta: {"correlation_id":"6e8b9d619f3148fd839ba0c5f6747df9","version":"1"}
X-Page: 1
-X-Runtime: 0.178587
-Ratelimit-Reset: 1701333947
-Link: ; rel="first", ; rel="last"
+Content-Type: application/json
+X-Per-Page: 2
+Referrer-Policy: strict-origin-when-cross-origin
+X-Frame-Options: SAMEORIGIN
+X-Prev-Page:
+Set-Cookie: _cfuvid=9pOTnEAVQzMgmNMabGvRXD3ad16MkUCZTAQQameWnO8-1710504206909-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
[{"id":27687675,"iid":1,"project_id":15578026,"title":"Please add an animated gif icon to the merge button","description":"I just want the merge button to hurt my eyes a little. :stuck_out_tongue_closed_eyes:","state":"closed","created_at":"2019-11-28T08:43:35.459Z","updated_at":"2019-11-28T08:46:23.304Z","closed_at":"2019-11-28T08:46:23.275Z","closed_by":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"labels":["bug","discussion"],"milestone":{"id":1082926,"iid":1,"project_id":15578026,"title":"1.0.0","description":"","state":"closed","created_at":"2019-11-28T08:42:30.301Z","updated_at":"2019-11-28T15:57:52.401Z","due_date":null,"start_date":null,"expired":false,"web_url":"https://gitlab.com/gitea/test_repo/-/milestones/1"},"assignees":[],"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"type":"ISSUE","assignee":null,"user_notes_count":0,"merge_requests_count":0,"upvotes":1,"downvotes":0,"due_date":null,"confidential":false,"discussion_locked":null,"issue_type":"issue","web_url":"https://gitlab.com/gitea/test_repo/-/issues/1","time_stats":{"time_estimate":0,"total_time_spent":0,"human_time_estimate":null,"human_total_time_spent":null},"task_completion_status":{"count":0,"completed_count":0},"blocking_issues_count":0,"has_tasks":true,"task_status":"0 of 0 checklist items completed","_links":{"self":"https://gitlab.com/api/v4/projects/15578026/issues/1","notes":"https://gitlab.com/api/v4/projects/15578026/issues/1/notes","award_emoji":"https://gitlab.com/api/v4/projects/15578026/issues/1/award_emoji","project":"https://gitlab.com/api/v4/projects/15578026","closed_as_duplicate_of":null},"references":{"short":"#1","relative":"#1","full":"gitea/test_repo#1"},"severity":"UNKNOWN","moved_to_id":null,"service_desk_reply_to":null},{"id":27687706,"iid":2,"project_id":15578026,"title":"Test issue","description":"This is test issue 2, do not touch!","state":"closed","created_at":"2019-11-28T08:44:46.277Z","updated_at":"2019-11-28T08:45:44.987Z","closed_at":"2019-11-28T08:45:44.959Z","closed_by":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"labels":["duplicate"],"milestone":{"id":1082927,"iid":2,"project_id":15578026,"title":"1.1.0","description":"","state":"active","created_at":"2019-11-28T08:42:44.575Z","updated_at":"2019-11-28T08:42:44.575Z","due_date":null,"start_date":null,"expired":false,"web_url":"https://gitlab.com/gitea/test_repo/-/milestones/2"},"assignees":[],"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"type":"ISSUE","assignee":null,"user_notes_count":2,"merge_requests_count":0,"upvotes":1,"downvotes":1,"due_date":null,"confidential":false,"discussion_locked":null,"issue_type":"issue","web_url":"https://gitlab.com/gitea/test_repo/-/issues/2","time_stats":{"time_estimate":0,"total_time_spent":0,"human_time_estimate":null,"human_total_time_spent":null},"task_completion_status":{"count":0,"completed_count":0},"blocking_issues_count":0,"has_tasks":true,"task_status":"0 of 0 checklist items completed","_links":{"self":"https://gitlab.com/api/v4/projects/15578026/issues/2","notes":"https://gitlab.com/api/v4/projects/15578026/issues/2/notes","award_emoji":"https://gitlab.com/api/v4/projects/15578026/issues/2/award_emoji","project":"https://gitlab.com/api/v4/projects/15578026","closed_as_duplicate_of":null},"references":{"short":"#2","relative":"#2","full":"gitea/test_repo#2"},"severity":"UNKNOWN","moved_to_id":null,"service_desk_reply_to":null}]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_labels!page=1&per_page=100 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Flabels%3Fpage=1&per_page=100
similarity index 83%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_labels!page=1&per_page=100
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Flabels%3Fpage=1&per_page=100
index c79889fbf4..95924923d1 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_labels!page=1&per_page=100
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Flabels%3Fpage=1&per_page=100
@@ -1,29 +1,24 @@
-Cache-Control: max-age=0, private, must-revalidate
-Strict-Transport-Security: max-age=31536000
-Gitlab-Lb: haproxy-main-28-lb-gprd
-Link: ; rel="first", ; rel="last"
X-Per-Page: 100
-X-Runtime: 0.149464
-X-Total: 9
-Ratelimit-Observed: 6
-Gitlab-Sv: localhost
-X-Frame-Options: SAMEORIGIN
-X-Gitlab-Meta: {"correlation_id":"2ccddf20767704a98ed6b582db3b103e","version":"1"}
-X-Next-Page:
X-Prev-Page:
-X-Total-Pages: 1
-Ratelimit-Remaining: 1994
-Etag: W/"5a3fb9bc7b1018070943f4aa1353f8b6"
-Ratelimit-Limit: 2000
-X-Page: 1
+X-Total: 9
Content-Type: application/json
-Vary: Origin, Accept-Encoding
-Set-Cookie: _cfuvid=geNpLvH8Cv5XeYfUVwtpaazw43v9lCcqHE.vyXGk3kU-1701333887126-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+X-Next-Page:
X-Content-Type-Options: nosniff
-Ratelimit-Reset: 1701333947
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:47 GMT
-Content-Security-Policy: default-src 'none'
-Referrer-Policy: strict-origin-when-cross-origin
+Gitlab-Lb: haproxy-main-56-lb-gprd
Cf-Cache-Status: MISS
+Set-Cookie: _cfuvid=5K2rwnMRyftEWt3OXSN3FeV8T9nf3Cgb20WFj.p4hyw-1710504206334-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Cache-Control: max-age=0, private, must-revalidate
+X-Runtime: 0.424823
+Strict-Transport-Security: max-age=31536000
+Link: ; rel="first", ; rel="last"
+Content-Security-Policy: default-src 'none'
+Vary: Origin, Accept-Encoding
+X-Gitlab-Meta: {"correlation_id":"b02b63b76c2351a971670a8db9c57af9","version":"1"}
+X-Page: 1
+X-Total-Pages: 1
+Etag: W/"5a3fb9bc7b1018070943f4aa1353f8b6"
+X-Frame-Options: SAMEORIGIN
+Referrer-Policy: strict-origin-when-cross-origin
+Gitlab-Sv: api-gke-us-east1-d
[{"id":12959095,"name":"bug","description":null,"description_html":"","text_color":"#FFFFFF","color":"#d9534f","subscribed":false,"priority":null,"is_project_label":true},{"id":12959097,"name":"confirmed","description":null,"description_html":"","text_color":"#FFFFFF","color":"#d9534f","subscribed":false,"priority":null,"is_project_label":true},{"id":12959096,"name":"critical","description":null,"description_html":"","text_color":"#FFFFFF","color":"#d9534f","subscribed":false,"priority":null,"is_project_label":true},{"id":12959100,"name":"discussion","description":null,"description_html":"","text_color":"#FFFFFF","color":"#428bca","subscribed":false,"priority":null,"is_project_label":true},{"id":12959098,"name":"documentation","description":null,"description_html":"","text_color":"#1F1E24","color":"#f0ad4e","subscribed":false,"priority":null,"is_project_label":true},{"id":12959554,"name":"duplicate","description":null,"description_html":"","text_color":"#FFFFFF","color":"#7F8C8D","subscribed":false,"priority":null,"is_project_label":true},{"id":12959102,"name":"enhancement","description":null,"description_html":"","text_color":"#FFFFFF","color":"#5cb85c","subscribed":false,"priority":null,"is_project_label":true},{"id":12959101,"name":"suggestion","description":null,"description_html":"","text_color":"#FFFFFF","color":"#428bca","subscribed":false,"priority":null,"is_project_label":true},{"id":12959099,"name":"support","description":null,"description_html":"","text_color":"#1F1E24","color":"#f0ad4e","subscribed":false,"priority":null,"is_project_label":true}]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_1_approvals b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F1%2Fapprovals
similarity index 57%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_1_approvals
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F1%2Fapprovals
index 80df508bc3..6a16667f83 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_1_approvals
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F1%2Fapprovals
@@ -1,22 +1,17 @@
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:51 GMT
-Ratelimit-Limit: 2000
-Content-Security-Policy: default-src 'none'
-X-Gitlab-Meta: {"correlation_id":"0a1a7339f3527e175a537afe058a6ac7","version":"1"}
-Ratelimit-Observed: 21
+Gitlab-Sv: api-gke-us-east1-c
+Set-Cookie: _cfuvid=zeoNBfBKfrdVGUvp0nfh4oigIhB1U14XXmzniKufB0A-1710504211497-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+X-Runtime: 0.137677
Cache-Control: max-age=0, private, must-revalidate
-Gitlab-Lb: haproxy-main-38-lb-gprd
+Vary: Origin, Accept-Encoding
X-Frame-Options: SAMEORIGIN
+X-Gitlab-Meta: {"correlation_id":"8585fe858d5623c4d3d1b77cfcdad845","version":"1"}
+Content-Security-Policy: default-src 'none'
+Etag: W/"632b3d0f41fe1650d82b84feaa7b125d"
Strict-Transport-Security: max-age=31536000
Cf-Cache-Status: MISS
Content-Type: application/json
-Etag: W/"19aa54b7d4531bd5ab98282e0b772f20"
X-Content-Type-Options: nosniff
-Set-Cookie: _cfuvid=J2edW64FLja6v0MZCh5tVbLYO42.VvIsTqQ.uj1Gr_k-1701333891171-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-Ratelimit-Reset: 1701333951
-Gitlab-Sv: localhost
-Vary: Origin, Accept-Encoding
-Ratelimit-Remaining: 1979
-X-Runtime: 0.155712
Referrer-Policy: strict-origin-when-cross-origin
+Gitlab-Lb: haproxy-main-55-lb-gprd
-{"id":43486906,"iid":1,"project_id":15578026,"title":"Update README.md","description":"add warning to readme","state":"merged","created_at":"2019-11-28T08:54:41.034Z","updated_at":"2019-11-28T16:02:08.377Z","merge_status":"can_be_merged","approved":true,"approvals_required":0,"approvals_left":0,"require_password_to_approve":false,"approved_by":[{"user":{"id":527793,"username":"axifive","name":"Alexey Terentyev","state":"active","locked":false,"avatar_url":"https://secure.gravatar.com/avatar/06683cd6b2e2c2ce0ab00fb80cc0729f?s=80\u0026d=identicon","web_url":"https://gitlab.com/axifive"}},{"user":{"id":4102996,"username":"zeripath","name":"zeripath","state":"active","locked":false,"avatar_url":"https://secure.gravatar.com/avatar/1ae18535c2b1aed798da090448997248?s=80\u0026d=identicon","web_url":"https://gitlab.com/zeripath"}}],"suggested_approvers":[],"approvers":[],"approver_groups":[],"user_has_approved":false,"user_can_approve":false,"approval_rules_left":[],"has_approval_rules":true,"merge_request_approvers_available":false,"multiple_approval_rules_available":false,"invalid_approvers_rules":[]}
\ No newline at end of file
+{"id":43486906,"iid":1,"project_id":15578026,"title":"Update README.md","description":"add warning to readme","state":"merged","created_at":"2019-11-28T08:54:41.034Z","updated_at":"2019-11-28T16:02:08.377Z","merge_status":"can_be_merged","approved":true,"approvals_required":0,"approvals_left":0,"require_password_to_approve":false,"approved_by":[{"user":{"id":527793,"username":"axifive","name":"Alexey Terentyev","state":"active","locked":false,"avatar_url":"https://secure.gravatar.com/avatar/b5eee878c9129969b55d221a823fd15e55aad8dc15d521f4170e3c93728e02b6?s=80\u0026d=identicon","web_url":"https://gitlab.com/axifive"}},{"user":{"id":4102996,"username":"zeripath","name":"zeripath","state":"active","locked":false,"avatar_url":"https://secure.gravatar.com/avatar/3bad2cdad37aa0bbb3ad276ce8f77e32a1a9567a7083f0866d8df8ed0e92e5b5?s=80\u0026d=identicon","web_url":"https://gitlab.com/zeripath"}}],"suggested_approvers":[],"approvers":[],"approver_groups":[],"user_has_approved":false,"user_can_approve":false,"approval_rules_left":[],"has_approval_rules":true,"merge_request_approvers_available":false,"multiple_approval_rules_available":false,"invalid_approvers_rules":[]}
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2
similarity index 85%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2
index 7119342f6c..8848af8e48 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2
@@ -1,22 +1,17 @@
-Etag: W/"914149155d75f8d8f7ed2e5351f0fadb"
-X-Runtime: 0.234286
-Ratelimit-Remaining: 1983
-Strict-Transport-Security: max-age=31536000
-Referrer-Policy: strict-origin-when-cross-origin
-Gitlab-Sv: localhost
-Set-Cookie: _cfuvid=aYLZ68TRL8gsnraUk.zZIxRvuv981nIhZNIO9vVpgbU-1701333890175-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-X-Content-Type-Options: nosniff
-X-Frame-Options: SAMEORIGIN
-Ratelimit-Observed: 17
-Content-Type: application/json
Cache-Control: max-age=0, private, must-revalidate
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:50 GMT
-Ratelimit-Limit: 2000
-Vary: Origin, Accept-Encoding
+X-Content-Type-Options: nosniff
+Etag: W/"914149155d75f8d8f7ed2e5351f0fadb"
+Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'none'
-Gitlab-Lb: haproxy-main-33-lb-gprd
+Vary: Origin, Accept-Encoding
+X-Runtime: 0.634688
Cf-Cache-Status: MISS
-Ratelimit-Reset: 1701333950
-X-Gitlab-Meta: {"correlation_id":"10d576ab8e82b745b7202a6daec3c5e1","version":"1"}
+Set-Cookie: _cfuvid=Z._ut3jKk_GobWpwV3pdT8AP8FDBG3hXVJphHhFBiBg-1710504210170-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Content-Type: application/json
+X-Gitlab-Meta: {"correlation_id":"c36a4f0267a05143404246325892000a","version":"1"}
+Strict-Transport-Security: max-age=31536000
+Gitlab-Lb: haproxy-main-59-lb-gprd
+Gitlab-Sv: api-gke-us-east1-d
+X-Frame-Options: SAMEORIGIN
{"id":43524600,"iid":2,"project_id":15578026,"title":"Test branch","description":"do not merge this PR","state":"opened","created_at":"2019-11-28T15:56:54.104Z","updated_at":"2020-04-19T19:24:21.108Z","merged_by":null,"merge_user":null,"merged_at":null,"closed_by":null,"closed_at":null,"target_branch":"master","source_branch":"feat/test","user_notes_count":0,"upvotes":1,"downvotes":0,"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"assignees":[],"assignee":null,"reviewers":[],"source_project_id":15578026,"target_project_id":15578026,"labels":["bug"],"draft":false,"work_in_progress":false,"milestone":{"id":1082926,"iid":1,"project_id":15578026,"title":"1.0.0","description":"","state":"closed","created_at":"2019-11-28T08:42:30.301Z","updated_at":"2019-11-28T15:57:52.401Z","due_date":null,"start_date":null,"expired":false,"web_url":"https://gitlab.com/gitea/test_repo/-/milestones/1"},"merge_when_pipeline_succeeds":false,"merge_status":"can_be_merged","detailed_merge_status":"mergeable","sha":"9f733b96b98a4175276edf6a2e1231489c3bdd23","merge_commit_sha":null,"squash_commit_sha":null,"discussion_locked":null,"should_remove_source_branch":null,"force_remove_source_branch":true,"prepared_at":"2019-11-28T15:56:54.104Z","reference":"!2","references":{"short":"!2","relative":"!2","full":"gitea/test_repo!2"},"web_url":"https://gitlab.com/gitea/test_repo/-/merge_requests/2","time_stats":{"time_estimate":0,"total_time_spent":0,"human_time_estimate":null,"human_total_time_spent":null},"squash":true,"squash_on_merge":true,"task_completion_status":{"count":0,"completed_count":0},"has_conflicts":false,"blocking_discussions_resolved":true,"approvals_before_merge":null,"subscribed":false,"changes_count":"1","latest_build_started_at":null,"latest_build_finished_at":null,"first_deployed_to_production_at":null,"pipeline":null,"head_pipeline":null,"diff_refs":{"base_sha":"c59c9b451acca9d106cc19d61d87afe3fbbb8b83","head_sha":"9f733b96b98a4175276edf6a2e1231489c3bdd23","start_sha":"c59c9b451acca9d106cc19d61d87afe3fbbb8b83"},"merge_error":null,"first_contribution":false,"user":{"can_merge":false}}
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_approvals b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2%2Fapprovals
similarity index 51%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_approvals
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2%2Fapprovals
index 02a1deae29..be119c18b7 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_approvals
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2%2Fapprovals
@@ -1,22 +1,17 @@
-Vary: Origin, Accept-Encoding
-Ratelimit-Remaining: 1978
-Gitlab-Lb: haproxy-main-04-lb-gprd
-Set-Cookie: _cfuvid=cKUtcpJODQwk9SDRn91k1DY8CY5Tg238DXGgT0a2go0-1701333891493-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-Content-Type: application/json
-Cf-Cache-Status: MISS
-Etag: W/"ce2f774c1b05c5c8a14ec9274444cba3"
-Ratelimit-Limit: 2000
+Set-Cookie: _cfuvid=sImrE_lz4VAFrw_o2FHA_8y6kxUoFm4G31.BEqR9M_E-1710504211811-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
X-Content-Type-Options: nosniff
-Cache-Control: max-age=0, private, must-revalidate
-X-Gitlab-Meta: {"correlation_id":"98ee1da556bdb3d764679ebab9ab5312","version":"1"}
-Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: SAMEORIGIN
-Gitlab-Sv: localhost
+X-Gitlab-Meta: {"correlation_id":"cf4eeb7f9cb45f6d15ef0297231a3250","version":"1"}
+X-Runtime: 0.163465
+Content-Type: application/json
+Vary: Origin, Accept-Encoding
+Gitlab-Lb: haproxy-main-17-lb-gprd
+Gitlab-Sv: api-gke-us-east1-d
+Referrer-Policy: strict-origin-when-cross-origin
+Cf-Cache-Status: MISS
+Cache-Control: max-age=0, private, must-revalidate
Content-Security-Policy: default-src 'none'
-X-Runtime: 0.165471
+Etag: W/"58109b687618e6b9e49ff812d5a911df"
Strict-Transport-Security: max-age=31536000
-Ratelimit-Observed: 22
-Ratelimit-Reset: 1701333951
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:51 GMT
-{"id":43524600,"iid":2,"project_id":15578026,"title":"Test branch","description":"do not merge this PR","state":"opened","created_at":"2019-11-28T15:56:54.104Z","updated_at":"2020-04-19T19:24:21.108Z","merge_status":"can_be_merged","approved":true,"approvals_required":0,"approvals_left":0,"require_password_to_approve":false,"approved_by":[{"user":{"id":4575606,"username":"real6543","name":"6543","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/4575606/avatar.png","web_url":"https://gitlab.com/real6543"}}],"suggested_approvers":[],"approvers":[],"approver_groups":[{"group":{"id":3181312,"web_url":"https://gitlab.com/groups/gitea","name":"gitea","path":"gitea","description":"Mirror of Gitea source code repositories","visibility":"public","share_with_group_lock":false,"require_two_factor_authentication":false,"two_factor_grace_period":48,"project_creation_level":"maintainer","auto_devops_enabled":null,"subgroup_creation_level":"owner","emails_disabled":false,"emails_enabled":true,"mentions_disabled":null,"lfs_enabled":true,"default_branch_protection":2,"default_branch_protection_defaults":{"allowed_to_push":[{"access_level":30}],"allow_force_push":true,"allowed_to_merge":[{"access_level":30}]},"avatar_url":"https://gitlab.com/uploads/-/system/group/avatar/3181312/gitea.png","request_access_enabled":true,"full_name":"gitea","full_path":"gitea","created_at":"2018-07-04T16:32:10.176Z","parent_id":null,"shared_runners_setting":"enabled","ldap_cn":null,"ldap_access":null,"wiki_access_level":"enabled"}}],"user_has_approved":false,"user_can_approve":false,"approval_rules_left":[],"has_approval_rules":true,"merge_request_approvers_available":false,"multiple_approval_rules_available":false,"invalid_approvers_rules":[]}
\ No newline at end of file
+{"id":43524600,"iid":2,"project_id":15578026,"title":"Test branch","description":"do not merge this PR","state":"opened","created_at":"2019-11-28T15:56:54.104Z","updated_at":"2020-04-19T19:24:21.108Z","merge_status":"can_be_merged","approved":true,"approvals_required":0,"approvals_left":0,"require_password_to_approve":false,"approved_by":[{"user":{"id":4575606,"username":"real6543","name":"6543","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/4575606/avatar.png","web_url":"https://gitlab.com/real6543"}}],"suggested_approvers":[],"approvers":[],"approver_groups":[{"group":{"id":3181312,"web_url":"https://gitlab.com/groups/gitea","name":"gitea","path":"gitea","description":"Mirror of Gitea source code repositories","visibility":"public","share_with_group_lock":false,"require_two_factor_authentication":false,"two_factor_grace_period":48,"project_creation_level":"maintainer","auto_devops_enabled":null,"subgroup_creation_level":"owner","emails_disabled":false,"emails_enabled":true,"mentions_disabled":null,"lfs_enabled":true,"math_rendering_limits_enabled":true,"lock_math_rendering_limits_enabled":false,"default_branch_protection":2,"default_branch_protection_defaults":{"allowed_to_push":[{"access_level":30}],"allow_force_push":true,"allowed_to_merge":[{"access_level":30}]},"avatar_url":"https://gitlab.com/uploads/-/system/group/avatar/3181312/gitea.png","request_access_enabled":true,"full_name":"gitea","full_path":"gitea","created_at":"2018-07-04T16:32:10.176Z","parent_id":null,"organization_id":1,"shared_runners_setting":"enabled","ldap_cn":null,"ldap_access":null,"wiki_access_level":"enabled"}}],"user_has_approved":false,"user_can_approve":false,"approval_rules_left":[],"has_approval_rules":true,"merge_request_approvers_available":false,"multiple_approval_rules_available":false,"invalid_approvers_rules":[]}
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji!page=1&per_page=1 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2%2Faward_emoji%3Fpage=1&per_page=1
similarity index 73%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji!page=1&per_page=1
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2%2Faward_emoji%3Fpage=1&per_page=1
index aab7a74df8..eb72d3fc54 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji!page=1&per_page=1
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2%2Faward_emoji%3Fpage=1&per_page=1
@@ -1,29 +1,24 @@
+Etag: W/"798718b23a2ec66b16cce20cb7155116"
+X-Gitlab-Meta: {"correlation_id":"64dce1b90fe8fbfda281a99e34d0905c","version":"1"}
+Link: ; rel="next", ; rel="first", ; rel="last"
+X-Frame-Options: SAMEORIGIN
+X-Runtime: 0.092139
+Content-Type: application/json
+X-Content-Type-Options: nosniff
X-Prev-Page:
-X-Runtime: 0.066211
-Strict-Transport-Security: max-age=31536000
-Content-Security-Policy: default-src 'none'
+Referrer-Policy: strict-origin-when-cross-origin
X-Per-Page: 1
X-Total: 2
-Ratelimit-Reset: 1701333950
-Ratelimit-Limit: 2000
-Cf-Cache-Status: MISS
-X-Content-Type-Options: nosniff
-X-Gitlab-Meta: {"correlation_id":"d0f8c843558e938161d7307b686f1cd9","version":"1"}
-Gitlab-Sv: localhost
-Set-Cookie: _cfuvid=WFMplweUX3zWl6uoteYRHeDcpElbTNYhWrIBbNEVC3A-1701333890399-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-Content-Type: application/json
-X-Total-Pages: 2
-Etag: W/"798718b23a2ec66b16cce20cb7155116"
-X-Next-Page: 2
-Link: ; rel="next", ; rel="first", ; rel="last"
-Vary: Origin, Accept-Encoding
-Ratelimit-Observed: 18
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:50 GMT
-X-Frame-Options: SAMEORIGIN
-Referrer-Policy: strict-origin-when-cross-origin
-Ratelimit-Remaining: 1982
-Gitlab-Lb: haproxy-main-31-lb-gprd
+Strict-Transport-Security: max-age=31536000
+Content-Security-Policy: default-src 'none'
Cache-Control: max-age=0, private, must-revalidate
+Vary: Origin, Accept-Encoding
+Gitlab-Lb: haproxy-main-30-lb-gprd
+Cf-Cache-Status: MISS
+Gitlab-Sv: api-gke-us-east1-b
+Set-Cookie: _cfuvid=vG12ThddZrDMG_flNdCfEfuN3Vma3YHPWrU1MJOBFhY-1710504210427-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+X-Next-Page: 2
X-Page: 1
+X-Total-Pages: 2
[{"id":5541414,"name":"thumbsup","user":{"id":4575606,"username":"real6543","name":"6543","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/4575606/avatar.png","web_url":"https://gitlab.com/real6543"},"created_at":"2020-09-02T23:42:34.310Z","updated_at":"2020-09-02T23:42:34.310Z","awardable_id":43524600,"awardable_type":"MergeRequest","url":null}]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji!page=2&per_page=1 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2%2Faward_emoji%3Fpage=2&per_page=1
similarity index 73%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji!page=2&per_page=1
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2%2Faward_emoji%3Fpage=2&per_page=1
index 5d85a567e8..63f7d02a17 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji!page=2&per_page=1
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2%2Faward_emoji%3Fpage=2&per_page=1
@@ -1,29 +1,24 @@
-Cf-Cache-Status: MISS
-Etag: W/"e6776aaa57e6a81bf8a2d8823272cc70"
-X-Frame-Options: SAMEORIGIN
-X-Gitlab-Meta: {"correlation_id":"db01aae11b0cf7febcf051706159faae","version":"1"}
-X-Total-Pages: 2
-Strict-Transport-Security: max-age=31536000
-Gitlab-Lb: haproxy-main-51-lb-gprd
-X-Total: 2
-Referrer-Policy: strict-origin-when-cross-origin
-Ratelimit-Limit: 2000
-Cache-Control: max-age=0, private, must-revalidate
X-Next-Page:
-Set-Cookie: _cfuvid=X0n26HdiufQTXsshb4pvCyuf0jDSstPZ8GnIiyx57YU-1701333890628-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-Link: ; rel="prev", ; rel="first", ; rel="last"
-Ratelimit-Observed: 19
-Ratelimit-Reset: 1701333950
Content-Type: application/json
Vary: Origin, Accept-Encoding
-X-Per-Page: 1
-X-Runtime: 0.067511
-Gitlab-Sv: localhost
-X-Prev-Page: 1
-Ratelimit-Remaining: 1981
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:50 GMT
+Cf-Cache-Status: MISS
Content-Security-Policy: default-src 'none'
-X-Content-Type-Options: nosniff
+X-Frame-Options: SAMEORIGIN
X-Page: 2
+Strict-Transport-Security: max-age=31536000
+Set-Cookie: _cfuvid=VgzG7aSZyu0lycKl6YVe9GTRYeLa0XUB5lv3pROs3tk-1710504210672-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Referrer-Policy: strict-origin-when-cross-origin
+Cache-Control: max-age=0, private, must-revalidate
+Etag: W/"e6776aaa57e6a81bf8a2d8823272cc70"
+X-Prev-Page: 1
+X-Runtime: 0.073747
+Link: ; rel="prev", ; rel="first", ; rel="last"
+X-Gitlab-Meta: {"correlation_id":"50f2f6c2fa586f2699010189215c0531","version":"1"}
+X-Total: 2
+X-Total-Pages: 2
+Gitlab-Lb: haproxy-main-60-lb-gprd
+X-Content-Type-Options: nosniff
+X-Per-Page: 1
+Gitlab-Sv: api-gke-us-east1-b
[{"id":5541415,"name":"tada","user":{"id":4575606,"username":"real6543","name":"6543","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/4575606/avatar.png","web_url":"https://gitlab.com/real6543"},"created_at":"2020-09-02T23:42:59.060Z","updated_at":"2020-09-02T23:42:59.060Z","awardable_id":43524600,"awardable_type":"MergeRequest","url":null}]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji!page=3&per_page=1 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2%2Faward_emoji%3Fpage=3&per_page=1
similarity index 62%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji!page=3&per_page=1
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2%2Faward_emoji%3Fpage=3&per_page=1
index aa5a50e45b..9cce5e0bdb 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests_2_award_emoji!page=3&per_page=1
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%2F2%2Faward_emoji%3Fpage=3&per_page=1
@@ -1,31 +1,26 @@
-X-Page: 3
-Strict-Transport-Security: max-age=31536000
-Accept-Ranges: bytes
-Set-Cookie: _cfuvid=CBSpRhUuajZbJ9Mc_r7SkVmZawoSi5ofuts2TGyHgRk-1701333890842-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-Cache-Control: max-age=0, private, must-revalidate
-X-Prev-Page:
X-Total-Pages: 2
-Ratelimit-Reset: 1701333950
-Gitlab-Sv: localhost
-X-Content-Type-Options: nosniff
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:50 GMT
-Gitlab-Lb: haproxy-main-05-lb-gprd
-Etag: W/"4f53cda18c2baa0c0354bb5f9a3ecbe5"
-Vary: Origin, Accept-Encoding
-X-Frame-Options: SAMEORIGIN
-X-Gitlab-Meta: {"correlation_id":"36817edd7cae21d5d6b875faf173ce80","version":"1"}
-X-Per-Page: 1
-X-Total: 2
-Referrer-Policy: strict-origin-when-cross-origin
-Content-Security-Policy: default-src 'none'
+Cache-Control: max-age=0, private, must-revalidate
Link: ; rel="first", ; rel="last"
-X-Next-Page:
-X-Runtime: 0.061075
-Ratelimit-Limit: 2000
-Ratelimit-Observed: 20
-Cf-Cache-Status: MISS
+X-Prev-Page:
Content-Type: application/json
+Etag: W/"4f53cda18c2baa0c0354bb5f9a3ecbe5"
+X-Per-Page: 1
+Cf-Cache-Status: MISS
+Accept-Ranges: bytes
+X-Content-Type-Options: nosniff
+X-Runtime: 0.064101
+X-Total: 2
+Gitlab-Lb: haproxy-main-18-lb-gprd
Content-Length: 2
-Ratelimit-Remaining: 1980
+Referrer-Policy: strict-origin-when-cross-origin
+Set-Cookie: _cfuvid=I18ivb.i14P1hql2L0PDHGFAIFBr6CdHc5Xp3CQ7Z78-1710504211202-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Vary: Origin, Accept-Encoding
+X-Next-Page:
+Gitlab-Sv: api-gke-us-east1-b
+Strict-Transport-Security: max-age=31536000
+Content-Security-Policy: default-src 'none'
+X-Frame-Options: SAMEORIGIN
+X-Gitlab-Meta: {"correlation_id":"6c902fe6782c24f23059e0ab39caf051","version":"1"}
+X-Page: 3
[]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests!page=1&per_page=1&view=simple b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%3Fpage=1&per_page=1&view=simple
similarity index 74%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests!page=1&per_page=1&view=simple
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%3Fpage=1&per_page=1&view=simple
index 7eec1ca917..1beb5e698c 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_merge_requests!page=1&per_page=1&view=simple
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmerge_requests%3Fpage=1&per_page=1&view=simple
@@ -1,29 +1,24 @@
-Content-Type: application/json
-X-Page: 1
-Link: ; rel="next", ; rel="first", ; rel="last"
-Vary: Origin, Accept-Encoding
-X-Runtime: 0.139912
-X-Gitlab-Meta: {"correlation_id":"002c20b78ace441f5585931fed7093ed","version":"1"}
-Gitlab-Sv: localhost
-Cache-Control: max-age=0, private, must-revalidate
-X-Total: 2
-X-Total-Pages: 2
-Ratelimit-Observed: 16
-Ratelimit-Limit: 2000
-X-Content-Type-Options: nosniff
-Ratelimit-Remaining: 1984
-Set-Cookie: _cfuvid=6nsOEFMJm7NgrvYZAMGwiJBdm5A5CU71S33zOdN8Kyo-1701333889768-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-X-Per-Page: 1
-X-Prev-Page:
-Strict-Transport-Security: max-age=31536000
-Ratelimit-Reset: 1701333949
-Gitlab-Lb: haproxy-main-09-lb-gprd
-X-Next-Page: 2
-Cf-Cache-Status: MISS
-Content-Security-Policy: default-src 'none'
Etag: W/"14f72c1f555b0e6348d338190e9e4839"
-X-Frame-Options: SAMEORIGIN
+X-Page: 1
Referrer-Policy: strict-origin-when-cross-origin
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:49 GMT
+Gitlab-Lb: haproxy-main-44-lb-gprd
+Content-Type: application/json
+Content-Security-Policy: default-src 'none'
+Set-Cookie: _cfuvid=xtxwnC3sB7qZrUtCFdAaMiSOKDnQPiLD3iYq9hTj39I-1710504209365-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+X-Per-Page: 1
+X-Runtime: 0.102877
+X-Gitlab-Meta: {"correlation_id":"a779d4e8ffae8bdf01f20a6d0c545247","version":"1"}
+Cf-Cache-Status: MISS
+X-Frame-Options: SAMEORIGIN
+X-Total-Pages: 2
+Cache-Control: max-age=0, private, must-revalidate
+Link: ; rel="next", ; rel="first", ; rel="last"
+X-Prev-Page:
+X-Content-Type-Options: nosniff
+X-Next-Page: 2
+Gitlab-Sv: api-gke-us-east1-d
+Vary: Origin, Accept-Encoding
+Strict-Transport-Security: max-age=31536000
+X-Total: 2
[{"id":43524600,"iid":2,"project_id":15578026,"title":"Test branch","description":"do not merge this PR","state":"opened","created_at":"2019-11-28T15:56:54.104Z","updated_at":"2020-04-19T19:24:21.108Z","web_url":"https://gitlab.com/gitea/test_repo/-/merge_requests/2"}]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_milestones!page=1&per_page=100&state=all b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmilestones%3Fpage=1&per_page=100&state=all
similarity index 61%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_milestones!page=1&per_page=100&state=all
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmilestones%3Fpage=1&per_page=100&state=all
index 78310e123d..6d7d482138 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_milestones!page=1&per_page=100&state=all
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Fmilestones%3Fpage=1&per_page=100&state=all
@@ -1,29 +1,24 @@
-X-Content-Type-Options: nosniff
-X-Next-Page:
-Ratelimit-Observed: 5
-X-Frame-Options: SAMEORIGIN
-X-Per-Page: 100
-Ratelimit-Limit: 2000
-Cf-Cache-Status: MISS
-Strict-Transport-Security: max-age=31536000
-Referrer-Policy: strict-origin-when-cross-origin
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:46 GMT
-Content-Security-Policy: default-src 'none'
-Link: ; rel="first", ; rel="last"
-X-Gitlab-Meta: {"correlation_id":"4f72373995f8681ce127c62d384745a3","version":"1"}
-Gitlab-Sv: localhost
-X-Runtime: 0.073691
-Ratelimit-Remaining: 1995
-Ratelimit-Reset: 1701333946
-Content-Type: application/json
-Etag: W/"c8e2d3a5f05ee29c58b665c86684f9f9"
-X-Page: 1
-Gitlab-Lb: haproxy-main-47-lb-gprd
-Vary: Origin, Accept-Encoding
X-Prev-Page:
+Vary: Origin, Accept-Encoding
+X-Gitlab-Meta: {"correlation_id":"2998cbf0e39d3b81710b1d1b82c03b80","version":"1"}
+Referrer-Policy: strict-origin-when-cross-origin
+Link: ; rel="first", ; rel="last"
+X-Page: 1
+Etag: W/"c8e2d3a5f05ee29c58b665c86684f9f9"
+Content-Security-Policy: default-src 'none'
X-Total: 2
-Cache-Control: max-age=0, private, must-revalidate
+Cf-Cache-Status: MISS
+Content-Type: application/json
+X-Next-Page:
+X-Frame-Options: SAMEORIGIN
X-Total-Pages: 1
-Set-Cookie: _cfuvid=ZfjvK5rh2nTUjEDt1Guwzd8zrl6uCDplfE8NBPbdJ7c-1701333886832-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Gitlab-Lb: haproxy-main-24-lb-gprd
+Set-Cookie: _cfuvid=UO1GaUJc3jsd8W85u2xy74QFY1Ez71cmGWi0WbQoYpU-1710504205756-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Cache-Control: max-age=0, private, must-revalidate
+X-Content-Type-Options: nosniff
+Strict-Transport-Security: max-age=31536000
+Gitlab-Sv: api-gke-us-east1-b
+X-Per-Page: 100
+X-Runtime: 0.078614
[{"id":1082927,"iid":2,"project_id":15578026,"title":"1.1.0","description":"","state":"active","created_at":"2019-11-28T08:42:44.575Z","updated_at":"2019-11-28T08:42:44.575Z","due_date":null,"start_date":null,"expired":false,"web_url":"https://gitlab.com/gitea/test_repo/-/milestones/2"},{"id":1082926,"iid":1,"project_id":15578026,"title":"1.0.0","description":"","state":"closed","created_at":"2019-11-28T08:42:30.301Z","updated_at":"2019-11-28T15:57:52.401Z","due_date":null,"start_date":null,"expired":false,"web_url":"https://gitlab.com/gitea/test_repo/-/milestones/1"}]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_releases!page=1&per_page=100 b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Freleases%3Fpage=1&per_page=100
similarity index 87%
rename from services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_releases!page=1&per_page=100
rename to services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Freleases%3Fpage=1&per_page=100
index 9be0b74729..fc63173e9f 100644
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026_releases!page=1&per_page=100
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2F15578026%2Freleases%3Fpage=1&per_page=100
@@ -1,29 +1,24 @@
-Strict-Transport-Security: max-age=31536000
-Referrer-Policy: strict-origin-when-cross-origin
-Ratelimit-Reset: 1701333947
-X-Total: 1
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:47 GMT
-Ratelimit-Limit: 2000
-X-Runtime: 0.178411
-Gitlab-Lb: haproxy-main-15-lb-gprd
-Vary: Origin, Accept-Encoding
-X-Page: 1
-X-Frame-Options: SAMEORIGIN
-Ratelimit-Observed: 7
-Cf-Cache-Status: MISS
-Etag: W/"dccc7159dc4b46989d13128a7d6ee859"
-X-Content-Type-Options: nosniff
-Ratelimit-Remaining: 1993
-X-Gitlab-Meta: {"correlation_id":"0044a3de3ede2f913cabe6e464dd73c2","version":"1"}
-X-Total-Pages: 1
-X-Next-Page:
-X-Per-Page: 100
Content-Type: application/json
Content-Security-Policy: default-src 'none'
-Set-Cookie: _cfuvid=h1ayMNs6W_kFPoFe28IpiaFUz1ZAPvY6npUWxARRx4I-1701333887452-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
Link: ; rel="first", ; rel="last"
-Gitlab-Sv: localhost
-Cache-Control: max-age=0, private, must-revalidate
X-Prev-Page:
+Gitlab-Sv: api-gke-us-east1-b
+X-Content-Type-Options: nosniff
+X-Runtime: 0.123532
+Vary: Origin, Accept-Encoding
+X-Per-Page: 100
+Referrer-Policy: strict-origin-when-cross-origin
+Set-Cookie: _cfuvid=Eoqdcle3awcN8Jyrig.dSmC4hTIPuXqZ5ruJIG9c56I-1710504206613-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+X-Total: 1
+Cf-Cache-Status: MISS
+X-Next-Page:
+Cache-Control: max-age=0, private, must-revalidate
+X-Gitlab-Meta: {"correlation_id":"1c01187e563b0819c5ad553bc7525ce8","version":"1"}
+Etag: W/"dccc7159dc4b46989d13128a7d6ee859"
+X-Page: 1
+Gitlab-Lb: haproxy-main-30-lb-gprd
+X-Frame-Options: SAMEORIGIN
+X-Total-Pages: 1
+Strict-Transport-Security: max-age=31536000
[{"name":"First Release","tag_name":"v0.9.99","description":"A test release","created_at":"2019-11-28T09:09:48.840Z","released_at":"2019-11-28T09:09:48.836Z","upcoming_release":false,"author":{"id":1241334,"username":"lafriks","name":"Lauris BH","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1241334/avatar.png","web_url":"https://gitlab.com/lafriks"},"commit":{"id":"0720a3ec57c1f843568298117b874319e7deee75","short_id":"0720a3ec","created_at":"2019-11-28T08:49:16.000+00:00","parent_ids":["93ea21ce45d35690c35e80961d239645139e872c"],"title":"Add new file","message":"Add new file","author_name":"Lauris BH","author_email":"lauris@nix.lv","authored_date":"2019-11-28T08:49:16.000+00:00","committer_name":"Lauris BH","committer_email":"lauris@nix.lv","committed_date":"2019-11-28T08:49:16.000+00:00","trailers":{},"extended_trailers":{},"web_url":"https://gitlab.com/gitea/test_repo/-/commit/0720a3ec57c1f843568298117b874319e7deee75"},"commit_path":"/gitea/test_repo/-/commit/0720a3ec57c1f843568298117b874319e7deee75","tag_path":"/gitea/test_repo/-/tags/v0.9.99","assets":{"count":4,"sources":[{"format":"zip","url":"https://gitlab.com/gitea/test_repo/-/archive/v0.9.99/test_repo-v0.9.99.zip"},{"format":"tar.gz","url":"https://gitlab.com/gitea/test_repo/-/archive/v0.9.99/test_repo-v0.9.99.tar.gz"},{"format":"tar.bz2","url":"https://gitlab.com/gitea/test_repo/-/archive/v0.9.99/test_repo-v0.9.99.tar.bz2"},{"format":"tar","url":"https://gitlab.com/gitea/test_repo/-/archive/v0.9.99/test_repo-v0.9.99.tar"}],"links":[]},"evidences":[{"sha":"89f1223473ee01f192a83d0cb89f4d1eac1de74f01ad","filepath":"https://gitlab.com/gitea/test_repo/-/releases/v0.9.99/evidences/52147.json","collected_at":"2019-11-28T09:09:48.888Z"}],"_links":{"closed_issues_url":"https://gitlab.com/gitea/test_repo/-/issues?release_tag=v0.9.99\u0026scope=all\u0026state=closed","closed_merge_requests_url":"https://gitlab.com/gitea/test_repo/-/merge_requests?release_tag=v0.9.99\u0026scope=all\u0026state=closed","merged_merge_requests_url":"https://gitlab.com/gitea/test_repo/-/merge_requests?release_tag=v0.9.99\u0026scope=all\u0026state=merged","opened_issues_url":"https://gitlab.com/gitea/test_repo/-/issues?release_tag=v0.9.99\u0026scope=all\u0026state=opened","opened_merge_requests_url":"https://gitlab.com/gitea/test_repo/-/merge_requests?release_tag=v0.9.99\u0026scope=all\u0026state=opened","self":"https://gitlab.com/gitea/test_repo/-/releases/v0.9.99"}}]
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2Fgitea%252Ftest_repo b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2Fgitea%252Ftest_repo
new file mode 100644
index 0000000000..96f1ea86b5
--- /dev/null
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fprojects%2Fgitea%252Ftest_repo
@@ -0,0 +1,17 @@
+Referrer-Policy: strict-origin-when-cross-origin
+Gitlab-Lb: haproxy-main-51-lb-gprd
+Cf-Cache-Status: MISS
+Etag: W/"8db4917b3be5f4ca0d101a702179b75a"
+X-Content-Type-Options: nosniff
+Strict-Transport-Security: max-age=31536000
+Gitlab-Sv: api-gke-us-east1-b
+Content-Type: application/json
+Cache-Control: max-age=0, private, must-revalidate
+X-Gitlab-Meta: {"correlation_id":"9b3859cf6d73ce5de261a56d286072a5","version":"1"}
+X-Runtime: 0.119487
+Content-Security-Policy: default-src 'none'
+Vary: Origin, Accept-Encoding
+Set-Cookie: _cfuvid=Cmc.ycVkdwA_tBvmR2tOVLQ5B.khzzU39ZUxgf4RNlw-1710504204838-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+X-Frame-Options: SAMEORIGIN
+
+{"id":15578026,"description":"Test repository for testing migration from gitlab to gitea","name":"test_repo","name_with_namespace":"gitea / test_repo","path":"test_repo","path_with_namespace":"gitea/test_repo","created_at":"2019-11-28T08:20:33.019Z","default_branch":"master","tag_list":["migration","test"],"topics":["migration","test"],"ssh_url_to_repo":"git@gitlab.com:gitea/test_repo.git","http_url_to_repo":"https://gitlab.com/gitea/test_repo.git","web_url":"https://gitlab.com/gitea/test_repo","readme_url":"https://gitlab.com/gitea/test_repo/-/blob/master/README.md","forks_count":1,"avatar_url":null,"star_count":0,"last_activity_at":"2020-04-19T19:46:04.527Z","namespace":{"id":3181312,"name":"gitea","path":"gitea","kind":"group","full_path":"gitea","parent_id":null,"avatar_url":"/uploads/-/system/group/avatar/3181312/gitea.png","web_url":"https://gitlab.com/groups/gitea"},"container_registry_image_prefix":"registry.gitlab.com/gitea/test_repo","_links":{"self":"https://gitlab.com/api/v4/projects/15578026","issues":"https://gitlab.com/api/v4/projects/15578026/issues","merge_requests":"https://gitlab.com/api/v4/projects/15578026/merge_requests","repo_branches":"https://gitlab.com/api/v4/projects/15578026/repository/branches","labels":"https://gitlab.com/api/v4/projects/15578026/labels","events":"https://gitlab.com/api/v4/projects/15578026/events","members":"https://gitlab.com/api/v4/projects/15578026/members","cluster_agents":"https://gitlab.com/api/v4/projects/15578026/cluster_agents"},"packages_enabled":true,"empty_repo":false,"archived":false,"visibility":"public","resolve_outdated_diff_discussions":false,"repository_object_format":"sha1","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"container_registry_enabled":true,"service_desk_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"enabled","analytics_access_level":"enabled","container_registry_access_level":"enabled","security_and_compliance_access_level":"private","releases_access_level":"enabled","environments_access_level":"enabled","feature_flags_access_level":"enabled","infrastructure_access_level":"enabled","monitor_access_level":"enabled","model_experiments_access_level":"enabled","model_registry_access_level":"enabled","emails_disabled":false,"emails_enabled":true,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":1241334,"import_status":"none","open_issues_count":0,"description_html":"\u003cp data-sourcepos=\"1:1-1:58\" dir=\"auto\"\u003eTest repository for testing migration from gitlab to gitea\u003c/p\u003e","updated_at":"2024-01-11T01:23:21.057Z","ci_config_path":null,"public_jobs":true,"shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"allow_merge_on_skipped_pipeline":null,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"ff","squash_option":"default_off","enforce_auth_checks_on_uploads":true,"suggestion_commit_message":null,"merge_commit_template":null,"squash_commit_template":null,"issue_branch_template":null,"warn_about_potentially_unwanted_characters":true,"autoclose_referenced_issues":true,"external_authorization_classification_label":"","requirements_enabled":false,"requirements_access_level":"enabled","security_and_compliance_enabled":false,"compliance_frameworks":[],"permissions":{"project_access":null,"group_access":null}}
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fversion b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fversion
new file mode 100644
index 0000000000..b8561d4303
--- /dev/null
+++ b/services/migrations/testdata/gitlab/full_download/GET_%2Fapi%2Fv4%2Fversion
@@ -0,0 +1,17 @@
+Content-Type: application/json
+Cache-Control: max-age=0, private, must-revalidate
+Vary: Origin, Accept-Encoding
+X-Frame-Options: SAMEORIGIN
+Strict-Transport-Security: max-age=31536000
+X-Gitlab-Meta: {"correlation_id":"5e1b0f0c600e3127952b0bc933bfe0fd","version":"1"}
+Referrer-Policy: strict-origin-when-cross-origin
+Gitlab-Sv: api-gke-us-east1-b
+Set-Cookie: _cfuvid=ve7lWeCgOflkqyU5mzcjS4rdE91f0uaUXBG.po.9VLs-1710504204253-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
+Etag: W/"a7e5ac2ae5500f226c1020b94327a605"
+X-Runtime: 0.025760
+Content-Security-Policy: default-src 'none'
+X-Content-Type-Options: nosniff
+Gitlab-Lb: haproxy-main-39-lb-gprd
+Cf-Cache-Status: MISS
+
+{"version":"16.10.0-pre","revision":"7da39369465","kas":{"enabled":true,"externalUrl":"wss://kas.gitlab.com","version":"v16.10.1"},"enterprise":true}
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026 b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026
deleted file mode 100644
index 4ecfe77e65..0000000000
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_15578026
+++ /dev/null
@@ -1,22 +0,0 @@
-X-Frame-Options: SAMEORIGIN
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:46 GMT
-Cf-Cache-Status: MISS
-Set-Cookie: _cfuvid=TkY5Br2q4C67LJ2jZWlgdQaosj3Z4aI81Qb27PNKXfo-1701333886606-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-Etag: W/"3cacfe29f44a69e84a577337eac55d89"
-X-Content-Type-Options: nosniff
-Ratelimit-Remaining: 1996
-Gitlab-Lb: haproxy-main-29-lb-gprd
-Cache-Control: max-age=0, private, must-revalidate
-Referrer-Policy: strict-origin-when-cross-origin
-X-Gitlab-Meta: {"correlation_id":"291f87cd975a51a7b756806ce7b53e2e","version":"1"}
-Ratelimit-Observed: 4
-Vary: Origin, Accept-Encoding
-Gitlab-Sv: localhost
-Content-Security-Policy: default-src 'none'
-Ratelimit-Reset: 1701333946
-Content-Type: application/json
-X-Runtime: 0.101081
-Strict-Transport-Security: max-age=31536000
-Ratelimit-Limit: 2000
-
-{"id":15578026,"description":"Test repository for testing migration from gitlab to gitea","name":"test_repo","name_with_namespace":"gitea / test_repo","path":"test_repo","path_with_namespace":"gitea/test_repo","created_at":"2019-11-28T08:20:33.019Z","default_branch":"master","tag_list":["migration","test"],"topics":["migration","test"],"ssh_url_to_repo":"git@gitlab.com:gitea/test_repo.git","http_url_to_repo":"https://gitlab.com/gitea/test_repo.git","web_url":"https://gitlab.com/gitea/test_repo","readme_url":"https://gitlab.com/gitea/test_repo/-/blob/master/README.md","forks_count":1,"avatar_url":null,"star_count":0,"last_activity_at":"2020-04-19T19:46:04.527Z","namespace":{"id":3181312,"name":"gitea","path":"gitea","kind":"group","full_path":"gitea","parent_id":null,"avatar_url":"/uploads/-/system/group/avatar/3181312/gitea.png","web_url":"https://gitlab.com/groups/gitea"},"container_registry_image_prefix":"registry.gitlab.com/gitea/test_repo","_links":{"self":"https://gitlab.com/api/v4/projects/15578026","issues":"https://gitlab.com/api/v4/projects/15578026/issues","merge_requests":"https://gitlab.com/api/v4/projects/15578026/merge_requests","repo_branches":"https://gitlab.com/api/v4/projects/15578026/repository/branches","labels":"https://gitlab.com/api/v4/projects/15578026/labels","events":"https://gitlab.com/api/v4/projects/15578026/events","members":"https://gitlab.com/api/v4/projects/15578026/members","cluster_agents":"https://gitlab.com/api/v4/projects/15578026/cluster_agents"},"packages_enabled":true,"empty_repo":false,"archived":false,"visibility":"public","resolve_outdated_diff_discussions":false,"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"container_registry_enabled":true,"service_desk_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"enabled","analytics_access_level":"enabled","container_registry_access_level":"enabled","security_and_compliance_access_level":"private","releases_access_level":"enabled","environments_access_level":"enabled","feature_flags_access_level":"enabled","infrastructure_access_level":"enabled","monitor_access_level":"enabled","model_experiments_access_level":"enabled","emails_disabled":false,"emails_enabled":true,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":1241334,"import_status":"none","open_issues_count":0,"description_html":"\u003cp data-sourcepos=\"1:1-1:58\" dir=\"auto\"\u003eTest repository for testing migration from gitlab to gitea\u003c/p\u003e","updated_at":"2022-08-26T19:41:46.691Z","ci_config_path":null,"public_jobs":true,"shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"allow_merge_on_skipped_pipeline":null,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"ff","squash_option":"default_off","enforce_auth_checks_on_uploads":true,"suggestion_commit_message":null,"merge_commit_template":null,"squash_commit_template":null,"issue_branch_template":null,"autoclose_referenced_issues":true,"external_authorization_classification_label":"","requirements_enabled":false,"requirements_access_level":"enabled","security_and_compliance_enabled":false,"compliance_frameworks":[],"permissions":{"project_access":null,"group_access":null}}
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_gitea%2Ftest_repo b/services/migrations/testdata/gitlab/full_download/_api_v4_projects_gitea%2Ftest_repo
deleted file mode 100644
index 07fed52a81..0000000000
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_projects_gitea%2Ftest_repo
+++ /dev/null
@@ -1,22 +0,0 @@
-Content-Security-Policy: default-src 'none'
-Vary: Origin, Accept-Encoding
-Referrer-Policy: strict-origin-when-cross-origin
-Gitlab-Sv: localhost
-X-Content-Type-Options: nosniff
-Ratelimit-Reset: 1701333946
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:46 GMT
-X-Runtime: 0.144599
-Content-Type: application/json
-X-Gitlab-Meta: {"correlation_id":"919887b868b11ed34c5917e98b4be40d","version":"1"}
-Ratelimit-Observed: 2
-Gitlab-Lb: haproxy-main-42-lb-gprd
-Strict-Transport-Security: max-age=31536000
-Cf-Cache-Status: MISS
-Ratelimit-Limit: 2000
-Set-Cookie: _cfuvid=BENIrMVlxs_tt.JplEkDKbUrMpOF_kjRRLJOifNTLqY-1701333886061-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-Etag: W/"3cacfe29f44a69e84a577337eac55d89"
-X-Frame-Options: SAMEORIGIN
-Cache-Control: max-age=0, private, must-revalidate
-Ratelimit-Remaining: 1998
-
-{"id":15578026,"description":"Test repository for testing migration from gitlab to gitea","name":"test_repo","name_with_namespace":"gitea / test_repo","path":"test_repo","path_with_namespace":"gitea/test_repo","created_at":"2019-11-28T08:20:33.019Z","default_branch":"master","tag_list":["migration","test"],"topics":["migration","test"],"ssh_url_to_repo":"git@gitlab.com:gitea/test_repo.git","http_url_to_repo":"https://gitlab.com/gitea/test_repo.git","web_url":"https://gitlab.com/gitea/test_repo","readme_url":"https://gitlab.com/gitea/test_repo/-/blob/master/README.md","forks_count":1,"avatar_url":null,"star_count":0,"last_activity_at":"2020-04-19T19:46:04.527Z","namespace":{"id":3181312,"name":"gitea","path":"gitea","kind":"group","full_path":"gitea","parent_id":null,"avatar_url":"/uploads/-/system/group/avatar/3181312/gitea.png","web_url":"https://gitlab.com/groups/gitea"},"container_registry_image_prefix":"registry.gitlab.com/gitea/test_repo","_links":{"self":"https://gitlab.com/api/v4/projects/15578026","issues":"https://gitlab.com/api/v4/projects/15578026/issues","merge_requests":"https://gitlab.com/api/v4/projects/15578026/merge_requests","repo_branches":"https://gitlab.com/api/v4/projects/15578026/repository/branches","labels":"https://gitlab.com/api/v4/projects/15578026/labels","events":"https://gitlab.com/api/v4/projects/15578026/events","members":"https://gitlab.com/api/v4/projects/15578026/members","cluster_agents":"https://gitlab.com/api/v4/projects/15578026/cluster_agents"},"packages_enabled":true,"empty_repo":false,"archived":false,"visibility":"public","resolve_outdated_diff_discussions":false,"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"container_registry_enabled":true,"service_desk_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"enabled","analytics_access_level":"enabled","container_registry_access_level":"enabled","security_and_compliance_access_level":"private","releases_access_level":"enabled","environments_access_level":"enabled","feature_flags_access_level":"enabled","infrastructure_access_level":"enabled","monitor_access_level":"enabled","model_experiments_access_level":"enabled","emails_disabled":false,"emails_enabled":true,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":1241334,"import_status":"none","open_issues_count":0,"description_html":"\u003cp data-sourcepos=\"1:1-1:58\" dir=\"auto\"\u003eTest repository for testing migration from gitlab to gitea\u003c/p\u003e","updated_at":"2022-08-26T19:41:46.691Z","ci_config_path":null,"public_jobs":true,"shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"allow_merge_on_skipped_pipeline":null,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"ff","squash_option":"default_off","enforce_auth_checks_on_uploads":true,"suggestion_commit_message":null,"merge_commit_template":null,"squash_commit_template":null,"issue_branch_template":null,"autoclose_referenced_issues":true,"external_authorization_classification_label":"","requirements_enabled":false,"requirements_access_level":"enabled","security_and_compliance_enabled":false,"compliance_frameworks":[],"permissions":{"project_access":null,"group_access":null}}
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/full_download/_api_v4_version b/services/migrations/testdata/gitlab/full_download/_api_v4_version
deleted file mode 100644
index 7c2cd320ad..0000000000
--- a/services/migrations/testdata/gitlab/full_download/_api_v4_version
+++ /dev/null
@@ -1,22 +0,0 @@
-Etag: W/"4e5c0a031c3aacb6ba0a3c19e67d7592"
-Ratelimit-Observed: 1
-Content-Type: application/json
-Ratelimit-Remaining: 1999
-Set-Cookie: _cfuvid=qtYbzv8YeGg4q7XaV0aAE2.YqWIp_xrYPGilXrlecsk-1701333885742-0-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None
-X-Gitlab-Meta: {"correlation_id":"c7c75f0406e1b1b9705a6d7e9bdb06a5","version":"1"}
-X-Runtime: 0.039264
-Cf-Cache-Status: MISS
-Content-Security-Policy: default-src 'none'
-Ratelimit-Reset: 1701333945
-Gitlab-Sv: localhost
-Strict-Transport-Security: max-age=31536000
-Vary: Origin, Accept-Encoding
-X-Content-Type-Options: nosniff
-Referrer-Policy: strict-origin-when-cross-origin
-Ratelimit-Resettime: Thu, 30 Nov 2023 08:45:45 GMT
-Gitlab-Lb: haproxy-main-23-lb-gprd
-Cache-Control: max-age=0, private, must-revalidate
-X-Frame-Options: SAMEORIGIN
-Ratelimit-Limit: 2000
-
-{"version":"16.7.0-pre","revision":"acd848a9228","kas":{"enabled":true,"externalUrl":"wss://kas.gitlab.com","version":"v16.7.0-rc2"},"enterprise":true}
\ No newline at end of file
diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996 b/services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fprojects%2F6590996
similarity index 100%
rename from services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996
rename to services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fprojects%2F6590996
diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_issues_2_award_emoji!page=1&per_page=10 b/services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fprojects%2F6590996%2Fissues%2F2%2Faward_emoji%3Fpage=1&per_page=10
similarity index 100%
rename from services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_issues_2_award_emoji!page=1&per_page=10
rename to services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fprojects%2F6590996%2Fissues%2F2%2Faward_emoji%3Fpage=1&per_page=10
diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_issues!page=1&per_page=10&sort=asc&state=all b/services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fprojects%2F6590996%2Fissues%3Fpage=1&per_page=10&sort=asc&state=all
similarity index 100%
rename from services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_issues!page=1&per_page=10&sort=asc&state=all
rename to services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fprojects%2F6590996%2Fissues%3Fpage=1&per_page=10&sort=asc&state=all
diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests_1 b/services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fprojects%2F6590996%2Fmerge_requests%2F1
similarity index 100%
rename from services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests_1
rename to services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fprojects%2F6590996%2Fmerge_requests%2F1
diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests_1_award_emoji!page=1&per_page=10 b/services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fprojects%2F6590996%2Fmerge_requests%2F1%2Faward_emoji%3Fpage=1&per_page=10
similarity index 100%
rename from services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests_1_award_emoji!page=1&per_page=10
rename to services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fprojects%2F6590996%2Fmerge_requests%2F1%2Faward_emoji%3Fpage=1&per_page=10
diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests!page=1&per_page=10&view=simple b/services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fprojects%2F6590996%2Fmerge_requests%3Fpage=1&per_page=10&view=simple
similarity index 100%
rename from services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_6590996_merge_requests!page=1&per_page=10&view=simple
rename to services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fprojects%2F6590996%2Fmerge_requests%3Fpage=1&per_page=10&view=simple
diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_troyengel%2Farchbuild b/services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fprojects%2Ftroyengel%252Farchbuild
similarity index 100%
rename from services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_projects_troyengel%2Farchbuild
rename to services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fprojects%2Ftroyengel%252Farchbuild
diff --git a/services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_version b/services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fversion
similarity index 100%
rename from services/migrations/testdata/gitlab/skipped_issue_number/_api_v4_version
rename to services/migrations/testdata/gitlab/skipped_issue_number/GET_%2Fapi%2Fv4%2Fversion
diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go
index 3418cf90df..de4a58f27b 100644
--- a/services/mirror/mirror_pull.go
+++ b/services/mirror/mirror_pull.go
@@ -593,7 +593,7 @@ func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, gi
m.Repo.DefaultBranch = firstName
}
// Update the git repository default branch
- if err := gitRepo.SetDefaultBranch(m.Repo.DefaultBranch); err != nil {
+ if err := gitrepo.SetDefaultBranch(ctx, m.Repo, m.Repo.DefaultBranch); err != nil {
if !git.IsErrUnsupportedVersion(err) {
log.Error("Failed to update default branch of underlying git repository %-v. Error: %v", m.Repo, err)
desc := fmt.Sprintf("Failed to update default branch of underlying git repository '%s': %v", m.Repo.RepoPath(), err)
diff --git a/services/packages/cleanup/cleanup.go b/services/packages/cleanup/cleanup.go
index 0ff8077bc9..5d5120c6a0 100644
--- a/services/packages/cleanup/cleanup.go
+++ b/services/packages/cleanup/cleanup.go
@@ -12,8 +12,8 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
- "code.gitea.io/gitea/modules/util"
packages_service "code.gitea.io/gitea/services/packages"
alpine_service "code.gitea.io/gitea/services/packages/alpine"
cargo_service "code.gitea.io/gitea/services/packages/cargo"
@@ -60,7 +60,7 @@ func ExecuteCleanupRules(outerCtx context.Context) error {
for _, p := range packages {
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
PackageID: p.ID,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Sort: packages_model.SortCreatedDesc,
Paginator: db.NewAbsoluteListOptions(pcr.KeepCount, 200),
})
diff --git a/services/packages/container/cleanup.go b/services/packages/container/cleanup.go
index dd3f158dbf..3f5f43bbc0 100644
--- a/services/packages/container/cleanup.go
+++ b/services/packages/container/cleanup.go
@@ -9,8 +9,8 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container"
+ "code.gitea.io/gitea/modules/optional"
container_module "code.gitea.io/gitea/modules/packages/container"
- "code.gitea.io/gitea/modules/util"
packages_service "code.gitea.io/gitea/services/packages"
digest "github.com/opencontainers/go-digest"
@@ -59,8 +59,8 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e
ExactMatch: true,
Value: container_model.UploadVersion,
},
- IsInternal: util.OptionalBoolTrue,
- HasFiles: util.OptionalBoolFalse,
+ IsInternal: optional.Some(true),
+ HasFiles: optional.Some(false),
})
if err != nil {
return err
diff --git a/services/packages/packages.go b/services/packages/packages.go
index 56d5cc04de..64b1ddd869 100644
--- a/services/packages/packages.go
+++ b/services/packages/packages.go
@@ -18,10 +18,10 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
- "code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"
)
@@ -330,7 +330,7 @@ func CheckCountQuotaExceeded(ctx context.Context, doer, owner *user_model.User)
if setting.Packages.LimitTotalOwnerCount > -1 {
totalCount, err := packages_model.CountVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: owner.ID,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
log.Error("CountVersions failed: %v", err)
@@ -640,7 +640,7 @@ func RemoveAllPackages(ctx context.Context, userID int64) (int, error) {
Page: 1,
},
OwnerID: userID,
- IsInternal: util.OptionalBoolNone,
+ IsInternal: optional.None[bool](),
})
if err != nil {
return count, fmt.Errorf("GetOwnedPackages[%d]: %w", userID, err)
diff --git a/services/pull/commit_status.go b/services/pull/commit_status.go
index 27ee572640..514bcee8f1 100644
--- a/services/pull/commit_status.go
+++ b/services/pull/commit_status.go
@@ -36,9 +36,9 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus,
}
}
- for _, commitStatus := range commitStatuses {
+ for _, gp := range requiredContextsGlob {
var targetStatus structs.CommitStatusState
- for _, gp := range requiredContextsGlob {
+ for _, commitStatus := range commitStatuses {
if gp.Match(commitStatus.Context) {
targetStatus = commitStatus.State
matchedCount++
@@ -46,17 +46,21 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus,
}
}
- if targetStatus != "" && targetStatus.NoBetterThan(returnedStatus) {
+ // If required rule not match any action, then it is pending
+ if targetStatus == "" {
+ if structs.CommitStatusPending.NoBetterThan(returnedStatus) {
+ returnedStatus = structs.CommitStatusPending
+ }
+ break
+ }
+
+ if targetStatus.NoBetterThan(returnedStatus) {
returnedStatus = targetStatus
}
}
}
- if matchedCount != len(requiredContexts) {
- return structs.CommitStatusPending
- }
-
- if matchedCount == 0 {
+ if matchedCount == 0 && returnedStatus == structs.CommitStatusSuccess {
status := git_model.CalcCommitStatus(commitStatuses)
if status != nil {
return status.State
diff --git a/services/pull/commit_status_test.go b/services/pull/commit_status_test.go
new file mode 100644
index 0000000000..592acdd55c
--- /dev/null
+++ b/services/pull/commit_status_test.go
@@ -0,0 +1,65 @@
+// Copyright 2024 The Gitea Authors.
+// All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package pull
+
+import (
+ "testing"
+
+ git_model "code.gitea.io/gitea/models/git"
+ "code.gitea.io/gitea/modules/structs"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMergeRequiredContextsCommitStatus(t *testing.T) {
+ testCases := [][]*git_model.CommitStatus{
+ {
+ {Context: "Build 1", State: structs.CommitStatusSuccess},
+ {Context: "Build 2", State: structs.CommitStatusSuccess},
+ {Context: "Build 3", State: structs.CommitStatusSuccess},
+ },
+ {
+ {Context: "Build 1", State: structs.CommitStatusSuccess},
+ {Context: "Build 2", State: structs.CommitStatusSuccess},
+ {Context: "Build 2t", State: structs.CommitStatusPending},
+ },
+ {
+ {Context: "Build 1", State: structs.CommitStatusSuccess},
+ {Context: "Build 2", State: structs.CommitStatusSuccess},
+ {Context: "Build 2t", State: structs.CommitStatusFailure},
+ },
+ {
+ {Context: "Build 1", State: structs.CommitStatusSuccess},
+ {Context: "Build 2", State: structs.CommitStatusSuccess},
+ {Context: "Build 2t", State: structs.CommitStatusSuccess},
+ },
+ {
+ {Context: "Build 1", State: structs.CommitStatusSuccess},
+ {Context: "Build 2", State: structs.CommitStatusSuccess},
+ {Context: "Build 2t", State: structs.CommitStatusSuccess},
+ },
+ }
+ testCasesRequiredContexts := [][]string{
+ {"Build*"},
+ {"Build*", "Build 2t*"},
+ {"Build*", "Build 2t*"},
+ {"Build*", "Build 2t*", "Build 3*"},
+ {"Build*", "Build *", "Build 2t*", "Build 1*"},
+ }
+
+ testCasesExpected := []structs.CommitStatusState{
+ structs.CommitStatusSuccess,
+ structs.CommitStatusPending,
+ structs.CommitStatusFailure,
+ structs.CommitStatusPending,
+ structs.CommitStatusSuccess,
+ }
+
+ for i, commitStatuses := range testCases {
+ if MergeRequiredContextsCommitStatus(commitStatuses, testCasesRequiredContexts[i]) != testCasesExpected[i] {
+ assert.Fail(t, "Test case failed", "Test case %d failed", i+1)
+ }
+ }
+}
diff --git a/services/pull/pull.go b/services/pull/pull.go
index ef6c589a2f..34f3391a63 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -21,7 +21,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/graceful"
@@ -31,6 +30,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sync"
"code.gitea.io/gitea/modules/util"
+ gitea_context "code.gitea.io/gitea/services/context"
issue_service "code.gitea.io/gitea/services/issue"
notify_service "code.gitea.io/gitea/services/notify"
)
diff --git a/services/pull/review.go b/services/pull/review.go
index 39644479f9..1220d54728 100644
--- a/services/pull/review.go
+++ b/services/pull/review.go
@@ -18,8 +18,8 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"
)
@@ -56,7 +56,7 @@ func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestLis
ListAll: true,
},
Type: issues_model.CommentTypeCode,
- Invalidated: util.OptionalBoolFalse,
+ Invalidated: optional.Some(false),
IssueIDs: issueIDs,
})
if err != nil {
@@ -327,7 +327,7 @@ func DismissApprovalReviews(ctx context.Context, doer *user_model.User, pull *is
},
IssueID: pull.IssueID,
Type: issues_model.ReviewTypeApprove,
- Dismissed: util.OptionalBoolFalse,
+ Dismissed: optional.Some(false),
})
if err != nil {
return err
@@ -394,7 +394,7 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string,
reviews, err := issues_model.FindReviews(ctx, issues_model.FindReviewOptions{
IssueID: review.IssueID,
ReviewerID: review.ReviewerID,
- Dismissed: util.OptionalBoolFalse,
+ Dismissed: optional.Some(false),
})
if err != nil {
return nil, err
diff --git a/services/repository/adopt.go b/services/repository/adopt.go
index 7ca68776b5..0ac3c774b7 100644
--- a/services/repository/adopt.go
+++ b/services/repository/adopt.go
@@ -127,24 +127,17 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r
repo.IsEmpty = false
- // Don't bother looking this repo in the context it won't be there
- gitRepo, err := gitrepo.OpenRepository(ctx, repo)
- if err != nil {
- return fmt.Errorf("openRepository: %w", err)
- }
- defer gitRepo.Close()
-
if len(defaultBranch) > 0 {
repo.DefaultBranch = defaultBranch
- if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
+ if err = gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil {
return fmt.Errorf("setDefaultBranch: %w", err)
}
} else {
- repo.DefaultBranch, err = gitRepo.GetDefaultBranch()
+ repo.DefaultBranch, err = gitrepo.GetDefaultBranch(ctx, repo)
if err != nil {
repo.DefaultBranch = setting.Repository.DefaultBranch
- if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
+ if err = gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil {
return fmt.Errorf("setDefaultBranch: %w", err)
}
}
@@ -188,7 +181,7 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r
repo.DefaultBranch = setting.Repository.DefaultBranch
}
- if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
+ if err = gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil {
return fmt.Errorf("setDefaultBranch: %w", err)
}
}
@@ -197,6 +190,13 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r
return fmt.Errorf("updateRepository: %w", err)
}
+ // Don't bother looking this repo in the context it won't be there
+ gitRepo, err := gitrepo.OpenRepository(ctx, repo)
+ if err != nil {
+ return fmt.Errorf("openRepository: %w", err)
+ }
+ defer gitRepo.Close()
+
if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
return fmt.Errorf("SyncReleasesWithTags: %w", err)
}
diff --git a/services/repository/archiver/archiver_test.go b/services/repository/archiver/archiver_test.go
index 5deec259da..ec6e9dfac3 100644
--- a/services/repository/archiver/archiver_test.go
+++ b/services/repository/archiver/archiver_test.go
@@ -10,7 +10,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
+ "code.gitea.io/gitea/services/contexttest"
_ "code.gitea.io/gitea/models/actions"
diff --git a/services/repository/branch.go b/services/repository/branch.go
index 39be3984ae..b683553245 100644
--- a/services/repository/branch.go
+++ b/services/repository/branch.go
@@ -23,7 +23,6 @@ import (
"code.gitea.io/gitea/modules/queue"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"
files_service "code.gitea.io/gitea/services/repository/files"
@@ -52,7 +51,7 @@ type Branch struct {
}
// LoadBranches loads branches from the repository limited by page & pageSize.
-func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, isDeletedBranch util.OptionalBool, keyword string, page, pageSize int) (*Branch, []*Branch, int64, error) {
+func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, isDeletedBranch optional.Option[bool], keyword string, page, pageSize int) (*Branch, []*Branch, int64, error) {
defaultDBBranch, err := git_model.GetBranch(ctx, repo.ID, repo.DefaultBranch)
if err != nil {
return nil, nil, 0, err
@@ -60,7 +59,7 @@ func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git
branchOpts := git_model.FindBranchOptions{
RepoID: repo.ID,
- IsDeletedBranch: isDeletedBranch.ToGeneric(),
+ IsDeletedBranch: isDeletedBranch,
ListOptions: db.ListOptions{
Page: page,
PageSize: pageSize,
@@ -226,44 +225,91 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri
return err
}
-// syncBranchToDB sync the branch information in the database. It will try to update the branch first,
-// if updated success with affect records > 0, then all are done. Because that means the branch has been in the database.
-// If no record is affected, that means the branch does not exist in database. So there are two possibilities.
-// One is this is a new branch, then we just need to insert the record. Another is the branches haven't been synced,
-// then we need to sync all the branches into database.
-func syncBranchToDB(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) error {
- cnt, err := git_model.UpdateBranch(ctx, repoID, pusherID, branchName, commit)
- if err != nil {
- return fmt.Errorf("git_model.UpdateBranch %d:%s failed: %v", repoID, branchName, err)
- }
- if cnt > 0 { // This means branch does exist, so it's a normal update. It also means the branch has been synced.
- return nil
+// SyncBranchesToDB sync the branch information in the database.
+// It will check whether the branches of the repository have never been synced before.
+// If so, it will sync all branches of the repository.
+// Otherwise, it will sync the branches that need to be updated.
+func SyncBranchesToDB(ctx context.Context, repoID, pusherID int64, branchNames, commitIDs []string, getCommit func(commitID string) (*git.Commit, error)) error {
+ // Some designs that make the code look strange but are made for performance optimization purposes:
+ // 1. Sync branches in a batch to reduce the number of DB queries.
+ // 2. Lazy load commit information since it may be not necessary.
+ // 3. Exit early if synced all branches of git repo when there's no branch in DB.
+ // 4. Check the branches in DB if they are already synced.
+ //
+ // If the user pushes many branches at once, the Git hook will call the internal API in batches, rather than all at once.
+ // See https://github.com/go-gitea/gitea/blob/cb52b17f92e2d2293f7c003649743464492bca48/cmd/hook.go#L27
+ // For the first batch, it will hit optimization 3.
+ // For other batches, it will hit optimization 4.
+
+ if len(branchNames) != len(commitIDs) {
+ return fmt.Errorf("branchNames and commitIDs length not match")
}
- // if user haven't visit UI but directly push to a branch after upgrading from 1.20 -> 1.21,
- // we cannot simply insert the branch but need to check we have branches or not
- hasBranch, err := db.Exist[git_model.Branch](ctx, git_model.FindBranchOptions{
- RepoID: repoID,
- IsDeletedBranch: optional.Some(false),
- }.ToConds())
- if err != nil {
- return err
- }
- if !hasBranch {
- if _, err = repo_module.SyncRepoBranches(ctx, repoID, pusherID); err != nil {
- return fmt.Errorf("repo_module.SyncRepoBranches %d:%s failed: %v", repoID, branchName, err)
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ branches, err := git_model.GetBranches(ctx, repoID, branchNames)
+ if err != nil {
+ return fmt.Errorf("git_model.GetBranches: %v", err)
+ }
+
+ if len(branches) == 0 {
+ // if user haven't visit UI but directly push to a branch after upgrading from 1.20 -> 1.21,
+ // we cannot simply insert the branch but need to check we have branches or not
+ hasBranch, err := db.Exist[git_model.Branch](ctx, git_model.FindBranchOptions{
+ RepoID: repoID,
+ IsDeletedBranch: optional.Some(false),
+ }.ToConds())
+ if err != nil {
+ return err
+ }
+ if !hasBranch {
+ if _, err = repo_module.SyncRepoBranches(ctx, repoID, pusherID); err != nil {
+ return fmt.Errorf("repo_module.SyncRepoBranches %d failed: %v", repoID, err)
+ }
+ return nil
+ }
+ }
+
+ branchMap := make(map[string]*git_model.Branch, len(branches))
+ for _, branch := range branches {
+ branchMap[branch.Name] = branch
+ }
+
+ newBranches := make([]*git_model.Branch, 0, len(branchNames))
+
+ for i, branchName := range branchNames {
+ commitID := commitIDs[i]
+ branch, exist := branchMap[branchName]
+ if exist && branch.CommitID == commitID && !branch.IsDeleted {
+ continue
+ }
+
+ commit, err := getCommit(commitID)
+ if err != nil {
+ return fmt.Errorf("get commit of %s failed: %v", branchName, err)
+ }
+
+ if exist {
+ if _, err := git_model.UpdateBranch(ctx, repoID, pusherID, branchName, commit); err != nil {
+ return fmt.Errorf("git_model.UpdateBranch %d:%s failed: %v", repoID, branchName, err)
+ }
+ return nil
+ }
+
+ // if database have branches but not this branch, it means this is a new branch
+ newBranches = append(newBranches, &git_model.Branch{
+ RepoID: repoID,
+ Name: branchName,
+ CommitID: commit.ID.String(),
+ CommitMessage: commit.Summary(),
+ PusherID: pusherID,
+ CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
+ })
+ }
+
+ if len(newBranches) > 0 {
+ return db.Insert(ctx, newBranches)
}
return nil
- }
-
- // if database have branches but not this branch, it means this is a new branch
- return db.Insert(ctx, &git_model.Branch{
- RepoID: repoID,
- Name: branchName,
- CommitID: commit.ID.String(),
- CommitMessage: commit.Summary(),
- PusherID: pusherID,
- CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
})
}
@@ -318,7 +364,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m
}
if isDefault {
- err2 = gitRepo.SetDefaultBranch(to)
+ err2 = gitrepo.SetDefaultBranch(ctx, repo, to)
if err2 != nil {
return err2
}
diff --git a/services/repository/commit.go b/services/repository/commit.go
index 2497910a83..e8c0262ef4 100644
--- a/services/repository/commit.go
+++ b/services/repository/commit.go
@@ -7,8 +7,8 @@ import (
"context"
"fmt"
- gitea_ctx "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util"
+ gitea_ctx "code.gitea.io/gitea/services/context"
)
type ContainedLinks struct { // TODO: better name?
diff --git a/services/repository/commitstatus/commitstatus.go b/services/repository/commitstatus/commitstatus.go
new file mode 100644
index 0000000000..145fc7d53c
--- /dev/null
+++ b/services/repository/commitstatus/commitstatus.go
@@ -0,0 +1,135 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package commitstatus
+
+import (
+ "context"
+ "crypto/sha256"
+ "fmt"
+
+ "code.gitea.io/gitea/models/db"
+ git_model "code.gitea.io/gitea/models/git"
+ repo_model "code.gitea.io/gitea/models/repo"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/cache"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/log"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/automerge"
+)
+
+func getCacheKey(repoID int64, brancheName string) string {
+ hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%d:%s", repoID, brancheName)))
+ return fmt.Sprintf("commit_status:%x", hashBytes)
+}
+
+func updateCommitStatusCache(ctx context.Context, repoID int64, branchName string, status api.CommitStatusState) error {
+ c := cache.GetCache()
+ return c.Put(getCacheKey(repoID, branchName), string(status), 3*24*60)
+}
+
+func deleteCommitStatusCache(ctx context.Context, repoID int64, branchName string) error {
+ c := cache.GetCache()
+ return c.Delete(getCacheKey(repoID, branchName))
+}
+
+// CreateCommitStatus creates a new CommitStatus given a bunch of parameters
+// NOTE: All text-values will be trimmed from whitespaces.
+// Requires: Repo, Creator, SHA
+func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creator *user_model.User, sha string, status *git_model.CommitStatus) error {
+ repoPath := repo.RepoPath()
+
+ // confirm that commit is exist
+ gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
+ if err != nil {
+ return fmt.Errorf("OpenRepository[%s]: %w", repoPath, err)
+ }
+ defer closer.Close()
+
+ objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
+
+ commit, err := gitRepo.GetCommit(sha)
+ if err != nil {
+ return fmt.Errorf("GetCommit[%s]: %w", sha, err)
+ }
+ if len(sha) != objectFormat.FullLength() {
+ // use complete commit sha
+ sha = commit.ID.String()
+ }
+
+ if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{
+ Repo: repo,
+ Creator: creator,
+ SHA: commit.ID,
+ CommitStatus: status,
+ }); err != nil {
+ return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
+ }
+
+ defaultBranchCommit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
+ if err != nil {
+ return fmt.Errorf("GetBranchCommit[%s]: %w", repo.DefaultBranch, err)
+ }
+
+ if commit.ID.String() == defaultBranchCommit.ID.String() { // since one commit status updated, the combined commit status should be invalid
+ if err := deleteCommitStatusCache(ctx, repo.ID, repo.DefaultBranch); err != nil {
+ log.Error("deleteCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
+ }
+ }
+
+ if status.State.IsSuccess() {
+ if err := automerge.MergeScheduledPullRequest(ctx, sha, repo); err != nil {
+ return fmt.Errorf("MergeScheduledPullRequest[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
+ }
+ }
+
+ return nil
+}
+
+// FindReposLastestCommitStatuses loading repository default branch latest combinded commit status with cache
+func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) {
+ results := make([]*git_model.CommitStatus, len(repos))
+ c := cache.GetCache()
+
+ for i, repo := range repos {
+ status, ok := c.Get(getCacheKey(repo.ID, repo.DefaultBranch)).(string)
+ if ok && status != "" {
+ results[i] = &git_model.CommitStatus{State: api.CommitStatusState(status)}
+ }
+ }
+
+ // collect the latest commit of each repo
+ // at most there are dozens of repos (limited by MaxResponseItems), so it's not a big problem at the moment
+ repoBranchNames := make(map[int64]string, len(repos))
+ for i, repo := range repos {
+ if results[i] == nil {
+ repoBranchNames[repo.ID] = repo.DefaultBranch
+ }
+ }
+
+ repoIDsToLatestCommitSHAs, err := git_model.FindBranchesByRepoAndBranchName(ctx, repoBranchNames)
+ if err != nil {
+ return nil, fmt.Errorf("FindBranchesByRepoAndBranchName: %v", err)
+ }
+
+ // call the database O(1) times to get the commit statuses for all repos
+ repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoIDsToLatestCommitSHAs, db.ListOptionsAll)
+ if err != nil {
+ return nil, fmt.Errorf("GetLatestCommitStatusForPairs: %v", err)
+ }
+
+ for i, repo := range repos {
+ if results[i] == nil {
+ results[i] = git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID])
+ if results[i].State != "" {
+ if err := updateCommitStatusCache(ctx, repo.ID, repo.DefaultBranch, results[i].State); err != nil {
+ log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
+ }
+ }
+ }
+ }
+
+ return results, nil
+}
diff --git a/services/repository/create.go b/services/repository/create.go
index c3b50ae747..d092d02a1f 100644
--- a/services/repository/create.go
+++ b/services/repository/create.go
@@ -157,7 +157,7 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re
}
// Apply changes and commit.
- if err = repo_module.InitRepoCommit(ctx, tmpDir, repo, u, opts.DefaultBranch); err != nil {
+ if err = initRepoCommit(ctx, tmpDir, repo, u, opts.DefaultBranch); err != nil {
return fmt.Errorf("initRepoCommit: %w", err)
}
}
@@ -177,12 +177,7 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re
if len(opts.DefaultBranch) > 0 {
repo.DefaultBranch = opts.DefaultBranch
- gitRepo, err := gitrepo.OpenRepository(ctx, repo)
- if err != nil {
- return fmt.Errorf("openRepository: %w", err)
- }
- defer gitRepo.Close()
- if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
+ if err = gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil {
return fmt.Errorf("setDefaultBranch: %w", err)
}
diff --git a/services/repository/files/commit.go b/services/repository/files/commit.go
index 512aec7c81..e0dad29273 100644
--- a/services/repository/files/commit.go
+++ b/services/repository/files/commit.go
@@ -5,61 +5,13 @@ package files
import (
"context"
- "fmt"
asymkey_model "code.gitea.io/gitea/models/asymkey"
- git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
- user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/services/automerge"
)
-// CreateCommitStatus creates a new CommitStatus given a bunch of parameters
-// NOTE: All text-values will be trimmed from whitespaces.
-// Requires: Repo, Creator, SHA
-func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creator *user_model.User, sha string, status *git_model.CommitStatus) error {
- repoPath := repo.RepoPath()
-
- // confirm that commit is exist
- gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
- if err != nil {
- return fmt.Errorf("OpenRepository[%s]: %w", repoPath, err)
- }
- defer closer.Close()
-
- objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
-
- commit, err := gitRepo.GetCommit(sha)
- if err != nil {
- gitRepo.Close()
- return fmt.Errorf("GetCommit[%s]: %w", sha, err)
- } else if len(sha) != objectFormat.FullLength() {
- // use complete commit sha
- sha = commit.ID.String()
- }
- gitRepo.Close()
-
- if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{
- Repo: repo,
- Creator: creator,
- SHA: commit.ID,
- CommitStatus: status,
- }); err != nil {
- return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
- }
-
- if status.State.IsSuccess() {
- if err := automerge.MergeScheduledPullRequest(ctx, sha, repo); err != nil {
- return fmt.Errorf("MergeScheduledPullRequest[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
- }
- }
-
- return nil
-}
-
// CountDivergingCommits determines how many commits a branch is ahead or behind the repository's base branch
func CountDivergingCommits(ctx context.Context, repo *repo_model.Repository, branch string) (*git.DivergeObject, error) {
divergence, err := git.GetDivergingCommits(ctx, repo.RepoPath(), repo.DefaultBranch, branch)
diff --git a/services/repository/files/content_test.go b/services/repository/files/content_test.go
index d50847789a..4811f9d327 100644
--- a/services/repository/files/content_test.go
+++ b/services/repository/files/content_test.go
@@ -7,9 +7,9 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/gitrepo"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/contexttest"
_ "code.gitea.io/gitea/models/actions"
diff --git a/services/repository/files/diff_test.go b/services/repository/files/diff_test.go
index fbd2f3e70f..7cec979d72 100644
--- a/services/repository/files/diff_test.go
+++ b/services/repository/files/diff_test.go
@@ -8,8 +8,8 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/services/contexttest"
"code.gitea.io/gitea/services/gitdiff"
"github.com/stretchr/testify/assert"
diff --git a/services/repository/files/file_test.go b/services/repository/files/file_test.go
index 675ddbddb3..a5b3aad91e 100644
--- a/services/repository/files/file_test.go
+++ b/services/repository/files/file_test.go
@@ -7,10 +7,10 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
diff --git a/services/repository/files/search.go b/services/repository/files/search.go
index f8317c4892..09c3ab5bf3 100644
--- a/services/repository/files/search.go
+++ b/services/repository/files/search.go
@@ -16,14 +16,18 @@ import (
)
type Result struct {
- RepoID int64 // ignored
- Filename string
- CommitID string // branch
- UpdatedUnix timeutil.TimeStamp // ignored
- Language string
- Color string
- LineNumbers []int64
- FormattedLines template.HTML
+ RepoID int64 // ignored
+ Filename string
+ CommitID string // branch
+ UpdatedUnix timeutil.TimeStamp // ignored
+ Language string
+ Color string
+ Lines []ResultLine
+}
+
+type ResultLine struct {
+ Num int64
+ FormattedContent template.HTML
}
const pHEAD = "HEAD:"
@@ -46,7 +50,8 @@ func NewRepoGrep(ctx context.Context, repo *repo_model.Repository, keyword strin
"-n", // line nums
"-i", // ignore case
"--full-name", // full file path, rel to repo
- //"--column", // for adding better highlighting support
+ //"--column", // for adding better highlighting support
+ "-e", // for queries starting with "-"
).
AddDynamicArguments(keyword).
AddArguments("HEAD").
@@ -57,6 +62,8 @@ func NewRepoGrep(ctx context.Context, repo *repo_model.Repository, keyword strin
for _, block := range strings.Split(stdout, "\n\n") {
res := Result{CommitID: repo.DefaultBranch}
+
+ linenum := []int64{}
code := []string{}
for _, line := range strings.Split(block, "\n") {
@@ -71,18 +78,32 @@ func NewRepoGrep(ctx context.Context, repo *repo_model.Repository, keyword strin
continue
}
- res.LineNumbers = append(res.LineNumbers, i)
+ linenum = append(linenum, i)
code = append(code, after)
}
}
- if res.Filename == "" || len(code) == 0 || len(res.LineNumbers) == 0 {
+ if res.Filename == "" || len(code) == 0 || len(linenum) == 0 {
continue
}
- res.FormattedLines, res.Language = highlight.Code(res.Filename, "", strings.Join(code, "\n"))
+ var hl template.HTML
+
+ hl, res.Language = highlight.Code(res.Filename, "", strings.Join(code, "\n"))
res.Color = enry.GetColor(res.Language)
+ hlCode := strings.Split(string(hl), "\n")
+ n := min(len(hlCode), len(linenum))
+
+ res.Lines = make([]ResultLine, n)
+
+ for i := 0; i < n; i++ {
+ res.Lines[i] = ResultLine{
+ Num: linenum[i],
+ FormattedContent: template.HTML(hlCode[i]),
+ }
+ }
+
data = append(data, &res)
}
diff --git a/services/repository/files/search_test.go b/services/repository/files/search_test.go
index c24bb731a8..2f2f87368d 100644
--- a/services/repository/files/search_test.go
+++ b/services/repository/files/search_test.go
@@ -4,7 +4,7 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
@@ -25,14 +25,16 @@ func TestNewRepoGrep(t *testing.T) {
expected := []*Result{
{
- RepoID: 0,
- Filename: "README.md",
- CommitID: "master",
- UpdatedUnix: 0,
- Language: "Markdown",
- Color: "#083fa1",
- LineNumbers: []int64{2, 3},
- FormattedLines: "\nDescription for repo1",
+ RepoID: 0,
+ Filename: "README.md",
+ CommitID: "master",
+ UpdatedUnix: 0,
+ Language: "Markdown",
+ Color: "#083fa1",
+ Lines: []ResultLine{
+ {Num: 2, FormattedContent: ""},
+ {Num: 3, FormattedContent: "Description for repo1"},
+ },
},
}
diff --git a/services/repository/files/tree_test.go b/services/repository/files/tree_test.go
index 528ef500df..508f20090d 100644
--- a/services/repository/files/tree_test.go
+++ b/services/repository/files/tree_test.go
@@ -7,8 +7,8 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
diff --git a/modules/repository/generate.go b/services/repository/generate.go
similarity index 94%
rename from modules/repository/generate.go
rename to services/repository/generate.go
index f622383bb5..9b09e271ab 100644
--- a/modules/repository/generate.go
+++ b/services/repository/generate.go
@@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
+ repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob"
@@ -242,7 +243,7 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
defaultBranch = templateRepo.DefaultBranch
}
- return InitRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch)
+ return initRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch)
}
func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository) (err error) {
@@ -271,12 +272,7 @@ func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *r
repo.DefaultBranch = templateRepo.DefaultBranch
}
- gitRepo, err := gitrepo.OpenRepository(ctx, repo)
- if err != nil {
- return fmt.Errorf("openRepository: %w", err)
- }
- defer gitRepo.Close()
- if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
+ if err = gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil {
return fmt.Errorf("setDefaultBranch: %w", err)
}
if err = UpdateRepository(ctx, repo, false); err != nil {
@@ -292,7 +288,7 @@ func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_mo
return err
}
- if err := UpdateRepoSize(ctx, generateRepo); err != nil {
+ if err := repo_module.UpdateRepoSize(ctx, generateRepo); err != nil {
return fmt.Errorf("failed to update size for repository: %w", err)
}
@@ -323,8 +319,8 @@ func (gro GenerateRepoOptions) IsValid() bool {
gro.IssueLabels || gro.ProtectedBranch // or other items as they are added
}
-// GenerateRepository generates a repository from a template
-func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts GenerateRepoOptions) (_ *repo_model.Repository, err error) {
+// generateRepository generates a repository from a template
+func generateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts GenerateRepoOptions) (_ *repo_model.Repository, err error) {
generateRepo := &repo_model.Repository{
OwnerID: owner.ID,
Owner: owner,
@@ -341,7 +337,7 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
ObjectFormatName: templateRepo.ObjectFormatName,
}
- if err = CreateRepositoryByExample(ctx, doer, owner, generateRepo, false, false); err != nil {
+ if err = repo_module.CreateRepositoryByExample(ctx, doer, owner, generateRepo, false, false); err != nil {
return nil, err
}
@@ -358,11 +354,11 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
}
}
- if err = CheckInitRepository(ctx, owner.Name, generateRepo.Name, generateRepo.ObjectFormatName); err != nil {
+ if err = repo_module.CheckInitRepository(ctx, owner.Name, generateRepo.Name, generateRepo.ObjectFormatName); err != nil {
return generateRepo, err
}
- if err = CheckDaemonExportOK(ctx, generateRepo); err != nil {
+ if err = repo_module.CheckDaemonExportOK(ctx, generateRepo); err != nil {
return generateRepo, fmt.Errorf("checkDaemonExportOK: %w", err)
}
diff --git a/modules/repository/generate_test.go b/services/repository/generate_test.go
similarity index 100%
rename from modules/repository/generate_test.go
rename to services/repository/generate_test.go
diff --git a/services/repository/init.go b/services/repository/init.go
new file mode 100644
index 0000000000..817fa4abd7
--- /dev/null
+++ b/services/repository/init.go
@@ -0,0 +1,83 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repository
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "time"
+
+ repo_model "code.gitea.io/gitea/models/repo"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
+ repo_module "code.gitea.io/gitea/modules/repository"
+ "code.gitea.io/gitea/modules/setting"
+ asymkey_service "code.gitea.io/gitea/services/asymkey"
+)
+
+// initRepoCommit temporarily changes with work directory.
+func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Repository, u *user_model.User, defaultBranch string) (err error) {
+ commitTimeStr := time.Now().Format(time.RFC3339)
+
+ sig := u.NewGitSig()
+ // Because this may call hooks we should pass in the environment
+ env := append(os.Environ(),
+ "GIT_AUTHOR_NAME="+sig.Name,
+ "GIT_AUTHOR_EMAIL="+sig.Email,
+ "GIT_AUTHOR_DATE="+commitTimeStr,
+ "GIT_COMMITTER_DATE="+commitTimeStr,
+ )
+ committerName := sig.Name
+ committerEmail := sig.Email
+
+ if stdout, _, err := git.NewCommand(ctx, "add", "--all").
+ SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)).
+ RunStdString(&git.RunOpts{Dir: tmpPath}); err != nil {
+ log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err)
+ return fmt.Errorf("git add --all: %w", err)
+ }
+
+ cmd := git.NewCommand(ctx, "commit", "--message=Initial commit").
+ AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)
+
+ sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u)
+ if sign {
+ cmd.AddOptionFormat("-S%s", keyID)
+
+ if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
+ // need to set the committer to the KeyID owner
+ committerName = signer.Name
+ committerEmail = signer.Email
+ }
+ } else {
+ cmd.AddArguments("--no-gpg-sign")
+ }
+
+ env = append(env,
+ "GIT_COMMITTER_NAME="+committerName,
+ "GIT_COMMITTER_EMAIL="+committerEmail,
+ )
+
+ if stdout, _, err := cmd.
+ SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)).
+ RunStdString(&git.RunOpts{Dir: tmpPath, Env: env}); err != nil {
+ log.Error("Failed to commit: %v: Stdout: %s\nError: %v", cmd.String(), stdout, err)
+ return fmt.Errorf("git commit: %w", err)
+ }
+
+ if len(defaultBranch) == 0 {
+ defaultBranch = setting.Repository.DefaultBranch
+ }
+
+ if stdout, _, err := git.NewCommand(ctx, "push", "origin").AddDynamicArguments("HEAD:" + defaultBranch).
+ SetDescription(fmt.Sprintf("initRepoCommit (git push): %s", tmpPath)).
+ RunStdString(&git.RunOpts{Dir: tmpPath, Env: repo_module.InternalPushingEnvironment(u, repo)}); err != nil {
+ log.Error("Failed to push back to HEAD: Stdout: %s\nError: %v", stdout, err)
+ return fmt.Errorf("git push: %w", err)
+ }
+
+ return nil
+}
diff --git a/services/repository/migrate.go b/services/repository/migrate.go
new file mode 100644
index 0000000000..5800f2b5cb
--- /dev/null
+++ b/services/repository/migrate.go
@@ -0,0 +1,288 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repository
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/organization"
+ repo_model "code.gitea.io/gitea/models/repo"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/lfs"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/migration"
+ repo_module "code.gitea.io/gitea/modules/repository"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/util"
+)
+
+// MigrateRepositoryGitData starts migrating git related data after created migrating repository
+func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
+ repo *repo_model.Repository, opts migration.MigrateOptions,
+ httpTransport *http.Transport,
+) (*repo_model.Repository, error) {
+ repoPath := repo_model.RepoPath(u.Name, opts.RepoName)
+
+ if u.IsOrganization() {
+ t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx)
+ if err != nil {
+ return nil, err
+ }
+ repo.NumWatches = t.NumMembers
+ } else {
+ repo.NumWatches = 1
+ }
+
+ migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second
+
+ var err error
+ if err = util.RemoveAll(repoPath); err != nil {
+ return repo, fmt.Errorf("Failed to remove %s: %w", repoPath, err)
+ }
+
+ if err = git.Clone(ctx, opts.CloneAddr, repoPath, git.CloneRepoOptions{
+ Mirror: true,
+ Quiet: true,
+ Timeout: migrateTimeout,
+ SkipTLSVerify: setting.Migrations.SkipTLSVerify,
+ }); err != nil {
+ if errors.Is(err, context.DeadlineExceeded) {
+ return repo, fmt.Errorf("Clone timed out. Consider increasing [git.timeout] MIGRATE in app.ini. Underlying Error: %w", err)
+ }
+ return repo, fmt.Errorf("Clone: %w", err)
+ }
+
+ if err := git.WriteCommitGraph(ctx, repoPath); err != nil {
+ return repo, err
+ }
+
+ if opts.Wiki {
+ wikiPath := repo_model.WikiPath(u.Name, opts.RepoName)
+ wikiRemotePath := repo_module.WikiRemoteURL(ctx, opts.CloneAddr)
+ if len(wikiRemotePath) > 0 {
+ if err := util.RemoveAll(wikiPath); err != nil {
+ return repo, fmt.Errorf("Failed to remove %s: %w", wikiPath, err)
+ }
+
+ if err := git.Clone(ctx, wikiRemotePath, wikiPath, git.CloneRepoOptions{
+ Mirror: true,
+ Quiet: true,
+ Timeout: migrateTimeout,
+ SkipTLSVerify: setting.Migrations.SkipTLSVerify,
+ }); err != nil {
+ log.Warn("Clone wiki: %v", err)
+ if err := util.RemoveAll(wikiPath); err != nil {
+ return repo, fmt.Errorf("Failed to remove %s: %w", wikiPath, err)
+ }
+ } else {
+ // Figure out the branch of the wiki we just cloned. We assume
+ // that the default branch is to be used, and we'll use the same
+ // name as the source.
+ gitRepo, err := git.OpenRepository(ctx, wikiPath)
+ if err != nil {
+ log.Warn("Failed to open wiki repository during migration: %v", err)
+ if err := util.RemoveAll(wikiPath); err != nil {
+ return repo, fmt.Errorf("Failed to remove %s: %w", wikiPath, err)
+ }
+ return repo, err
+ }
+ defer gitRepo.Close()
+
+ branch, err := gitrepo.GetDefaultBranch(ctx, repo)
+ if err != nil {
+ log.Warn("Failed to get the default branch of a migrated wiki repo: %v", err)
+ if err := util.RemoveAll(wikiPath); err != nil {
+ return repo, fmt.Errorf("Failed to remove %s: %w", wikiPath, err)
+ }
+
+ return repo, err
+ }
+ repo.WikiBranch = branch
+
+ if err := git.WriteCommitGraph(ctx, wikiPath); err != nil {
+ return repo, err
+ }
+ }
+ }
+ }
+
+ if repo.OwnerID == u.ID {
+ repo.Owner = u
+ }
+
+ if err = repo_module.CheckDaemonExportOK(ctx, repo); err != nil {
+ return repo, fmt.Errorf("checkDaemonExportOK: %w", err)
+ }
+
+ if stdout, _, err := git.NewCommand(ctx, "update-server-info").
+ SetDescription(fmt.Sprintf("MigrateRepositoryGitData(git update-server-info): %s", repoPath)).
+ RunStdString(&git.RunOpts{Dir: repoPath}); err != nil {
+ log.Error("MigrateRepositoryGitData(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
+ return repo, fmt.Errorf("error in MigrateRepositoryGitData(git update-server-info): %w", err)
+ }
+
+ gitRepo, err := git.OpenRepository(ctx, repoPath)
+ if err != nil {
+ return repo, fmt.Errorf("OpenRepository: %w", err)
+ }
+ defer gitRepo.Close()
+
+ repo.IsEmpty, err = gitRepo.IsEmpty()
+ if err != nil {
+ return repo, fmt.Errorf("git.IsEmpty: %w", err)
+ }
+
+ if !repo.IsEmpty {
+ if len(repo.DefaultBranch) == 0 {
+ // Try to get HEAD branch and set it as default branch.
+ headBranch, err := gitRepo.GetHEADBranch()
+ if err != nil {
+ return repo, fmt.Errorf("GetHEADBranch: %w", err)
+ }
+ if headBranch != nil {
+ repo.DefaultBranch = headBranch.Name
+ }
+ }
+
+ if _, err := repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, u.ID); err != nil {
+ return repo, fmt.Errorf("SyncRepoBranchesWithRepo: %v", err)
+ }
+
+ if !opts.Releases {
+ // note: this will greatly improve release (tag) sync
+ // for pull-mirrors with many tags
+ repo.IsMirror = opts.Mirror
+ if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
+ log.Error("Failed to synchronize tags to releases for repository: %v", err)
+ }
+ }
+
+ if opts.LFS {
+ endpoint := lfs.DetermineEndpoint(opts.CloneAddr, opts.LFSEndpoint)
+ lfsClient := lfs.NewClient(endpoint, httpTransport)
+ if err = repo_module.StoreMissingLfsObjectsInRepository(ctx, repo, gitRepo, lfsClient); err != nil {
+ log.Error("Failed to store missing LFS objects for repository: %v", err)
+ }
+ }
+ }
+
+ ctx, committer, err := db.TxContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ defer committer.Close()
+
+ if opts.Mirror {
+ remoteAddress, err := util.SanitizeURL(opts.CloneAddr)
+ if err != nil {
+ return repo, err
+ }
+ mirrorModel := repo_model.Mirror{
+ RepoID: repo.ID,
+ Interval: setting.Mirror.DefaultInterval,
+ EnablePrune: true,
+ NextUpdateUnix: timeutil.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval),
+ LFS: opts.LFS,
+ RemoteAddress: remoteAddress,
+ }
+ if opts.LFS {
+ mirrorModel.LFSEndpoint = opts.LFSEndpoint
+ }
+
+ if opts.MirrorInterval != "" {
+ parsedInterval, err := time.ParseDuration(opts.MirrorInterval)
+ if err != nil {
+ log.Error("Failed to set Interval: %v", err)
+ return repo, err
+ }
+ if parsedInterval == 0 {
+ mirrorModel.Interval = 0
+ mirrorModel.NextUpdateUnix = 0
+ } else if parsedInterval < setting.Mirror.MinInterval {
+ err := fmt.Errorf("interval %s is set below Minimum Interval of %s", parsedInterval, setting.Mirror.MinInterval)
+ log.Error("Interval: %s is too frequent", opts.MirrorInterval)
+ return repo, err
+ } else {
+ mirrorModel.Interval = parsedInterval
+ mirrorModel.NextUpdateUnix = timeutil.TimeStampNow().AddDuration(parsedInterval)
+ }
+ }
+
+ if err = repo_model.InsertMirror(ctx, &mirrorModel); err != nil {
+ return repo, fmt.Errorf("InsertOne: %w", err)
+ }
+
+ repo.IsMirror = true
+ if err = UpdateRepository(ctx, repo, false); err != nil {
+ return nil, err
+ }
+
+ // this is necessary for sync local tags from remote
+ configName := fmt.Sprintf("remote.%s.fetch", mirrorModel.GetRemoteName())
+ if stdout, _, err := git.NewCommand(ctx, "config").
+ AddOptionValues("--add", configName, `+refs/tags/*:refs/tags/*`).
+ RunStdString(&git.RunOpts{Dir: repoPath}); err != nil {
+ log.Error("MigrateRepositoryGitData(git config --add +refs/tags/*:refs/tags/*) in %v: Stdout: %s\nError: %v", repo, stdout, err)
+ return repo, fmt.Errorf("error in MigrateRepositoryGitData(git config --add +refs/tags/*:refs/tags/*): %w", err)
+ }
+ } else {
+ if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
+ log.Error("Failed to update size for repository: %v", err)
+ }
+ if repo, err = CleanUpMigrateInfo(ctx, repo); err != nil {
+ return nil, err
+ }
+ }
+
+ return repo, committer.Commit()
+}
+
+// cleanUpMigrateGitConfig removes mirror info which prevents "push --all".
+// This also removes possible user credentials.
+func cleanUpMigrateGitConfig(ctx context.Context, repoPath string) error {
+ cmd := git.NewCommand(ctx, "remote", "rm", "origin")
+ // if the origin does not exist
+ _, stderr, err := cmd.RunStdString(&git.RunOpts{
+ Dir: repoPath,
+ })
+ if err != nil && !strings.HasPrefix(stderr, "fatal: No such remote") {
+ return err
+ }
+ return nil
+}
+
+// CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors.
+func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo_model.Repository, error) {
+ repoPath := repo.RepoPath()
+ if err := repo_module.CreateDelegateHooks(repoPath); err != nil {
+ return repo, fmt.Errorf("createDelegateHooks: %w", err)
+ }
+ if repo.HasWiki() {
+ if err := repo_module.CreateDelegateHooks(repo.WikiPath()); err != nil {
+ return repo, fmt.Errorf("createDelegateHooks.(wiki): %w", err)
+ }
+ }
+
+ _, _, err := git.NewCommand(ctx, "remote", "rm", "origin").RunStdString(&git.RunOpts{Dir: repoPath})
+ if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") {
+ return repo, fmt.Errorf("CleanUpMigrateInfo: %w", err)
+ }
+
+ if repo.HasWiki() {
+ if err := cleanUpMigrateGitConfig(ctx, repo.WikiPath()); err != nil {
+ return repo, fmt.Errorf("cleanUpMigrateGitConfig (wiki): %w", err)
+ }
+ }
+
+ return repo, UpdateRepository(ctx, repo, false)
+}
diff --git a/services/repository/push.go b/services/repository/push.go
index bb080e30cc..8b27f07196 100644
--- a/services/repository/push.go
+++ b/services/repository/push.go
@@ -11,7 +11,6 @@ import (
"time"
"code.gitea.io/gitea/models/db"
- git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/cache"
@@ -183,7 +182,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
repo.DefaultBranch = refName
repo.IsEmpty = false
if repo.DefaultBranch != setting.Repository.DefaultBranch {
- if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
+ if err := gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil {
if !git.IsErrUnsupportedVersion(err) {
return err
}
@@ -259,10 +258,6 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
commits.Commits = commits.Commits[:setting.UI.FeedMaxCommitNum]
}
- if err = syncBranchToDB(ctx, repo.ID, opts.PusherID, branch, newCommit); err != nil {
- return fmt.Errorf("git_model.UpdateBranch %s:%s failed: %v", repo.FullName(), branch, err)
- }
-
notify_service.PushCommits(ctx, pusher, repo, opts, commits)
// Cache for big repository
@@ -275,10 +270,6 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
// close all related pulls
log.Error("close related pull request failed: %v", err)
}
-
- if err := git_model.AddDeletedBranch(ctx, repo.ID, branch, pusher.ID); err != nil {
- return fmt.Errorf("AddDeletedBranch %s:%s failed: %v", repo.FullName(), branch, err)
- }
}
// Even if user delete a branch on a repository which he didn't watch, he will be watch that.
diff --git a/services/repository/template.go b/services/repository/template.go
index 06cf05026f..36a680c8e2 100644
--- a/services/repository/template.go
+++ b/services/repository/template.go
@@ -11,7 +11,6 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- repo_module "code.gitea.io/gitea/modules/repository"
notify_service "code.gitea.io/gitea/services/notify"
)
@@ -63,7 +62,7 @@ func GenerateProtectedBranch(ctx context.Context, templateRepo, generateRepo *re
}
// GenerateRepository generates a repository from a template
-func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts repo_module.GenerateRepoOptions) (_ *repo_model.Repository, err error) {
+func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts GenerateRepoOptions) (_ *repo_model.Repository, err error) {
if !doer.IsAdmin && !owner.CanCreateRepo() {
return nil, repo_model.ErrReachLimitOfRepo{
Limit: owner.MaxRepoCreation,
@@ -72,14 +71,14 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
var generateRepo *repo_model.Repository
if err = db.WithTx(ctx, func(ctx context.Context) error {
- generateRepo, err = repo_module.GenerateRepository(ctx, doer, owner, templateRepo, opts)
+ generateRepo, err = generateRepository(ctx, doer, owner, templateRepo, opts)
if err != nil {
return err
}
// Git Content
if opts.GitContent && !templateRepo.IsEmpty {
- if err = repo_module.GenerateGitContent(ctx, templateRepo, generateRepo); err != nil {
+ if err = GenerateGitContent(ctx, templateRepo, generateRepo); err != nil {
return err
}
}
diff --git a/services/user/email.go b/services/user/email.go
index 0b579cf792..9dc5270842 100644
--- a/services/user/email.go
+++ b/services/user/email.go
@@ -14,12 +14,13 @@ import (
"code.gitea.io/gitea/modules/util"
)
-func AddOrSetPrimaryEmailAddress(ctx context.Context, u *user_model.User, emailStr string) error {
+// AdminAddOrSetPrimaryEmailAddress is used by admins to add or set a user's primary email address
+func AdminAddOrSetPrimaryEmailAddress(ctx context.Context, u *user_model.User, emailStr string) error {
if strings.EqualFold(u.Email, emailStr) {
return nil
}
- if err := user_model.ValidateEmail(emailStr); err != nil {
+ if err := user_model.ValidateEmailForAdmin(emailStr); err != nil {
return err
}
diff --git a/services/user/email_test.go b/services/user/email_test.go
index 66d4821346..0784b4f803 100644
--- a/services/user/email_test.go
+++ b/services/user/email_test.go
@@ -10,11 +10,13 @@ import (
organization_model "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/setting"
+ "github.com/gobwas/glob"
"github.com/stretchr/testify/assert"
)
-func TestAddOrSetPrimaryEmailAddress(t *testing.T) {
+func TestAdminAddOrSetPrimaryEmailAddress(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 27})
@@ -28,7 +30,7 @@ func TestAddOrSetPrimaryEmailAddress(t *testing.T) {
assert.NotEqual(t, "new-primary@example.com", primary.Email)
assert.Equal(t, user.Email, primary.Email)
- assert.NoError(t, AddOrSetPrimaryEmailAddress(db.DefaultContext, user, "new-primary@example.com"))
+ assert.NoError(t, AdminAddOrSetPrimaryEmailAddress(db.DefaultContext, user, "new-primary@example.com"))
primary, err = user_model.GetPrimaryEmailAddressOfUser(db.DefaultContext, user.ID)
assert.NoError(t, err)
@@ -39,7 +41,19 @@ func TestAddOrSetPrimaryEmailAddress(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, emails, 2)
- assert.NoError(t, AddOrSetPrimaryEmailAddress(db.DefaultContext, user, "user27@example.com"))
+ setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("example.org")}
+ defer func() {
+ setting.Service.EmailDomainAllowList = []glob.Glob{}
+ }()
+
+ assert.NoError(t, AdminAddOrSetPrimaryEmailAddress(db.DefaultContext, user, "new-primary2@example2.com"))
+
+ primary, err = user_model.GetPrimaryEmailAddressOfUser(db.DefaultContext, user.ID)
+ assert.NoError(t, err)
+ assert.Equal(t, "new-primary2@example2.com", primary.Email)
+ assert.Equal(t, user.Email, primary.Email)
+
+ assert.NoError(t, AdminAddOrSetPrimaryEmailAddress(db.DefaultContext, user, "user27@example.com"))
primary, err = user_model.GetPrimaryEmailAddressOfUser(db.DefaultContext, user.ID)
assert.NoError(t, err)
@@ -48,7 +62,7 @@ func TestAddOrSetPrimaryEmailAddress(t *testing.T) {
emails, err = user_model.GetEmailAddresses(db.DefaultContext, user.ID)
assert.NoError(t, err)
- assert.Len(t, emails, 2)
+ assert.Len(t, emails, 3)
}
func TestReplacePrimaryEmailAddress(t *testing.T) {
diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go
index 16819f846a..32f1a3de45 100644
--- a/services/webhook/deliver.go
+++ b/services/webhook/deliver.go
@@ -32,36 +32,17 @@ import (
"github.com/gobwas/glob"
)
-// Deliver deliver hook task
-func Deliver(ctx context.Context, t *webhook_model.HookTask) error {
- w, err := webhook_model.GetWebhookByID(ctx, t.HookID)
- if err != nil {
- return err
- }
-
- defer func() {
- err := recover()
- if err == nil {
- return
- }
- // There was a panic whilst delivering a hook...
- log.Error("PANIC whilst trying to deliver webhook task[%d] to webhook %s Panic: %v\nStacktrace: %s", t.ID, w.URL, err, log.Stack(2))
- }()
-
- t.IsDelivered = true
-
- var req *http.Request
-
+func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (req *http.Request, body []byte, err error) {
switch w.HTTPMethod {
case "":
- log.Info("HTTP Method for webhook %s empty, setting to POST as default", w.URL)
+ log.Info("HTTP Method for %s webhook %s [ID: %d] is not set, defaulting to POST", w.Type, w.URL, w.ID)
fallthrough
case http.MethodPost:
switch w.ContentType {
case webhook_model.ContentTypeJSON:
req, err = http.NewRequest("POST", w.URL, strings.NewReader(t.PayloadContent))
if err != nil {
- return err
+ return nil, nil, err
}
req.Header.Set("Content-Type", "application/json")
@@ -72,50 +53,58 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error {
req, err = http.NewRequest("POST", w.URL, strings.NewReader(forms.Encode()))
if err != nil {
- return err
+ return nil, nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ default:
+ return nil, nil, fmt.Errorf("invalid content type: %v", w.ContentType)
}
case http.MethodGet:
u, err := url.Parse(w.URL)
if err != nil {
- return fmt.Errorf("unable to deliver webhook task[%d] as cannot parse webhook url %s: %w", t.ID, w.URL, err)
+ return nil, nil, fmt.Errorf("invalid URL: %w", err)
}
vals := u.Query()
vals["payload"] = []string{t.PayloadContent}
u.RawQuery = vals.Encode()
req, err = http.NewRequest("GET", u.String(), nil)
if err != nil {
- return fmt.Errorf("unable to deliver webhook task[%d] as unable to create HTTP request for webhook url %s: %w", t.ID, w.URL, err)
+ return nil, nil, err
}
case http.MethodPut:
switch w.Type {
- case webhook_module.MATRIX:
+ case webhook_module.MATRIX: // used when t.Version == 1
txnID, err := getMatrixTxnID([]byte(t.PayloadContent))
if err != nil {
- return err
+ return nil, nil, err
}
url := fmt.Sprintf("%s/%s", w.URL, url.PathEscape(txnID))
req, err = http.NewRequest("PUT", url, strings.NewReader(t.PayloadContent))
if err != nil {
- return fmt.Errorf("unable to deliver webhook task[%d] as cannot create matrix request for webhook url %s: %w", t.ID, w.URL, err)
+ return nil, nil, err
}
default:
- return fmt.Errorf("invalid http method for webhook task[%d] in webhook %s: %v", t.ID, w.URL, w.HTTPMethod)
+ return nil, nil, fmt.Errorf("invalid http method: %v", w.HTTPMethod)
}
default:
- return fmt.Errorf("invalid http method for webhook task[%d] in webhook %s: %v", t.ID, w.URL, w.HTTPMethod)
+ return nil, nil, fmt.Errorf("invalid http method: %v", w.HTTPMethod)
}
+ body = []byte(t.PayloadContent)
+ return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body)
+}
+
+func addDefaultHeaders(req *http.Request, secret []byte, t *webhook_model.HookTask, payloadContent []byte) error {
var signatureSHA1 string
var signatureSHA256 string
- if len(w.Secret) > 0 {
- sig1 := hmac.New(sha1.New, []byte(w.Secret))
- sig256 := hmac.New(sha256.New, []byte(w.Secret))
- _, err = io.MultiWriter(sig1, sig256).Write([]byte(t.PayloadContent))
+ if len(secret) > 0 {
+ sig1 := hmac.New(sha1.New, secret)
+ sig256 := hmac.New(sha256.New, secret)
+ _, err := io.MultiWriter(sig1, sig256).Write(payloadContent)
if err != nil {
- log.Error("prepareWebhooks.sigWrite: %v", err)
+ // this error should never happen, since the hashes are writing to []byte and always return a nil error.
+ return fmt.Errorf("prepareWebhooks.sigWrite: %w", err)
}
signatureSHA1 = hex.EncodeToString(sig1.Sum(nil))
signatureSHA256 = hex.EncodeToString(sig256.Sum(nil))
@@ -140,15 +129,36 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error {
req.Header["X-GitHub-Delivery"] = []string{t.UUID}
req.Header["X-GitHub-Event"] = []string{event}
req.Header["X-GitHub-Event-Type"] = []string{eventType}
+ return nil
+}
- // Add Authorization Header
- authorization, err := w.HeaderAuthorization()
+// Deliver creates the [http.Request] (depending on the webhook type), sends it
+// and records the status and response.
+func Deliver(ctx context.Context, t *webhook_model.HookTask) error {
+ w, err := webhook_model.GetWebhookByID(ctx, t.HookID)
if err != nil {
- log.Error("Webhook could not get Authorization header [%d]: %v", w.ID, err)
return err
}
- if authorization != "" {
- req.Header["Authorization"] = []string{authorization}
+
+ defer func() {
+ err := recover()
+ if err == nil {
+ return
+ }
+ // There was a panic whilst delivering a hook...
+ log.Error("PANIC whilst trying to deliver webhook task[%d] to webhook %s Panic: %v\nStacktrace: %s", t.ID, w.URL, err, log.Stack(2))
+ }()
+
+ t.IsDelivered = true
+
+ newRequest := webhookRequesters[w.Type]
+ if t.PayloadVersion == 1 || newRequest == nil {
+ newRequest = newDefaultRequest
+ }
+
+ req, body, err := newRequest(ctx, w, t)
+ if err != nil {
+ return fmt.Errorf("cannot create http request for webhook %s[%d %s]: %w", w.Type, w.ID, w.URL, err)
}
// Record delivery information.
@@ -156,11 +166,22 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error {
URL: req.URL.String(),
HTTPMethod: req.Method,
Headers: map[string]string{},
+ Body: string(body),
}
for k, vals := range req.Header {
t.RequestInfo.Headers[k] = strings.Join(vals, ",")
}
+ // Add Authorization Header
+ authorization, err := w.HeaderAuthorization()
+ if err != nil {
+ return fmt.Errorf("cannot get Authorization header for webhook %s[%d %s]: %w", w.Type, w.ID, w.URL, err)
+ }
+ if authorization != "" {
+ req.Header.Set("Authorization", authorization)
+ t.RequestInfo.Headers["Authorization"] = "******"
+ }
+
t.ResponseInfo = &webhook_model.HookResponse{
Headers: map[string]string{},
}
diff --git a/services/webhook/deliver_test.go b/services/webhook/deliver_test.go
index eca2ba244b..bc06e43e03 100644
--- a/services/webhook/deliver_test.go
+++ b/services/webhook/deliver_test.go
@@ -5,10 +5,12 @@ package webhook
import (
"context"
+ "io"
"net/http"
"net/http/httptest"
"net/url"
"os"
+ "strings"
"testing"
"time"
@@ -17,7 +19,6 @@ import (
webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/hostmatcher"
"code.gitea.io/gitea/modules/setting"
- api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
"github.com/stretchr/testify/assert"
@@ -114,13 +115,15 @@ func TestWebhookDeliverAuthorizationHeader(t *testing.T) {
assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook))
db.GetEngine(db.DefaultContext).NoAutoTime().DB().Logger.ShowSQL(true)
- hookTask := &webhook_model.HookTask{HookID: hook.ID, EventType: webhook_module.HookEventPush, Payloader: &api.PushPayload{}}
+ hookTask := &webhook_model.HookTask{
+ HookID: hook.ID,
+ EventType: webhook_module.HookEventPush,
+ PayloadVersion: 2,
+ }
hookTask, err = webhook_model.CreateHookTask(db.DefaultContext, hookTask)
assert.NoError(t, err)
- if !assert.NotNil(t, hookTask) {
- return
- }
+ assert.NotNil(t, hookTask)
assert.NoError(t, Deliver(context.Background(), hookTask))
select {
@@ -130,4 +133,202 @@ func TestWebhookDeliverAuthorizationHeader(t *testing.T) {
}
assert.True(t, hookTask.IsSucceed)
+ assert.Equal(t, "******", hookTask.RequestInfo.Headers["Authorization"])
+}
+
+func TestWebhookDeliverHookTask(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ done := make(chan struct{}, 1)
+ s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, "PUT", r.Method)
+ switch r.URL.Path {
+ case "/webhook/66d222a5d6349e1311f551e50722d837e30fce98":
+ // Version 1
+ assert.Equal(t, "push", r.Header.Get("X-GitHub-Event"))
+ assert.Equal(t, "", r.Header.Get("Content-Type"))
+ body, err := io.ReadAll(r.Body)
+ assert.NoError(t, err)
+ assert.Equal(t, `{"data": 42}`, string(body))
+
+ case "/webhook/6db5dc1e282529a8c162c7fe93dd2667494eeb51":
+ // Version 2
+ assert.Equal(t, "push", r.Header.Get("X-GitHub-Event"))
+ assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
+ body, err := io.ReadAll(r.Body)
+ assert.NoError(t, err)
+ assert.Len(t, body, 2147)
+
+ default:
+ w.WriteHeader(404)
+ t.Fatalf("unexpected url path %s", r.URL.Path)
+ return
+ }
+ w.WriteHeader(200)
+ done <- struct{}{}
+ }))
+ t.Cleanup(s.Close)
+
+ hook := &webhook_model.Webhook{
+ RepoID: 3,
+ IsActive: true,
+ Type: webhook_module.MATRIX,
+ URL: s.URL + "/webhook",
+ HTTPMethod: "PUT",
+ ContentType: webhook_model.ContentTypeJSON,
+ Meta: `{"message_type":0}`, // text
+ }
+ assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook))
+
+ t.Run("Version 1", func(t *testing.T) {
+ hookTask := &webhook_model.HookTask{
+ HookID: hook.ID,
+ EventType: webhook_module.HookEventPush,
+ PayloadContent: `{"data": 42}`,
+ PayloadVersion: 1,
+ }
+
+ hookTask, err := webhook_model.CreateHookTask(db.DefaultContext, hookTask)
+ assert.NoError(t, err)
+ assert.NotNil(t, hookTask)
+
+ assert.NoError(t, Deliver(context.Background(), hookTask))
+ select {
+ case <-done:
+ case <-time.After(5 * time.Second):
+ t.Fatal("waited to long for request to happen")
+ }
+
+ assert.True(t, hookTask.IsSucceed)
+ })
+
+ t.Run("Version 2", func(t *testing.T) {
+ p := pushTestPayload()
+ data, err := p.JSONPayload()
+ assert.NoError(t, err)
+
+ hookTask := &webhook_model.HookTask{
+ HookID: hook.ID,
+ EventType: webhook_module.HookEventPush,
+ PayloadContent: string(data),
+ PayloadVersion: 2,
+ }
+
+ hookTask, err = webhook_model.CreateHookTask(db.DefaultContext, hookTask)
+ assert.NoError(t, err)
+ assert.NotNil(t, hookTask)
+
+ assert.NoError(t, Deliver(context.Background(), hookTask))
+ select {
+ case <-done:
+ case <-time.After(5 * time.Second):
+ t.Fatal("waited to long for request to happen")
+ }
+
+ assert.True(t, hookTask.IsSucceed)
+ })
+}
+
+func TestWebhookDeliverSpecificTypes(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ type hookCase struct {
+ gotBody chan []byte
+ expectedMethod string
+ }
+
+ cases := map[string]hookCase{
+ webhook_module.SLACK: {
+ gotBody: make(chan []byte, 1),
+ },
+ webhook_module.DISCORD: {
+ gotBody: make(chan []byte, 1),
+ },
+ webhook_module.DINGTALK: {
+ gotBody: make(chan []byte, 1),
+ },
+ webhook_module.TELEGRAM: {
+ gotBody: make(chan []byte, 1),
+ },
+ webhook_module.MSTEAMS: {
+ gotBody: make(chan []byte, 1),
+ },
+ webhook_module.FEISHU: {
+ gotBody: make(chan []byte, 1),
+ },
+ webhook_module.MATRIX: {
+ gotBody: make(chan []byte, 1),
+ expectedMethod: "PUT",
+ },
+ webhook_module.WECHATWORK: {
+ gotBody: make(chan []byte, 1),
+ },
+ webhook_module.PACKAGIST: {
+ gotBody: make(chan []byte, 1),
+ },
+ }
+
+ s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, "application/json", r.Header.Get("Content-Type"), r.URL.Path)
+
+ typ := strings.Split(r.URL.Path, "/")[1] // take first segment (after skipping leading slash)
+ hc := cases[typ]
+
+ if hc.expectedMethod != "" {
+ assert.Equal(t, hc.expectedMethod, r.Method, r.URL.Path)
+ } else {
+ assert.Equal(t, "POST", r.Method, r.URL.Path)
+ }
+
+ require.NotNil(t, hc.gotBody, r.URL.Path)
+ body, err := io.ReadAll(r.Body)
+ assert.NoError(t, err)
+ w.WriteHeader(200)
+ hc.gotBody <- body
+ }))
+ t.Cleanup(s.Close)
+
+ p := pushTestPayload()
+ data, err := p.JSONPayload()
+ assert.NoError(t, err)
+
+ for typ, hc := range cases {
+ typ := typ
+ hc := hc
+ t.Run(typ, func(t *testing.T) {
+ t.Parallel()
+ hook := &webhook_model.Webhook{
+ RepoID: 3,
+ IsActive: true,
+ Type: typ,
+ URL: s.URL + "/" + typ,
+ HTTPMethod: "", // should fallback to POST, when left unset by the specific hook
+ ContentType: 0, // set to 0 so that falling back to default request fails with "invalid content type"
+ Meta: "{}",
+ }
+ assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook))
+
+ hookTask := &webhook_model.HookTask{
+ HookID: hook.ID,
+ EventType: webhook_module.HookEventPush,
+ PayloadContent: string(data),
+ PayloadVersion: 2,
+ }
+
+ hookTask, err := webhook_model.CreateHookTask(db.DefaultContext, hookTask)
+ assert.NoError(t, err)
+ assert.NotNil(t, hookTask)
+
+ assert.NoError(t, Deliver(context.Background(), hookTask))
+ select {
+ case gotBody := <-hc.gotBody:
+ assert.NotEqual(t, string(data), string(gotBody), "request body must be different from the event payload")
+ assert.Equal(t, hookTask.RequestInfo.Body, string(gotBody), "request body was not saved")
+ case <-time.After(5 * time.Second):
+ t.Fatal("waited to long for request to happen")
+ }
+
+ assert.True(t, hookTask.IsSucceed)
+ })
+ }
}
diff --git a/services/webhook/dingtalk.go b/services/webhook/dingtalk.go
index d615e7254f..c57d04415a 100644
--- a/services/webhook/dingtalk.go
+++ b/services/webhook/dingtalk.go
@@ -4,12 +4,14 @@
package webhook
import (
+ "context"
"fmt"
+ "net/http"
"net/url"
"strings"
+ webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -22,19 +24,8 @@ type (
DingtalkPayload dingtalk.Payload
)
-var _ PayloadConvertor = &DingtalkPayload{}
-
-// JSONPayload Marshals the DingtalkPayload to json
-func (d *DingtalkPayload) JSONPayload() ([]byte, error) {
- data, err := json.MarshalIndent(d, "", " ")
- if err != nil {
- return []byte{}, err
- }
- return data, nil
-}
-
// Create implements PayloadConvertor Create method
-func (d *DingtalkPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
+func (dc dingtalkConvertor) Create(p *api.CreatePayload) (DingtalkPayload, error) {
// created tag/branch
refName := git.RefName(p.Ref).ShortName()
title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName)
@@ -43,7 +34,7 @@ func (d *DingtalkPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
}
// Delete implements PayloadConvertor Delete method
-func (d *DingtalkPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
+func (dc dingtalkConvertor) Delete(p *api.DeletePayload) (DingtalkPayload, error) {
// created tag/branch
refName := git.RefName(p.Ref).ShortName()
title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName)
@@ -52,14 +43,14 @@ func (d *DingtalkPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
}
// Fork implements PayloadConvertor Fork method
-func (d *DingtalkPayload) Fork(p *api.ForkPayload) (api.Payloader, error) {
+func (dc dingtalkConvertor) Fork(p *api.ForkPayload) (DingtalkPayload, error) {
title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName)
return createDingtalkPayload(title, title, fmt.Sprintf("view forked repo %s", p.Repo.FullName), p.Repo.HTMLURL), nil
}
// Push implements PayloadConvertor Push method
-func (d *DingtalkPayload) Push(p *api.PushPayload) (api.Payloader, error) {
+func (dc dingtalkConvertor) Push(p *api.PushPayload) (DingtalkPayload, error) {
var (
branchName = git.RefName(p.Ref).ShortName()
commitDesc string
@@ -100,14 +91,14 @@ func (d *DingtalkPayload) Push(p *api.PushPayload) (api.Payloader, error) {
}
// Issue implements PayloadConvertor Issue method
-func (d *DingtalkPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
+func (dc dingtalkConvertor) Issue(p *api.IssuePayload) (DingtalkPayload, error) {
text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true)
return createDingtalkPayload(issueTitle, text+"\r\n\r\n"+attachmentText, "view issue", p.Issue.HTMLURL), nil
}
// Wiki implements PayloadConvertor Wiki method
-func (d *DingtalkPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) {
+func (dc dingtalkConvertor) Wiki(p *api.WikiPayload) (DingtalkPayload, error) {
text, _, _ := getWikiPayloadInfo(p, noneLinkFormatter, true)
url := p.Repository.HTMLURL + "/wiki/" + url.PathEscape(p.Page)
@@ -115,27 +106,27 @@ func (d *DingtalkPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) {
}
// IssueComment implements PayloadConvertor IssueComment method
-func (d *DingtalkPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) {
+func (dc dingtalkConvertor) IssueComment(p *api.IssueCommentPayload) (DingtalkPayload, error) {
text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true)
return createDingtalkPayload(issueTitle, text+"\r\n\r\n"+p.Comment.Body, "view issue comment", p.Comment.HTMLURL), nil
}
// PullRequest implements PayloadConvertor PullRequest method
-func (d *DingtalkPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) {
+func (dc dingtalkConvertor) PullRequest(p *api.PullRequestPayload) (DingtalkPayload, error) {
text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true)
return createDingtalkPayload(issueTitle, text+"\r\n\r\n"+attachmentText, "view pull request", p.PullRequest.HTMLURL), nil
}
// Review implements PayloadConvertor Review method
-func (d *DingtalkPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) {
+func (dc dingtalkConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (DingtalkPayload, error) {
var text, title string
switch p.Action {
case api.HookIssueReviewed:
action, err := parseHookPullRequestEventType(event)
if err != nil {
- return nil, err
+ return DingtalkPayload{}, err
}
title = fmt.Sprintf("[%s] Pull request review %s : #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title)
@@ -146,14 +137,14 @@ func (d *DingtalkPayload) Review(p *api.PullRequestPayload, event webhook_module
}
// Repository implements PayloadConvertor Repository method
-func (d *DingtalkPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) {
+func (dc dingtalkConvertor) Repository(p *api.RepositoryPayload) (DingtalkPayload, error) {
switch p.Action {
case api.HookRepoCreated:
title := fmt.Sprintf("[%s] Repository created", p.Repository.FullName)
return createDingtalkPayload(title, title, "view repository", p.Repository.HTMLURL), nil
case api.HookRepoDeleted:
title := fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
- return &DingtalkPayload{
+ return DingtalkPayload{
MsgType: "text",
Text: struct {
Content string `json:"content"`
@@ -163,24 +154,24 @@ func (d *DingtalkPayload) Repository(p *api.RepositoryPayload) (api.Payloader, e
}, nil
}
- return nil, nil
+ return DingtalkPayload{}, nil
}
// Release implements PayloadConvertor Release method
-func (d *DingtalkPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
+func (dc dingtalkConvertor) Release(p *api.ReleasePayload) (DingtalkPayload, error) {
text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true)
return createDingtalkPayload(text, text, "view release", p.Release.HTMLURL), nil
}
-func (d *DingtalkPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
+func (dc dingtalkConvertor) Package(p *api.PackagePayload) (DingtalkPayload, error) {
text, _ := getPackagePayloadInfo(p, noneLinkFormatter, true)
return createDingtalkPayload(text, text, "view package", p.Package.HTMLURL), nil
}
-func createDingtalkPayload(title, text, singleTitle, singleURL string) *DingtalkPayload {
- return &DingtalkPayload{
+func createDingtalkPayload(title, text, singleTitle, singleURL string) DingtalkPayload {
+ return DingtalkPayload{
MsgType: "actionCard",
ActionCard: dingtalk.ActionCard{
Text: strings.TrimSpace(text),
@@ -195,7 +186,10 @@ func createDingtalkPayload(title, text, singleTitle, singleURL string) *Dingtalk
}
}
-// GetDingtalkPayload converts a ding talk webhook into a DingtalkPayload
-func GetDingtalkPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) {
- return convertPayloader(new(DingtalkPayload), p, event)
+type dingtalkConvertor struct{}
+
+var _ payloadConvertor[DingtalkPayload] = dingtalkConvertor{}
+
+func newDingtalkRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
+ return newJSONRequest(dingtalkConvertor{}, w, t, true)
}
diff --git a/services/webhook/dingtalk_test.go b/services/webhook/dingtalk_test.go
index a03fa46f14..25f47347d0 100644
--- a/services/webhook/dingtalk_test.go
+++ b/services/webhook/dingtalk_test.go
@@ -4,9 +4,12 @@
package webhook
import (
+ "context"
"net/url"
"testing"
+ webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -24,248 +27,226 @@ func TestDingTalkPayload(t *testing.T) {
}
return ""
}
+ dc := dingtalkConvertor{}
t.Run("Create", func(t *testing.T) {
p := createTestPayload()
- d := new(DingtalkPayload)
- pl, err := d.Create(p)
+ pl, err := dc.Create(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "[test/repo] branch test created", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "[test/repo] branch test created", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view ref test", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "[test/repo] branch test created", pl.ActionCard.Text)
+ assert.Equal(t, "[test/repo] branch test created", pl.ActionCard.Title)
+ assert.Equal(t, "view ref test", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.ActionCard.SingleURL))
})
t.Run("Delete", func(t *testing.T) {
p := deleteTestPayload()
- d := new(DingtalkPayload)
- pl, err := d.Delete(p)
+ pl, err := dc.Delete(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "[test/repo] branch test deleted", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "[test/repo] branch test deleted", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view ref test", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "[test/repo] branch test deleted", pl.ActionCard.Text)
+ assert.Equal(t, "[test/repo] branch test deleted", pl.ActionCard.Title)
+ assert.Equal(t, "view ref test", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.ActionCard.SingleURL))
})
t.Run("Fork", func(t *testing.T) {
p := forkTestPayload()
- d := new(DingtalkPayload)
- pl, err := d.Fork(p)
+ pl, err := dc.Fork(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "test/repo2 is forked to test/repo", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "test/repo2 is forked to test/repo", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view forked repo test/repo", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "test/repo2 is forked to test/repo", pl.ActionCard.Text)
+ assert.Equal(t, "test/repo2 is forked to test/repo", pl.ActionCard.Title)
+ assert.Equal(t, "view forked repo test/repo", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo", parseRealSingleURL(pl.ActionCard.SingleURL))
})
t.Run("Push", func(t *testing.T) {
p := pushTestPayload()
- d := new(DingtalkPayload)
- pl, err := d.Push(p)
+ pl, err := dc.Push(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "[test/repo:test] 2 new commits", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view commits", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.ActionCard.Text)
+ assert.Equal(t, "[test/repo:test] 2 new commits", pl.ActionCard.Title)
+ assert.Equal(t, "view commits", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.ActionCard.SingleURL))
})
t.Run("Issue", func(t *testing.T) {
p := issueTestPayload()
- d := new(DingtalkPayload)
p.Action = api.HookIssueOpened
- pl, err := d.Issue(p)
+ pl, err := dc.Issue(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "[test/repo] Issue opened: #2 crash by user1\r\n\r\nissue body", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "#2 crash", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view issue", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo/issues/2", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "[test/repo] Issue opened: #2 crash by user1\r\n\r\nissue body", pl.ActionCard.Text)
+ assert.Equal(t, "#2 crash", pl.ActionCard.Title)
+ assert.Equal(t, "view issue", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo/issues/2", parseRealSingleURL(pl.ActionCard.SingleURL))
p.Action = api.HookIssueClosed
- pl, err = d.Issue(p)
+ pl, err = dc.Issue(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "[test/repo] Issue closed: #2 crash by user1", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "#2 crash", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view issue", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo/issues/2", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "[test/repo] Issue closed: #2 crash by user1", pl.ActionCard.Text)
+ assert.Equal(t, "#2 crash", pl.ActionCard.Title)
+ assert.Equal(t, "view issue", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo/issues/2", parseRealSingleURL(pl.ActionCard.SingleURL))
})
t.Run("IssueComment", func(t *testing.T) {
p := issueCommentTestPayload()
- d := new(DingtalkPayload)
- pl, err := d.IssueComment(p)
+ pl, err := dc.IssueComment(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "[test/repo] New comment on issue #2 crash by user1\r\n\r\nmore info needed", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "#2 crash", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view issue comment", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "[test/repo] New comment on issue #2 crash by user1\r\n\r\nmore info needed", pl.ActionCard.Text)
+ assert.Equal(t, "#2 crash", pl.ActionCard.Title)
+ assert.Equal(t, "view issue comment", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", parseRealSingleURL(pl.ActionCard.SingleURL))
})
t.Run("PullRequest", func(t *testing.T) {
p := pullRequestTestPayload()
- d := new(DingtalkPayload)
- pl, err := d.PullRequest(p)
+ pl, err := dc.PullRequest(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug by user1\r\n\r\nfixes bug #2", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "#12 Fix bug", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view pull request", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug by user1\r\n\r\nfixes bug #2", pl.ActionCard.Text)
+ assert.Equal(t, "#12 Fix bug", pl.ActionCard.Title)
+ assert.Equal(t, "view pull request", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", parseRealSingleURL(pl.ActionCard.SingleURL))
})
t.Run("PullRequestComment", func(t *testing.T) {
p := pullRequestCommentTestPayload()
- d := new(DingtalkPayload)
- pl, err := d.IssueComment(p)
+ pl, err := dc.IssueComment(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug by user1\r\n\r\nchanges requested", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "#12 Fix bug", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view issue comment", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug by user1\r\n\r\nchanges requested", pl.ActionCard.Text)
+ assert.Equal(t, "#12 Fix bug", pl.ActionCard.Title)
+ assert.Equal(t, "view issue comment", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", parseRealSingleURL(pl.ActionCard.SingleURL))
})
t.Run("Review", func(t *testing.T) {
p := pullRequestTestPayload()
p.Action = api.HookIssueReviewed
- d := new(DingtalkPayload)
- pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved)
+ pl, err := dc.Review(p, webhook_module.HookEventPullRequestReviewApproved)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug\r\n\r\ngood job", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view pull request", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug\r\n\r\ngood job", pl.ActionCard.Text)
+ assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug", pl.ActionCard.Title)
+ assert.Equal(t, "view pull request", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", parseRealSingleURL(pl.ActionCard.SingleURL))
})
t.Run("Repository", func(t *testing.T) {
p := repositoryTestPayload()
- d := new(DingtalkPayload)
- pl, err := d.Repository(p)
+ pl, err := dc.Repository(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "[test/repo] Repository created", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "[test/repo] Repository created", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view repository", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "[test/repo] Repository created", pl.ActionCard.Text)
+ assert.Equal(t, "[test/repo] Repository created", pl.ActionCard.Title)
+ assert.Equal(t, "view repository", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo", parseRealSingleURL(pl.ActionCard.SingleURL))
})
t.Run("Package", func(t *testing.T) {
p := packageTestPayload()
- d := new(DingtalkPayload)
- pl, err := d.Package(p)
+ pl, err := dc.Package(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view package", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.ActionCard.Text)
+ assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.ActionCard.Title)
+ assert.Equal(t, "view package", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", parseRealSingleURL(pl.ActionCard.SingleURL))
})
t.Run("Wiki", func(t *testing.T) {
p := wikiTestPayload()
- d := new(DingtalkPayload)
p.Action = api.HookWikiCreated
- pl, err := d.Wiki(p)
+ pl, err := dc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view wiki", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.ActionCard.Text)
+ assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.ActionCard.Title)
+ assert.Equal(t, "view wiki", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.ActionCard.SingleURL))
p.Action = api.HookWikiEdited
- pl, err = d.Wiki(p)
+ pl, err = dc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view wiki", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.ActionCard.Text)
+ assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.ActionCard.Title)
+ assert.Equal(t, "view wiki", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.ActionCard.SingleURL))
p.Action = api.HookWikiDeleted
- pl, err = d.Wiki(p)
+ pl, err = dc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view wiki", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.ActionCard.Text)
+ assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.ActionCard.Title)
+ assert.Equal(t, "view wiki", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.ActionCard.SingleURL))
})
t.Run("Release", func(t *testing.T) {
p := pullReleaseTestPayload()
- d := new(DingtalkPayload)
- pl, err := d.Release(p)
+ pl, err := dc.Release(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.(*DingtalkPayload).ActionCard.Text)
- assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.(*DingtalkPayload).ActionCard.Title)
- assert.Equal(t, "view release", pl.(*DingtalkPayload).ActionCard.SingleTitle)
- assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
+ assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.ActionCard.Text)
+ assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.ActionCard.Title)
+ assert.Equal(t, "view release", pl.ActionCard.SingleTitle)
+ assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", parseRealSingleURL(pl.ActionCard.SingleURL))
})
}
func TestDingTalkJSONPayload(t *testing.T) {
p := pushTestPayload()
-
- pl, err := new(DingtalkPayload).Push(p)
+ data, err := p.JSONPayload()
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DingtalkPayload{}, pl)
- json, err := pl.JSONPayload()
+ hook := &webhook_model.Webhook{
+ RepoID: 3,
+ IsActive: true,
+ Type: webhook_module.DINGTALK,
+ URL: "https://dingtalk.example.com/",
+ Meta: ``,
+ HTTPMethod: "POST",
+ }
+ task := &webhook_model.HookTask{
+ HookID: hook.ID,
+ EventType: webhook_module.HookEventPush,
+ PayloadContent: string(data),
+ PayloadVersion: 2,
+ }
+
+ req, reqBody, err := newDingtalkRequest(context.Background(), hook, task)
+ require.NotNil(t, req)
+ require.NotNil(t, reqBody)
require.NoError(t, err)
- assert.NotEmpty(t, json)
+
+ assert.Equal(t, "POST", req.Method)
+ assert.Equal(t, "https://dingtalk.example.com/", req.URL.String())
+ assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
+ assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+ var body DingtalkPayload
+ err = json.NewDecoder(req.Body).Decode(&body)
+ assert.NoError(t, err)
+ assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", body.ActionCard.Text)
}
diff --git a/services/webhook/discord.go b/services/webhook/discord.go
index e2ac1410b8..659754d5e0 100644
--- a/services/webhook/discord.go
+++ b/services/webhook/discord.go
@@ -4,8 +4,10 @@
package webhook
import (
+ "context"
"errors"
"fmt"
+ "net/http"
"net/url"
"strconv"
"strings"
@@ -98,19 +100,8 @@ var (
redColor = color("ff3232")
)
-// JSONPayload Marshals the DiscordPayload to json
-func (d *DiscordPayload) JSONPayload() ([]byte, error) {
- data, err := json.MarshalIndent(d, "", " ")
- if err != nil {
- return []byte{}, err
- }
- return data, nil
-}
-
-var _ PayloadConvertor = &DiscordPayload{}
-
// Create implements PayloadConvertor Create method
-func (d *DiscordPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
+func (d discordConvertor) Create(p *api.CreatePayload) (DiscordPayload, error) {
// created tag/branch
refName := git.RefName(p.Ref).ShortName()
title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName)
@@ -119,7 +110,7 @@ func (d *DiscordPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
}
// Delete implements PayloadConvertor Delete method
-func (d *DiscordPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
+func (d discordConvertor) Delete(p *api.DeletePayload) (DiscordPayload, error) {
// deleted tag/branch
refName := git.RefName(p.Ref).ShortName()
title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName)
@@ -128,14 +119,14 @@ func (d *DiscordPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
}
// Fork implements PayloadConvertor Fork method
-func (d *DiscordPayload) Fork(p *api.ForkPayload) (api.Payloader, error) {
+func (d discordConvertor) Fork(p *api.ForkPayload) (DiscordPayload, error) {
title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName)
return d.createPayload(p.Sender, title, "", p.Repo.HTMLURL, greenColor), nil
}
// Push implements PayloadConvertor Push method
-func (d *DiscordPayload) Push(p *api.PushPayload) (api.Payloader, error) {
+func (d discordConvertor) Push(p *api.PushPayload) (DiscordPayload, error) {
var (
branchName = git.RefName(p.Ref).ShortName()
commitDesc string
@@ -170,35 +161,35 @@ func (d *DiscordPayload) Push(p *api.PushPayload) (api.Payloader, error) {
}
// Issue implements PayloadConvertor Issue method
-func (d *DiscordPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
+func (d discordConvertor) Issue(p *api.IssuePayload) (DiscordPayload, error) {
title, _, text, color := getIssuesPayloadInfo(p, noneLinkFormatter, false)
return d.createPayload(p.Sender, title, text, p.Issue.HTMLURL, color), nil
}
// IssueComment implements PayloadConvertor IssueComment method
-func (d *DiscordPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) {
+func (d discordConvertor) IssueComment(p *api.IssueCommentPayload) (DiscordPayload, error) {
title, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false)
return d.createPayload(p.Sender, title, p.Comment.Body, p.Comment.HTMLURL, color), nil
}
// PullRequest implements PayloadConvertor PullRequest method
-func (d *DiscordPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) {
+func (d discordConvertor) PullRequest(p *api.PullRequestPayload) (DiscordPayload, error) {
title, _, text, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false)
return d.createPayload(p.Sender, title, text, p.PullRequest.HTMLURL, color), nil
}
// Review implements PayloadConvertor Review method
-func (d *DiscordPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) {
+func (d discordConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (DiscordPayload, error) {
var text, title string
var color int
switch p.Action {
case api.HookIssueReviewed:
action, err := parseHookPullRequestEventType(event)
if err != nil {
- return nil, err
+ return DiscordPayload{}, err
}
title = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title)
@@ -220,7 +211,7 @@ func (d *DiscordPayload) Review(p *api.PullRequestPayload, event webhook_module.
}
// Repository implements PayloadConvertor Repository method
-func (d *DiscordPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) {
+func (d discordConvertor) Repository(p *api.RepositoryPayload) (DiscordPayload, error) {
var title, url string
var color int
switch p.Action {
@@ -237,7 +228,7 @@ func (d *DiscordPayload) Repository(p *api.RepositoryPayload) (api.Payloader, er
}
// Wiki implements PayloadConvertor Wiki method
-func (d *DiscordPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) {
+func (d discordConvertor) Wiki(p *api.WikiPayload) (DiscordPayload, error) {
text, color, _ := getWikiPayloadInfo(p, noneLinkFormatter, false)
htmlLink := p.Repository.HTMLURL + "/wiki/" + url.PathEscape(p.Page)
@@ -250,30 +241,35 @@ func (d *DiscordPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) {
}
// Release implements PayloadConvertor Release method
-func (d *DiscordPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
+func (d discordConvertor) Release(p *api.ReleasePayload) (DiscordPayload, error) {
text, color := getReleasePayloadInfo(p, noneLinkFormatter, false)
return d.createPayload(p.Sender, text, p.Release.Note, p.Release.HTMLURL, color), nil
}
-func (d *DiscordPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
+func (d discordConvertor) Package(p *api.PackagePayload) (DiscordPayload, error) {
text, color := getPackagePayloadInfo(p, noneLinkFormatter, false)
return d.createPayload(p.Sender, text, "", p.Package.HTMLURL, color), nil
}
-// GetDiscordPayload converts a discord webhook into a DiscordPayload
-func GetDiscordPayload(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) {
- s := new(DiscordPayload)
+type discordConvertor struct {
+ Username string
+ AvatarURL string
+}
- discord := &DiscordMeta{}
- if err := json.Unmarshal([]byte(meta), &discord); err != nil {
- return s, errors.New("GetDiscordPayload meta json:" + err.Error())
+var _ payloadConvertor[DiscordPayload] = discordConvertor{}
+
+func newDiscordRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
+ meta := &DiscordMeta{}
+ if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
+ return nil, nil, fmt.Errorf("newDiscordRequest meta json: %w", err)
}
- s.Username = discord.Username
- s.AvatarURL = discord.IconURL
-
- return convertPayloader(s, p, event)
+ sc := discordConvertor{
+ Username: meta.Username,
+ AvatarURL: meta.IconURL,
+ }
+ return newJSONRequest(sc, w, t, true)
}
func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, error) {
@@ -291,8 +287,8 @@ func parseHookPullRequestEventType(event webhook_module.HookEventType) (string,
}
}
-func (d *DiscordPayload) createPayload(s *api.User, title, text, url string, color int) *DiscordPayload {
- return &DiscordPayload{
+func (d discordConvertor) createPayload(s *api.User, title, text, url string, color int) DiscordPayload {
+ return DiscordPayload{
Username: d.Username,
AvatarURL: d.AvatarURL,
Embeds: []DiscordEmbed{
diff --git a/services/webhook/discord_test.go b/services/webhook/discord_test.go
index b567cbc395..c04b95383b 100644
--- a/services/webhook/discord_test.go
+++ b/services/webhook/discord_test.go
@@ -4,8 +4,11 @@
package webhook
import (
+ "context"
"testing"
+ webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -15,295 +18,274 @@ import (
)
func TestDiscordPayload(t *testing.T) {
+ dc := discordConvertor{}
+
t.Run("Create", func(t *testing.T) {
p := createTestPayload()
- d := new(DiscordPayload)
- pl, err := d.Create(p)
+ pl, err := dc.Create(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "[test/repo] branch test created", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "[test/repo] branch test created", pl.Embeds[0].Title)
+ assert.Empty(t, pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
})
t.Run("Delete", func(t *testing.T) {
p := deleteTestPayload()
- d := new(DiscordPayload)
- pl, err := d.Delete(p)
+ pl, err := dc.Delete(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "[test/repo] branch test deleted", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "[test/repo] branch test deleted", pl.Embeds[0].Title)
+ assert.Empty(t, pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
})
t.Run("Fork", func(t *testing.T) {
p := forkTestPayload()
- d := new(DiscordPayload)
- pl, err := d.Fork(p)
+ pl, err := dc.Fork(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "test/repo2 is forked to test/repo", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "test/repo2 is forked to test/repo", pl.Embeds[0].Title)
+ assert.Empty(t, pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
})
t.Run("Push", func(t *testing.T) {
p := pushTestPayload()
- d := new(DiscordPayload)
- pl, err := d.Push(p)
+ pl, err := dc.Push(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "[test/repo:test] 2 new commits", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "[test/repo:test] 2 new commits", pl.Embeds[0].Title)
+ assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
})
t.Run("Issue", func(t *testing.T) {
p := issueTestPayload()
- d := new(DiscordPayload)
p.Action = api.HookIssueOpened
- pl, err := d.Issue(p)
+ pl, err := dc.Issue(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Equal(t, "issue body", pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.Embeds[0].Title)
+ assert.Equal(t, "issue body", pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
p.Action = api.HookIssueClosed
- pl, err = d.Issue(p)
+ pl, err = dc.Issue(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.Embeds[0].Title)
+ assert.Empty(t, pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
})
t.Run("IssueComment", func(t *testing.T) {
p := issueCommentTestPayload()
- d := new(DiscordPayload)
- pl, err := d.IssueComment(p)
+ pl, err := dc.IssueComment(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Equal(t, "more info needed", pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.Embeds[0].Title)
+ assert.Equal(t, "more info needed", pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
})
t.Run("PullRequest", func(t *testing.T) {
p := pullRequestTestPayload()
- d := new(DiscordPayload)
- pl, err := d.PullRequest(p)
+ pl, err := dc.PullRequest(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Equal(t, "fixes bug #2", pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.Embeds[0].Title)
+ assert.Equal(t, "fixes bug #2", pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
})
t.Run("PullRequestComment", func(t *testing.T) {
p := pullRequestCommentTestPayload()
- d := new(DiscordPayload)
- pl, err := d.IssueComment(p)
+ pl, err := dc.IssueComment(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Equal(t, "changes requested", pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.Embeds[0].Title)
+ assert.Equal(t, "changes requested", pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
})
t.Run("Review", func(t *testing.T) {
p := pullRequestTestPayload()
p.Action = api.HookIssueReviewed
- d := new(DiscordPayload)
- pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved)
+ pl, err := dc.Review(p, webhook_module.HookEventPullRequestReviewApproved)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Equal(t, "good job", pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.Embeds[0].Title)
+ assert.Equal(t, "good job", pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
})
t.Run("Repository", func(t *testing.T) {
p := repositoryTestPayload()
- d := new(DiscordPayload)
- pl, err := d.Repository(p)
+ pl, err := dc.Repository(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "[test/repo] Repository created", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "[test/repo] Repository created", pl.Embeds[0].Title)
+ assert.Empty(t, pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
})
t.Run("Package", func(t *testing.T) {
p := packageTestPayload()
- d := new(DiscordPayload)
- pl, err := d.Package(p)
+ pl, err := dc.Package(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "Package created: GiteaContainer:latest", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "Package created: GiteaContainer:latest", pl.Embeds[0].Title)
+ assert.Empty(t, pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
})
t.Run("Wiki", func(t *testing.T) {
p := wikiTestPayload()
- d := new(DiscordPayload)
p.Action = api.HookWikiCreated
- pl, err := d.Wiki(p)
+ pl, err := dc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Equal(t, "Wiki change comment", pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.Embeds[0].Title)
+ assert.Equal(t, "Wiki change comment", pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
p.Action = api.HookWikiEdited
- pl, err = d.Wiki(p)
+ pl, err = dc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Equal(t, "Wiki change comment", pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.Embeds[0].Title)
+ assert.Equal(t, "Wiki change comment", pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
p.Action = api.HookWikiDeleted
- pl, err = d.Wiki(p)
+ pl, err = dc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.Embeds[0].Title)
+ assert.Empty(t, pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
})
t.Run("Release", func(t *testing.T) {
p := pullReleaseTestPayload()
- d := new(DiscordPayload)
- pl, err := d.Release(p)
+ pl, err := dc.Release(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
- assert.Equal(t, "[test/repo] Release created: v1.0", pl.(*DiscordPayload).Embeds[0].Title)
- assert.Equal(t, "Note of first stable release", pl.(*DiscordPayload).Embeds[0].Description)
- assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", pl.(*DiscordPayload).Embeds[0].URL)
- assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
- assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
- assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
+ assert.Len(t, pl.Embeds, 1)
+ assert.Equal(t, "[test/repo] Release created: v1.0", pl.Embeds[0].Title)
+ assert.Equal(t, "Note of first stable release", pl.Embeds[0].Description)
+ assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", pl.Embeds[0].URL)
+ assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
+ assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
+ assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
})
}
func TestDiscordJSONPayload(t *testing.T) {
p := pushTestPayload()
-
- pl, err := new(DiscordPayload).Push(p)
+ data, err := p.JSONPayload()
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &DiscordPayload{}, pl)
- json, err := pl.JSONPayload()
+ hook := &webhook_model.Webhook{
+ RepoID: 3,
+ IsActive: true,
+ Type: webhook_module.DISCORD,
+ URL: "https://discord.example.com/",
+ Meta: `{}`,
+ HTTPMethod: "POST",
+ }
+ task := &webhook_model.HookTask{
+ HookID: hook.ID,
+ EventType: webhook_module.HookEventPush,
+ PayloadContent: string(data),
+ PayloadVersion: 2,
+ }
+
+ req, reqBody, err := newDiscordRequest(context.Background(), hook, task)
+ require.NotNil(t, req)
+ require.NotNil(t, reqBody)
require.NoError(t, err)
- assert.NotEmpty(t, json)
+
+ assert.Equal(t, "POST", req.Method)
+ assert.Equal(t, "https://discord.example.com/", req.URL.String())
+ assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
+ assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+ var body DiscordPayload
+ err = json.NewDecoder(req.Body).Decode(&body)
+ assert.NoError(t, err)
+ assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", body.Embeds[0].Description)
}
diff --git a/services/webhook/feishu.go b/services/webhook/feishu.go
index 556443e70b..1ec436894b 100644
--- a/services/webhook/feishu.go
+++ b/services/webhook/feishu.go
@@ -4,11 +4,13 @@
package webhook
import (
+ "context"
"fmt"
+ "net/http"
"strings"
+ webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
)
@@ -23,8 +25,8 @@ type (
}
)
-func newFeishuTextPayload(text string) *FeishuPayload {
- return &FeishuPayload{
+func newFeishuTextPayload(text string) FeishuPayload {
+ return FeishuPayload{
MsgType: "text",
Content: struct {
Text string `json:"text"`
@@ -34,19 +36,8 @@ func newFeishuTextPayload(text string) *FeishuPayload {
}
}
-// JSONPayload Marshals the FeishuPayload to json
-func (f *FeishuPayload) JSONPayload() ([]byte, error) {
- data, err := json.MarshalIndent(f, "", " ")
- if err != nil {
- return []byte{}, err
- }
- return data, nil
-}
-
-var _ PayloadConvertor = &FeishuPayload{}
-
// Create implements PayloadConvertor Create method
-func (f *FeishuPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
+func (fc feishuConvertor) Create(p *api.CreatePayload) (FeishuPayload, error) {
// created tag/branch
refName := git.RefName(p.Ref).ShortName()
text := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName)
@@ -55,7 +46,7 @@ func (f *FeishuPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
}
// Delete implements PayloadConvertor Delete method
-func (f *FeishuPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
+func (fc feishuConvertor) Delete(p *api.DeletePayload) (FeishuPayload, error) {
// created tag/branch
refName := git.RefName(p.Ref).ShortName()
text := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName)
@@ -64,14 +55,14 @@ func (f *FeishuPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
}
// Fork implements PayloadConvertor Fork method
-func (f *FeishuPayload) Fork(p *api.ForkPayload) (api.Payloader, error) {
+func (fc feishuConvertor) Fork(p *api.ForkPayload) (FeishuPayload, error) {
text := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName)
return newFeishuTextPayload(text), nil
}
// Push implements PayloadConvertor Push method
-func (f *FeishuPayload) Push(p *api.PushPayload) (api.Payloader, error) {
+func (fc feishuConvertor) Push(p *api.PushPayload) (FeishuPayload, error) {
var (
branchName = git.RefName(p.Ref).ShortName()
commitDesc string
@@ -96,48 +87,40 @@ func (f *FeishuPayload) Push(p *api.PushPayload) (api.Payloader, error) {
}
// Issue implements PayloadConvertor Issue method
-func (f *FeishuPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
+func (fc feishuConvertor) Issue(p *api.IssuePayload) (FeishuPayload, error) {
title, link, by, operator, result, assignees := getIssuesInfo(p)
- var res api.Payloader
if assignees != "" {
if p.Action == api.HookIssueAssigned || p.Action == api.HookIssueUnassigned || p.Action == api.HookIssueMilestoned {
- res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, result, assignees, p.Issue.Body))
- } else {
- res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, assignees, p.Issue.Body))
+ return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, result, assignees, p.Issue.Body)), nil
}
- } else {
- res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.Issue.Body))
+ return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, assignees, p.Issue.Body)), nil
}
- return res, nil
+ return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.Issue.Body)), nil
}
// IssueComment implements PayloadConvertor IssueComment method
-func (f *FeishuPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) {
+func (fc feishuConvertor) IssueComment(p *api.IssueCommentPayload) (FeishuPayload, error) {
title, link, by, operator := getIssuesCommentInfo(p)
return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.Comment.Body)), nil
}
// PullRequest implements PayloadConvertor PullRequest method
-func (f *FeishuPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) {
+func (fc feishuConvertor) PullRequest(p *api.PullRequestPayload) (FeishuPayload, error) {
title, link, by, operator, result, assignees := getPullRequestInfo(p)
- var res api.Payloader
if assignees != "" {
if p.Action == api.HookIssueAssigned || p.Action == api.HookIssueUnassigned || p.Action == api.HookIssueMilestoned {
- res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, result, assignees, p.PullRequest.Body))
- } else {
- res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, assignees, p.PullRequest.Body))
+ return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, result, assignees, p.PullRequest.Body)), nil
}
- } else {
- res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.PullRequest.Body))
+ return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, assignees, p.PullRequest.Body)), nil
}
- return res, nil
+ return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.PullRequest.Body)), nil
}
// Review implements PayloadConvertor Review method
-func (f *FeishuPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) {
+func (fc feishuConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (FeishuPayload, error) {
action, err := parseHookPullRequestEventType(event)
if err != nil {
- return nil, err
+ return FeishuPayload{}, err
}
title := fmt.Sprintf("[%s] Pull request review %s : #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title)
@@ -147,7 +130,7 @@ func (f *FeishuPayload) Review(p *api.PullRequestPayload, event webhook_module.H
}
// Repository implements PayloadConvertor Repository method
-func (f *FeishuPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) {
+func (fc feishuConvertor) Repository(p *api.RepositoryPayload) (FeishuPayload, error) {
var text string
switch p.Action {
case api.HookRepoCreated:
@@ -158,30 +141,33 @@ func (f *FeishuPayload) Repository(p *api.RepositoryPayload) (api.Payloader, err
return newFeishuTextPayload(text), nil
}
- return nil, nil
+ return FeishuPayload{}, nil
}
// Wiki implements PayloadConvertor Wiki method
-func (f *FeishuPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) {
+func (fc feishuConvertor) Wiki(p *api.WikiPayload) (FeishuPayload, error) {
text, _, _ := getWikiPayloadInfo(p, noneLinkFormatter, true)
return newFeishuTextPayload(text), nil
}
// Release implements PayloadConvertor Release method
-func (f *FeishuPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
+func (fc feishuConvertor) Release(p *api.ReleasePayload) (FeishuPayload, error) {
text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true)
return newFeishuTextPayload(text), nil
}
-func (f *FeishuPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
+func (fc feishuConvertor) Package(p *api.PackagePayload) (FeishuPayload, error) {
text, _ := getPackagePayloadInfo(p, noneLinkFormatter, true)
return newFeishuTextPayload(text), nil
}
-// GetFeishuPayload converts a ding talk webhook into a FeishuPayload
-func GetFeishuPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) {
- return convertPayloader(new(FeishuPayload), p, event)
+type feishuConvertor struct{}
+
+var _ payloadConvertor[FeishuPayload] = feishuConvertor{}
+
+func newFeishuRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
+ return newJSONRequest(feishuConvertor{}, w, t, true)
}
diff --git a/services/webhook/feishu_test.go b/services/webhook/feishu_test.go
index 98bc50dede..ef18333fd4 100644
--- a/services/webhook/feishu_test.go
+++ b/services/webhook/feishu_test.go
@@ -4,8 +4,11 @@
package webhook
import (
+ "context"
"testing"
+ webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -14,199 +17,177 @@ import (
)
func TestFeishuPayload(t *testing.T) {
+ fc := feishuConvertor{}
t.Run("Create", func(t *testing.T) {
p := createTestPayload()
- d := new(FeishuPayload)
- pl, err := d.Create(p)
+ pl, err := fc.Create(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, `[test/repo] branch test created`, pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, `[test/repo] branch test created`, pl.Content.Text)
})
t.Run("Delete", func(t *testing.T) {
p := deleteTestPayload()
- d := new(FeishuPayload)
- pl, err := d.Delete(p)
+ pl, err := fc.Delete(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, `[test/repo] branch test deleted`, pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, `[test/repo] branch test deleted`, pl.Content.Text)
})
t.Run("Fork", func(t *testing.T) {
p := forkTestPayload()
- d := new(FeishuPayload)
- pl, err := d.Fork(p)
+ pl, err := fc.Fork(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, `test/repo2 is forked to test/repo`, pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, `test/repo2 is forked to test/repo`, pl.Content.Text)
})
t.Run("Push", func(t *testing.T) {
p := pushTestPayload()
- d := new(FeishuPayload)
- pl, err := d.Push(p)
+ pl, err := fc.Push(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, "[test/repo:test] \r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, "[test/repo:test] \r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.Content.Text)
})
t.Run("Issue", func(t *testing.T) {
p := issueTestPayload()
- d := new(FeishuPayload)
p.Action = api.HookIssueOpened
- pl, err := d.Issue(p)
+ pl, err := fc.Issue(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, "[Issue-test/repo #2]: opened\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\nAssignees: user1\n\nissue body", pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, "[Issue-test/repo #2]: opened\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\nAssignees: user1\n\nissue body", pl.Content.Text)
p.Action = api.HookIssueClosed
- pl, err = d.Issue(p)
+ pl, err = fc.Issue(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, "[Issue-test/repo #2]: closed\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\nAssignees: user1\n\nissue body", pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, "[Issue-test/repo #2]: closed\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\nAssignees: user1\n\nissue body", pl.Content.Text)
})
t.Run("IssueComment", func(t *testing.T) {
p := issueCommentTestPayload()
- d := new(FeishuPayload)
- pl, err := d.IssueComment(p)
+ pl, err := fc.IssueComment(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, "[Comment-test/repo #2]: created\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\n\nmore info needed", pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, "[Comment-test/repo #2]: created\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\n\nmore info needed", pl.Content.Text)
})
t.Run("PullRequest", func(t *testing.T) {
p := pullRequestTestPayload()
- d := new(FeishuPayload)
- pl, err := d.PullRequest(p)
+ pl, err := fc.PullRequest(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, "[PullRequest-test/repo #12]: opened\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\nAssignees: user1\n\nfixes bug #2", pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, "[PullRequest-test/repo #12]: opened\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\nAssignees: user1\n\nfixes bug #2", pl.Content.Text)
})
t.Run("PullRequestComment", func(t *testing.T) {
p := pullRequestCommentTestPayload()
- d := new(FeishuPayload)
- pl, err := d.IssueComment(p)
+ pl, err := fc.IssueComment(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, "[Comment-test/repo #12]: created\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\n\nchanges requested", pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, "[Comment-test/repo #12]: created\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\n\nchanges requested", pl.Content.Text)
})
t.Run("Review", func(t *testing.T) {
p := pullRequestTestPayload()
p.Action = api.HookIssueReviewed
- d := new(FeishuPayload)
- pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved)
+ pl, err := fc.Review(p, webhook_module.HookEventPullRequestReviewApproved)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug\r\n\r\ngood job", pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug\r\n\r\ngood job", pl.Content.Text)
})
t.Run("Repository", func(t *testing.T) {
p := repositoryTestPayload()
- d := new(FeishuPayload)
- pl, err := d.Repository(p)
+ pl, err := fc.Repository(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, "[test/repo] Repository created", pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, "[test/repo] Repository created", pl.Content.Text)
})
t.Run("Package", func(t *testing.T) {
p := packageTestPayload()
- d := new(FeishuPayload)
- pl, err := d.Package(p)
+ pl, err := fc.Package(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.Content.Text)
})
t.Run("Wiki", func(t *testing.T) {
p := wikiTestPayload()
- d := new(FeishuPayload)
p.Action = api.HookWikiCreated
- pl, err := d.Wiki(p)
+ pl, err := fc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.Content.Text)
p.Action = api.HookWikiEdited
- pl, err = d.Wiki(p)
+ pl, err = fc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.Content.Text)
p.Action = api.HookWikiDeleted
- pl, err = d.Wiki(p)
+ pl, err = fc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.Content.Text)
})
t.Run("Release", func(t *testing.T) {
p := pullReleaseTestPayload()
- d := new(FeishuPayload)
- pl, err := d.Release(p)
+ pl, err := fc.Release(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.(*FeishuPayload).Content.Text)
+ assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.Content.Text)
})
}
func TestFeishuJSONPayload(t *testing.T) {
p := pushTestPayload()
-
- pl, err := new(FeishuPayload).Push(p)
+ data, err := p.JSONPayload()
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &FeishuPayload{}, pl)
- json, err := pl.JSONPayload()
+ hook := &webhook_model.Webhook{
+ RepoID: 3,
+ IsActive: true,
+ Type: webhook_module.FEISHU,
+ URL: "https://feishu.example.com/",
+ Meta: `{}`,
+ HTTPMethod: "POST",
+ }
+ task := &webhook_model.HookTask{
+ HookID: hook.ID,
+ EventType: webhook_module.HookEventPush,
+ PayloadContent: string(data),
+ PayloadVersion: 2,
+ }
+
+ req, reqBody, err := newFeishuRequest(context.Background(), hook, task)
+ require.NotNil(t, req)
+ require.NotNil(t, reqBody)
require.NoError(t, err)
- assert.NotEmpty(t, json)
+
+ assert.Equal(t, "POST", req.Method)
+ assert.Equal(t, "https://feishu.example.com/", req.URL.String())
+ assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
+ assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+ var body FeishuPayload
+ err = json.NewDecoder(req.Body).Decode(&body)
+ assert.NoError(t, err)
+ assert.Equal(t, "[test/repo:test] \r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", body.Content.Text)
}
diff --git a/services/webhook/matrix.go b/services/webhook/matrix.go
index 602d16ef39..0329804a8b 100644
--- a/services/webhook/matrix.go
+++ b/services/webhook/matrix.go
@@ -4,11 +4,12 @@
package webhook
import (
+ "bytes"
+ "context"
"crypto/sha1"
"encoding/hex"
- "errors"
"fmt"
- "html"
+ "net/http"
"net/url"
"regexp"
"strings"
@@ -23,6 +24,37 @@ import (
webhook_module "code.gitea.io/gitea/modules/webhook"
)
+func newMatrixRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
+ meta := &MatrixMeta{}
+ if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
+ return nil, nil, fmt.Errorf("GetMatrixPayload meta json: %w", err)
+ }
+ mc := matrixConvertor{
+ MsgType: messageTypeText[meta.MessageType],
+ }
+ payload, err := newPayload(mc, []byte(t.PayloadContent), t.EventType)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ body, err := json.MarshalIndent(payload, "", " ")
+ if err != nil {
+ return nil, nil, err
+ }
+
+ txnID, err := getMatrixTxnID(body)
+ if err != nil {
+ return nil, nil, err
+ }
+ req, err := http.NewRequest(http.MethodPut, w.URL+"/"+txnID, bytes.NewReader(body))
+ if err != nil {
+ return nil, nil, err
+ }
+ req.Header.Set("Content-Type", "application/json")
+
+ return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body) // likely useless, but has always been sent historially
+}
+
const matrixPayloadSizeLimit = 1024 * 64
// MatrixMeta contains the Matrix metadata
@@ -46,8 +78,6 @@ func GetMatrixHook(w *webhook_model.Webhook) *MatrixMeta {
return s
}
-var _ PayloadConvertor = &MatrixPayload{}
-
// MatrixPayload contains payload for a Matrix room
type MatrixPayload struct {
Body string `json:"body"`
@@ -57,90 +87,79 @@ type MatrixPayload struct {
Commits []*api.PayloadCommit `json:"io.gitea.commits,omitempty"`
}
-// JSONPayload Marshals the MatrixPayload to json
-func (m *MatrixPayload) JSONPayload() ([]byte, error) {
- data, err := json.MarshalIndent(m, "", " ")
- if err != nil {
- return []byte{}, err
- }
- return data, nil
+var _ payloadConvertor[MatrixPayload] = matrixConvertor{}
+
+type matrixConvertor struct {
+ MsgType string
}
-// MatrixLinkFormatter creates a link compatible with Matrix
-func MatrixLinkFormatter(url, text string) string {
- return fmt.Sprintf(`%s `, html.EscapeString(url), html.EscapeString(text))
+func (m matrixConvertor) newPayload(text string, commits ...*api.PayloadCommit) (MatrixPayload, error) {
+ return MatrixPayload{
+ Body: getMessageBody(text),
+ MsgType: m.MsgType,
+ Format: "org.matrix.custom.html",
+ FormattedBody: text,
+ Commits: commits,
+ }, nil
}
-// MatrixLinkToRef Matrix-formatter link to a repo ref
-func MatrixLinkToRef(repoURL, ref string) string {
- refName := git.RefName(ref).ShortName()
- switch {
- case strings.HasPrefix(ref, git.BranchPrefix):
- return MatrixLinkFormatter(repoURL+"/src/branch/"+util.PathEscapeSegments(refName), refName)
- case strings.HasPrefix(ref, git.TagPrefix):
- return MatrixLinkFormatter(repoURL+"/src/tag/"+util.PathEscapeSegments(refName), refName)
- default:
- return MatrixLinkFormatter(repoURL+"/src/commit/"+util.PathEscapeSegments(refName), refName)
- }
-}
-
-// Create implements PayloadConvertor Create method
-func (m *MatrixPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
- repoLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
+// Create implements payloadConvertor Create method
+func (m matrixConvertor) Create(p *api.CreatePayload) (MatrixPayload, error) {
+ repoLink := htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
refLink := MatrixLinkToRef(p.Repo.HTMLURL, p.Ref)
text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName)
- return getMatrixPayload(text, nil, m.MsgType), nil
+ return m.newPayload(text)
}
// Delete composes Matrix payload for delete a branch or tag.
-func (m *MatrixPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
+func (m matrixConvertor) Delete(p *api.DeletePayload) (MatrixPayload, error) {
refName := git.RefName(p.Ref).ShortName()
- repoLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
+ repoLink := htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName)
- return getMatrixPayload(text, nil, m.MsgType), nil
+ return m.newPayload(text)
}
// Fork composes Matrix payload for forked by a repository.
-func (m *MatrixPayload) Fork(p *api.ForkPayload) (api.Payloader, error) {
- baseLink := MatrixLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName)
- forkLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
+func (m matrixConvertor) Fork(p *api.ForkPayload) (MatrixPayload, error) {
+ baseLink := htmlLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName)
+ forkLink := htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink)
- return getMatrixPayload(text, nil, m.MsgType), nil
+ return m.newPayload(text)
}
-// Issue implements PayloadConvertor Issue method
-func (m *MatrixPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
- text, _, _, _ := getIssuesPayloadInfo(p, MatrixLinkFormatter, true)
+// Issue implements payloadConvertor Issue method
+func (m matrixConvertor) Issue(p *api.IssuePayload) (MatrixPayload, error) {
+ text, _, _, _ := getIssuesPayloadInfo(p, htmlLinkFormatter, true)
- return getMatrixPayload(text, nil, m.MsgType), nil
+ return m.newPayload(text)
}
-// IssueComment implements PayloadConvertor IssueComment method
-func (m *MatrixPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) {
- text, _, _ := getIssueCommentPayloadInfo(p, MatrixLinkFormatter, true)
+// IssueComment implements payloadConvertor IssueComment method
+func (m matrixConvertor) IssueComment(p *api.IssueCommentPayload) (MatrixPayload, error) {
+ text, _, _ := getIssueCommentPayloadInfo(p, htmlLinkFormatter, true)
- return getMatrixPayload(text, nil, m.MsgType), nil
+ return m.newPayload(text)
}
-// Wiki implements PayloadConvertor Wiki method
-func (m *MatrixPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) {
- text, _, _ := getWikiPayloadInfo(p, MatrixLinkFormatter, true)
+// Wiki implements payloadConvertor Wiki method
+func (m matrixConvertor) Wiki(p *api.WikiPayload) (MatrixPayload, error) {
+ text, _, _ := getWikiPayloadInfo(p, htmlLinkFormatter, true)
- return getMatrixPayload(text, nil, m.MsgType), nil
+ return m.newPayload(text)
}
-// Release implements PayloadConvertor Release method
-func (m *MatrixPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
- text, _ := getReleasePayloadInfo(p, MatrixLinkFormatter, true)
+// Release implements payloadConvertor Release method
+func (m matrixConvertor) Release(p *api.ReleasePayload) (MatrixPayload, error) {
+ text, _ := getReleasePayloadInfo(p, htmlLinkFormatter, true)
- return getMatrixPayload(text, nil, m.MsgType), nil
+ return m.newPayload(text)
}
-// Push implements PayloadConvertor Push method
-func (m *MatrixPayload) Push(p *api.PushPayload) (api.Payloader, error) {
+// Push implements payloadConvertor Push method
+func (m matrixConvertor) Push(p *api.PushPayload) (MatrixPayload, error) {
var commitDesc string
if p.TotalCommits == 1 {
@@ -149,13 +168,13 @@ func (m *MatrixPayload) Push(p *api.PushPayload) (api.Payloader, error) {
commitDesc = fmt.Sprintf("%d commits", p.TotalCommits)
}
- repoLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
+ repoLink := htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
branchLink := MatrixLinkToRef(p.Repo.HTMLURL, p.Ref)
text := fmt.Sprintf("[%s] %s pushed %s to %s: ", repoLink, p.Pusher.UserName, commitDesc, branchLink)
// for each commit, generate a new line text
for i, commit := range p.Commits {
- text += fmt.Sprintf("%s: %s - %s", MatrixLinkFormatter(commit.URL, commit.ID[:7]), commit.Message, commit.Author.Name)
+ text += fmt.Sprintf("%s: %s - %s", htmlLinkFormatter(commit.URL, commit.ID[:7]), commit.Message, commit.Author.Name)
// add linebreak to each commit but the last
if i < len(p.Commits)-1 {
text += " "
@@ -163,41 +182,41 @@ func (m *MatrixPayload) Push(p *api.PushPayload) (api.Payloader, error) {
}
- return getMatrixPayload(text, p.Commits, m.MsgType), nil
+ return m.newPayload(text, p.Commits...)
}
-// PullRequest implements PayloadConvertor PullRequest method
-func (m *MatrixPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) {
- text, _, _, _ := getPullRequestPayloadInfo(p, MatrixLinkFormatter, true)
+// PullRequest implements payloadConvertor PullRequest method
+func (m matrixConvertor) PullRequest(p *api.PullRequestPayload) (MatrixPayload, error) {
+ text, _, _, _ := getPullRequestPayloadInfo(p, htmlLinkFormatter, true)
- return getMatrixPayload(text, nil, m.MsgType), nil
+ return m.newPayload(text)
}
-// Review implements PayloadConvertor Review method
-func (m *MatrixPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) {
- senderLink := MatrixLinkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)
+// Review implements payloadConvertor Review method
+func (m matrixConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (MatrixPayload, error) {
+ senderLink := htmlLinkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)
title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)
- titleLink := MatrixLinkFormatter(p.PullRequest.HTMLURL, title)
- repoLink := MatrixLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
+ titleLink := htmlLinkFormatter(p.PullRequest.HTMLURL, title)
+ repoLink := htmlLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
var text string
switch p.Action {
case api.HookIssueReviewed:
action, err := parseHookPullRequestEventType(event)
if err != nil {
- return nil, err
+ return MatrixPayload{}, err
}
text = fmt.Sprintf("[%s] Pull request review %s: %s by %s", repoLink, action, titleLink, senderLink)
}
- return getMatrixPayload(text, nil, m.MsgType), nil
+ return m.newPayload(text)
}
-// Repository implements PayloadConvertor Repository method
-func (m *MatrixPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) {
- senderLink := MatrixLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
- repoLink := MatrixLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
+// Repository implements payloadConvertor Repository method
+func (m matrixConvertor) Repository(p *api.RepositoryPayload) (MatrixPayload, error) {
+ senderLink := htmlLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
+ repoLink := htmlLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
var text string
switch p.Action {
@@ -206,13 +225,12 @@ func (m *MatrixPayload) Repository(p *api.RepositoryPayload) (api.Payloader, err
case api.HookRepoDeleted:
text = fmt.Sprintf("[%s] Repository deleted by %s", repoLink, senderLink)
}
-
- return getMatrixPayload(text, nil, m.MsgType), nil
+ return m.newPayload(text)
}
-func (m *MatrixPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
- senderLink := MatrixLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
- packageLink := MatrixLinkFormatter(p.Package.HTMLURL, p.Package.Name)
+func (m matrixConvertor) Package(p *api.PackagePayload) (MatrixPayload, error) {
+ senderLink := htmlLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
+ packageLink := htmlLinkFormatter(p.Package.HTMLURL, p.Package.Name)
var text string
switch p.Action {
@@ -222,31 +240,7 @@ func (m *MatrixPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
text = fmt.Sprintf("[%s] Package deleted by %s", packageLink, senderLink)
}
- return getMatrixPayload(text, nil, m.MsgType), nil
-}
-
-// GetMatrixPayload converts a Matrix webhook into a MatrixPayload
-func GetMatrixPayload(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) {
- s := new(MatrixPayload)
-
- matrix := &MatrixMeta{}
- if err := json.Unmarshal([]byte(meta), &matrix); err != nil {
- return s, errors.New("GetMatrixPayload meta json:" + err.Error())
- }
-
- s.MsgType = messageTypeText[matrix.MessageType]
-
- return convertPayloader(s, p, event)
-}
-
-func getMatrixPayload(text string, commits []*api.PayloadCommit, msgType string) *MatrixPayload {
- p := MatrixPayload{}
- p.FormattedBody = text
- p.Body = getMessageBody(text)
- p.Format = "org.matrix.custom.html"
- p.MsgType = msgType
- p.Commits = commits
- return &p
+ return m.newPayload(text)
}
var urlRegex = regexp.MustCompile(`]*?href="([^">]*?)">(.*?) `)
@@ -271,3 +265,16 @@ func getMatrixTxnID(payload []byte) (string, error) {
return hex.EncodeToString(h.Sum(nil)), nil
}
+
+// MatrixLinkToRef Matrix-formatter link to a repo ref
+func MatrixLinkToRef(repoURL, ref string) string {
+ refName := git.RefName(ref).ShortName()
+ switch {
+ case strings.HasPrefix(ref, git.BranchPrefix):
+ return htmlLinkFormatter(repoURL+"/src/branch/"+util.PathEscapeSegments(refName), refName)
+ case strings.HasPrefix(ref, git.TagPrefix):
+ return htmlLinkFormatter(repoURL+"/src/tag/"+util.PathEscapeSegments(refName), refName)
+ default:
+ return htmlLinkFormatter(repoURL+"/src/commit/"+util.PathEscapeSegments(refName), refName)
+ }
+}
diff --git a/services/webhook/matrix_test.go b/services/webhook/matrix_test.go
index 99a22fbd7e..058f8e3c5f 100644
--- a/services/webhook/matrix_test.go
+++ b/services/webhook/matrix_test.go
@@ -4,8 +4,11 @@
package webhook
import (
+ "context"
"testing"
+ webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -14,217 +17,213 @@ import (
)
func TestMatrixPayload(t *testing.T) {
+ mc := matrixConvertor{
+ MsgType: "m.text",
+ }
+
t.Run("Create", func(t *testing.T) {
p := createTestPayload()
- d := new(MatrixPayload)
- pl, err := d.Create(p)
+ pl, err := mc.Create(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo):[test](http://localhost:3000/test/repo/src/branch/test)] branch created by user1", pl.(*MatrixPayload).Body)
- assert.Equal(t, `[test/repo :test ] branch created by user1`, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo):[test](http://localhost:3000/test/repo/src/branch/test)] branch created by user1", pl.Body)
+ assert.Equal(t, `[test/repo :test ] branch created by user1`, pl.FormattedBody)
})
t.Run("Delete", func(t *testing.T) {
p := deleteTestPayload()
- d := new(MatrixPayload)
- pl, err := d.Delete(p)
+ pl, err := mc.Delete(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo):test] branch deleted by user1", pl.(*MatrixPayload).Body)
- assert.Equal(t, `[test/repo :test] branch deleted by user1`, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo):test] branch deleted by user1", pl.Body)
+ assert.Equal(t, `[test/repo :test] branch deleted by user1`, pl.FormattedBody)
})
t.Run("Fork", func(t *testing.T) {
p := forkTestPayload()
- d := new(MatrixPayload)
- pl, err := d.Fork(p)
+ pl, err := mc.Fork(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, "[test/repo2](http://localhost:3000/test/repo2) is forked to [test/repo](http://localhost:3000/test/repo)", pl.(*MatrixPayload).Body)
- assert.Equal(t, `test/repo2 is forked to test/repo `, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, "[test/repo2](http://localhost:3000/test/repo2) is forked to [test/repo](http://localhost:3000/test/repo)", pl.Body)
+ assert.Equal(t, `test/repo2 is forked to test/repo `, pl.FormattedBody)
})
t.Run("Push", func(t *testing.T) {
p := pushTestPayload()
- d := new(MatrixPayload)
- pl, err := d.Push(p)
+ pl, err := mc.Push(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] user1 pushed 2 commits to [test](http://localhost:3000/test/repo/src/branch/test):\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1", pl.(*MatrixPayload).Body)
- assert.Equal(t, `[test/repo ] user1 pushed 2 commits to test :2020558 : commit message - user12020558 : commit message - user1`, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] user1 pushed 2 commits to [test](http://localhost:3000/test/repo/src/branch/test):\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1", pl.Body)
+ assert.Equal(t, `[test/repo ] user1 pushed 2 commits to test :2020558 : commit message - user12020558 : commit message - user1`, pl.FormattedBody)
})
t.Run("Issue", func(t *testing.T) {
p := issueTestPayload()
- d := new(MatrixPayload)
p.Action = api.HookIssueOpened
- pl, err := d.Issue(p)
+ pl, err := mc.Issue(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue opened: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body)
- assert.Equal(t, `[test/repo ] Issue opened: #2 crash by user1 `, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue opened: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.Body)
+ assert.Equal(t, `[test/repo ] Issue opened: #2 crash by user1 `, pl.FormattedBody)
p.Action = api.HookIssueClosed
- pl, err = d.Issue(p)
+ pl, err = mc.Issue(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue closed: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body)
- assert.Equal(t, `[test/repo ] Issue closed: #2 crash by user1 `, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue closed: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.Body)
+ assert.Equal(t, `[test/repo ] Issue closed: #2 crash by user1 `, pl.FormattedBody)
})
t.Run("IssueComment", func(t *testing.T) {
p := issueCommentTestPayload()
- d := new(MatrixPayload)
- pl, err := d.IssueComment(p)
+ pl, err := mc.IssueComment(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on issue [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body)
- assert.Equal(t, `[test/repo ] New comment on issue #2 crash by user1 `, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on issue [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.Body)
+ assert.Equal(t, `[test/repo ] New comment on issue #2 crash by user1 `, pl.FormattedBody)
})
t.Run("PullRequest", func(t *testing.T) {
p := pullRequestTestPayload()
- d := new(MatrixPayload)
- pl, err := d.PullRequest(p)
+ pl, err := mc.PullRequest(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request opened: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body)
- assert.Equal(t, `[test/repo ] Pull request opened: #12 Fix bug by user1 `, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request opened: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.Body)
+ assert.Equal(t, `[test/repo ] Pull request opened: #12 Fix bug by user1 `, pl.FormattedBody)
})
t.Run("PullRequestComment", func(t *testing.T) {
p := pullRequestCommentTestPayload()
- d := new(MatrixPayload)
- pl, err := d.IssueComment(p)
+ pl, err := mc.IssueComment(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on pull request [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body)
- assert.Equal(t, `[test/repo ] New comment on pull request #12 Fix bug by user1 `, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on pull request [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.Body)
+ assert.Equal(t, `[test/repo ] New comment on pull request #12 Fix bug by user1 `, pl.FormattedBody)
})
t.Run("Review", func(t *testing.T) {
p := pullRequestTestPayload()
p.Action = api.HookIssueReviewed
- d := new(MatrixPayload)
- pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved)
+ pl, err := mc.Review(p, webhook_module.HookEventPullRequestReviewApproved)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request review approved: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body)
- assert.Equal(t, `[test/repo ] Pull request review approved: #12 Fix bug by user1 `, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request review approved: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.Body)
+ assert.Equal(t, `[test/repo ] Pull request review approved: #12 Fix bug by user1 `, pl.FormattedBody)
})
t.Run("Repository", func(t *testing.T) {
p := repositoryTestPayload()
- d := new(MatrixPayload)
- pl, err := d.Repository(p)
+ pl, err := mc.Repository(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, `[[test/repo](http://localhost:3000/test/repo)] Repository created by [user1](https://try.gitea.io/user1)`, pl.(*MatrixPayload).Body)
- assert.Equal(t, `[test/repo ] Repository created by user1 `, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, `[[test/repo](http://localhost:3000/test/repo)] Repository created by [user1](https://try.gitea.io/user1)`, pl.Body)
+ assert.Equal(t, `[test/repo ] Repository created by user1 `, pl.FormattedBody)
})
t.Run("Package", func(t *testing.T) {
p := packageTestPayload()
- d := new(MatrixPayload)
- pl, err := d.Package(p)
+ pl, err := mc.Package(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, `[[GiteaContainer](http://localhost:3000/user1/-/packages/container/GiteaContainer/latest)] Package published by [user1](https://try.gitea.io/user1)`, pl.(*MatrixPayload).Body)
- assert.Equal(t, `[GiteaContainer ] Package published by user1 `, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, `[[GiteaContainer](http://localhost:3000/user1/-/packages/container/GiteaContainer/latest)] Package published by [user1](https://try.gitea.io/user1)`, pl.Body)
+ assert.Equal(t, `[GiteaContainer ] Package published by user1 `, pl.FormattedBody)
})
t.Run("Wiki", func(t *testing.T) {
p := wikiTestPayload()
- d := new(MatrixPayload)
p.Action = api.HookWikiCreated
- pl, err := d.Wiki(p)
+ pl, err := mc.Wiki(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New wiki page '[index](http://localhost:3000/test/repo/wiki/index)' (Wiki change comment) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body)
- assert.Equal(t, `[test/repo ] New wiki page 'index ' (Wiki change comment) by user1 `, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New wiki page '[index](http://localhost:3000/test/repo/wiki/index)' (Wiki change comment) by [user1](https://try.gitea.io/user1)", pl.Body)
+ assert.Equal(t, `[test/repo ] New wiki page 'index ' (Wiki change comment) by user1 `, pl.FormattedBody)
p.Action = api.HookWikiEdited
- pl, err = d.Wiki(p)
+ pl, err = mc.Wiki(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Wiki page '[index](http://localhost:3000/test/repo/wiki/index)' edited (Wiki change comment) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body)
- assert.Equal(t, `[test/repo ] Wiki page 'index ' edited (Wiki change comment) by user1 `, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Wiki page '[index](http://localhost:3000/test/repo/wiki/index)' edited (Wiki change comment) by [user1](https://try.gitea.io/user1)", pl.Body)
+ assert.Equal(t, `[test/repo ] Wiki page 'index ' edited (Wiki change comment) by user1 `, pl.FormattedBody)
p.Action = api.HookWikiDeleted
- pl, err = d.Wiki(p)
+ pl, err = mc.Wiki(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Wiki page '[index](http://localhost:3000/test/repo/wiki/index)' deleted by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body)
- assert.Equal(t, `[test/repo ] Wiki page 'index ' deleted by user1 `, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Wiki page '[index](http://localhost:3000/test/repo/wiki/index)' deleted by [user1](https://try.gitea.io/user1)", pl.Body)
+ assert.Equal(t, `[test/repo ] Wiki page 'index ' deleted by user1 `, pl.FormattedBody)
})
t.Run("Release", func(t *testing.T) {
p := pullReleaseTestPayload()
- d := new(MatrixPayload)
- pl, err := d.Release(p)
+ pl, err := mc.Release(p)
require.NoError(t, err)
require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Release created: [v1.0](http://localhost:3000/test/repo/releases/tag/v1.0) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body)
- assert.Equal(t, `[test/repo ] Release created: v1.0 by user1 `, pl.(*MatrixPayload).FormattedBody)
+ assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Release created: [v1.0](http://localhost:3000/test/repo/releases/tag/v1.0) by [user1](https://try.gitea.io/user1)", pl.Body)
+ assert.Equal(t, `[test/repo ] Release created: v1.0 by user1 `, pl.FormattedBody)
})
}
func TestMatrixJSONPayload(t *testing.T) {
p := pushTestPayload()
-
- pl, err := new(MatrixPayload).Push(p)
+ data, err := p.JSONPayload()
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MatrixPayload{}, pl)
- json, err := pl.JSONPayload()
+ hook := &webhook_model.Webhook{
+ RepoID: 3,
+ IsActive: true,
+ Type: webhook_module.MATRIX,
+ URL: "https://matrix.example.com/_matrix/client/r0/rooms/ROOM_ID/send/m.room.message",
+ Meta: `{"message_type":0}`, // text
+ }
+ task := &webhook_model.HookTask{
+ HookID: hook.ID,
+ EventType: webhook_module.HookEventPush,
+ PayloadContent: string(data),
+ PayloadVersion: 2,
+ }
+
+ req, reqBody, err := newMatrixRequest(context.Background(), hook, task)
+ require.NotNil(t, req)
+ require.NotNil(t, reqBody)
require.NoError(t, err)
- assert.NotEmpty(t, json)
+
+ assert.Equal(t, "PUT", req.Method)
+ assert.Equal(t, "/_matrix/client/r0/rooms/ROOM_ID/send/m.room.message/6db5dc1e282529a8c162c7fe93dd2667494eeb51", req.URL.Path)
+ assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
+ assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+ var body MatrixPayload
+ err = json.NewDecoder(req.Body).Decode(&body)
+ assert.NoError(t, err)
+ assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] user1 pushed 2 commits to [test](http://localhost:3000/test/repo/src/branch/test):\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1", body.Body)
}
func Test_getTxnID(t *testing.T) {
diff --git a/services/webhook/msteams.go b/services/webhook/msteams.go
index 37810b4cd3..99d0106184 100644
--- a/services/webhook/msteams.go
+++ b/services/webhook/msteams.go
@@ -4,12 +4,14 @@
package webhook
import (
+ "context"
"fmt"
+ "net/http"
"net/url"
"strings"
+ webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -56,19 +58,8 @@ type (
}
)
-// JSONPayload Marshals the MSTeamsPayload to json
-func (m *MSTeamsPayload) JSONPayload() ([]byte, error) {
- data, err := json.MarshalIndent(m, "", " ")
- if err != nil {
- return []byte{}, err
- }
- return data, nil
-}
-
-var _ PayloadConvertor = &MSTeamsPayload{}
-
// Create implements PayloadConvertor Create method
-func (m *MSTeamsPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
+func (m msteamsConvertor) Create(p *api.CreatePayload) (MSTeamsPayload, error) {
// created tag/branch
refName := git.RefName(p.Ref).ShortName()
title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName)
@@ -85,7 +76,7 @@ func (m *MSTeamsPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
}
// Delete implements PayloadConvertor Delete method
-func (m *MSTeamsPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
+func (m msteamsConvertor) Delete(p *api.DeletePayload) (MSTeamsPayload, error) {
// deleted tag/branch
refName := git.RefName(p.Ref).ShortName()
title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName)
@@ -102,7 +93,7 @@ func (m *MSTeamsPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
}
// Fork implements PayloadConvertor Fork method
-func (m *MSTeamsPayload) Fork(p *api.ForkPayload) (api.Payloader, error) {
+func (m msteamsConvertor) Fork(p *api.ForkPayload) (MSTeamsPayload, error) {
title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName)
return createMSTeamsPayload(
@@ -117,7 +108,7 @@ func (m *MSTeamsPayload) Fork(p *api.ForkPayload) (api.Payloader, error) {
}
// Push implements PayloadConvertor Push method
-func (m *MSTeamsPayload) Push(p *api.PushPayload) (api.Payloader, error) {
+func (m msteamsConvertor) Push(p *api.PushPayload) (MSTeamsPayload, error) {
var (
branchName = git.RefName(p.Ref).ShortName()
commitDesc string
@@ -160,7 +151,7 @@ func (m *MSTeamsPayload) Push(p *api.PushPayload) (api.Payloader, error) {
}
// Issue implements PayloadConvertor Issue method
-func (m *MSTeamsPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
+func (m msteamsConvertor) Issue(p *api.IssuePayload) (MSTeamsPayload, error) {
title, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter, false)
return createMSTeamsPayload(
@@ -175,7 +166,7 @@ func (m *MSTeamsPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
}
// IssueComment implements PayloadConvertor IssueComment method
-func (m *MSTeamsPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) {
+func (m msteamsConvertor) IssueComment(p *api.IssueCommentPayload) (MSTeamsPayload, error) {
title, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false)
return createMSTeamsPayload(
@@ -190,7 +181,7 @@ func (m *MSTeamsPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader
}
// PullRequest implements PayloadConvertor PullRequest method
-func (m *MSTeamsPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) {
+func (m msteamsConvertor) PullRequest(p *api.PullRequestPayload) (MSTeamsPayload, error) {
title, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false)
return createMSTeamsPayload(
@@ -205,14 +196,14 @@ func (m *MSTeamsPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader,
}
// Review implements PayloadConvertor Review method
-func (m *MSTeamsPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) {
+func (m msteamsConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (MSTeamsPayload, error) {
var text, title string
var color int
switch p.Action {
case api.HookIssueReviewed:
action, err := parseHookPullRequestEventType(event)
if err != nil {
- return nil, err
+ return MSTeamsPayload{}, err
}
title = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title)
@@ -242,7 +233,7 @@ func (m *MSTeamsPayload) Review(p *api.PullRequestPayload, event webhook_module.
}
// Repository implements PayloadConvertor Repository method
-func (m *MSTeamsPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) {
+func (m msteamsConvertor) Repository(p *api.RepositoryPayload) (MSTeamsPayload, error) {
var title, url string
var color int
switch p.Action {
@@ -267,7 +258,7 @@ func (m *MSTeamsPayload) Repository(p *api.RepositoryPayload) (api.Payloader, er
}
// Wiki implements PayloadConvertor Wiki method
-func (m *MSTeamsPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) {
+func (m msteamsConvertor) Wiki(p *api.WikiPayload) (MSTeamsPayload, error) {
title, color, _ := getWikiPayloadInfo(p, noneLinkFormatter, false)
return createMSTeamsPayload(
@@ -282,7 +273,7 @@ func (m *MSTeamsPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) {
}
// Release implements PayloadConvertor Release method
-func (m *MSTeamsPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
+func (m msteamsConvertor) Release(p *api.ReleasePayload) (MSTeamsPayload, error) {
title, color := getReleasePayloadInfo(p, noneLinkFormatter, false)
return createMSTeamsPayload(
@@ -296,7 +287,7 @@ func (m *MSTeamsPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
), nil
}
-func (m *MSTeamsPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
+func (m msteamsConvertor) Package(p *api.PackagePayload) (MSTeamsPayload, error) {
title, color := getPackagePayloadInfo(p, noneLinkFormatter, false)
return createMSTeamsPayload(
@@ -310,12 +301,7 @@ func (m *MSTeamsPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
), nil
}
-// GetMSTeamsPayload converts a MSTeams webhook into a MSTeamsPayload
-func GetMSTeamsPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) {
- return convertPayloader(new(MSTeamsPayload), p, event)
-}
-
-func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTarget string, color int, fact *MSTeamsFact) *MSTeamsPayload {
+func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTarget string, color int, fact *MSTeamsFact) MSTeamsPayload {
facts := make([]MSTeamsFact, 0, 2)
if r != nil {
facts = append(facts, MSTeamsFact{
@@ -327,7 +313,7 @@ func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTar
facts = append(facts, *fact)
}
- return &MSTeamsPayload{
+ return MSTeamsPayload{
Type: "MessageCard",
Context: "https://schema.org/extensions",
ThemeColor: fmt.Sprintf("%x", color),
@@ -356,3 +342,11 @@ func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTar
},
}
}
+
+type msteamsConvertor struct{}
+
+var _ payloadConvertor[MSTeamsPayload] = msteamsConvertor{}
+
+func newMSTeamsRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
+ return newJSONRequest(msteamsConvertor{}, w, t, true)
+}
diff --git a/services/webhook/msteams_test.go b/services/webhook/msteams_test.go
index 8d1aed6040..01e08b918e 100644
--- a/services/webhook/msteams_test.go
+++ b/services/webhook/msteams_test.go
@@ -4,8 +4,11 @@
package webhook
import (
+ "context"
"testing"
+ webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -14,22 +17,20 @@ import (
)
func TestMSTeamsPayload(t *testing.T) {
+ mc := msteamsConvertor{}
t.Run("Create", func(t *testing.T) {
p := createTestPayload()
- d := new(MSTeamsPayload)
- pl, err := d.Create(p)
+ pl, err := mc.Create(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "[test/repo] branch test created", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "[test/repo] branch test created", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "[test/repo] branch test created", pl.Title)
+ assert.Equal(t, "[test/repo] branch test created", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Empty(t, pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 2)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repo.FullName, fact.Value)
} else if fact.Name == "branch:" {
@@ -38,27 +39,24 @@ func TestMSTeamsPayload(t *testing.T) {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.PotentialAction[0].Targets[0].URI)
})
t.Run("Delete", func(t *testing.T) {
p := deleteTestPayload()
- d := new(MSTeamsPayload)
- pl, err := d.Delete(p)
+ pl, err := mc.Delete(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "[test/repo] branch test deleted", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "[test/repo] branch test deleted", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "[test/repo] branch test deleted", pl.Title)
+ assert.Equal(t, "[test/repo] branch test deleted", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Empty(t, pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 2)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repo.FullName, fact.Value)
} else if fact.Name == "branch:" {
@@ -67,27 +65,24 @@ func TestMSTeamsPayload(t *testing.T) {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.PotentialAction[0].Targets[0].URI)
})
t.Run("Fork", func(t *testing.T) {
p := forkTestPayload()
- d := new(MSTeamsPayload)
- pl, err := d.Fork(p)
+ pl, err := mc.Fork(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "test/repo2 is forked to test/repo", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "test/repo2 is forked to test/repo", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "test/repo2 is forked to test/repo", pl.Title)
+ assert.Equal(t, "test/repo2 is forked to test/repo", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Empty(t, pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 2)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repo.FullName, fact.Value)
} else if fact.Name == "Forkee:" {
@@ -96,27 +91,24 @@ func TestMSTeamsPayload(t *testing.T) {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo", pl.PotentialAction[0].Targets[0].URI)
})
t.Run("Push", func(t *testing.T) {
p := pushTestPayload()
- d := new(MSTeamsPayload)
- pl, err := d.Push(p)
+ pl, err := mc.Push(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "[test/repo:test] 2 new commits", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "[test/repo:test] 2 new commits", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "[test/repo:test] 2 new commits", pl.Title)
+ assert.Equal(t, "[test/repo:test] 2 new commits", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 2)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repo.FullName, fact.Value)
} else if fact.Name == "Commit count:" {
@@ -125,28 +117,25 @@ func TestMSTeamsPayload(t *testing.T) {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.PotentialAction[0].Targets[0].URI)
})
t.Run("Issue", func(t *testing.T) {
p := issueTestPayload()
- d := new(MSTeamsPayload)
p.Action = api.HookIssueOpened
- pl, err := d.Issue(p)
+ pl, err := mc.Issue(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Equal(t, "issue body", pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.Title)
+ assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Equal(t, "issue body", pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 2)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repository.FullName, fact.Value)
} else if fact.Name == "Issue #:" {
@@ -155,23 +144,21 @@ func TestMSTeamsPayload(t *testing.T) {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.PotentialAction[0].Targets[0].URI)
p.Action = api.HookIssueClosed
- pl, err = d.Issue(p)
+ pl, err = mc.Issue(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.Title)
+ assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Empty(t, pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 2)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repository.FullName, fact.Value)
} else if fact.Name == "Issue #:" {
@@ -180,27 +167,24 @@ func TestMSTeamsPayload(t *testing.T) {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.PotentialAction[0].Targets[0].URI)
})
t.Run("IssueComment", func(t *testing.T) {
p := issueCommentTestPayload()
- d := new(MSTeamsPayload)
- pl, err := d.IssueComment(p)
+ pl, err := mc.IssueComment(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Equal(t, "more info needed", pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.Title)
+ assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Equal(t, "more info needed", pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 2)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repository.FullName, fact.Value)
} else if fact.Name == "Issue #:" {
@@ -209,27 +193,24 @@ func TestMSTeamsPayload(t *testing.T) {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", pl.PotentialAction[0].Targets[0].URI)
})
t.Run("PullRequest", func(t *testing.T) {
p := pullRequestTestPayload()
- d := new(MSTeamsPayload)
- pl, err := d.PullRequest(p)
+ pl, err := mc.PullRequest(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Equal(t, "fixes bug #2", pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.Title)
+ assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Equal(t, "fixes bug #2", pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 2)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repository.FullName, fact.Value)
} else if fact.Name == "Pull request #:" {
@@ -238,27 +219,24 @@ func TestMSTeamsPayload(t *testing.T) {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.PotentialAction[0].Targets[0].URI)
})
t.Run("PullRequestComment", func(t *testing.T) {
p := pullRequestCommentTestPayload()
- d := new(MSTeamsPayload)
- pl, err := d.IssueComment(p)
+ pl, err := mc.IssueComment(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Equal(t, "changes requested", pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.Title)
+ assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Equal(t, "changes requested", pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 2)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repository.FullName, fact.Value)
} else if fact.Name == "Issue #:" {
@@ -267,28 +245,25 @@ func TestMSTeamsPayload(t *testing.T) {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", pl.PotentialAction[0].Targets[0].URI)
})
t.Run("Review", func(t *testing.T) {
p := pullRequestTestPayload()
p.Action = api.HookIssueReviewed
- d := new(MSTeamsPayload)
- pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved)
+ pl, err := mc.Review(p, webhook_module.HookEventPullRequestReviewApproved)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Equal(t, "good job", pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.Title)
+ assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Equal(t, "good job", pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 2)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repository.FullName, fact.Value)
} else if fact.Name == "Pull request #:" {
@@ -297,155 +272,139 @@ func TestMSTeamsPayload(t *testing.T) {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.PotentialAction[0].Targets[0].URI)
})
t.Run("Repository", func(t *testing.T) {
p := repositoryTestPayload()
- d := new(MSTeamsPayload)
- pl, err := d.Repository(p)
+ pl, err := mc.Repository(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "[test/repo] Repository created", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "[test/repo] Repository created", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 1)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "[test/repo] Repository created", pl.Title)
+ assert.Equal(t, "[test/repo] Repository created", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Empty(t, pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 1)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repository.FullName, fact.Value)
} else {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo", pl.PotentialAction[0].Targets[0].URI)
})
t.Run("Package", func(t *testing.T) {
p := packageTestPayload()
- d := new(MSTeamsPayload)
- pl, err := d.Package(p)
+ pl, err := mc.Package(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "Package created: GiteaContainer:latest", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "Package created: GiteaContainer:latest", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 1)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "Package created: GiteaContainer:latest", pl.Title)
+ assert.Equal(t, "Package created: GiteaContainer:latest", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Empty(t, pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 1)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Package:" {
assert.Equal(t, p.Package.Name, fact.Value)
} else {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", pl.PotentialAction[0].Targets[0].URI)
})
t.Run("Wiki", func(t *testing.T) {
p := wikiTestPayload()
- d := new(MSTeamsPayload)
p.Action = api.HookWikiCreated
- pl, err := d.Wiki(p)
+ pl, err := mc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Equal(t, "", pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.Title)
+ assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Equal(t, "", pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 2)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repository.FullName, fact.Value)
} else {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.PotentialAction[0].Targets[0].URI)
p.Action = api.HookWikiEdited
- pl, err = d.Wiki(p)
+ pl, err = mc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Equal(t, "", pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.Title)
+ assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Equal(t, "", pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 2)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repository.FullName, fact.Value)
} else {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.PotentialAction[0].Targets[0].URI)
p.Action = api.HookWikiDeleted
- pl, err = d.Wiki(p)
+ pl, err = mc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.Title)
+ assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Empty(t, pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 2)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repository.FullName, fact.Value)
} else {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.PotentialAction[0].Targets[0].URI)
})
t.Run("Release", func(t *testing.T) {
p := pullReleaseTestPayload()
- d := new(MSTeamsPayload)
- pl, err := d.Release(p)
+ pl, err := mc.Release(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- assert.Equal(t, "[test/repo] Release created: v1.0", pl.(*MSTeamsPayload).Title)
- assert.Equal(t, "[test/repo] Release created: v1.0", pl.(*MSTeamsPayload).Summary)
- assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
- assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
- assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text)
- assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 2)
- for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
+ assert.Equal(t, "[test/repo] Release created: v1.0", pl.Title)
+ assert.Equal(t, "[test/repo] Release created: v1.0", pl.Summary)
+ assert.Len(t, pl.Sections, 1)
+ assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
+ assert.Empty(t, pl.Sections[0].Text)
+ assert.Len(t, pl.Sections[0].Facts, 2)
+ for _, fact := range pl.Sections[0].Facts {
if fact.Name == "Repository:" {
assert.Equal(t, p.Repository.FullName, fact.Value)
} else if fact.Name == "Tag:" {
@@ -454,21 +413,43 @@ func TestMSTeamsPayload(t *testing.T) {
t.Fail()
}
}
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
- assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
- assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
+ assert.Len(t, pl.PotentialAction, 1)
+ assert.Len(t, pl.PotentialAction[0].Targets, 1)
+ assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", pl.PotentialAction[0].Targets[0].URI)
})
}
func TestMSTeamsJSONPayload(t *testing.T) {
p := pushTestPayload()
-
- pl, err := new(MSTeamsPayload).Push(p)
+ data, err := p.JSONPayload()
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &MSTeamsPayload{}, pl)
- json, err := pl.JSONPayload()
+ hook := &webhook_model.Webhook{
+ RepoID: 3,
+ IsActive: true,
+ Type: webhook_module.MSTEAMS,
+ URL: "https://msteams.example.com/",
+ Meta: ``,
+ HTTPMethod: "POST",
+ }
+ task := &webhook_model.HookTask{
+ HookID: hook.ID,
+ EventType: webhook_module.HookEventPush,
+ PayloadContent: string(data),
+ PayloadVersion: 2,
+ }
+
+ req, reqBody, err := newMSTeamsRequest(context.Background(), hook, task)
+ require.NotNil(t, req)
+ require.NotNil(t, reqBody)
require.NoError(t, err)
- assert.NotEmpty(t, json)
+
+ assert.Equal(t, "POST", req.Method)
+ assert.Equal(t, "https://msteams.example.com/", req.URL.String())
+ assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
+ assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+ var body MSTeamsPayload
+ err = json.NewDecoder(req.Body).Decode(&body)
+ assert.NoError(t, err)
+ assert.Equal(t, "[test/repo:test] 2 new commits", body.Summary)
}
diff --git a/services/webhook/packagist.go b/services/webhook/packagist.go
index 714a4c076e..0a2b62c89a 100644
--- a/services/webhook/packagist.go
+++ b/services/webhook/packagist.go
@@ -4,17 +4,18 @@
package webhook
import (
- "errors"
+ "context"
+ "fmt"
+ "net/http"
webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
- api "code.gitea.io/gitea/modules/structs"
- webhook_module "code.gitea.io/gitea/modules/webhook"
)
type (
- // PackagistPayload represents
+ // PackagistPayload represents a packagist payload
+ // as expected by https://packagist.org/about
PackagistPayload struct {
PackagistRepository struct {
URL string `json:"url"`
@@ -38,84 +39,19 @@ func GetPackagistHook(w *webhook_model.Webhook) *PackagistMeta {
return s
}
-// JSONPayload Marshals the PackagistPayload to json
-func (f *PackagistPayload) JSONPayload() ([]byte, error) {
- data, err := json.MarshalIndent(f, "", " ")
- if err != nil {
- return []byte{}, err
+// newPackagistRequest creates a request with the [PackagistPayload] for packagist (same payload for all events).
+func newPackagistRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
+ meta := &PackagistMeta{}
+ if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
+ return nil, nil, fmt.Errorf("newpackagistRequest meta json: %w", err)
}
- return data, nil
-}
-var _ PayloadConvertor = &PackagistPayload{}
-
-// Create implements PayloadConvertor Create method
-func (f *PackagistPayload) Create(_ *api.CreatePayload) (api.Payloader, error) {
- return nil, nil
-}
-
-// Delete implements PayloadConvertor Delete method
-func (f *PackagistPayload) Delete(_ *api.DeletePayload) (api.Payloader, error) {
- return nil, nil
-}
-
-// Fork implements PayloadConvertor Fork method
-func (f *PackagistPayload) Fork(_ *api.ForkPayload) (api.Payloader, error) {
- return nil, nil
-}
-
-// Push implements PayloadConvertor Push method
-func (f *PackagistPayload) Push(_ *api.PushPayload) (api.Payloader, error) {
- return f, nil
-}
-
-// Issue implements PayloadConvertor Issue method
-func (f *PackagistPayload) Issue(_ *api.IssuePayload) (api.Payloader, error) {
- return nil, nil
-}
-
-// IssueComment implements PayloadConvertor IssueComment method
-func (f *PackagistPayload) IssueComment(_ *api.IssueCommentPayload) (api.Payloader, error) {
- return nil, nil
-}
-
-// PullRequest implements PayloadConvertor PullRequest method
-func (f *PackagistPayload) PullRequest(_ *api.PullRequestPayload) (api.Payloader, error) {
- return nil, nil
-}
-
-// Review implements PayloadConvertor Review method
-func (f *PackagistPayload) Review(_ *api.PullRequestPayload, _ webhook_module.HookEventType) (api.Payloader, error) {
- return nil, nil
-}
-
-// Repository implements PayloadConvertor Repository method
-func (f *PackagistPayload) Repository(_ *api.RepositoryPayload) (api.Payloader, error) {
- return nil, nil
-}
-
-// Wiki implements PayloadConvertor Wiki method
-func (f *PackagistPayload) Wiki(_ *api.WikiPayload) (api.Payloader, error) {
- return nil, nil
-}
-
-// Release implements PayloadConvertor Release method
-func (f *PackagistPayload) Release(_ *api.ReleasePayload) (api.Payloader, error) {
- return nil, nil
-}
-
-func (f *PackagistPayload) Package(_ *api.PackagePayload) (api.Payloader, error) {
- return nil, nil
-}
-
-// GetPackagistPayload converts a packagist webhook into a PackagistPayload
-func GetPackagistPayload(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) {
- s := new(PackagistPayload)
-
- packagist := &PackagistMeta{}
- if err := json.Unmarshal([]byte(meta), &packagist); err != nil {
- return s, errors.New("GetPackagistPayload meta json:" + err.Error())
+ payload := PackagistPayload{
+ PackagistRepository: struct {
+ URL string `json:"url"`
+ }{
+ URL: meta.PackageURL,
+ },
}
- s.PackagistRepository.URL = packagist.PackageURL
- return convertPayloader(s, p, event)
+ return newJSONRequestWithPayload(payload, w, t, false)
}
diff --git a/services/webhook/packagist_test.go b/services/webhook/packagist_test.go
index 26d01b0555..e6a963a9dd 100644
--- a/services/webhook/packagist_test.go
+++ b/services/webhook/packagist_test.go
@@ -4,8 +4,12 @@
package webhook
import (
+ "context"
+ "fmt"
"testing"
+ webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -14,155 +18,53 @@ import (
)
func TestPackagistPayload(t *testing.T) {
- t.Run("Create", func(t *testing.T) {
- p := createTestPayload()
+ payloads := []api.Payloader{
+ createTestPayload(),
+ deleteTestPayload(),
+ forkTestPayload(),
+ pushTestPayload(),
+ issueTestPayload(),
+ issueCommentTestPayload(),
+ pullRequestCommentTestPayload(),
+ pullRequestTestPayload(),
+ repositoryTestPayload(),
+ packageTestPayload(),
+ wikiTestPayload(),
+ pullReleaseTestPayload(),
+ }
- d := new(PackagistPayload)
- pl, err := d.Create(p)
- require.NoError(t, err)
- require.Nil(t, pl)
- })
+ for _, payloader := range payloads {
+ t.Run(fmt.Sprintf("%T", payloader), func(t *testing.T) {
+ data, err := payloader.JSONPayload()
+ require.NoError(t, err)
- t.Run("Delete", func(t *testing.T) {
- p := deleteTestPayload()
+ hook := &webhook_model.Webhook{
+ RepoID: 3,
+ IsActive: true,
+ Type: webhook_module.PACKAGIST,
+ URL: "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN",
+ Meta: `{"package_url":"https://packagist.org/packages/example"}`,
+ HTTPMethod: "POST",
+ }
+ task := &webhook_model.HookTask{
+ HookID: hook.ID,
+ EventType: webhook_module.HookEventPush,
+ PayloadContent: string(data),
+ PayloadVersion: 2,
+ }
- d := new(PackagistPayload)
- pl, err := d.Delete(p)
- require.NoError(t, err)
- require.Nil(t, pl)
- })
+ req, reqBody, err := newPackagistRequest(context.Background(), hook, task)
+ require.NotNil(t, req)
+ require.NotNil(t, reqBody)
+ require.NoError(t, err)
- t.Run("Fork", func(t *testing.T) {
- p := forkTestPayload()
-
- d := new(PackagistPayload)
- pl, err := d.Fork(p)
- require.NoError(t, err)
- require.Nil(t, pl)
- })
-
- t.Run("Push", func(t *testing.T) {
- p := pushTestPayload()
-
- d := new(PackagistPayload)
- d.PackagistRepository.URL = "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN"
- pl, err := d.Push(p)
- require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &PackagistPayload{}, pl)
-
- assert.Equal(t, "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN", pl.(*PackagistPayload).PackagistRepository.URL)
- })
-
- t.Run("Issue", func(t *testing.T) {
- p := issueTestPayload()
-
- d := new(PackagistPayload)
- p.Action = api.HookIssueOpened
- pl, err := d.Issue(p)
- require.NoError(t, err)
- require.Nil(t, pl)
-
- p.Action = api.HookIssueClosed
- pl, err = d.Issue(p)
- require.NoError(t, err)
- require.Nil(t, pl)
- })
-
- t.Run("IssueComment", func(t *testing.T) {
- p := issueCommentTestPayload()
-
- d := new(PackagistPayload)
- pl, err := d.IssueComment(p)
- require.NoError(t, err)
- require.Nil(t, pl)
- })
-
- t.Run("PullRequest", func(t *testing.T) {
- p := pullRequestTestPayload()
-
- d := new(PackagistPayload)
- pl, err := d.PullRequest(p)
- require.NoError(t, err)
- require.Nil(t, pl)
- })
-
- t.Run("PullRequestComment", func(t *testing.T) {
- p := pullRequestCommentTestPayload()
-
- d := new(PackagistPayload)
- pl, err := d.IssueComment(p)
- require.NoError(t, err)
- require.Nil(t, pl)
- })
-
- t.Run("Review", func(t *testing.T) {
- p := pullRequestTestPayload()
- p.Action = api.HookIssueReviewed
-
- d := new(PackagistPayload)
- pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved)
- require.NoError(t, err)
- require.Nil(t, pl)
- })
-
- t.Run("Repository", func(t *testing.T) {
- p := repositoryTestPayload()
-
- d := new(PackagistPayload)
- pl, err := d.Repository(p)
- require.NoError(t, err)
- require.Nil(t, pl)
- })
-
- t.Run("Package", func(t *testing.T) {
- p := packageTestPayload()
-
- d := new(PackagistPayload)
- pl, err := d.Package(p)
- require.NoError(t, err)
- require.Nil(t, pl)
- })
-
- t.Run("Wiki", func(t *testing.T) {
- p := wikiTestPayload()
-
- d := new(PackagistPayload)
- p.Action = api.HookWikiCreated
- pl, err := d.Wiki(p)
- require.NoError(t, err)
- require.Nil(t, pl)
-
- p.Action = api.HookWikiEdited
- pl, err = d.Wiki(p)
- require.NoError(t, err)
- require.Nil(t, pl)
-
- p.Action = api.HookWikiDeleted
- pl, err = d.Wiki(p)
- require.NoError(t, err)
- require.Nil(t, pl)
- })
-
- t.Run("Release", func(t *testing.T) {
- p := pullReleaseTestPayload()
-
- d := new(PackagistPayload)
- pl, err := d.Release(p)
- require.NoError(t, err)
- require.Nil(t, pl)
- })
-}
-
-func TestPackagistJSONPayload(t *testing.T) {
- p := pushTestPayload()
-
- pl, err := new(PackagistPayload).Push(p)
- require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &PackagistPayload{}, pl)
-
- json, err := pl.JSONPayload()
- require.NoError(t, err)
- assert.NotEmpty(t, json)
+ assert.Equal(t, "POST", req.Method)
+ assert.Equal(t, "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN", req.URL.String())
+ assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+ var body PackagistPayload
+ err = json.NewDecoder(req.Body).Decode(&body)
+ assert.NoError(t, err)
+ assert.Equal(t, "https://packagist.org/packages/example", body.PackagistRepository.URL)
+ })
+ }
}
diff --git a/services/webhook/payloader.go b/services/webhook/payloader.go
index bd482c04ea..f87e6e4eec 100644
--- a/services/webhook/payloader.go
+++ b/services/webhook/payloader.go
@@ -4,58 +4,112 @@
package webhook
import (
+ "bytes"
+ "fmt"
+ "net/http"
+
+ webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
)
-// PayloadConvertor defines the interface to convert system webhook payload to external payload
-type PayloadConvertor interface {
- api.Payloader
- Create(*api.CreatePayload) (api.Payloader, error)
- Delete(*api.DeletePayload) (api.Payloader, error)
- Fork(*api.ForkPayload) (api.Payloader, error)
- Issue(*api.IssuePayload) (api.Payloader, error)
- IssueComment(*api.IssueCommentPayload) (api.Payloader, error)
- Push(*api.PushPayload) (api.Payloader, error)
- PullRequest(*api.PullRequestPayload) (api.Payloader, error)
- Review(*api.PullRequestPayload, webhook_module.HookEventType) (api.Payloader, error)
- Repository(*api.RepositoryPayload) (api.Payloader, error)
- Release(*api.ReleasePayload) (api.Payloader, error)
- Wiki(*api.WikiPayload) (api.Payloader, error)
- Package(*api.PackagePayload) (api.Payloader, error)
+// payloadConvertor defines the interface to convert system payload to webhook payload
+type payloadConvertor[T any] interface {
+ Create(*api.CreatePayload) (T, error)
+ Delete(*api.DeletePayload) (T, error)
+ Fork(*api.ForkPayload) (T, error)
+ Issue(*api.IssuePayload) (T, error)
+ IssueComment(*api.IssueCommentPayload) (T, error)
+ Push(*api.PushPayload) (T, error)
+ PullRequest(*api.PullRequestPayload) (T, error)
+ Review(*api.PullRequestPayload, webhook_module.HookEventType) (T, error)
+ Repository(*api.RepositoryPayload) (T, error)
+ Release(*api.ReleasePayload) (T, error)
+ Wiki(*api.WikiPayload) (T, error)
+ Package(*api.PackagePayload) (T, error)
}
-func convertPayloader(s PayloadConvertor, p api.Payloader, event webhook_module.HookEventType) (api.Payloader, error) {
+func convertUnmarshalledJSON[T, P any](convert func(P) (T, error), data []byte) (T, error) {
+ var p P
+ if err := json.Unmarshal(data, &p); err != nil {
+ var t T
+ return t, fmt.Errorf("could not unmarshal payload: %w", err)
+ }
+ return convert(p)
+}
+
+func newPayload[T any](rc payloadConvertor[T], data []byte, event webhook_module.HookEventType) (T, error) {
switch event {
case webhook_module.HookEventCreate:
- return s.Create(p.(*api.CreatePayload))
+ return convertUnmarshalledJSON(rc.Create, data)
case webhook_module.HookEventDelete:
- return s.Delete(p.(*api.DeletePayload))
+ return convertUnmarshalledJSON(rc.Delete, data)
case webhook_module.HookEventFork:
- return s.Fork(p.(*api.ForkPayload))
+ return convertUnmarshalledJSON(rc.Fork, data)
case webhook_module.HookEventIssues, webhook_module.HookEventIssueAssign, webhook_module.HookEventIssueLabel, webhook_module.HookEventIssueMilestone:
- return s.Issue(p.(*api.IssuePayload))
+ return convertUnmarshalledJSON(rc.Issue, data)
case webhook_module.HookEventIssueComment, webhook_module.HookEventPullRequestComment:
- pl, ok := p.(*api.IssueCommentPayload)
- if ok {
- return s.IssueComment(pl)
- }
- return s.PullRequest(p.(*api.PullRequestPayload))
+ // previous code sometimes sent s.PullRequest(p.(*api.PullRequestPayload))
+ // however I couldn't find in notifier.go such a payload with an HookEvent***Comment event
+
+ // History (most recent first):
+ // - refactored in https://github.com/go-gitea/gitea/pull/12310
+ // - assertion added in https://github.com/go-gitea/gitea/pull/12046
+ // - issue raised in https://github.com/go-gitea/gitea/issues/11940#issuecomment-645713996
+ // > That's because for HookEventPullRequestComment event, some places use IssueCommentPayload and others use PullRequestPayload
+
+ // In modules/actions/workflows.go:183 the type assertion is always payload.(*api.IssueCommentPayload)
+ return convertUnmarshalledJSON(rc.IssueComment, data)
case webhook_module.HookEventPush:
- return s.Push(p.(*api.PushPayload))
+ return convertUnmarshalledJSON(rc.Push, data)
case webhook_module.HookEventPullRequest, webhook_module.HookEventPullRequestAssign, webhook_module.HookEventPullRequestLabel,
webhook_module.HookEventPullRequestMilestone, webhook_module.HookEventPullRequestSync, webhook_module.HookEventPullRequestReviewRequest:
- return s.PullRequest(p.(*api.PullRequestPayload))
+ return convertUnmarshalledJSON(rc.PullRequest, data)
case webhook_module.HookEventPullRequestReviewApproved, webhook_module.HookEventPullRequestReviewRejected, webhook_module.HookEventPullRequestReviewComment:
- return s.Review(p.(*api.PullRequestPayload), event)
+ return convertUnmarshalledJSON(func(p *api.PullRequestPayload) (T, error) {
+ return rc.Review(p, event)
+ }, data)
case webhook_module.HookEventRepository:
- return s.Repository(p.(*api.RepositoryPayload))
+ return convertUnmarshalledJSON(rc.Repository, data)
case webhook_module.HookEventRelease:
- return s.Release(p.(*api.ReleasePayload))
+ return convertUnmarshalledJSON(rc.Release, data)
case webhook_module.HookEventWiki:
- return s.Wiki(p.(*api.WikiPayload))
+ return convertUnmarshalledJSON(rc.Wiki, data)
case webhook_module.HookEventPackage:
- return s.Package(p.(*api.PackagePayload))
+ return convertUnmarshalledJSON(rc.Package, data)
}
- return s, nil
+ var t T
+ return t, fmt.Errorf("newPayload unsupported event: %s", event)
+}
+
+func newJSONRequest[T any](pc payloadConvertor[T], w *webhook_model.Webhook, t *webhook_model.HookTask, withDefaultHeaders bool) (*http.Request, []byte, error) {
+ payload, err := newPayload(pc, []byte(t.PayloadContent), t.EventType)
+ if err != nil {
+ return nil, nil, err
+ }
+ return newJSONRequestWithPayload(payload, w, t, withDefaultHeaders)
+}
+
+func newJSONRequestWithPayload(payload any, w *webhook_model.Webhook, t *webhook_model.HookTask, withDefaultHeaders bool) (*http.Request, []byte, error) {
+ body, err := json.MarshalIndent(payload, "", " ")
+ if err != nil {
+ return nil, nil, err
+ }
+
+ method := w.HTTPMethod
+ if method == "" {
+ method = http.MethodPost
+ }
+
+ req, err := http.NewRequest(method, w.URL, bytes.NewReader(body))
+ if err != nil {
+ return nil, nil, err
+ }
+ req.Header.Set("Content-Type", "application/json")
+
+ if withDefaultHeaders {
+ return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body)
+ }
+ return req, body, nil
}
diff --git a/services/webhook/slack.go b/services/webhook/slack.go
index 945b0662d8..ba8bac27d9 100644
--- a/services/webhook/slack.go
+++ b/services/webhook/slack.go
@@ -4,8 +4,9 @@
package webhook
import (
- "errors"
+ "context"
"fmt"
+ "net/http"
"regexp"
"strings"
@@ -39,7 +40,6 @@ func GetSlackHook(w *webhook_model.Webhook) *SlackMeta {
type SlackPayload struct {
Channel string `json:"channel"`
Text string `json:"text"`
- Color string `json:"-"`
Username string `json:"username"`
IconURL string `json:"icon_url"`
UnfurlLinks int `json:"unfurl_links"`
@@ -56,15 +56,6 @@ type SlackAttachment struct {
Text string `json:"text"`
}
-// JSONPayload Marshals the SlackPayload to json
-func (s *SlackPayload) JSONPayload() ([]byte, error) {
- data, err := json.MarshalIndent(s, "", " ")
- if err != nil {
- return []byte{}, err
- }
- return data, nil
-}
-
// SlackTextFormatter replaces &, <, > with HTML characters
// see: https://api.slack.com/docs/formatting
func SlackTextFormatter(s string) string {
@@ -98,10 +89,8 @@ func SlackLinkToRef(repoURL, ref string) string {
return SlackLinkFormatter(url, refName)
}
-var _ PayloadConvertor = &SlackPayload{}
-
-// Create implements PayloadConvertor Create method
-func (s *SlackPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
+// Create implements payloadConvertor Create method
+func (s slackConvertor) Create(p *api.CreatePayload) (SlackPayload, error) {
repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
refLink := SlackLinkToRef(p.Repo.HTMLURL, p.Ref)
text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName)
@@ -110,7 +99,7 @@ func (s *SlackPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
}
// Delete composes Slack payload for delete a branch or tag.
-func (s *SlackPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
+func (s slackConvertor) Delete(p *api.DeletePayload) (SlackPayload, error) {
refName := git.RefName(p.Ref).ShortName()
repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName)
@@ -119,7 +108,7 @@ func (s *SlackPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
}
// Fork composes Slack payload for forked by a repository.
-func (s *SlackPayload) Fork(p *api.ForkPayload) (api.Payloader, error) {
+func (s slackConvertor) Fork(p *api.ForkPayload) (SlackPayload, error) {
baseLink := SlackLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName)
forkLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink)
@@ -127,8 +116,8 @@ func (s *SlackPayload) Fork(p *api.ForkPayload) (api.Payloader, error) {
return s.createPayload(text, nil), nil
}
-// Issue implements PayloadConvertor Issue method
-func (s *SlackPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
+// Issue implements payloadConvertor Issue method
+func (s slackConvertor) Issue(p *api.IssuePayload) (SlackPayload, error) {
text, issueTitle, attachmentText, color := getIssuesPayloadInfo(p, SlackLinkFormatter, true)
var attachments []SlackAttachment
@@ -146,8 +135,8 @@ func (s *SlackPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
return s.createPayload(text, attachments), nil
}
-// IssueComment implements PayloadConvertor IssueComment method
-func (s *SlackPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) {
+// IssueComment implements payloadConvertor IssueComment method
+func (s slackConvertor) IssueComment(p *api.IssueCommentPayload) (SlackPayload, error) {
text, issueTitle, color := getIssueCommentPayloadInfo(p, SlackLinkFormatter, true)
return s.createPayload(text, []SlackAttachment{{
@@ -158,28 +147,28 @@ func (s *SlackPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader,
}}), nil
}
-// Wiki implements PayloadConvertor Wiki method
-func (s *SlackPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) {
+// Wiki implements payloadConvertor Wiki method
+func (s slackConvertor) Wiki(p *api.WikiPayload) (SlackPayload, error) {
text, _, _ := getWikiPayloadInfo(p, SlackLinkFormatter, true)
return s.createPayload(text, nil), nil
}
-// Release implements PayloadConvertor Release method
-func (s *SlackPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
+// Release implements payloadConvertor Release method
+func (s slackConvertor) Release(p *api.ReleasePayload) (SlackPayload, error) {
text, _ := getReleasePayloadInfo(p, SlackLinkFormatter, true)
return s.createPayload(text, nil), nil
}
-func (s *SlackPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
+func (s slackConvertor) Package(p *api.PackagePayload) (SlackPayload, error) {
text, _ := getPackagePayloadInfo(p, SlackLinkFormatter, true)
return s.createPayload(text, nil), nil
}
-// Push implements PayloadConvertor Push method
-func (s *SlackPayload) Push(p *api.PushPayload) (api.Payloader, error) {
+// Push implements payloadConvertor Push method
+func (s slackConvertor) Push(p *api.PushPayload) (SlackPayload, error) {
// n new commits
var (
commitDesc string
@@ -219,8 +208,8 @@ func (s *SlackPayload) Push(p *api.PushPayload) (api.Payloader, error) {
}}), nil
}
-// PullRequest implements PayloadConvertor PullRequest method
-func (s *SlackPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) {
+// PullRequest implements payloadConvertor PullRequest method
+func (s slackConvertor) PullRequest(p *api.PullRequestPayload) (SlackPayload, error) {
text, issueTitle, attachmentText, color := getPullRequestPayloadInfo(p, SlackLinkFormatter, true)
var attachments []SlackAttachment
@@ -238,8 +227,8 @@ func (s *SlackPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, er
return s.createPayload(text, attachments), nil
}
-// Review implements PayloadConvertor Review method
-func (s *SlackPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) {
+// Review implements payloadConvertor Review method
+func (s slackConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (SlackPayload, error) {
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)
titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index)
@@ -250,7 +239,7 @@ func (s *SlackPayload) Review(p *api.PullRequestPayload, event webhook_module.Ho
case api.HookIssueReviewed:
action, err := parseHookPullRequestEventType(event)
if err != nil {
- return nil, err
+ return SlackPayload{}, err
}
text = fmt.Sprintf("[%s] Pull request review %s: [%s](%s) by %s", repoLink, action, title, titleLink, senderLink)
@@ -259,8 +248,8 @@ func (s *SlackPayload) Review(p *api.PullRequestPayload, event webhook_module.Ho
return s.createPayload(text, nil), nil
}
-// Repository implements PayloadConvertor Repository method
-func (s *SlackPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) {
+// Repository implements payloadConvertor Repository method
+func (s slackConvertor) Repository(p *api.RepositoryPayload) (SlackPayload, error) {
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
var text string
@@ -275,8 +264,8 @@ func (s *SlackPayload) Repository(p *api.RepositoryPayload) (api.Payloader, erro
return s.createPayload(text, nil), nil
}
-func (s *SlackPayload) createPayload(text string, attachments []SlackAttachment) *SlackPayload {
- return &SlackPayload{
+func (s slackConvertor) createPayload(text string, attachments []SlackAttachment) SlackPayload {
+ return SlackPayload{
Channel: s.Channel,
Text: text,
Username: s.Username,
@@ -285,21 +274,27 @@ func (s *SlackPayload) createPayload(text string, attachments []SlackAttachment)
}
}
-// GetSlackPayload converts a slack webhook into a SlackPayload
-func GetSlackPayload(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) {
- s := new(SlackPayload)
+type slackConvertor struct {
+ Channel string
+ Username string
+ IconURL string
+ Color string
+}
- slack := &SlackMeta{}
- if err := json.Unmarshal([]byte(meta), &slack); err != nil {
- return s, errors.New("GetSlackPayload meta json:" + err.Error())
+var _ payloadConvertor[SlackPayload] = slackConvertor{}
+
+func newSlackRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
+ meta := &SlackMeta{}
+ if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
+ return nil, nil, fmt.Errorf("newSlackRequest meta json: %w", err)
}
-
- s.Channel = slack.Channel
- s.Username = slack.Username
- s.IconURL = slack.IconURL
- s.Color = slack.Color
-
- return convertPayloader(s, p, event)
+ sc := slackConvertor{
+ Channel: meta.Channel,
+ Username: meta.Username,
+ IconURL: meta.IconURL,
+ Color: meta.Color,
+ }
+ return newJSONRequest(sc, w, t, true)
}
var slackChannel = regexp.MustCompile(`^#?[a-z0-9_-]{1,80}$`)
diff --git a/services/webhook/slack_test.go b/services/webhook/slack_test.go
index b1340963e2..7ebf16aba2 100644
--- a/services/webhook/slack_test.go
+++ b/services/webhook/slack_test.go
@@ -4,8 +4,11 @@
package webhook
import (
+ "context"
"testing"
+ webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -14,201 +17,180 @@ import (
)
func TestSlackPayload(t *testing.T) {
+ sc := slackConvertor{}
+
t.Run("Create", func(t *testing.T) {
p := createTestPayload()
- d := new(SlackPayload)
- pl, err := d.Create(p)
+ pl, err := sc.Create(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "[:] branch created by user1", pl.(*SlackPayload).Text)
+ assert.Equal(t, "[:] branch created by user1", pl.Text)
})
t.Run("Delete", func(t *testing.T) {
p := deleteTestPayload()
- d := new(SlackPayload)
- pl, err := d.Delete(p)
+ pl, err := sc.Delete(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "[:test] branch deleted by user1", pl.(*SlackPayload).Text)
+ assert.Equal(t, "[:test] branch deleted by user1", pl.Text)
})
t.Run("Fork", func(t *testing.T) {
p := forkTestPayload()
- d := new(SlackPayload)
- pl, err := d.Fork(p)
+ pl, err := sc.Fork(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, " is forked to ", pl.(*SlackPayload).Text)
+ assert.Equal(t, " is forked to ", pl.Text)
})
t.Run("Push", func(t *testing.T) {
p := pushTestPayload()
- d := new(SlackPayload)
- pl, err := d.Push(p)
+ pl, err := sc.Push(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "[:] 2 new commits pushed by user1", pl.(*SlackPayload).Text)
+ assert.Equal(t, "[:] 2 new commits pushed by user1", pl.Text)
})
t.Run("Issue", func(t *testing.T) {
p := issueTestPayload()
- d := new(SlackPayload)
p.Action = api.HookIssueOpened
- pl, err := d.Issue(p)
+ pl, err := sc.Issue(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "[] Issue opened: by ", pl.(*SlackPayload).Text)
+ assert.Equal(t, "[] Issue opened: by ", pl.Text)
p.Action = api.HookIssueClosed
- pl, err = d.Issue(p)
+ pl, err = sc.Issue(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "[] Issue closed: by ", pl.(*SlackPayload).Text)
+ assert.Equal(t, "[] Issue closed: by ", pl.Text)
})
t.Run("IssueComment", func(t *testing.T) {
p := issueCommentTestPayload()
- d := new(SlackPayload)
- pl, err := d.IssueComment(p)
+ pl, err := sc.IssueComment(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "[] New comment on issue by ", pl.(*SlackPayload).Text)
+ assert.Equal(t, "[] New comment on issue by ", pl.Text)
})
t.Run("PullRequest", func(t *testing.T) {
p := pullRequestTestPayload()
- d := new(SlackPayload)
- pl, err := d.PullRequest(p)
+ pl, err := sc.PullRequest(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "[] Pull request opened: by ", pl.(*SlackPayload).Text)
+ assert.Equal(t, "[] Pull request opened: by ", pl.Text)
})
t.Run("PullRequestComment", func(t *testing.T) {
p := pullRequestCommentTestPayload()
- d := new(SlackPayload)
- pl, err := d.IssueComment(p)
+ pl, err := sc.IssueComment(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "[] New comment on pull request by ", pl.(*SlackPayload).Text)
+ assert.Equal(t, "[] New comment on pull request by ", pl.Text)
})
t.Run("Review", func(t *testing.T) {
p := pullRequestTestPayload()
p.Action = api.HookIssueReviewed
- d := new(SlackPayload)
- pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved)
+ pl, err := sc.Review(p, webhook_module.HookEventPullRequestReviewApproved)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "[] Pull request review approved: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by ", pl.(*SlackPayload).Text)
+ assert.Equal(t, "[] Pull request review approved: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by ", pl.Text)
})
t.Run("Repository", func(t *testing.T) {
p := repositoryTestPayload()
- d := new(SlackPayload)
- pl, err := d.Repository(p)
+ pl, err := sc.Repository(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "[] Repository created by ", pl.(*SlackPayload).Text)
+ assert.Equal(t, "[] Repository created by ", pl.Text)
})
t.Run("Package", func(t *testing.T) {
p := packageTestPayload()
- d := new(SlackPayload)
- pl, err := d.Package(p)
+ pl, err := sc.Package(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "Package created: by ", pl.(*SlackPayload).Text)
+ assert.Equal(t, "Package created: by ", pl.Text)
})
t.Run("Wiki", func(t *testing.T) {
p := wikiTestPayload()
- d := new(SlackPayload)
p.Action = api.HookWikiCreated
- pl, err := d.Wiki(p)
+ pl, err := sc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "[] New wiki page '' (Wiki change comment) by ", pl.(*SlackPayload).Text)
+ assert.Equal(t, "[] New wiki page '' (Wiki change comment) by ", pl.Text)
p.Action = api.HookWikiEdited
- pl, err = d.Wiki(p)
+ pl, err = sc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "[] Wiki page '' edited (Wiki change comment) by ", pl.(*SlackPayload).Text)
+ assert.Equal(t, "[] Wiki page '' edited (Wiki change comment) by ", pl.Text)
p.Action = api.HookWikiDeleted
- pl, err = d.Wiki(p)
+ pl, err = sc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "[] Wiki page '' deleted by ", pl.(*SlackPayload).Text)
+ assert.Equal(t, "[] Wiki page '' deleted by ", pl.Text)
})
t.Run("Release", func(t *testing.T) {
p := pullReleaseTestPayload()
- d := new(SlackPayload)
- pl, err := d.Release(p)
+ pl, err := sc.Release(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- assert.Equal(t, "[] Release created: by ", pl.(*SlackPayload).Text)
+ assert.Equal(t, "[] Release created: by ", pl.Text)
})
}
func TestSlackJSONPayload(t *testing.T) {
p := pushTestPayload()
-
- pl, err := new(SlackPayload).Push(p)
+ data, err := p.JSONPayload()
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &SlackPayload{}, pl)
- json, err := pl.JSONPayload()
+ hook := &webhook_model.Webhook{
+ RepoID: 3,
+ IsActive: true,
+ Type: webhook_module.SLACK,
+ URL: "https://slack.example.com/",
+ Meta: `{}`,
+ HTTPMethod: "POST",
+ }
+ task := &webhook_model.HookTask{
+ HookID: hook.ID,
+ EventType: webhook_module.HookEventPush,
+ PayloadContent: string(data),
+ PayloadVersion: 2,
+ }
+
+ req, reqBody, err := newSlackRequest(context.Background(), hook, task)
+ require.NotNil(t, req)
+ require.NotNil(t, reqBody)
require.NoError(t, err)
- assert.NotEmpty(t, json)
+
+ assert.Equal(t, "POST", req.Method)
+ assert.Equal(t, "https://slack.example.com/", req.URL.String())
+ assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
+ assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+ var body SlackPayload
+ err = json.NewDecoder(req.Body).Decode(&body)
+ assert.NoError(t, err)
+ assert.Equal(t, "[:] 2 new commits pushed by user1", body.Text)
}
func TestIsValidSlackChannel(t *testing.T) {
diff --git a/services/webhook/telegram.go b/services/webhook/telegram.go
index 1bdc74e183..e4a5b5a424 100644
--- a/services/webhook/telegram.go
+++ b/services/webhook/telegram.go
@@ -4,14 +4,15 @@
package webhook
import (
+ "context"
"fmt"
+ "net/http"
"strings"
webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/markup"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
)
@@ -41,22 +42,8 @@ func GetTelegramHook(w *webhook_model.Webhook) *TelegramMeta {
return s
}
-var _ PayloadConvertor = &TelegramPayload{}
-
-// JSONPayload Marshals the TelegramPayload to json
-func (t *TelegramPayload) JSONPayload() ([]byte, error) {
- t.ParseMode = "HTML"
- t.DisableWebPreview = true
- t.Message = markup.Sanitize(t.Message)
- data, err := json.MarshalIndent(t, "", " ")
- if err != nil {
- return []byte{}, err
- }
- return data, nil
-}
-
// Create implements PayloadConvertor Create method
-func (t *TelegramPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
+func (t telegramConvertor) Create(p *api.CreatePayload) (TelegramPayload, error) {
// created tag/branch
refName := git.RefName(p.Ref).ShortName()
title := fmt.Sprintf(`[%s ] %s %s created`, p.Repo.HTMLURL, p.Repo.FullName, p.RefType,
@@ -66,7 +53,7 @@ func (t *TelegramPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
}
// Delete implements PayloadConvertor Delete method
-func (t *TelegramPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
+func (t telegramConvertor) Delete(p *api.DeletePayload) (TelegramPayload, error) {
// created tag/branch
refName := git.RefName(p.Ref).ShortName()
title := fmt.Sprintf(`[%s ] %s %s deleted`, p.Repo.HTMLURL, p.Repo.FullName, p.RefType,
@@ -76,14 +63,14 @@ func (t *TelegramPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
}
// Fork implements PayloadConvertor Fork method
-func (t *TelegramPayload) Fork(p *api.ForkPayload) (api.Payloader, error) {
+func (t telegramConvertor) Fork(p *api.ForkPayload) (TelegramPayload, error) {
title := fmt.Sprintf(`%s is forked to %s `, p.Forkee.FullName, p.Repo.HTMLURL, p.Repo.FullName)
return createTelegramPayload(title), nil
}
// Push implements PayloadConvertor Push method
-func (t *TelegramPayload) Push(p *api.PushPayload) (api.Payloader, error) {
+func (t telegramConvertor) Push(p *api.PushPayload) (TelegramPayload, error) {
var (
branchName = git.RefName(p.Ref).ShortName()
commitDesc string
@@ -121,34 +108,34 @@ func (t *TelegramPayload) Push(p *api.PushPayload) (api.Payloader, error) {
}
// Issue implements PayloadConvertor Issue method
-func (t *TelegramPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
+func (t telegramConvertor) Issue(p *api.IssuePayload) (TelegramPayload, error) {
text, _, attachmentText, _ := getIssuesPayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayload(text + "\n\n" + attachmentText), nil
}
// IssueComment implements PayloadConvertor IssueComment method
-func (t *TelegramPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) {
+func (t telegramConvertor) IssueComment(p *api.IssueCommentPayload) (TelegramPayload, error) {
text, _, _ := getIssueCommentPayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayload(text + "\n" + p.Comment.Body), nil
}
// PullRequest implements PayloadConvertor PullRequest method
-func (t *TelegramPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) {
+func (t telegramConvertor) PullRequest(p *api.PullRequestPayload) (TelegramPayload, error) {
text, _, attachmentText, _ := getPullRequestPayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayload(text + "\n" + attachmentText), nil
}
// Review implements PayloadConvertor Review method
-func (t *TelegramPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) {
+func (t telegramConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (TelegramPayload, error) {
var text, attachmentText string
switch p.Action {
case api.HookIssueReviewed:
action, err := parseHookPullRequestEventType(event)
if err != nil {
- return nil, err
+ return TelegramPayload{}, err
}
text = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title)
@@ -159,7 +146,7 @@ func (t *TelegramPayload) Review(p *api.PullRequestPayload, event webhook_module
}
// Repository implements PayloadConvertor Repository method
-func (t *TelegramPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) {
+func (t telegramConvertor) Repository(p *api.RepositoryPayload) (TelegramPayload, error) {
var title string
switch p.Action {
case api.HookRepoCreated:
@@ -169,36 +156,39 @@ func (t *TelegramPayload) Repository(p *api.RepositoryPayload) (api.Payloader, e
title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
return createTelegramPayload(title), nil
}
- return nil, nil
+ return TelegramPayload{}, nil
}
// Wiki implements PayloadConvertor Wiki method
-func (t *TelegramPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) {
+func (t telegramConvertor) Wiki(p *api.WikiPayload) (TelegramPayload, error) {
text, _, _ := getWikiPayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayload(text), nil
}
// Release implements PayloadConvertor Release method
-func (t *TelegramPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
+func (t telegramConvertor) Release(p *api.ReleasePayload) (TelegramPayload, error) {
text, _ := getReleasePayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayload(text), nil
}
-func (t *TelegramPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
+func (t telegramConvertor) Package(p *api.PackagePayload) (TelegramPayload, error) {
text, _ := getPackagePayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayload(text), nil
}
-// GetTelegramPayload converts a telegram webhook into a TelegramPayload
-func GetTelegramPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) {
- return convertPayloader(new(TelegramPayload), p, event)
-}
-
-func createTelegramPayload(message string) *TelegramPayload {
- return &TelegramPayload{
+func createTelegramPayload(message string) TelegramPayload {
+ return TelegramPayload{
Message: strings.TrimSpace(message),
}
}
+
+type telegramConvertor struct{}
+
+var _ payloadConvertor[TelegramPayload] = telegramConvertor{}
+
+func newTelegramRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
+ return newJSONRequest(telegramConvertor{}, w, t, true)
+}
diff --git a/services/webhook/telegram_test.go b/services/webhook/telegram_test.go
index 5b9927d057..27ab96cd09 100644
--- a/services/webhook/telegram_test.go
+++ b/services/webhook/telegram_test.go
@@ -4,8 +4,11 @@
package webhook
import (
+ "context"
"testing"
+ webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -14,199 +17,177 @@ import (
)
func TestTelegramPayload(t *testing.T) {
+ tc := telegramConvertor{}
t.Run("Create", func(t *testing.T) {
p := createTestPayload()
- d := new(TelegramPayload)
- pl, err := d.Create(p)
+ pl, err := tc.Create(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, `[test/repo ] branch test created`, pl.(*TelegramPayload).Message)
+ assert.Equal(t, `[test/repo ] branch test created`, pl.Message)
})
t.Run("Delete", func(t *testing.T) {
p := deleteTestPayload()
- d := new(TelegramPayload)
- pl, err := d.Delete(p)
+ pl, err := tc.Delete(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, `[test/repo ] branch test deleted`, pl.(*TelegramPayload).Message)
+ assert.Equal(t, `[test/repo ] branch test deleted`, pl.Message)
})
t.Run("Fork", func(t *testing.T) {
p := forkTestPayload()
- d := new(TelegramPayload)
- pl, err := d.Fork(p)
+ pl, err := tc.Fork(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, `test/repo2 is forked to test/repo `, pl.(*TelegramPayload).Message)
+ assert.Equal(t, `test/repo2 is forked to test/repo `, pl.Message)
})
t.Run("Push", func(t *testing.T) {
p := pushTestPayload()
- d := new(TelegramPayload)
- pl, err := d.Push(p)
+ pl, err := tc.Push(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, "[test/repo :test ] 2 new commits\n[2020558 ] commit message - user1\n[2020558 ] commit message - user1", pl.(*TelegramPayload).Message)
+ assert.Equal(t, "[test/repo :test ] 2 new commits\n[2020558 ] commit message - user1\n[2020558 ] commit message - user1", pl.Message)
})
t.Run("Issue", func(t *testing.T) {
p := issueTestPayload()
- d := new(TelegramPayload)
p.Action = api.HookIssueOpened
- pl, err := d.Issue(p)
+ pl, err := tc.Issue(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, "[test/repo ] Issue opened: #2 crash by user1 \n\nissue body", pl.(*TelegramPayload).Message)
+ assert.Equal(t, "[test/repo ] Issue opened: #2 crash by user1 \n\nissue body", pl.Message)
p.Action = api.HookIssueClosed
- pl, err = d.Issue(p)
+ pl, err = tc.Issue(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, `[test/repo ] Issue closed: #2 crash by user1 `, pl.(*TelegramPayload).Message)
+ assert.Equal(t, `[test/repo ] Issue closed: #2 crash by user1 `, pl.Message)
})
t.Run("IssueComment", func(t *testing.T) {
p := issueCommentTestPayload()
- d := new(TelegramPayload)
- pl, err := d.IssueComment(p)
+ pl, err := tc.IssueComment(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, "[test/repo ] New comment on issue #2 crash by user1 \nmore info needed", pl.(*TelegramPayload).Message)
+ assert.Equal(t, "[test/repo ] New comment on issue #2 crash by user1 \nmore info needed", pl.Message)
})
t.Run("PullRequest", func(t *testing.T) {
p := pullRequestTestPayload()
- d := new(TelegramPayload)
- pl, err := d.PullRequest(p)
+ pl, err := tc.PullRequest(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, "[test/repo ] Pull request opened: #12 Fix bug by user1 \nfixes bug #2", pl.(*TelegramPayload).Message)
+ assert.Equal(t, "[test/repo ] Pull request opened: #12 Fix bug by user1 \nfixes bug #2", pl.Message)
})
t.Run("PullRequestComment", func(t *testing.T) {
p := pullRequestCommentTestPayload()
- d := new(TelegramPayload)
- pl, err := d.IssueComment(p)
+ pl, err := tc.IssueComment(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, "[test/repo ] New comment on pull request #12 Fix bug by user1 \nchanges requested", pl.(*TelegramPayload).Message)
+ assert.Equal(t, "[test/repo ] New comment on pull request #12 Fix bug by user1 \nchanges requested", pl.Message)
})
t.Run("Review", func(t *testing.T) {
p := pullRequestTestPayload()
p.Action = api.HookIssueReviewed
- d := new(TelegramPayload)
- pl, err := d.Review(p, webhook_module.HookEventPullRequestReviewApproved)
+ pl, err := tc.Review(p, webhook_module.HookEventPullRequestReviewApproved)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug\ngood job", pl.(*TelegramPayload).Message)
+ assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug\ngood job", pl.Message)
})
t.Run("Repository", func(t *testing.T) {
p := repositoryTestPayload()
- d := new(TelegramPayload)
- pl, err := d.Repository(p)
+ pl, err := tc.Repository(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, `[test/repo ] Repository created`, pl.(*TelegramPayload).Message)
+ assert.Equal(t, `[test/repo ] Repository created`, pl.Message)
})
t.Run("Package", func(t *testing.T) {
p := packageTestPayload()
- d := new(TelegramPayload)
- pl, err := d.Package(p)
+ pl, err := tc.Package(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, `Package created: GiteaContainer:latest by user1 `, pl.(*TelegramPayload).Message)
+ assert.Equal(t, `Package created: GiteaContainer:latest by user1 `, pl.Message)
})
t.Run("Wiki", func(t *testing.T) {
p := wikiTestPayload()
- d := new(TelegramPayload)
p.Action = api.HookWikiCreated
- pl, err := d.Wiki(p)
+ pl, err := tc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, `[test/repo ] New wiki page 'index ' (Wiki change comment) by user1 `, pl.(*TelegramPayload).Message)
+ assert.Equal(t, `[test/repo ] New wiki page 'index ' (Wiki change comment) by user1 `, pl.Message)
p.Action = api.HookWikiEdited
- pl, err = d.Wiki(p)
+ pl, err = tc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, `[test/repo ] Wiki page 'index ' edited (Wiki change comment) by user1 `, pl.(*TelegramPayload).Message)
+ assert.Equal(t, `[test/repo ] Wiki page 'index ' edited (Wiki change comment) by user1 `, pl.Message)
p.Action = api.HookWikiDeleted
- pl, err = d.Wiki(p)
+ pl, err = tc.Wiki(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, `[test/repo ] Wiki page 'index ' deleted by user1 `, pl.(*TelegramPayload).Message)
+ assert.Equal(t, `[test/repo ] Wiki page 'index ' deleted by user1 `, pl.Message)
})
t.Run("Release", func(t *testing.T) {
p := pullReleaseTestPayload()
- d := new(TelegramPayload)
- pl, err := d.Release(p)
+ pl, err := tc.Release(p)
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- assert.Equal(t, `[test/repo ] Release created: v1.0 by user1 `, pl.(*TelegramPayload).Message)
+ assert.Equal(t, `[test/repo ] Release created: v1.0 by user1 `, pl.Message)
})
}
func TestTelegramJSONPayload(t *testing.T) {
p := pushTestPayload()
-
- pl, err := new(TelegramPayload).Push(p)
+ data, err := p.JSONPayload()
require.NoError(t, err)
- require.NotNil(t, pl)
- require.IsType(t, &TelegramPayload{}, pl)
- json, err := pl.JSONPayload()
+ hook := &webhook_model.Webhook{
+ RepoID: 3,
+ IsActive: true,
+ Type: webhook_module.TELEGRAM,
+ URL: "https://telegram.example.com/",
+ Meta: ``,
+ HTTPMethod: "POST",
+ }
+ task := &webhook_model.HookTask{
+ HookID: hook.ID,
+ EventType: webhook_module.HookEventPush,
+ PayloadContent: string(data),
+ PayloadVersion: 2,
+ }
+
+ req, reqBody, err := newTelegramRequest(context.Background(), hook, task)
+ require.NotNil(t, req)
+ require.NotNil(t, reqBody)
require.NoError(t, err)
- assert.NotEmpty(t, json)
+
+ assert.Equal(t, "POST", req.Method)
+ assert.Equal(t, "https://telegram.example.com/", req.URL.String())
+ assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
+ assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+ var body TelegramPayload
+ err = json.NewDecoder(req.Body).Decode(&body)
+ assert.NoError(t, err)
+ assert.Equal(t, "[test/repo :test ] 2 new commits\n[2020558 ] commit message - user1\n[2020558 ] commit message - user1", body.Message)
}
diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go
index 068be29ccb..e6646501da 100644
--- a/services/webhook/webhook.go
+++ b/services/webhook/webhook.go
@@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
+ "net/http"
"strings"
"code.gitea.io/gitea/models/db"
@@ -16,6 +17,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
@@ -25,48 +27,16 @@ import (
"github.com/gobwas/glob"
)
-type webhook struct {
- name webhook_module.HookType
- payloadCreator func(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error)
-}
-
-var webhooks = map[webhook_module.HookType]*webhook{
- webhook_module.SLACK: {
- name: webhook_module.SLACK,
- payloadCreator: GetSlackPayload,
- },
- webhook_module.DISCORD: {
- name: webhook_module.DISCORD,
- payloadCreator: GetDiscordPayload,
- },
- webhook_module.DINGTALK: {
- name: webhook_module.DINGTALK,
- payloadCreator: GetDingtalkPayload,
- },
- webhook_module.TELEGRAM: {
- name: webhook_module.TELEGRAM,
- payloadCreator: GetTelegramPayload,
- },
- webhook_module.MSTEAMS: {
- name: webhook_module.MSTEAMS,
- payloadCreator: GetMSTeamsPayload,
- },
- webhook_module.FEISHU: {
- name: webhook_module.FEISHU,
- payloadCreator: GetFeishuPayload,
- },
- webhook_module.MATRIX: {
- name: webhook_module.MATRIX,
- payloadCreator: GetMatrixPayload,
- },
- webhook_module.WECHATWORK: {
- name: webhook_module.WECHATWORK,
- payloadCreator: GetWechatworkPayload,
- },
- webhook_module.PACKAGIST: {
- name: webhook_module.PACKAGIST,
- payloadCreator: GetPackagistPayload,
- },
+var webhookRequesters = map[webhook_module.HookType]func(context.Context, *webhook_model.Webhook, *webhook_model.HookTask) (req *http.Request, body []byte, err error){
+ webhook_module.SLACK: newSlackRequest,
+ webhook_module.DISCORD: newDiscordRequest,
+ webhook_module.DINGTALK: newDingtalkRequest,
+ webhook_module.TELEGRAM: newTelegramRequest,
+ webhook_module.MSTEAMS: newMSTeamsRequest,
+ webhook_module.FEISHU: newFeishuRequest,
+ webhook_module.MATRIX: newMatrixRequest,
+ webhook_module.WECHATWORK: newWechatworkRequest,
+ webhook_module.PACKAGIST: newPackagistRequest,
}
// IsValidHookTaskType returns true if a webhook registered
@@ -74,7 +44,7 @@ func IsValidHookTaskType(name string) bool {
if name == webhook_module.FORGEJO || name == webhook_module.GITEA || name == webhook_module.GOGS {
return true
}
- _, ok := webhooks[name]
+ _, ok := webhookRequesters[name]
return ok
}
@@ -158,7 +128,9 @@ func checkBranch(w *webhook_model.Webhook, branch string) bool {
return g.Match(branch)
}
-// PrepareWebhook creates a hook task and enqueues it for processing
+// PrepareWebhook creates a hook task and enqueues it for processing.
+// The payload is saved as-is. The adjustments depending on the webhook type happen
+// right before delivery, in the [Deliver] method.
func PrepareWebhook(ctx context.Context, w *webhook_model.Webhook, event webhook_module.HookEventType, p api.Payloader) error {
// Skip sending if webhooks are disabled.
if setting.DisableWebhooks {
@@ -192,25 +164,19 @@ func PrepareWebhook(ctx context.Context, w *webhook_model.Webhook, event webhook
}
}
- var payloader api.Payloader
- var err error
- webhook, ok := webhooks[w.Type]
- if ok {
- payloader, err = webhook.payloadCreator(p, event, w.Meta)
- if err != nil {
- return fmt.Errorf("create payload for %s[%s]: %w", w.Type, event, err)
- }
- } else {
- payloader = p
+ payload, err := p.JSONPayload()
+ if err != nil {
+ return fmt.Errorf("JSONPayload for %s: %w", event, err)
}
task, err := webhook_model.CreateHookTask(ctx, &webhook_model.HookTask{
- HookID: w.ID,
- Payloader: payloader,
- EventType: event,
+ HookID: w.ID,
+ PayloadContent: string(payload),
+ EventType: event,
+ PayloadVersion: 2,
})
if err != nil {
- return fmt.Errorf("CreateHookTask: %w", err)
+ return fmt.Errorf("CreateHookTask for %s: %w", event, err)
}
return enqueueHookTask(task.ID)
@@ -225,7 +191,7 @@ func PrepareWebhooks(ctx context.Context, source EventSource, event webhook_modu
if source.Repository != nil {
repoHooks, err := db.Find[webhook_model.Webhook](ctx, webhook_model.ListWebhookOptions{
RepoID: source.Repository.ID,
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
})
if err != nil {
return fmt.Errorf("ListWebhooksByOpts: %w", err)
@@ -239,7 +205,7 @@ func PrepareWebhooks(ctx context.Context, source EventSource, event webhook_modu
if owner != nil {
ownerHooks, err := db.Find[webhook_model.Webhook](ctx, webhook_model.ListWebhookOptions{
OwnerID: owner.ID,
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
})
if err != nil {
return fmt.Errorf("ListWebhooksByOpts: %w", err)
@@ -248,7 +214,7 @@ func PrepareWebhooks(ctx context.Context, source EventSource, event webhook_modu
}
// Add any admin-defined system webhooks
- systemHooks, err := webhook_model.GetSystemWebhooks(ctx, util.OptionalBoolTrue)
+ systemHooks, err := webhook_model.GetSystemWebhooks(ctx, optional.Some(true))
if err != nil {
return fmt.Errorf("GetSystemWebhooks: %w", err)
}
diff --git a/services/webhook/webhook_test.go b/services/webhook/webhook_test.go
index 338b94360b..5f5c146232 100644
--- a/services/webhook/webhook_test.go
+++ b/services/webhook/webhook_test.go
@@ -77,7 +77,3 @@ func TestPrepareWebhooksBranchFilterNoMatch(t *testing.T) {
unittest.AssertNotExistsBean(t, hookTask)
}
}
-
-// TODO TestHookTask_deliver
-
-// TODO TestDeliverHooks
diff --git a/services/webhook/wechatwork.go b/services/webhook/wechatwork.go
index 80245c7e77..46e7856ecf 100644
--- a/services/webhook/wechatwork.go
+++ b/services/webhook/wechatwork.go
@@ -4,11 +4,13 @@
package webhook
import (
+ "context"
"fmt"
+ "net/http"
"strings"
+ webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
)
@@ -28,20 +30,8 @@ type (
}
)
-// SetSecret sets the Wechatwork secret
-func (f *WechatworkPayload) SetSecret(_ string) {}
-
-// JSONPayload Marshals the WechatworkPayload to json
-func (f *WechatworkPayload) JSONPayload() ([]byte, error) {
- data, err := json.MarshalIndent(f, "", " ")
- if err != nil {
- return []byte{}, err
- }
- return data, nil
-}
-
-func newWechatworkMarkdownPayload(title string) *WechatworkPayload {
- return &WechatworkPayload{
+func newWechatworkMarkdownPayload(title string) WechatworkPayload {
+ return WechatworkPayload{
Msgtype: "markdown",
Markdown: struct {
Content string `json:"content"`
@@ -51,10 +41,8 @@ func newWechatworkMarkdownPayload(title string) *WechatworkPayload {
}
}
-var _ PayloadConvertor = &WechatworkPayload{}
-
// Create implements PayloadConvertor Create method
-func (f *WechatworkPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
+func (wc wechatworkConvertor) Create(p *api.CreatePayload) (WechatworkPayload, error) {
// created tag/branch
refName := git.RefName(p.Ref).ShortName()
title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName)
@@ -63,7 +51,7 @@ func (f *WechatworkPayload) Create(p *api.CreatePayload) (api.Payloader, error)
}
// Delete implements PayloadConvertor Delete method
-func (f *WechatworkPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
+func (wc wechatworkConvertor) Delete(p *api.DeletePayload) (WechatworkPayload, error) {
// created tag/branch
refName := git.RefName(p.Ref).ShortName()
title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName)
@@ -72,14 +60,14 @@ func (f *WechatworkPayload) Delete(p *api.DeletePayload) (api.Payloader, error)
}
// Fork implements PayloadConvertor Fork method
-func (f *WechatworkPayload) Fork(p *api.ForkPayload) (api.Payloader, error) {
+func (wc wechatworkConvertor) Fork(p *api.ForkPayload) (WechatworkPayload, error) {
title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName)
return newWechatworkMarkdownPayload(title), nil
}
// Push implements PayloadConvertor Push method
-func (f *WechatworkPayload) Push(p *api.PushPayload) (api.Payloader, error) {
+func (wc wechatworkConvertor) Push(p *api.PushPayload) (WechatworkPayload, error) {
var (
branchName = git.RefName(p.Ref).ShortName()
commitDesc string
@@ -108,7 +96,7 @@ func (f *WechatworkPayload) Push(p *api.PushPayload) (api.Payloader, error) {
}
// Issue implements PayloadConvertor Issue method
-func (f *WechatworkPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
+func (wc wechatworkConvertor) Issue(p *api.IssuePayload) (WechatworkPayload, error) {
text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true)
var content string
content += fmt.Sprintf(" >%s \n >%s \n > %s \n [%s](%s)", text, attachmentText, issueTitle, p.Issue.HTMLURL, p.Issue.HTMLURL)
@@ -117,7 +105,7 @@ func (f *WechatworkPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
}
// IssueComment implements PayloadConvertor IssueComment method
-func (f *WechatworkPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) {
+func (wc wechatworkConvertor) IssueComment(p *api.IssueCommentPayload) (WechatworkPayload, error) {
text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true)
var content string
content += fmt.Sprintf(" >%s \n >%s \n >%s \n [%s](%s)", text, p.Comment.Body, issueTitle, p.Comment.HTMLURL, p.Comment.HTMLURL)
@@ -126,7 +114,7 @@ func (f *WechatworkPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloa
}
// PullRequest implements PayloadConvertor PullRequest method
-func (f *WechatworkPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) {
+func (wc wechatworkConvertor) PullRequest(p *api.PullRequestPayload) (WechatworkPayload, error) {
text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true)
pr := fmt.Sprintf("> %s \r\n > %s \r\n > %s \r\n",
text, issueTitle, attachmentText)
@@ -135,13 +123,13 @@ func (f *WechatworkPayload) PullRequest(p *api.PullRequestPayload) (api.Payloade
}
// Review implements PayloadConvertor Review method
-func (f *WechatworkPayload) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (api.Payloader, error) {
+func (wc wechatworkConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (WechatworkPayload, error) {
var text, title string
switch p.Action {
case api.HookIssueReviewed:
action, err := parseHookPullRequestEventType(event)
if err != nil {
- return nil, err
+ return WechatworkPayload{}, err
}
title = fmt.Sprintf("[%s] Pull request review %s : #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title)
text = p.Review.Content
@@ -151,7 +139,7 @@ func (f *WechatworkPayload) Review(p *api.PullRequestPayload, event webhook_modu
}
// Repository implements PayloadConvertor Repository method
-func (f *WechatworkPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) {
+func (wc wechatworkConvertor) Repository(p *api.RepositoryPayload) (WechatworkPayload, error) {
var title string
switch p.Action {
case api.HookRepoCreated:
@@ -162,30 +150,33 @@ func (f *WechatworkPayload) Repository(p *api.RepositoryPayload) (api.Payloader,
return newWechatworkMarkdownPayload(title), nil
}
- return nil, nil
+ return WechatworkPayload{}, nil
}
// Wiki implements PayloadConvertor Wiki method
-func (f *WechatworkPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) {
+func (wc wechatworkConvertor) Wiki(p *api.WikiPayload) (WechatworkPayload, error) {
text, _, _ := getWikiPayloadInfo(p, noneLinkFormatter, true)
return newWechatworkMarkdownPayload(text), nil
}
// Release implements PayloadConvertor Release method
-func (f *WechatworkPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
+func (wc wechatworkConvertor) Release(p *api.ReleasePayload) (WechatworkPayload, error) {
text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true)
return newWechatworkMarkdownPayload(text), nil
}
-func (f *WechatworkPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
+func (wc wechatworkConvertor) Package(p *api.PackagePayload) (WechatworkPayload, error) {
text, _ := getPackagePayloadInfo(p, noneLinkFormatter, true)
return newWechatworkMarkdownPayload(text), nil
}
-// GetWechatworkPayload GetWechatworkPayload converts a ding talk webhook into a WechatworkPayload
-func GetWechatworkPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) {
- return convertPayloader(new(WechatworkPayload), p, event)
+type wechatworkConvertor struct{}
+
+var _ payloadConvertor[WechatworkPayload] = wechatworkConvertor{}
+
+func newWechatworkRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
+ return newJSONRequest(wechatworkConvertor{}, w, t, true)
}
diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go
index 81e0b84ea8..01c8cf987f 100644
--- a/services/wiki/wiki.go
+++ b/services/wiki/wiki.go
@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/sync"
@@ -87,7 +88,7 @@ func NormalizeWikiBranch(ctx context.Context, repo *repo_model.Repository, to st
return err
}
- if err := gitRepo.SetDefaultBranch(to); err != nil {
+ if err := gitrepo.SetDefaultBranch(ctx, repo, to); err != nil {
return err
}
diff --git a/tailwind.config.js b/tailwind.config.js
index 8c474c33a8..d783268bd7 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,16 +1,41 @@
import {readFileSync} from 'node:fs';
import {env} from 'node:process';
-import {parse} from 'css-variables-parser';
+import {parse} from 'postcss';
const isProduction = env.NODE_ENV !== 'development';
+function extractRootVars(css) {
+ const root = parse(css);
+ const vars = new Set();
+ root.walkRules((rule) => {
+ if (rule.selector !== ':root') return;
+ rule.each((decl) => {
+ if (decl.value && decl.prop.startsWith('--')) {
+ vars.add(decl.prop.substring(2));
+ }
+ });
+ });
+ return Array.from(vars);
+}
+
+const vars = extractRootVars([
+ readFileSync(new URL('web_src/css/themes/theme-gitea-light.css', import.meta.url), 'utf8'),
+ readFileSync(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url), 'utf8'),
+].join('\n'));
+
export default {
prefix: 'tw-',
+ important: true, // the frameworks are mixed together, so tailwind needs to override other framework's styles
content: [
isProduction && '!./templates/devtest/**/*',
isProduction && '!./web_src/js/standalone/devtest.js',
+ '!./templates/swagger/v1_json.tmpl',
+ '!./templates/user/auth/oidc_wellknown.tmpl',
+ '!**/*_test.go',
+ '!./modules/{public,options,templates}/bindata.go',
+ './{build,models,modules,routers,services}/**/*.go',
'./templates/**/*.tmpl',
- './web_src/**/*.{js,vue}',
+ './web_src/js/**/*.{js,vue}',
].filter(Boolean),
blocklist: [
// classes that don't work without CSS variables from "@tailwind base" which we don't use
@@ -22,15 +47,10 @@ export default {
theme: {
colors: {
// make `tw-bg-red` etc work with our CSS variables
- ...Object.fromEntries(
- Object.keys(parse([
- readFileSync(new URL('web_src/css/themes/theme-gitea-light.css', import.meta.url), 'utf8'),
- readFileSync(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url), 'utf8'),
- ].join('\n'), {})).filter((prop) => prop.startsWith('color-')).map((prop) => {
- const color = prop.substring(6);
- return [color, `var(--color-${color})`];
- })
- ),
+ ...Object.fromEntries(vars.filter((prop) => prop.startsWith('color-')).map((prop) => {
+ const color = prop.substring(6);
+ return [color, `var(--color-${color})`];
+ })),
inherit: 'inherit',
current: 'currentcolor',
transparent: 'transparent',
diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl
index 65e82ba26f..2187761828 100644
--- a/templates/admin/auth/edit.tmpl
+++ b/templates/admin/auth/edit.tmpl
@@ -438,7 +438,7 @@
{{ctx.Locale.Tr "admin.auths.tips"}}
-
GMail Settings:
+
{{ctx.Locale.Tr "admin.auths.tips.gmail_settings"}}
Host: smtp.gmail.com, Port: 587, Enable TLS Encryption: true
{{ctx.Locale.Tr "admin.auths.tips.oauth2.general"}}:
diff --git a/templates/admin/auth/new.tmpl b/templates/admin/auth/new.tmpl
index f32f77d5dc..d8935341f4 100644
--- a/templates/admin/auth/new.tmpl
+++ b/templates/admin/auth/new.tmpl
@@ -82,7 +82,7 @@
{{ctx.Locale.Tr "admin.auths.tips"}}
-
GMail Settings:
+
{{ctx.Locale.Tr "admin.auths.tips.gmail_settings"}}
Host: smtp.gmail.com, Port: 587, Enable TLS Encryption: true
{{ctx.Locale.Tr "admin.auths.tips.oauth2.general"}}:
diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl
index 8088315f17..cc7d338589 100644
--- a/templates/admin/dashboard.tmpl
+++ b/templates/admin/dashboard.tmpl
@@ -2,7 +2,7 @@
{{if .NeedUpdate}}
-
{{(ctx.Locale.Tr "admin.dashboard.new_version_hint" .RemoteVersion AppVer) | Str2html}}
+
{{ctx.Locale.Tr "admin.dashboard.new_version_hint" .RemoteVersion AppVer}}
{{end}}