diff --git a/.deadcode-out b/.deadcode-out
index 39b03c9704..39b2a5d2d6 100644
--- a/.deadcode-out
+++ b/.deadcode-out
@@ -14,6 +14,7 @@ package "code.gitea.io/gitea/models"
func (ErrUpdateTaskNotExist).Error
func (ErrUpdateTaskNotExist).Unwrap
func IsErrSHANotFound
+ func IsErrMergeDivergingFastForwardOnly
func GetYamlFixturesAccess
package "code.gitea.io/gitea/models/actions"
@@ -289,12 +290,14 @@ package "code.gitea.io/gitea/modules/timeutil"
package "code.gitea.io/gitea/modules/translation"
func (MockLocale).Language
+ func (MockLocale).TrString
func (MockLocale).Tr
func (MockLocale).TrN
func (MockLocale).PrettyNumber
package "code.gitea.io/gitea/modules/util"
func UnsafeStringToBytes
+ func OptionalBoolFromGeneric
package "code.gitea.io/gitea/modules/util/filebuffer"
func CreateFromReader
diff --git a/.dockerignore b/.dockerignore
index 80cbeb040c..d1a08977a5 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -71,7 +71,6 @@ cpu.out
/tests/e2e/test-artifacts
/tests/e2e/test-snapshots
/tests/*.ini
-/node_modules
/yarn.lock
/yarn-error.log
/npm-debug.log*
diff --git a/.eslintrc.yaml b/.eslintrc.yaml
index 78042a7598..e9991c02ba 100644
--- a/.eslintrc.yaml
+++ b/.eslintrc.yaml
@@ -12,6 +12,7 @@ plugins:
- "@eslint-community/eslint-plugin-eslint-comments"
- "@stylistic/eslint-plugin-js"
- eslint-plugin-array-func
+ - eslint-plugin-github
- eslint-plugin-i
- eslint-plugin-jquery
- eslint-plugin-no-jquery
@@ -209,6 +210,29 @@ rules:
func-names: [0]
func-style: [0]
getter-return: [2]
+ github/a11y-aria-label-is-well-formatted: [0]
+ github/a11y-no-title-attribute: [0]
+ github/a11y-no-visually-hidden-interactive-element: [0]
+ github/a11y-role-supports-aria-props: [0]
+ github/a11y-svg-has-accessible-name: [0]
+ github/array-foreach: [0]
+ github/async-currenttarget: [2]
+ github/async-preventdefault: [2]
+ github/authenticity-token: [0]
+ github/get-attribute: [0]
+ github/js-class-name: [0]
+ github/no-blur: [0]
+ github/no-d-none: [0]
+ github/no-dataset: [2]
+ github/no-dynamic-script-tag: [2]
+ github/no-implicit-buggy-globals: [2]
+ github/no-inner-html: [0]
+ github/no-innerText: [2]
+ github/no-then: [2]
+ github/no-useless-passive: [2]
+ github/prefer-observers: [2]
+ github/require-passive-events: [2]
+ github/unescaped-html-literal: [0]
grouped-accessor-pairs: [2]
guard-for-in: [0]
id-blacklist: [0]
@@ -272,7 +296,7 @@ rules:
jquery/no-delegate: [2]
jquery/no-each: [0]
jquery/no-extend: [2]
- jquery/no-fade: [0]
+ jquery/no-fade: [2]
jquery/no-filter: [0]
jquery/no-find: [0]
jquery/no-global-eval: [2]
@@ -285,7 +309,7 @@ rules:
jquery/no-is-function: [2]
jquery/no-is: [0]
jquery/no-load: [2]
- jquery/no-map: [0]
+ jquery/no-map: [2]
jquery/no-merge: [2]
jquery/no-param: [2]
jquery/no-parent: [0]
@@ -427,7 +451,7 @@ rules:
no-jquery/no-load: [2]
no-jquery/no-map-collection: [0]
no-jquery/no-map-util: [2]
- no-jquery/no-map: [0]
+ no-jquery/no-map: [2]
no-jquery/no-merge: [2]
no-jquery/no-node-name: [2]
no-jquery/no-noop: [2]
@@ -558,7 +582,6 @@ rules:
prefer-rest-params: [2]
prefer-spread: [2]
prefer-template: [2]
- quotes: [2, single, {avoidEscape: true, allowTemplateLiterals: true}]
radix: [2, as-needed]
regexp/confusing-quantifier: [2]
regexp/control-character-escape: [2]
@@ -811,7 +834,7 @@ rules:
wc/no-constructor-params: [2]
wc/no-constructor: [2]
wc/no-customized-built-in-elements: [2]
- wc/no-exports-with-element: [2]
+ wc/no-exports-with-element: [0]
wc/no-invalid-element-name: [2]
wc/no-invalid-extends: [2]
wc/no-method-prefixed-with-on: [2]
diff --git a/.forgejo/testdata/build-release/Dockerfile b/.forgejo/testdata/build-release/Dockerfile
index 4b6933845c..8dccad281c 100644
--- a/.forgejo/testdata/build-release/Dockerfile
+++ b/.forgejo/testdata/build-release/Dockerfile
@@ -1,3 +1,4 @@
-FROM public.ecr.aws/docker/library/alpine:3.18
+FROM code.forgejo.org/oci/alpine:3.19
+ARG RELEASE_VERSION=unkown
RUN mkdir -p /app/gitea
-RUN ( echo '#!/bin/sh' ; echo "echo forgejo v1.2.3" ) > /app/gitea/gitea ; chmod +x /app/gitea/gitea
+RUN ( echo '#!/bin/sh' ; echo "echo forgejo v$RELEASE_VERSION" ) > /app/gitea/gitea ; chmod +x /app/gitea/gitea
diff --git a/.forgejo/workflows/build-release-integration.yml b/.forgejo/workflows/build-release-integration.yml
index 32e67964ba..e066f0af5e 100644
--- a/.forgejo/workflows/build-release-integration.yml
+++ b/.forgejo/workflows/build-release-integration.yml
@@ -34,10 +34,10 @@ jobs:
lxc-ip-prefix: 10.0.9
- name: publish the forgejo release
+ shell: bash
run: |
set -x
- version=1.2.3
cat > /etc/docker/daemon.json < $binary$suffix
+ if test "$suffix" = .xz ; then
+ unxz --keep $binary$suffix
+ fi
+ chmod +x $binary
+ ./$binary --version | grep $version
+ curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$binary$suffix.sha256 > $binary$suffix.sha256
+ shasum -a 256 --check $binary$suffix.sha256
+ rm $binary$suffix
+ done
+ done
+
+ local sources=forgejo-src-$version.tar.gz
+ curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$sources > $sources
+ curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$sources.sha256 > $sources.sha256
+ shasum -a 256 --check $sources.sha256
+
+ docker pull ${{ steps.forgejo.outputs.host-port }}/root/forgejo:$version
+ docker pull ${{ steps.forgejo.outputs.host-port }}/root/forgejo:$version-rootless
+ }
+
#
# Create a new project with a fake forgejo and the release workflow only
#
@@ -62,46 +93,41 @@ jobs:
cp $dir/Dockerfile $dir/Dockerfile.rootless
forgejo-test-helper.sh push $dir $url root forgejo
- sha=$(forgejo-test-helper.sh branch_tip $url root/forgejo main)
+
+ forgejo-curl.sh api_json -X PUT --data-raw '{"data":"${{ steps.forgejo.outputs.token }}"}' $url/api/v1/repos/root/forgejo/actions/secrets/TOKEN
+ forgejo-curl.sh api_json -X PUT --data-raw '{"data":"root"}' $url/api/v1/repos/root/forgejo/actions/secrets/DOER
+ forgejo-curl.sh api_json -X PUT --data-raw '{"data":"true"}' $url/api/v1/repos/root/forgejo/actions/secrets/VERBOSE
#
# Push a tag to trigger the release workflow and wait for it to complete
#
+ version=1.2.3
+ sha=$(forgejo-test-helper.sh branch_tip $url root/forgejo main)
forgejo-curl.sh api_json --data-raw '{"tag_name": "v'$version'", "target": "'$sha'"}' $url/api/v1/repos/root/forgejo/tags
- forgejo-curl.sh api_json -X PUT --data-raw '{"data":"${{ steps.forgejo.outputs.token }}"}' $url/api/v1/repos/root/forgejo/actions/secrets/TOKEN
- forgejo-curl.sh api_json -X PUT --data-raw '{"data":"root"}' $url/api/v1/repos/root/forgejo/actions/secrets/DOER
LOOPS=180 forgejo-test-helper.sh wait_success "$url" root/forgejo $sha
+ sanity_check $url $version
#
- # uncomment to see the logs even when everything is reported to be working ok
+ # Push a commit to a branch that triggers the build of a test release
#
- #cat $FORGEJO_RUNNER_LOGS
+ version=1.2-test
+ (
+ git clone $url/root/forgejo /tmp/forgejo
+ cd /tmp/forgejo
+ date > DATE
+ git config user.email root@example.com
+ git config user.name username
+ git add .
+ git commit -m 'update'
+ git push $url/root/forgejo main:forgejo
+ )
+ sha=$(forgejo-test-helper.sh branch_tip $url root/forgejo forgejo)
+ LOOPS=180 forgejo-test-helper.sh wait_success "$url" root/forgejo $sha
+ sanity_check $url $version
- #
- # Minimal sanity checks. e2e test is for the setup-forgejo
- # action and the infrastructure playbook. Since the binary
- # is a script shell it does not test the sanity of the cross
- # build, only the sanity of the naming of the binaries.
- #
- for arch in amd64 arm64 arm-6 ; do
- binary=forgejo-$version-linux-$arch
- for suffix in '' '.xz' ; do
- curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$binary$suffix > $binary$suffix
- if test "$suffix" = .xz ; then
- unxz --keep $binary$suffix
- fi
- chmod +x $binary
- ./$binary --version | grep $version
- curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$binary$suffix.sha256 > $binary$suffix.sha256
- shasum -a 256 --check $binary$suffix.sha256
- rm $binary$suffix
- done
- done
-
- sources=forgejo-src-$version.tar.gz
- curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$sources > $sources
- curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$sources.sha256 > $sources.sha256
- shasum -a 256 --check $sources.sha256
-
- docker pull ${{ steps.forgejo.outputs.host-port }}/root/forgejo:$version
- docker pull ${{ steps.forgejo.outputs.host-port }}/root/forgejo:$version-rootless
+ - name: full logs
+ if: always()
+ run: |
+ sed -e 's/^/[RUNNER LOGS] /' ${{ steps.forgejo.outputs.runner-logs }}
+ docker logs forgejo | sed -e 's/^/[FORGEJO LOGS]/'
+ sleep 5 # hack to avoid mixing outputs in Forgejo v1.21
diff --git a/.forgejo/workflows/build-release.yml b/.forgejo/workflows/build-release.yml
index ebd99ea62f..c012991b3a 100644
--- a/.forgejo/workflows/build-release.yml
+++ b/.forgejo/workflows/build-release.yml
@@ -14,11 +14,12 @@
# secrets.CASCADE_DESTINATION_TOKEN: scope read:user, write:repository, write:issue
# vars.CASCADE_DESTINATION_DOER: forgejo-ci
#
-name: Build release
-
on:
push:
- tags: 'v*'
+ tags: 'v[0-9]+.[0-9]+.*'
+ branches:
+ - 'forgejo'
+ - 'v*/forgejo'
jobs:
release:
@@ -27,6 +28,8 @@ jobs:
if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root'
steps:
- uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
- name: Sanitize the name of the repository
id: repository
@@ -43,17 +46,39 @@ jobs:
go-version: "1.21"
check-latest: true
- - name: version from ref_name
- id: tag-version
+ - name: version from ref
+ id: release-info
+ shell: bash
run: |
- version="${{ github.ref_name }}"
- version=${version##*v}
- echo "value=$version" >> "$GITHUB_OUTPUT"
+ set -x
+ ref="${{ github.ref }}"
+ if [[ $ref =~ ^refs/heads/ ]] ; then
+ if test "$ref" = "refs/heads/forgejo" ; then
+ version=$(git tag -l --sort=version:refname --merged | grep -v -e '-test$' | tail -1 | sed -E -e 's/^(v[0-9]+\.[0-9]+).*/\1/')-test
+ else
+ version=${ref#refs/heads/}
+ version=${version%/forgejo}-test
+ fi
+ override=true
+ fi
+ if [[ $ref =~ ^refs/tags/ ]] ; then
+ version=${ref#refs/tags/}
+ override=false
+ fi
+ if test -z "$version" ; then
+ echo failed to figure out the release version from the reference=$ref
+ exit 1
+ fi
+ version=${version#v}
+ git describe --exclude '*-test' --tags --always
+ echo "sha=${{ github.sha }}" >> "$GITHUB_OUTPUT"
+ echo "version=$version" >> "$GITHUB_OUTPUT"
+ echo "override=$override" >> "$GITHUB_OUTPUT"
- name: release notes
id: release-notes
run: |
- anchor=${{ steps.tag-version.outputs.value }}
+ anchor=${{ steps.release-info.outputs.version }}
anchor=${anchor//./-}
cat >> "$GITHUB_OUTPUT" < /dev/null
+ curl -sS -X DELETE $url/api/v1/repos/forgejo-experimental/forgejo/tags/$tag > /dev/null
+ fi
+ # actions/checkout@v3 sets http.https://codeberg.org/.extraheader with the automatic token.
+ # Get rid of it so it does not prevent using the token that has write permissions
+ git config --local --unset http.https://codeberg.org/.extraheader
+ if test -f .git/shallow ; then
+ echo "unexptected .git/shallow file is present"
+ echo "it suggests a checkout --depth X was used which may prevent pushing the commit"
+ echo "it happens when actions/checkout is called without depth: 0"
+ fi
+ git push $url/forgejo-experimental/forgejo ${{ steps.release-info.outputs.sha }}:refs/tags/$tag
diff --git a/.forgejo/workflows/mirror.yml b/.forgejo/workflows/mirror.yml
index 3a4099db5d..599c8c01ff 100644
--- a/.forgejo/workflows/mirror.yml
+++ b/.forgejo/workflows/mirror.yml
@@ -1,10 +1,8 @@
name: mirror
on:
- push:
- branches:
- - 'forgejo'
- - 'v*/forgejo'
+ schedule:
+ - cron: '@daily'
jobs:
mirror:
diff --git a/.forgejo/workflows/publish-release.yml b/.forgejo/workflows/publish-release.yml
index 1afa941952..eaa14c3693 100644
--- a/.forgejo/workflows/publish-release.yml
+++ b/.forgejo/workflows/publish-release.yml
@@ -42,16 +42,19 @@ jobs:
- uses: actions/checkout@v3
- name: copy & sign
- uses: https://code.forgejo.org/forgejo/forgejo-build-publish/publish@v1
+ uses: https://code.forgejo.org/forgejo/forgejo-build-publish/publish@v4
with:
- forgejo: ${{ vars.FORGEJO }}
+ from-forgejo: ${{ vars.FORGEJO }}
+ to-forgejo: ${{ vars.FORGEJO }}
from-owner: ${{ vars.FROM_OWNER }}
to-owner: ${{ vars.TO_OWNER }}
repo: ${{ vars.REPO }}
- ref-name: ${{ github.ref_name }}
release-notes: "See https://codeberg.org/forgejo/forgejo/src/branch/forgejo/RELEASE-NOTES.md#{ANCHOR}"
- doer: ${{ vars.DOER }}
- token: ${{ secrets.TOKEN }}
+ ref-name: ${{ github.ref_name }}
+ sha: ${{ github.sha }}
+ from-token: ${{ secrets.TOKEN }}
+ to-doer: ${{ vars.DOER }}
+ to-token: ${{ secrets.TOKEN }}
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }}
verbose: ${{ vars.VERBOSE }}
diff --git a/.forgejo/workflows/testing.yml b/.forgejo/workflows/testing.yml
index a4b54c4da5..80fd87152e 100644
--- a/.forgejo/workflows/testing.yml
+++ b/.forgejo/workflows/testing.yml
@@ -23,10 +23,22 @@ jobs:
- 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
+ container:
+ image: 'docker.io/node:20-bookworm'
+ steps:
+ - uses: https://code.forgejo.org/actions/checkout@v3
+ - run: make deps-frontend
+ - run: make lint-frontend
+ - run: make checks-frontend
+ - run: make test-frontend
+ - run: make frontend
test-unit:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker
- needs: [backend-checks]
+ needs: [backend-checks, frontend-checks]
container:
image: 'docker.io/node:20-bookworm'
services:
@@ -67,7 +79,7 @@ jobs:
test-mysql:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker
- needs: [backend-checks]
+ needs: [backend-checks, frontend-checks]
container:
image: 'docker.io/node:20-bookworm'
services:
@@ -113,7 +125,7 @@ jobs:
test-pgsql:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker
- needs: [backend-checks]
+ needs: [backend-checks, frontend-checks]
container:
image: 'docker.io/node:20-bookworm'
services:
@@ -161,7 +173,7 @@ jobs:
test-sqlite:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker
- needs: [backend-checks]
+ needs: [backend-checks, frontend-checks]
container:
image: 'docker.io/node:20-bookworm'
steps:
diff --git a/.gitpod.yml b/.gitpod.yml
index 35b22c45ae..ed2f57f4bf 100644
--- a/.gitpod.yml
+++ b/.gitpod.yml
@@ -10,10 +10,19 @@ tasks:
- name: Run backend
command: |
gp sync-await setup
- if [ ! -f custom/conf/app.ini ]
- then
+
+ # Get the URL and extract the domain
+ url=$(gp url 3000)
+ domain=$(echo $url | awk -F[/:] '{print $4}')
+
+ if [ -f custom/conf/app.ini ]; then
+ sed -i "s|^ROOT_URL =.*|ROOT_URL = ${url}/|" custom/conf/app.ini
+ sed -i "s|^DOMAIN =.*|DOMAIN = ${domain}|" custom/conf/app.ini
+ sed -i "s|^SSH_DOMAIN =.*|SSH_DOMAIN = ${domain}|" custom/conf/app.ini
+ sed -i "s|^NO_REPLY_ADDRESS =.*|SSH_DOMAIN = noreply.${domain}|" custom/conf/app.ini
+ else
mkdir -p custom/conf/
- echo -e "[server]\nROOT_URL=$(gp url 3000)/" > custom/conf/app.ini
+ echo -e "[server]\nROOT_URL = ${url}/" > custom/conf/app.ini
echo -e "\n[database]\nDB_TYPE = sqlite3\nPATH = $GITPOD_REPO_ROOT/data/gitea.db" >> custom/conf/app.ini
fi
export TAGS="sqlite sqlite_unlock_notify"
diff --git a/.stylelintrc.yaml b/.stylelintrc.yaml
index a44294ee76..7dd0a566f2 100644
--- a/.stylelintrc.yaml
+++ b/.stylelintrc.yaml
@@ -98,7 +98,7 @@ rules:
at-rule-allowed-list: null
at-rule-disallowed-list: null
at-rule-empty-line-before: null
- at-rule-no-unknown: true
+ at-rule-no-unknown: [true, {ignoreAtRules: [tailwind]}]
at-rule-no-vendor-prefix: true
at-rule-property-required-list: null
block-no-empty: true
diff --git a/Dockerfile b/Dockerfile
index 98d1d707b5..c7c15c0b52 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -5,7 +5,7 @@ FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21-alpine3.19 as build
ARG GOPROXY
ENV GOPROXY ${GOPROXY:-direct}
-ARG GITEA_VERSION
+ARG RELEASE_VERSION
ARG TAGS="sqlite sqlite_unlock_notify"
ENV TAGS "bindata timetzdata $TAGS"
ARG CGO_EXTRA_CFLAGS
@@ -33,10 +33,10 @@ RUN apk --no-cache add build-base git nodejs npm
COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
-RUN make clean-all
+RUN make clean
RUN make frontend
RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini
-RUN make go-check generate-backend static-executable && xx-verify gitea
+RUN make RELEASE_VERSION=$RELEASE_VERSION go-check generate-backend static-executable && xx-verify gitea
# Copy local files
COPY docker/root /tmp/local
diff --git a/Dockerfile.rootless b/Dockerfile.rootless
index 0c09df61a9..bf3c92bbc5 100644
--- a/Dockerfile.rootless
+++ b/Dockerfile.rootless
@@ -5,7 +5,7 @@ FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21-alpine3.19 as build
ARG GOPROXY
ENV GOPROXY ${GOPROXY:-direct}
-ARG GITEA_VERSION
+ARG RELEASE_VERSION
ARG TAGS="sqlite sqlite_unlock_notify"
ENV TAGS "bindata timetzdata $TAGS"
ARG CGO_EXTRA_CFLAGS
@@ -33,10 +33,10 @@ RUN apk --no-cache add build-base git nodejs npm
COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
-RUN make clean-all
+RUN make clean
RUN make frontend
RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini
-RUN make go-check generate-backend static-executable && xx-verify gitea
+RUN make RELEASE_VERSION=$RELEASE_VERSION go-check generate-backend static-executable && xx-verify gitea
# Copy local files
COPY docker/rootless /tmp/local
diff --git a/Makefile b/Makefile
index aea352e6d5..9d0a92bdfb 100644
--- a/Makefile
+++ b/Makefile
@@ -29,10 +29,10 @@ XGO_VERSION := go-1.21.x
AIR_PACKAGE ?= github.com/cosmtrek/air@v1.49.0
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0
-GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2
+GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.56.1
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
-MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4
-SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.5
+MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.4.1
+SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.6-0.20240201115257-bcc7c78b7786
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.0.3
@@ -85,19 +85,18 @@ endif
STORED_VERSION_FILE := VERSION
HUGO_VERSION ?= 0.111.3
+GITEA_COMPATIBILITY ?= gitea-1.22.0
STORED_VERSION=$(shell cat $(STORED_VERSION_FILE) 2>/dev/null)
ifneq ($(STORED_VERSION),)
- GITEA_VERSION ?= $(STORED_VERSION)
+ FORGEJO_VERSION ?= $(STORED_VERSION)
else
- GITEA_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
+ FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//')+${GITEA_COMPATIBILITY}
endif
-VERSION = ${GITEA_VERSION}
+RELEASE_VERSION ?= ${FORGEJO_VERSION}
+VERSION ?= ${RELEASE_VERSION}
-# SemVer
-FORGEJO_VERSION := 7.0.0+0-gitea-1.22.0
-
-LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)"
+LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)"
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
@@ -107,7 +106,7 @@ GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/m
FOMANTIC_WORK_DIR := web_src/fomantic
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
-WEBPACK_CONFIGS := webpack.config.js
+WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/img/webpack
@@ -134,6 +133,8 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN
GO_DIRS := build cmd models modules routers services tests
WEB_DIRS := web_src/js web_src/css
+SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github
+
GO_SOURCES := $(wildcard *.go)
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go)
GO_SOURCES += $(GENERATED_GO_DEST)
@@ -154,8 +155,8 @@ endif
FORGEJO_API_SPEC := public/assets/forgejo/api.v1.yml
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
-SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|g
-SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|"basePath": "/api/v1"|g
+SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape}}/api/v1"|g
+SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape}}/api/v1"|"basePath": "/api/v1"|g
SWAGGER_EXCLUDE := code.gitea.io/sdk
SWAGGER_NEWLINE_COMMAND := -e '$$a\'
SWAGGER_SPEC_BRANDING := s|Gitea API|Forgejo API|g
@@ -212,6 +213,8 @@ help:
@echo " - lint-swagger lint swagger files"
@echo " - lint-templates lint template files"
@echo " - lint-yaml lint yaml files"
+ @echo " - lint-spell lint spelling"
+ @echo " - lint-spell-fix lint spelling and fix issues"
@echo " - checks run various consistency checks"
@echo " - checks-frontend check frontend files"
@echo " - checks-backend check backend files"
@@ -303,10 +306,6 @@ fmt-check: fmt
exit 1; \
fi
-.PHONY: misspell-check
-misspell-check:
- go run $(MISSPELL_PACKAGE) -error $(GO_DIRS) $(WEB_DIRS)
-
.PHONY: $(TAGS_EVIDENCE)
$(TAGS_EVIDENCE):
@mkdir -p $(MAKE_EVIDENCE_DIR)
@@ -368,13 +367,13 @@ checks: checks-frontend checks-backend
checks-frontend: lockfile-check svg-check
.PHONY: checks-backend
-checks-backend: tidy-check swagger-check fmt-check misspell-check forgejo-api-validate swagger-validate security-check
+checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check
.PHONY: lint
-lint: lint-frontend lint-backend
+lint: lint-frontend lint-backend lint-spell
.PHONY: lint-fix
-lint-fix: lint-frontend-fix lint-backend-fix
+lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix
.PHONY: lint-frontend
lint-frontend: lint-js lint-css
@@ -412,6 +411,14 @@ lint-swagger: node_modules
lint-md: node_modules
npx markdownlint docs *.md
+.PHONY: lint-spell
+lint-spell:
+ @go run $(MISSPELL_PACKAGE) -error $(SPELLCHECK_FILES)
+
+.PHONY: lint-spell-fix
+lint-spell-fix:
+ @go run $(MISSPELL_PACKAGE) -w $(SPELLCHECK_FILES)
+
.PHONY: lint-go
lint-go:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS)
@@ -617,8 +624,7 @@ test-mssql\#%: integrations.mssql.test generate-ini-mssql
test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
.PHONY: playwright
-playwright: $(PLAYWRIGHT_DIR)
- npm install --no-save @playwright/test
+playwright: deps-frontend
npx playwright install $(PLAYWRIGHT_FLAGS)
.PHONY: test-e2e%
@@ -631,7 +637,7 @@ test-e2e: test-e2e-sqlite
.PHONY: test-e2e-sqlite
test-e2e-sqlite: playwright e2e.sqlite.test generate-ini-sqlite
- GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test
+ GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test -test.run TestE2e
.PHONY: test-e2e-sqlite\#%
test-e2e-sqlite\#%: playwright e2e.sqlite.test generate-ini-sqlite
@@ -639,7 +645,7 @@ test-e2e-sqlite\#%: playwright e2e.sqlite.test generate-ini-sqlite
.PHONY: test-e2e-mysql
test-e2e-mysql: playwright e2e.mysql.test generate-ini-mysql
- GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test
+ GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test -test.run TestE2e
.PHONY: test-e2e-mysql\#%
test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql
@@ -647,7 +653,7 @@ test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql
.PHONY: test-e2e-pgsql
test-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql
- GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test
+ GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test -test.run TestE2e
.PHONY: test-e2e-pgsql\#%
test-e2e-pgsql\#%: playwright e2e.pgsql.test generate-ini-pgsql
@@ -655,12 +661,17 @@ test-e2e-pgsql\#%: playwright e2e.pgsql.test generate-ini-pgsql
.PHONY: test-e2e-mssql
test-e2e-mssql: playwright e2e.mssql.test generate-ini-mssql
- GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./e2e.mssql.test
+ GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./e2e.mssql.test -test.run TestE2e
.PHONY: test-e2e-mssql\#%
test-e2e-mssql\#%: playwright e2e.mssql.test generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./e2e.mssql.test -test.run TestE2e/$*
+.PHONY: test-e2e-debugserver
+test-e2e-debugserver: e2e.sqlite.test generate-ini-sqlite
+ sed -i s/3003/3000/g tests/sqlite.ini
+ GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test -test.run TestDebugserver -test.timeout 24h
+
.PHONY: bench-sqlite
bench-sqlite: integrations.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
@@ -997,7 +1008,7 @@ generate-gitignore:
.PHONY: generate-images
generate-images: | node_modules
- npm install --no-save --no-package-lock fabric@5 imagemin-zopfli@7
+ npm install --no-save fabric@6.0.0-beta19 imagemin-zopfli@7
node build/generate-images.js $(TAGS)
.PHONY: generate-manpage
@@ -1015,3 +1026,8 @@ docker:
# This endif closes the if at the top of the file
endif
+
+# Disable parallel execution because it would break some targets that don't
+# specify exact dependencies like 'backend' which does currently not depend
+# on 'frontend' to enable Node.js-less builds from source tarballs.
+.NOTPARALLEL:
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 65f79e3628..c72c898f67 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -4,6 +4,54 @@ 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.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:
+
+```shell
+$ git clone https://codeberg.org/forgejo/forgejo/
+$ git -C forgejo log --oneline --no-merges v1.21.5-0..v1.21.6-0
+```
+
+This stable release contains bug fixes and a **security fix**, as explained in the [v1.21.6-0 companion blog post](https://forgejo.org/2024-02-release-v1-21-6-0/).
+
+* Recommended Action
+
+ We **strongly 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.6+0-gitea-1.21.6`
+
+* Security fix
+
+ * [Fix XSS vulnerabilities](https://codeberg.org/forgejo/forgejo/pulls/2434). It enabled attackers to inject client-side scripts into web pages displayed to Forgejo visitors.
+
+* 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.
+
+ * [Always write proc-receive hook for all git versions](https://codeberg.org/forgejo/forgejo/commit/a1fb6a2346193439dafaee5acf071632246e6dd7).
+ * [Fix debian InRelease Acquire-By-Hash newline](https://codeberg.org/forgejo/forgejo/commit/8a2c4e9ff2743f47a8d1f081b9e35dcc16431115).
+ * [Fix missing link on outgoing new release notifications](https://codeberg.org/forgejo/forgejo/commit/3a061083d65bdfc9acf0cb5839b84f6a9c17a727).
+ * [Workaround to clean up old reviews on creating a new one](https://codeberg.org/forgejo/forgejo/commit/8377ecbfe1f2b72ec7d65c46cbc9022ad0ccd75f).
+ * [Fix push to create with capitalize repo name](https://codeberg.org/forgejo/forgejo/commit/8782275c9c66ad6fc7c44503d7df9dae7196aa65).
+ * In Markdown [don't try to make the link absolute if the link has a schema that's defined in `[markdown].CUSTOM_URL_SCHEMES`](https://codeberg.org/forgejo/forgejo/commit/6c100083c29fb0ccf0cc52e8767e540a260d9468), because they can't be made absolute.
+ * [Fix Ctrl+Enter on submitting review comment](https://codeberg.org/forgejo/forgejo/commit/1c3a31d85112d10fb948d6f0b763191ed6f68e90).
+ * In Git version v2.43.1, the behavior of `GIT_FLUSH` was accidentially flipped. This causes Forgejo to hang on the `check-attr` command, because no output was being flushed. [Workaround this by detecting if Git v2.43.1 is used and set `GIT_FLUSH=0` thus getting the correct behavior](https://codeberg.org/forgejo/forgejo/commit/ff468ab5e426582b068586ce13d5a5348365e783).
+ * [When setting `url.host` on a URL object with no port specified (like is the case of default port), the resulting URL's port will not change. Workaround this quirk in the URL standard by explicitly setting port for the http and https protocols](https://codeberg.org/forgejo/forgejo/commit/628e1036cfbcfae442cb6494249fe11410447056).
+ * [Fix elasticsearch Request Entity Too Large](https://codeberg.org/forgejo/forgejo/commit/e6f59f6e1489d63d53de0da1de406a7a71a82adb).
+ * [Do not send update/delete release notifications when it is in a draft state](https://codeberg.org/forgejo/forgejo/commit/3c54a1dbf62e56d948feb1008512900140033737).
+ * [Do not run Forgejo Actions workflows synchronized events on the same commit as the one used to create a pull request](https://codeberg.org/forgejo/forgejo/commit/ce96379aef6e92cff2e9982031d5248ef8b01947).
+ * [Fix a MySQL performance regression introduced in v1.21.4-0](https://codeberg.org/forgejo/forgejo/commit/af98a0a7c6f4cbb5340974958ebe4389e3bf4e9a).
+ * [Fix Internal Server Error when resolving comments](https://codeberg.org/forgejo/forgejo/commit/ad67d9ef1a219b21309f811c14e7353cbc4982e3).
+ * Packages
+ * Swift: [fix a failure to resolve from package registry](https://codeberg.org/forgejo/forgejo/commit/fab6780fda5d8ded020a98253a793e87ed94f634).
+ * Alpine: [if the APKINFO contains an install if condition, write it in the APKINDEX](https://codeberg.org/forgejo/forgejo/commit/7afbc62057b876fb6711ef58743f664a2509dde4).
+ * org-mode files
+ * [It is possible that the description of an `Regularlink` is `Text` and not another `Regularlink`](https://codeberg.org/forgejo/forgejo/commit/781d2a68ccb276bf13caf0b378b74d9efeab3d39).
+ * [Fix relative links on orgmode](https://codeberg.org/forgejo/forgejo/commit/fa700333ba2649d14f1670dd2745957704a33b40).
+
## 1.21.5-0
The [complete list of commits](https://codeberg.org/forgejo/forgejo/commits/branch/v1.21/forgejo) included in the `Forgejo v1.21.5-0` release can be reviewed from the command line with:
diff --git a/assets/go-licenses.json b/assets/go-licenses.json
index 48dfabaeae..2aab21595b 100644
--- a/assets/go-licenses.json
+++ b/assets/go-licenses.json
@@ -24,6 +24,11 @@
"path": "codeberg.org/gusted/mcaptcha/LICENSE",
"licenseText": "Copyright © 2022 William Zijl\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
},
+ {
+ "name": "connectrpc.com/connect",
+ "path": "connectrpc.com/connect/LICENSE",
+ "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright 2021-2024 The Connect Authors\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n"
+ },
{
"name": "dario.cat/mergo",
"path": "dario.cat/mergo/LICENSE",
@@ -229,11 +234,6 @@
"path": "github.com/bradfitz/gomemcache/memcache/LICENSE",
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n"
},
- {
- "name": "github.com/bufbuild/connect-go",
- "path": "github.com/bufbuild/connect-go/LICENSE",
- "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright 2021-2022 Buf Technologies, Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n"
- },
{
"name": "github.com/buildkite/terminal-to-html/v3",
"path": "github.com/buildkite/terminal-to-html/v3/LICENSE",
diff --git a/build/generate-images.js b/build/generate-images.js
index a3a0f8d8f3..db31d19e2a 100755
--- a/build/generate-images.js
+++ b/build/generate-images.js
@@ -1,20 +1,13 @@
#!/usr/bin/env node
import imageminZopfli from 'imagemin-zopfli';
import {optimize} from 'svgo';
-import {fabric} from 'fabric';
+import {loadSVGFromString, Canvas, Rect, util} from 'fabric/node';
import {readFile, writeFile} from 'node:fs/promises';
+import {argv, exit} from 'node:process';
-function exit(err) {
+function doExit(err) {
if (err) console.error(err);
- process.exit(err ? 1 : 0);
-}
-
-function loadSvg(svg) {
- return new Promise((resolve) => {
- fabric.loadSVGFromString(svg, (objects, options) => {
- resolve({objects, options});
- });
- });
+ exit(err ? 1 : 0);
}
async function generate(svg, path, {size, bg}) {
@@ -35,14 +28,14 @@ async function generate(svg, path, {size, bg}) {
return;
}
- const {objects, options} = await loadSvg(svg);
- const canvas = new fabric.Canvas();
+ const {objects, options} = await loadSVGFromString(svg);
+ const canvas = new Canvas();
canvas.setDimensions({width: size, height: size});
const ctx = canvas.getContext('2d');
ctx.scale(options.width ? (size / options.width) : 1, options.height ? (size / options.height) : 1);
if (bg) {
- canvas.add(new fabric.Rect({
+ canvas.add(new Rect({
left: 0,
top: 0,
height: size * (1 / (size / options.height)),
@@ -51,7 +44,7 @@ async function generate(svg, path, {size, bg}) {
}));
}
- canvas.add(fabric.util.groupSVGElements(objects, options));
+ canvas.add(util.groupSVGElements(objects, options));
canvas.renderAll();
let png = Buffer.from([]);
@@ -64,7 +57,7 @@ async function generate(svg, path, {size, bg}) {
}
async function main() {
- const gitea = process.argv.slice(2).includes('gitea');
+ const gitea = argv.slice(2).includes('gitea');
const logoSvg = await readFile(new URL('../assets/logo.svg', import.meta.url), 'utf8');
const faviconSvg = await readFile(new URL('../assets/favicon.svg', import.meta.url), 'utf8');
@@ -79,4 +72,8 @@ async function main() {
]);
}
-main().then(exit).catch(exit);
+try {
+ doExit(await main());
+} catch (err) {
+ doExit(err);
+}
diff --git a/build/generate-svg.js b/build/generate-svg.js
index b845da9367..660ac9157e 100755
--- a/build/generate-svg.js
+++ b/build/generate-svg.js
@@ -4,15 +4,16 @@ import {optimize} from 'svgo';
import {parse} from 'node:path';
import {readFile, writeFile, mkdir} from 'node:fs/promises';
import {fileURLToPath} from 'node:url';
+import {exit} from 'node:process';
const glob = (pattern) => fastGlob.sync(pattern, {
cwd: fileURLToPath(new URL('..', import.meta.url)),
absolute: true,
});
-function exit(err) {
+function doExit(err) {
if (err) console.error(err);
- process.exit(err ? 1 : 0);
+ exit(err ? 1 : 0);
}
async function processFile(file, {prefix, fullName} = {}) {
@@ -59,8 +60,11 @@ async function main() {
await Promise.all([
...processFiles('node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}),
...processFiles('web_src/svg/*.svg'),
- ...processFiles('public/assets/img/gitea.svg', {fullName: 'gitea-gitea'}),
]);
}
-main().then(exit).catch(exit);
+try {
+ doExit(await main());
+} catch (err) {
+ doExit(err);
+}
diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go
index fefe18d39c..a257ce21c8 100644
--- a/cmd/admin_user_create.go
+++ b/cmd/admin_user_create.go
@@ -10,8 +10,8 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
pwd "code.gitea.io/gitea/modules/auth/password"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"github.com/urfave/cli/v2"
)
@@ -123,10 +123,10 @@ func runCreateUser(c *cli.Context) error {
changePassword = c.Bool("must-change-password")
}
- restricted := util.OptionalBoolNone
+ restricted := optional.None[bool]()
if c.IsSet("restricted") {
- restricted = util.OptionalBoolOf(c.Bool("restricted"))
+ restricted = optional.Some(c.Bool("restricted"))
}
// default user visibility in app.ini
@@ -142,7 +142,7 @@ func runCreateUser(c *cli.Context) error {
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
IsRestricted: restricted,
}
diff --git a/cmd/forgejo/actions.go b/cmd/forgejo/actions.go
index afda831227..fc6b5f70f7 100644
--- a/cmd/forgejo/actions.go
+++ b/cmd/forgejo/actions.go
@@ -35,7 +35,8 @@ func SubcmdActionsGenerateRunnerToken(ctx context.Context) *cli.Command {
return &cli.Command{
Name: "generate-runner-token",
Usage: "Generate a new token for a runner to use to register with the server",
- Action: prepareWorkPathAndCustomConf(ctx, func(cliCtx *cli.Context) error { return RunGenerateActionsRunnerToken(ctx, cliCtx) }),
+ Before: prepareWorkPathAndCustomConf(ctx),
+ Action: func(cliCtx *cli.Context) error { return RunGenerateActionsRunnerToken(ctx, cliCtx) },
Flags: []cli.Flag{
&cli.StringFlag{
Name: "scope",
@@ -59,7 +60,8 @@ func SubcmdActionsRegister(ctx context.Context) *cli.Command {
return &cli.Command{
Name: "register",
Usage: "Idempotent registration of a runner using a shared secret",
- Action: prepareWorkPathAndCustomConf(ctx, func(cliCtx *cli.Context) error { return RunRegister(ctx, cliCtx) }),
+ Before: prepareWorkPathAndCustomConf(ctx),
+ Action: func(cliCtx *cli.Context) error { return RunRegister(ctx, cliCtx) },
Flags: []cli.Flag{
&cli.StringFlag{
Name: "secret",
@@ -219,25 +221,3 @@ func RunGenerateActionsRunnerToken(ctx context.Context, cliCtx *cli.Context) err
}
return nil
}
-
-func prepareWorkPathAndCustomConf(ctx context.Context, action cli.ActionFunc) func(cliCtx *cli.Context) error {
- return func(cliCtx *cli.Context) error {
- if !ContextGetNoInit(ctx) {
- var args setting.ArgWorkPathAndCustomConf
- // from children to parent, check the global flags
- for _, curCtx := range cliCtx.Lineage() {
- if curCtx.IsSet("work-path") && args.WorkPath == "" {
- args.WorkPath = curCtx.String("work-path")
- }
- if curCtx.IsSet("custom-path") && args.CustomPath == "" {
- args.CustomPath = curCtx.String("custom-path")
- }
- if curCtx.IsSet("config") && args.CustomConf == "" {
- args.CustomConf = curCtx.String("config")
- }
- }
- setting.InitWorkPathAndCommonConfig(os.Getenv, args)
- }
- return action(cliCtx)
- }
-}
diff --git a/cmd/forgejo/forgejo.go b/cmd/forgejo/forgejo.go
index affb39157a..710996e1c0 100644
--- a/cmd/forgejo/forgejo.go
+++ b/cmd/forgejo/forgejo.go
@@ -145,3 +145,25 @@ func handleCliResponseExtra(ctx context.Context, extra private.ResponseExtra) er
}
return cli.Exit(extra.Error, 1)
}
+
+func prepareWorkPathAndCustomConf(ctx context.Context) func(c *cli.Context) error {
+ return func(c *cli.Context) error {
+ if !ContextGetNoInit(ctx) {
+ var args setting.ArgWorkPathAndCustomConf
+ // from children to parent, check the global flags
+ for _, curCtx := range c.Lineage() {
+ if curCtx.IsSet("work-path") && args.WorkPath == "" {
+ args.WorkPath = curCtx.String("work-path")
+ }
+ if curCtx.IsSet("custom-path") && args.CustomPath == "" {
+ args.CustomPath = curCtx.String("custom-path")
+ }
+ if curCtx.IsSet("config") && args.CustomConf == "" {
+ args.CustomConf = curCtx.String("config")
+ }
+ }
+ setting.InitWorkPathAndCommonConfig(os.Getenv, args)
+ }
+ return nil
+ }
+}
diff --git a/cmd/keys.go b/cmd/keys.go
index 28b20958b9..81425a5722 100644
--- a/cmd/keys.go
+++ b/cmd/keys.go
@@ -71,7 +71,7 @@ func runKeys(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
- setup(ctx, false)
+ setup(ctx, c.Bool("debug"))
authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content)
// do not use handleCliResponseExtra or cli.NewExitError, if it exists immediately, it breaks some tests like Test_CmdKeys
diff --git a/cmd/serv.go b/cmd/serv.go
index e014877bc6..9d26515254 100644
--- a/cmd/serv.go
+++ b/cmd/serv.go
@@ -63,21 +63,10 @@ func setup(ctx context.Context, debug bool) {
setupConsoleLogger(log.FATAL, false, os.Stderr)
}
setting.MustInstalled()
- if debug {
- setting.RunMode = "dev"
- }
-
- // Check if setting.RepoRootPath exists. It could be the case that it doesn't exist, this can happen when
- // `[repository]` `ROOT` is a relative path and $GITEA_WORK_DIR isn't passed to the SSH connection.
if _, err := os.Stat(setting.RepoRootPath); err != nil {
- if os.IsNotExist(err) {
- _ = fail(ctx, "Incorrect configuration, no repository directory.", "Directory `[repository].ROOT` %q was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository].ROOT` an absolute value.", setting.RepoRootPath)
- } else {
- _ = fail(ctx, "Incorrect configuration, repository directory is inaccessible", "Directory `[repository].ROOT` %q is inaccessible. err: %v", setting.RepoRootPath, err)
- }
+ _ = fail(ctx, "Unable to access repository path", "Unable to access repository path %q, err: %v", setting.RepoRootPath, err)
return
}
-
if err := git.InitSimple(context.Background()); err != nil {
_ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err)
}
@@ -216,16 +205,18 @@ func runServ(c *cli.Context) error {
}
}
- // LowerCase and trim the repoPath as that's how they are stored.
- repoPath = strings.ToLower(strings.TrimSpace(repoPath))
-
rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 {
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
}
- username := strings.ToLower(rr[0])
- reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git"))
+ username := rr[0]
+ reponame := strings.TrimSuffix(rr[1], ".git")
+
+ // LowerCase and trim the repoPath as that's how they are stored.
+ // This should be done after splitting the repoPath into username and reponame
+ // so that username and reponame are not affected.
+ repoPath = strings.ToLower(strings.TrimSpace(repoPath))
if alphaDashDotPattern.MatchString(reponame) {
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 6d7b3bedf6..04714e5502 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -991,6 +991,9 @@ LEVEL = Info
;; Disable stars feature.
;DISABLE_STARS = false
;;
+;; Disable repository forking.
+;DISABLE_FORKS = false
+;;
;; The default branch name of new repositories
;DEFAULT_BRANCH = main
;;
@@ -1061,7 +1064,7 @@ LEVEL = Info
;; List of keywords used in Pull Request comments to automatically reopen a related issue
;REOPEN_KEYWORDS = reopen,reopens,reopened
;;
-;; Set default merge style for repository creating, valid options: merge, rebase, rebase-merge, squash
+;; Set default merge style for repository creating, valid options: merge, rebase, rebase-merge, squash, fast-forward-only
;DEFAULT_MERGE_STYLE = merge
;;
;; In the default merge message for squash commits include at most this many commits
@@ -1489,6 +1492,9 @@ 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
+;; - deletion: a user cannot delete their own account
+;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 b7c6ceb431..aa2cbcee5d 100644
--- a/docs/content/administration/config-cheat-sheet.en-us.md
+++ b/docs/content/administration/config-cheat-sheet.en-us.md
@@ -126,7 +126,7 @@ In addition, there is _`StaticRootPath`_ which can be set as a built-in at build
keywords used in Pull Request comments to automatically close a related issue
- `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: List of keywords used in Pull Request comments to automatically reopen
a related issue
-- `DEFAULT_MERGE_STYLE`: **merge**: Set default merge style for repository creating, valid options: `merge`, `rebase`, `rebase-merge`, `squash`
+- `DEFAULT_MERGE_STYLE`: **merge**: Set default merge style for repository creating, valid options: `merge`, `rebase`, `rebase-merge`, `squash`, `fast-forward-only`
- `DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT`: **50**: In the default merge message for squash commits include at most this many commits. Set to `-1` to include all commits
- `DEFAULT_MERGE_MESSAGE_SIZE`: **5120**: In the default merge message for squash commits limit the size of the commit messages. Set to `-1` to have no limit. Only used if `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES` is `true`.
- `DEFAULT_MERGE_MESSAGE_ALL_AUTHORS`: **false**: In the default merge message for squash commits walk all commits to include all authors in the Co-authored-by otherwise just use those in the limited list
diff --git a/docs/content/administration/config-cheat-sheet.zh-cn.md b/docs/content/administration/config-cheat-sheet.zh-cn.md
index 2cee70daab..01906930cb 100644
--- a/docs/content/administration/config-cheat-sheet.zh-cn.md
+++ b/docs/content/administration/config-cheat-sheet.zh-cn.md
@@ -29,7 +29,7 @@ menu:
[ini](https://github.com/go-ini/ini/#recursive-values) 这里的说明。
标注了 :exclamation: 的配置项表明除非你真的理解这个配置项的意义,否则最好使用默认值。
-在下面的默认值中,`$XYZ`代表环境变量`XYZ`的值(详见:`enviroment-to-ini`)。 _`XxYyZz`_是指默认配置的一部分列出的值。这些在 app.ini 文件中不起作用,仅在此处列出作为文档说明。
+在下面的默认值中,`$XYZ`代表环境变量`XYZ`的值(详见:`environment-to-ini`)。 _`XxYyZz`_是指默认配置的一部分列出的值。这些在 app.ini 文件中不起作用,仅在此处列出作为文档说明。
包含`#`或者`;`的变量必须使用引号(`` ` ``或者`""""`)包裹,否则会被解析为注释。
@@ -125,7 +125,7 @@ menu:
- `CLOSE_KEYWORDS`: **close**, **closes**, **closed**, **fix**, **fixes**, **fixed**, **resolve**, **resolves**, **resolved**: 在拉取请求评论中用于自动关闭相关问题的关键词列表。
- `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: 在拉取请求评论中用于自动重新打开相关问题的
关键词列表。
-- `DEFAULT_MERGE_STYLE`: **merge**: 设置创建仓库的默认合并方式,可选: `merge`, `rebase`, `rebase-merge`, `squash`
+- `DEFAULT_MERGE_STYLE`: **merge**: 设置创建仓库的默认合并方式,可选: `merge`, `rebase`, `rebase-merge`, `squash`, `fast-forward-only`
- `DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT`: **50**: 在默认合并消息中,对于`squash`提交,最多包括此数量的提交。设置为 -1 以包括所有提交。
- `DEFAULT_MERGE_MESSAGE_SIZE`: **5120**: 在默认的合并消息中,对于`squash`提交,限制提交消息的大小。设置为 `-1`以取消限制。仅在`POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`为`true`时使用。
- `DEFAULT_MERGE_MESSAGE_ALL_AUTHORS`: **false**: 在默认合并消息中,对于`squash`提交,遍历所有提交以包括所有作者的`Co-authored-by`,否则仅使用限定列表中的作者。
diff --git a/docs/content/contributing/guidelines-backend.en-us.md b/docs/content/contributing/guidelines-backend.en-us.md
index 084b3886e8..3159a5ff7d 100644
--- a/docs/content/contributing/guidelines-backend.en-us.md
+++ b/docs/content/contributing/guidelines-backend.en-us.md
@@ -101,6 +101,10 @@ i.e. `services/user`, `models/repository`.
Since there are some packages which use the same package name, it is possible that you find packages like `modules/user`, `models/user`, and `services/user`. When these packages are imported in one Go file, it's difficult to know which package we are using and if it's a variable name or an import name. So, we always recommend to use import aliases. To differ from package variables which are commonly in camelCase, just use **snake_case** for import aliases.
i.e. `import user_service "code.gitea.io/gitea/services/user"`
+### Implementing `io.Closer`
+
+If a type implements `io.Closer`, calling `Close` multiple times must not fail or `panic` but return an error or `nil`.
+
### Important Gotchas
- Never write `x.Update(exemplar)` without an explicit `WHERE` clause:
diff --git a/docs/content/development/hacking-on-gitea.en-us.md b/docs/content/development/hacking-on-gitea.en-us.md
index 4b132c49d9..df8a9047d6 100644
--- a/docs/content/development/hacking-on-gitea.en-us.md
+++ b/docs/content/development/hacking-on-gitea.en-us.md
@@ -243,10 +243,10 @@ documentation using:
make generate-swagger
```
-You should validate your generated Swagger file and spell-check it with:
+You should validate your generated Swagger file:
```bash
-make swagger-validate misspell-check
+make swagger-validate
```
You should commit the changed swagger JSON file. The continuous integration
diff --git a/docs/content/development/hacking-on-gitea.zh-cn.md b/docs/content/development/hacking-on-gitea.zh-cn.md
index 364bbf1ffe..2dba3c92b6 100644
--- a/docs/content/development/hacking-on-gitea.zh-cn.md
+++ b/docs/content/development/hacking-on-gitea.zh-cn.md
@@ -228,10 +228,10 @@ Gitea Logo的 PNG 和 SVG 版本是使用 `TAGS="gitea" make generate-images`
make generate-swagger
```
-您应该验证生成的 Swagger 文件并使用以下命令对其进行拼写检查:
+您应该验证生成的 Swagger 文件:
```bash
-make swagger-validate misspell-check
+make swagger-validate
```
您应该提交更改后的 swagger JSON 文件。持续集成服务器将使用以下方法检查是否已完成:
diff --git a/docs/content/installation/from-source.en-us.md b/docs/content/installation/from-source.en-us.md
index 601e074745..cd9fd56511 100644
--- a/docs/content/installation/from-source.en-us.md
+++ b/docs/content/installation/from-source.en-us.md
@@ -27,13 +27,7 @@ Next, [install Node.js with npm](https://nodejs.org/en/download/) which is
required to build the JavaScript and CSS files. The minimum supported Node.js
version is @minNodeVersion@ and the latest LTS version is recommended.
-**Note**: When executing make tasks that require external tools, like
-`make misspell-check`, Gitea will automatically download and build these as
-necessary. To be able to use these, you must have the `"$GOPATH/bin"` directory
-on the executable path. If you don't add the go bin directory to the
-executable path, you will have to manage this yourself.
-
-**Note 2**: Go version @minGoVersion@ or higher is required. However, it is recommended to
+**Note**: Go version @minGoVersion@ or higher is required. However, it is recommended to
obtain the same version as our continuous integration, see the advice given in
[Hacking on Gitea](development/hacking-on-gitea.md)
diff --git a/docs/content/installation/from-source.zh-cn.md b/docs/content/installation/from-source.zh-cn.md
index c2bd5785b2..3ff7efb4ed 100644
--- a/docs/content/installation/from-source.zh-cn.md
+++ b/docs/content/installation/from-source.zh-cn.md
@@ -21,9 +21,7 @@ menu:
接下来,[安装 Node.js 和 npm](https://nodejs.org/zh-cn/download/), 这是构建 JavaScript 和 CSS 文件所需的。最低支持的 Node.js 版本是 @minNodeVersion@,建议使用最新的 LTS 版本。
-**注意**:当执行需要外部工具的 make 任务(如`make misspell-check`)时,Gitea 将根据需要自动下载和构建这些工具。为了能够实现这个目的,你必须将`"$GOPATH/bin"`目录添加到可执行路径中。如果没有将 Go 的二进制目录添加到可执行路径中,你需要自行解决产生的问题。
-
-**注意2**:需要 Go 版本 @minGoVersion@ 或更高版本。不过,建议获取与我们的持续集成(continuous integration, CI)相同的版本,请参阅在 [Hacking on Gitea](development/hacking-on-gitea.md) 中给出的建议。
+**注意**:需要 Go 版本 @minGoVersion@ 或更高版本。不过,建议获取与我们的持续集成(continuous integration, CI)相同的版本,请参阅在 [Hacking on Gitea](development/hacking-on-gitea.md) 中给出的建议。
## 下载
diff --git a/docs/content/usage/packages/overview.en-us.md b/docs/content/usage/packages/overview.en-us.md
index 44d18ff482..89fc6f286e 100644
--- a/docs/content/usage/packages/overview.en-us.md
+++ b/docs/content/usage/packages/overview.en-us.md
@@ -42,7 +42,7 @@ The following package managers are currently supported:
| [PyPI](usage/packages/pypi.md) | Python | `pip`, `twine` |
| [RPM](usage/packages/rpm.md) | - | `yum`, `dnf`, `zypper` |
| [RubyGems](usage/packages/rubygems.md) | Ruby | `gem`, `Bundler` |
-| [Swift](usage/packages/rubygems.md) | Swift | `swift` |
+| [Swift](usage/packages/swift.md) | Swift | `swift` |
| [Vagrant](usage/packages/vagrant.md) | - | `vagrant` |
**The following paragraphs only apply if Packages are not globally disabled!**
diff --git a/docs/content/usage/packages/swift.en-us.md b/docs/content/usage/packages/swift.en-us.md
index 606fa20b36..38eb155641 100644
--- a/docs/content/usage/packages/swift.en-us.md
+++ b/docs/content/usage/packages/swift.en-us.md
@@ -26,7 +26,8 @@ To work with the Swift package registry, you need to use [swift](https://www.swi
To register the package registry and provide credentials, execute:
```shell
-swift package-registry set https://gitea.example.com/api/packages/{owner}/swift -login {username} -password {password}
+swift package-registry set https://gitea.example.com/api/packages/{owner}/swift
+swift package-registry login https://gitea.example.com/api/packages/{owner}/swift --username {username} --password {password}
```
| Placeholder | Description |
diff --git a/go.mod b/go.mod
index 03f3cedbd2..c993526b36 100644
--- a/go.mod
+++ b/go.mod
@@ -3,10 +3,11 @@ module code.gitea.io/gitea
go 1.21
require (
- code.gitea.io/actions-proto-go v0.3.1
+ code.gitea.io/actions-proto-go v0.4.0
code.gitea.io/gitea-vet v0.2.3
code.gitea.io/sdk/gitea v0.17.1
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
+ connectrpc.com/connect v1.15.0
gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669
gitea.com/go-chi/cache v0.2.0
gitea.com/go-chi/captcha v0.0.0-20230415143339-2c0754df4384
@@ -19,7 +20,6 @@ require (
github.com/alecthomas/chroma/v2 v2.12.0
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/blevesearch/bleve/v2 v2.3.10
- github.com/bufbuild/connect-go v1.10.0
github.com/buildkite/terminal-to-html/v3 v3.10.1
github.com/caddyserver/certmagic v0.20.0
github.com/chi-middleware/proxy v1.1.1
@@ -102,11 +102,11 @@ require (
github.com/yuin/goldmark v1.6.0
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0
- golang.org/x/crypto v0.18.0
+ golang.org/x/crypto v0.19.0
golang.org/x/image v0.15.0
- golang.org/x/net v0.20.0
+ golang.org/x/net v0.21.0
golang.org/x/oauth2 v0.16.0
- golang.org/x/sys v0.16.0
+ golang.org/x/sys v0.17.0
golang.org/x/text v0.14.0
golang.org/x/tools v0.17.0
google.golang.org/grpc v1.60.1
diff --git a/go.sum b/go.sum
index c43b51cd38..f8bf0567de 100644
--- a/go.sum
+++ b/go.sum
@@ -35,14 +35,16 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-code.gitea.io/actions-proto-go v0.3.1 h1:PMyiQtBKb8dNnpEO2R5rcZdXSis+UQZVo/SciMtR1aU=
-code.gitea.io/actions-proto-go v0.3.1/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A=
+code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU=
+code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI=
code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8=
code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM=
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
+connectrpc.com/connect v1.15.0 h1:lFdeCbZrVVDydAqwr4xGV2y+ULn+0Z73s5JBj2LikWo=
+connectrpc.com/connect v1.15.0/go.mod h1:bQmjpDY8xItMnttnurVgOkHUBMRT9cpsNi2O4AjKhmA=
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=
@@ -173,8 +175,6 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
-github.com/bufbuild/connect-go v1.10.0 h1:QAJ3G9A1OYQW2Jbk3DeoJbkCxuKArrvZgDt47mjdTbg=
-github.com/bufbuild/connect-go v1.10.0/go.mod h1:CAIePUgkDR5pAFaylSMtNK45ANQjp9JvpluG20rhpV8=
github.com/buildkite/terminal-to-html/v3 v3.10.1 h1:znT9eD26LQ59dDJJEpMCwkP4wEptEAPi74hsTBuHdEo=
github.com/buildkite/terminal-to-html/v3 v3.10.1/go.mod h1:qtuRyYs6/Sw3FS9jUyVEaANHgHGqZsGqMknPLyau5cQ=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
@@ -898,8 +898,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
-golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
+golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -981,8 +981,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
-golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1061,8 +1061,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
-golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1073,8 +1073,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
-golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
-golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
+golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/main.go b/main.go
index 56f97caf27..b8cc5668e1 100644
--- a/main.go
+++ b/main.go
@@ -29,6 +29,8 @@ var (
Version = "development" // program version for this build
Tags = "" // the Golang build tags
MakeVersion = "" // "make" program version if built with make
+
+ ReleaseVersion = ""
)
var ForgejoVersion = "1.0.0"
@@ -54,11 +56,18 @@ func main() {
log.GetManager().Close()
os.Exit(code)
}
- app := cmd.NewMainApp(Version, formatBuiltWith())
+ app := cmd.NewMainApp(Version, formatReleaseVersion()+formatBuiltWith())
_ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp
log.GetManager().Close()
}
+func formatReleaseVersion() string {
+ if len(ReleaseVersion) > 0 {
+ return " (release name " + ReleaseVersion + ")"
+ }
+ return ""
+}
+
func formatBuiltWith() string {
version := runtime.Version()
if len(MakeVersion) > 0 {
diff --git a/models/actions/artifact.go b/models/actions/artifact.go
index 5390f6288f..3d0a288e62 100644
--- a/models/actions/artifact.go
+++ b/models/actions/artifact.go
@@ -26,6 +26,8 @@ const (
ArtifactStatusUploadConfirmed // 2, ArtifactStatusUploadConfirmed is the status of an artifact upload that is confirmed
ArtifactStatusUploadError // 3, ArtifactStatusUploadError is the status of an artifact upload that is errored
ArtifactStatusExpired // 4, ArtifactStatusExpired is the status of an artifact that is expired
+ ArtifactStatusPendingDeletion // 5, ArtifactStatusPendingDeletion is the status of an artifact that is pending deletion
+ ArtifactStatusDeleted // 6, ArtifactStatusDeleted is the status of an artifact that is deleted
)
func init() {
@@ -147,8 +149,28 @@ func ListNeedExpiredArtifacts(ctx context.Context) ([]*ActionArtifact, error) {
Where("expired_unix < ? AND status = ?", timeutil.TimeStamp(time.Now().Unix()), ArtifactStatusUploadConfirmed).Find(&arts)
}
+// ListPendingDeleteArtifacts returns all artifacts in pending-delete status.
+// limit is the max number of artifacts to return.
+func ListPendingDeleteArtifacts(ctx context.Context, limit int) ([]*ActionArtifact, error) {
+ arts := make([]*ActionArtifact, 0, limit)
+ return arts, db.GetEngine(ctx).
+ Where("status = ?", ArtifactStatusPendingDeletion).Limit(limit).Find(&arts)
+}
+
// SetArtifactExpired sets an artifact to expired
func SetArtifactExpired(ctx context.Context, artifactID int64) error {
_, err := db.GetEngine(ctx).Where("id=? AND status = ?", artifactID, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusExpired)})
return err
}
+
+// SetArtifactNeedDelete sets an artifact to need-delete, cron job will delete it
+func SetArtifactNeedDelete(ctx context.Context, runID int64, name string) error {
+ _, err := db.GetEngine(ctx).Where("run_id=? AND artifact_name=? AND status = ?", runID, name, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusPendingDeletion)})
+ return err
+}
+
+// SetArtifactDeleted sets an artifact to deleted
+func SetArtifactDeleted(ctx context.Context, artifactID int64) error {
+ _, err := db.GetEngine(ctx).ID(artifactID).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusDeleted)})
+ return err
+}
diff --git a/models/actions/runner.go b/models/actions/runner.go
index 4103ba4477..b646146ee6 100644
--- a/models/actions/runner.go
+++ b/models/actions/runner.go
@@ -97,7 +97,7 @@ func (r *ActionRunner) StatusName() string {
}
func (r *ActionRunner) StatusLocaleName(lang translation.Locale) string {
- return lang.Tr("actions.runners.status." + r.StatusName())
+ return lang.TrString("actions.runners.status." + r.StatusName())
}
func (r *ActionRunner) IsOnline() bool {
diff --git a/models/actions/status.go b/models/actions/status.go
index c97578f2ac..eda2234137 100644
--- a/models/actions/status.go
+++ b/models/actions/status.go
@@ -41,7 +41,7 @@ func (s Status) String() string {
// LocaleString returns the locale string name of the Status
func (s Status) LocaleString(lang translation.Locale) string {
- return lang.Tr("actions.status." + s.String())
+ return lang.TrString("actions.status." + s.String())
}
// IsDone returns whether the Status is final
diff --git a/models/db/engine.go b/models/db/engine.go
index 41207674e4..660ea1f5e3 100755
--- a/models/db/engine.go
+++ b/models/db/engine.go
@@ -7,6 +7,7 @@ package db
import (
"context"
"database/sql"
+ "errors"
"fmt"
"io"
"reflect"
@@ -145,6 +146,7 @@ func InitEngine(ctx context.Context) error {
xormEngine.SetMaxOpenConns(setting.Database.MaxOpenConns)
xormEngine.SetMaxIdleConns(setting.Database.MaxIdleConns)
xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
+ xormEngine.SetConnMaxIdleTime(setting.Database.ConnMaxIdleTime)
xormEngine.SetDefaultContext(ctx)
if setting.Database.SlowQueryThreshold > 0 {
@@ -342,7 +344,7 @@ func (ErrorQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, e
}
func (h *ErrorQueryHook) AfterProcess(c *contexts.ContextHook) error {
- if c.Err != nil {
+ if c.Err != nil && !errors.Is(c.Err, context.Canceled) {
h.Logger.Log(8, log.ERROR, "[Error SQL Query] %s %v - %v", c.SQL, c.Args, c.Err)
}
return nil
diff --git a/models/error.go b/models/error.go
index 83dfe29805..75c53245de 100644
--- a/models/error.go
+++ b/models/error.go
@@ -493,6 +493,23 @@ func (err ErrMergeUnrelatedHistories) Error() string {
return fmt.Sprintf("Merge UnrelatedHistories Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
}
+// ErrMergeDivergingFastForwardOnly represents an error if a fast-forward-only merge fails because the branches diverge
+type ErrMergeDivergingFastForwardOnly struct {
+ StdOut string
+ StdErr string
+ Err error
+}
+
+// IsErrMergeDivergingFastForwardOnly checks if an error is a ErrMergeDivergingFastForwardOnly.
+func IsErrMergeDivergingFastForwardOnly(err error) bool {
+ _, ok := err.(ErrMergeDivergingFastForwardOnly)
+ return ok
+}
+
+func (err ErrMergeDivergingFastForwardOnly) Error() string {
+ return fmt.Sprintf("Merge DivergingFastForwardOnly Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
+}
+
// ErrRebaseConflicts represents an error if rebase fails with a conflict
type ErrRebaseConflicts struct {
Style repo_model.MergeStyle
diff --git a/models/fixtures/access.yml b/models/fixtures/access.yml
index 1bb6a9a8ac..641c453eb7 100644
--- a/models/fixtures/access.yml
+++ b/models/fixtures/access.yml
@@ -135,3 +135,27 @@
user_id: 31
repo_id: 28
mode: 4
+
+-
+ id: 24
+ user_id: 38
+ repo_id: 60
+ mode: 2
+
+-
+ id: 25
+ user_id: 38
+ repo_id: 61
+ mode: 1
+
+-
+ id: 26
+ user_id: 39
+ repo_id: 61
+ mode: 1
+
+-
+ id: 27
+ user_id: 40
+ repo_id: 61
+ mode: 4
diff --git a/models/fixtures/collaboration.yml b/models/fixtures/collaboration.yml
index ef77d22b24..7603bdad32 100644
--- a/models/fixtures/collaboration.yml
+++ b/models/fixtures/collaboration.yml
@@ -45,3 +45,9 @@
repo_id: 22
user_id: 18
mode: 2 # write
+
+-
+ id: 9
+ repo_id: 60
+ user_id: 38
+ mode: 2 # write
diff --git a/models/fixtures/email_address.yml b/models/fixtures/email_address.yml
index 67a99f43e2..b2a0432635 100644
--- a/models/fixtures/email_address.yml
+++ b/models/fixtures/email_address.yml
@@ -293,3 +293,27 @@
lower_email: user37@example.com
is_activated: true
is_primary: true
+
+-
+ id: 38
+ uid: 38
+ email: user38@example.com
+ lower_email: user38@example.com
+ is_activated: true
+ is_primary: true
+
+-
+ id: 39
+ uid: 39
+ email: user39@example.com
+ lower_email: user39@example.com
+ is_activated: true
+ is_primary: true
+
+-
+ id: 40
+ uid: 40
+ email: user40@example.com
+ lower_email: user40@example.com
+ is_activated: true
+ is_primary: true
diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml
index 0c9b6ff406..ca5b1c6cd1 100644
--- a/models/fixtures/issue.yml
+++ b/models/fixtures/issue.yml
@@ -338,3 +338,37 @@
created_unix: 978307210
updated_unix: 978307210
is_locked: false
+
+-
+ id: 21
+ repo_id: 60
+ index: 1
+ poster_id: 39
+ original_author_id: 0
+ name: repo60 pull1
+ content: content for the 1st issue
+ milestone_id: 0
+ priority: 0
+ is_closed: false
+ is_pull: true
+ num_comments: 0
+ created_unix: 1707270422
+ updated_unix: 1707270422
+ is_locked: false
+
+-
+ id: 22
+ repo_id: 61
+ index: 1
+ poster_id: 40
+ original_author_id: 0
+ name: repo61 pull1
+ content: content for the 1st issue
+ milestone_id: 0
+ priority: 0
+ is_closed: false
+ is_pull: true
+ num_comments: 0
+ created_unix: 1707270422
+ updated_unix: 1707270422
+ is_locked: false
diff --git a/models/fixtures/org_user.yml b/models/fixtures/org_user.yml
index 8d58169a32..a7fbcb2c5a 100644
--- a/models/fixtures/org_user.yml
+++ b/models/fixtures/org_user.yml
@@ -99,3 +99,21 @@
uid: 5
org_id: 36
is_public: true
+
+-
+ id: 18
+ uid: 38
+ org_id: 41
+ is_public: true
+
+-
+ id: 19
+ uid: 39
+ org_id: 41
+ is_public: true
+
+-
+ id: 20
+ uid: 40
+ org_id: 41
+ is_public: true
diff --git a/models/fixtures/pull_request.yml b/models/fixtures/pull_request.yml
index 560674c370..3fc8ce630d 100644
--- a/models/fixtures/pull_request.yml
+++ b/models/fixtures/pull_request.yml
@@ -9,6 +9,7 @@
head_branch: branch1
base_branch: master
merge_base: 4a357436d925b5c974181ff12a994538ddc5a269
+ merged_commit_id: 1a8823cd1a9549fde083f992f6b9b87a7ab74fb3
has_merged: true
merger_id: 2
@@ -98,3 +99,21 @@
index: 1
head_repo_id: 23
base_repo_id: 23
+
+-
+ id: 9
+ type: 0 # gitea pull request
+ status: 2 # mergable
+ issue_id: 21
+ index: 1
+ head_repo_id: 60
+ base_repo_id: 60
+
+-
+ id: 10
+ type: 0 # gitea pull request
+ status: 2 # mergable
+ issue_id: 22
+ index: 1
+ head_repo_id: 61
+ base_repo_id: 61
diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml
index e3590b06f0..e4fc5d9d00 100644
--- a/models/fixtures/repo_unit.yml
+++ b/models/fixtures/repo_unit.yml
@@ -708,3 +708,45 @@
type: 1
config: "{}"
created_unix: 946684810
+
+-
+ id: 102
+ repo_id: 60
+ type: 1
+ config: "{}"
+ created_unix: 946684810
+
+-
+ id: 103
+ repo_id: 60
+ type: 2
+ config: "{\"EnableTimetracker\":true,\"AllowOnlyContributorsToTrackTime\":true}"
+ created_unix: 946684810
+
+-
+ id: 104
+ repo_id: 60
+ type: 3
+ config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
+ created_unix: 946684810
+
+-
+ id: 105
+ repo_id: 61
+ type: 1
+ config: "{}"
+ created_unix: 946684810
+
+-
+ id: 106
+ repo_id: 61
+ type: 2
+ config: "{\"EnableTimetracker\":true,\"AllowOnlyContributorsToTrackTime\":true}"
+ created_unix: 946684810
+
+-
+ id: 107
+ repo_id: 61
+ type: 3
+ config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
+ created_unix: 946684810
diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml
index 8ef58b64ac..9d08c7bb0a 100644
--- a/models/fixtures/repository.yml
+++ b/models/fixtures/repository.yml
@@ -1720,3 +1720,65 @@
is_private: true
status: 0
num_issues: 0
+
+-
+ id: 60
+ owner_id: 40
+ owner_name: user40
+ lower_name: repo60
+ name: repo60
+ default_branch: main
+ num_watches: 0
+ num_stars: 0
+ num_forks: 0
+ num_issues: 0
+ num_closed_issues: 0
+ num_pulls: 1
+ num_closed_pulls: 0
+ num_milestones: 0
+ num_closed_milestones: 0
+ num_projects: 0
+ num_closed_projects: 0
+ is_private: false
+ is_empty: false
+ is_archived: false
+ is_mirror: false
+ status: 0
+ is_fork: false
+ fork_id: 0
+ is_template: false
+ template_id: 0
+ size: 0
+ is_fsck_enabled: true
+ close_issues_via_commit_in_any_branch: false
+
+-
+ id: 61
+ owner_id: 41
+ owner_name: org41
+ lower_name: repo61
+ name: repo61
+ default_branch: main
+ num_watches: 0
+ num_stars: 0
+ num_forks: 0
+ num_issues: 0
+ num_closed_issues: 0
+ num_pulls: 1
+ num_closed_pulls: 0
+ num_milestones: 0
+ num_closed_milestones: 0
+ num_projects: 0
+ num_closed_projects: 0
+ is_private: false
+ is_empty: false
+ is_archived: false
+ is_mirror: false
+ status: 0
+ is_fork: false
+ fork_id: 0
+ is_template: false
+ template_id: 0
+ size: 0
+ is_fsck_enabled: true
+ close_issues_via_commit_in_any_branch: false
diff --git a/models/fixtures/team.yml b/models/fixtures/team.yml
index 295e51e39c..149fe90888 100644
--- a/models/fixtures/team.yml
+++ b/models/fixtures/team.yml
@@ -217,3 +217,25 @@
num_members: 1
includes_all_repositories: false
can_create_org_repo: true
+
+-
+ id: 21
+ org_id: 41
+ lower_name: owners
+ name: Owners
+ authorize: 4 # owner
+ num_repos: 1
+ num_members: 1
+ includes_all_repositories: true
+ can_create_org_repo: true
+
+-
+ id: 22
+ org_id: 41
+ lower_name: team1
+ name: Team1
+ authorize: 1 # read
+ num_repos: 1
+ num_members: 2
+ includes_all_repositories: false
+ can_create_org_repo: false
diff --git a/models/fixtures/team_repo.yml b/models/fixtures/team_repo.yml
index 8497720892..a29078107e 100644
--- a/models/fixtures/team_repo.yml
+++ b/models/fixtures/team_repo.yml
@@ -63,3 +63,15 @@
org_id: 17
team_id: 9
repo_id: 24
+
+-
+ id: 12
+ org_id: 41
+ team_id: 21
+ repo_id: 61
+
+-
+ id: 13
+ org_id: 41
+ team_id: 22
+ repo_id: 61
diff --git a/models/fixtures/team_unit.yml b/models/fixtures/team_unit.yml
index c5531aa57a..de0e8d738b 100644
--- a/models/fixtures/team_unit.yml
+++ b/models/fixtures/team_unit.yml
@@ -286,3 +286,39 @@
team_id: 2
type: 8
access_mode: 2
+
+-
+ id: 49
+ team_id: 21
+ type: 1
+ access_mode: 4
+
+-
+ id: 50
+ team_id: 21
+ type: 2
+ access_mode: 4
+
+-
+ id: 51
+ team_id: 21
+ type: 3
+ access_mode: 4
+
+-
+ id: 52
+ team_id: 22
+ type: 1
+ access_mode: 1
+
+-
+ id: 53
+ team_id: 22
+ type: 2
+ access_mode: 1
+
+-
+ id: 54
+ team_id: 22
+ type: 3
+ access_mode: 1
diff --git a/models/fixtures/team_user.yml b/models/fixtures/team_user.yml
index 9142fe609a..02d57ae644 100644
--- a/models/fixtures/team_user.yml
+++ b/models/fixtures/team_user.yml
@@ -129,3 +129,21 @@
org_id: 17
team_id: 9
uid: 15
+
+-
+ id: 23
+ org_id: 41
+ team_id: 21
+ uid: 40
+
+-
+ id: 24
+ org_id: 41
+ team_id: 22
+ uid: 38
+
+-
+ id: 25
+ org_id: 41
+ team_id: 22
+ uid: 39
diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml
index 16aa2a3ff1..07df059dc5 100644
--- a/models/fixtures/user.yml
+++ b/models/fixtures/user.yml
@@ -1369,3 +1369,151 @@
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+
+-
+ id: 38
+ lower_name: user38
+ name: user38
+ full_name: User38
+ email: user38@example.com
+ keep_email_private: false
+ email_notifications_preference: enabled
+ passwd: ZogKvWdyEx:password
+ passwd_hash_algo: dummy
+ must_change_password: false
+ login_source: 0
+ login_name: user38
+ type: 0
+ salt: ZogKvWdyEx
+ max_repo_creation: -1
+ is_active: true
+ is_admin: false
+ is_restricted: false
+ allow_git_hook: false
+ allow_import_local: false
+ allow_create_organization: true
+ prohibit_login: false
+ avatar: avatar38
+ avatar_email: user38@example.com
+ use_custom_avatar: false
+ num_followers: 0
+ num_following: 0
+ num_stars: 0
+ num_repos: 0
+ num_teams: 0
+ num_members: 0
+ visibility: 0
+ repo_admin_change_team_access: false
+ theme: ""
+ keep_activity_private: false
+
+-
+ id: 39
+ lower_name: user39
+ name: user39
+ full_name: User39
+ email: user39@example.com
+ keep_email_private: false
+ email_notifications_preference: enabled
+ passwd: ZogKvWdyEx:password
+ passwd_hash_algo: dummy
+ must_change_password: false
+ login_source: 0
+ login_name: user39
+ type: 0
+ salt: ZogKvWdyEx
+ max_repo_creation: -1
+ is_active: true
+ is_admin: false
+ is_restricted: false
+ allow_git_hook: false
+ allow_import_local: false
+ allow_create_organization: true
+ prohibit_login: false
+ avatar: avatar39
+ avatar_email: user39@example.com
+ use_custom_avatar: false
+ num_followers: 0
+ num_following: 0
+ num_stars: 0
+ num_repos: 0
+ num_teams: 0
+ num_members: 0
+ visibility: 0
+ repo_admin_change_team_access: false
+ theme: ""
+ keep_activity_private: false
+
+-
+ id: 40
+ lower_name: user40
+ name: user40
+ full_name: User40
+ email: user40@example.com
+ keep_email_private: false
+ email_notifications_preference: onmention
+ passwd: ZogKvWdyEx:password
+ passwd_hash_algo: dummy
+ must_change_password: false
+ login_source: 0
+ login_name: user40
+ type: 0
+ salt: ZogKvWdyEx
+ max_repo_creation: -1
+ is_active: true
+ is_admin: false
+ is_restricted: false
+ allow_git_hook: false
+ allow_import_local: false
+ allow_create_organization: true
+ prohibit_login: false
+ avatar: avatar40
+ avatar_email: user40@example.com
+ use_custom_avatar: false
+ num_followers: 0
+ num_following: 0
+ num_stars: 0
+ num_repos: 1
+ num_teams: 0
+ num_members: 0
+ visibility: 0
+ repo_admin_change_team_access: false
+ theme: ""
+ keep_activity_private: false
+
+-
+ id: 41
+ lower_name: org41
+ name: org41
+ full_name: Org41
+ email: org41@example.com
+ keep_email_private: false
+ email_notifications_preference: onmention
+ passwd: ZogKvWdyEx:password
+ passwd_hash_algo: dummy
+ must_change_password: false
+ login_source: 0
+ login_name: org41
+ type: 1
+ salt: ZogKvWdyEx
+ max_repo_creation: -1
+ is_active: false
+ is_admin: false
+ is_restricted: false
+ allow_git_hook: false
+ allow_import_local: false
+ allow_create_organization: true
+ prohibit_login: false
+ avatar: avatar41
+ avatar_email: org41@example.com
+ use_custom_avatar: false
+ num_followers: 0
+ num_following: 0
+ num_stars: 0
+ num_repos: 1
+ num_teams: 2
+ num_members: 3
+ visibility: 0
+ repo_admin_change_team_access: false
+ theme: ""
+ keep_activity_private: false
diff --git a/models/git/branch_list.go b/models/git/branch_list.go
index 0e8d28038a..8319e5ecd0 100644
--- a/models/git/branch_list.go
+++ b/models/git/branch_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"
)
@@ -67,7 +67,7 @@ type FindBranchOptions struct {
db.ListOptions
RepoID int64
ExcludeBranchNames []string
- IsDeletedBranch util.OptionalBool
+ IsDeletedBranch optional.Option[bool]
OrderBy string
Keyword string
}
@@ -81,8 +81,8 @@ func (opts FindBranchOptions) ToConds() builder.Cond {
if len(opts.ExcludeBranchNames) > 0 {
cond = cond.And(builder.NotIn("name", opts.ExcludeBranchNames))
}
- if !opts.IsDeletedBranch.IsNone() {
- cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.IsTrue()})
+ if opts.IsDeletedBranch.Has() {
+ cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.Value()})
}
if opts.Keyword != "" {
cond = cond.And(builder.Like{"name", opts.Keyword})
@@ -92,7 +92,7 @@ func (opts FindBranchOptions) ToConds() builder.Cond {
func (opts FindBranchOptions) ToOrders() string {
orderBy := opts.OrderBy
- if !opts.IsDeletedBranch.IsFalse() { // if deleted branch included, put them at the end
+ if opts.IsDeletedBranch.ValueOrDefault(true) { // if deleted branch included, put them at the end
if orderBy != "" {
orderBy += ", "
}
diff --git a/models/git/branch_test.go b/models/git/branch_test.go
index d480e2ec30..b984244cd2 100644
--- a/models/git/branch_test.go
+++ b/models/git/branch_test.go
@@ -12,7 +12,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert"
)
@@ -49,7 +49,7 @@ func TestGetDeletedBranches(t *testing.T) {
branches, err := db.Find[git_model.Branch](db.DefaultContext, git_model.FindBranchOptions{
ListOptions: db.ListOptionsAll,
RepoID: repo.ID,
- IsDeletedBranch: util.OptionalBoolTrue,
+ IsDeletedBranch: optional.Some(true),
})
assert.NoError(t, err)
assert.Len(t, branches, 2)
diff --git a/models/git/commit_status.go b/models/git/commit_status.go
index 1118b6cc8c..2d1d1bcb06 100644
--- a/models/git/commit_status.go
+++ b/models/git/commit_status.go
@@ -194,7 +194,7 @@ func (status *CommitStatus) APIURL(ctx context.Context) string {
// LocaleString returns the locale string name of the Status
func (status *CommitStatus) LocaleString(lang translation.Locale) string {
- return lang.Tr("repo.commitstatus." + status.State.String())
+ return lang.TrString("repo.commitstatus." + status.State.String())
}
// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc
diff --git a/models/git/protected_branch_list.go b/models/git/protected_branch_list.go
index eeb307e245..613333a5a2 100644
--- a/models/git/protected_branch_list.go
+++ b/models/git/protected_branch_list.go
@@ -8,7 +8,7 @@ import (
"sort"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
"github.com/gobwas/glob"
)
@@ -56,7 +56,7 @@ func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string)
Page: page,
},
RepoID: repoID,
- IsDeletedBranch: util.OptionalBoolFalse,
+ IsDeletedBranch: optional.Some(false),
})
if err != nil {
return nil, err
diff --git a/models/issues/comment.go b/models/issues/comment.go
index d9acca7e48..49c3159f6f 100644
--- a/models/issues/comment.go
+++ b/models/issues/comment.go
@@ -210,12 +210,12 @@ const (
// LocaleString returns the locale string name of the role
func (r RoleInRepo) LocaleString(lang translation.Locale) string {
- return lang.Tr("repo.issues.role." + string(r))
+ return lang.TrString("repo.issues.role." + string(r))
}
// LocaleHelper returns the locale tooltip of the role
func (r RoleInRepo) LocaleHelper(lang translation.Locale) string {
- return lang.Tr("repo.issues.role." + string(r) + "_helper")
+ return lang.TrString("repo.issues.role." + string(r) + "_helper")
}
// Comment represents a comment in commit and issue page.
@@ -695,8 +695,15 @@ func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository
}
func (c *Comment) loadReview(ctx context.Context) (err error) {
+ if c.ReviewID == 0 {
+ return nil
+ }
if c.Review == nil {
if c.Review, err = GetReviewByID(ctx, c.ReviewID); err != nil {
+ // review request which has been replaced by actual reviews doesn't exist in database anymore, so ignorem them.
+ if c.Type == CommentTypeReviewRequest {
+ return nil
+ }
return err
}
}
@@ -856,6 +863,9 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
// Check comment type.
switch opts.Type {
case CommentTypeCode:
+ if err = updateAttachments(ctx, opts, comment); err != nil {
+ return err
+ }
if comment.ReviewID != 0 {
if comment.Review == nil {
if err := comment.loadReview(ctx); err != nil {
@@ -873,22 +883,9 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
}
fallthrough
case CommentTypeReview:
- // Check attachments
- attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
- if err != nil {
- return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", opts.Attachments, err)
+ if err = updateAttachments(ctx, opts, comment); err != nil {
+ return err
}
-
- for i := range attachments {
- attachments[i].IssueID = opts.Issue.ID
- attachments[i].CommentID = comment.ID
- // No assign value could be 0, so ignore AllCols().
- if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
- return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
- }
- }
-
- comment.Attachments = attachments
case CommentTypeReopen, CommentTypeClose:
if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
return err
@@ -898,6 +895,23 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
return UpdateIssueCols(ctx, opts.Issue, "updated_unix")
}
+func updateAttachments(ctx context.Context, opts *CreateCommentOptions, comment *Comment) error {
+ attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
+ if err != nil {
+ return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", opts.Attachments, err)
+ }
+ for i := range attachments {
+ attachments[i].IssueID = opts.Issue.ID
+ attachments[i].CommentID = comment.ID
+ // No assign value could be 0, so ignore AllCols().
+ if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
+ return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
+ }
+ }
+ comment.Attachments = attachments
+ return nil
+}
+
func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) {
var content string
var commentType CommentType
diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go
index 384a595dd9..cef2ea281c 100644
--- a/models/issues/comment_code.go
+++ b/models/issues/comment_code.go
@@ -14,15 +14,58 @@ import (
"xorm.io/builder"
)
+// CodeConversation contains the comment of a given review
+type CodeConversation []*Comment
+
+// CodeConversationsAtLine contains the conversations for a given line
+type CodeConversationsAtLine map[int64][]CodeConversation
+
+// CodeConversationsAtLineAndTreePath contains the conversations for a given TreePath and line
+type CodeConversationsAtLineAndTreePath map[string]CodeConversationsAtLine
+
+func newCodeConversationsAtLineAndTreePath(comments []*Comment) CodeConversationsAtLineAndTreePath {
+ tree := make(CodeConversationsAtLineAndTreePath)
+ for _, comment := range comments {
+ tree.insertComment(comment)
+ }
+ return tree
+}
+
+func (tree CodeConversationsAtLineAndTreePath) insertComment(comment *Comment) {
+ // attempt to append comment to existing conversations (i.e. list of comments belonging to the same review)
+ for i, conversation := range tree[comment.TreePath][comment.Line] {
+ if conversation[0].ReviewID == comment.ReviewID {
+ tree[comment.TreePath][comment.Line][i] = append(conversation, comment)
+ return
+ }
+ }
+
+ // no previous conversation was found at this line, create it
+ if tree[comment.TreePath] == nil {
+ tree[comment.TreePath] = make(map[int64][]CodeConversation)
+ }
+
+ tree[comment.TreePath][comment.Line] = append(tree[comment.TreePath][comment.Line], CodeConversation{comment})
+}
+
+// FetchCodeConversations will return a 2d-map: ["Path"]["Line"] = List of CodeConversation (one per review) for this line
+func FetchCodeConversations(ctx context.Context, issue *Issue, doer *user_model.User, showOutdatedComments bool) (CodeConversationsAtLineAndTreePath, error) {
+ opts := FindCommentsOptions{
+ Type: CommentTypeCode,
+ IssueID: issue.ID,
+ }
+ comments, err := findCodeComments(ctx, opts, issue, doer, nil, showOutdatedComments)
+ if err != nil {
+ return nil, err
+ }
+
+ return newCodeConversationsAtLineAndTreePath(comments), nil
+}
+
// CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS
type CodeComments map[string]map[int64][]*Comment
-// FetchCodeComments will return a 2d-map: ["Path"]["Line"] = Comments at line
-func FetchCodeComments(ctx context.Context, issue *Issue, currentUser *user_model.User, showOutdatedComments bool) (CodeComments, error) {
- return fetchCodeCommentsByReview(ctx, issue, currentUser, nil, showOutdatedComments)
-}
-
-func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *user_model.User, review *Review, showOutdatedComments bool) (CodeComments, error) {
+func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, doer *user_model.User, review *Review, showOutdatedComments bool) (CodeComments, error) {
pathToLineToComment := make(CodeComments)
if review == nil {
review = &Review{ID: 0}
@@ -33,7 +76,7 @@ func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *u
ReviewID: review.ID,
}
- comments, err := findCodeComments(ctx, opts, issue, currentUser, review, showOutdatedComments)
+ comments, err := findCodeComments(ctx, opts, issue, doer, review, showOutdatedComments)
if err != nil {
return nil, err
}
@@ -47,7 +90,7 @@ func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *u
return pathToLineToComment, nil
}
-func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, currentUser *user_model.User, review *Review, showOutdatedComments bool) ([]*Comment, error) {
+func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, doer *user_model.User, review *Review, showOutdatedComments bool) ([]*Comment, error) {
var comments CommentList
if review == nil {
review = &Review{ID: 0}
@@ -91,7 +134,7 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
if re, ok := reviews[comment.ReviewID]; ok && re != nil {
// If the review is pending only the author can see the comments (except if the review is set)
if review.ID == 0 && re.Type == ReviewTypePending &&
- (currentUser == nil || currentUser.ID != re.ReviewerID) {
+ (doer == nil || doer.ID != re.ReviewerID) {
continue
}
comment.Review = re
@@ -121,13 +164,14 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
return comments[:n], nil
}
-// FetchCodeCommentsByLine fetches the code comments for a given treePath and line number
-func FetchCodeCommentsByLine(ctx context.Context, issue *Issue, currentUser *user_model.User, treePath string, line int64, showOutdatedComments bool) ([]*Comment, error) {
+// FetchCodeConversation fetches the code conversation of a given comment (same review, treePath and line number)
+func FetchCodeConversation(ctx context.Context, comment *Comment, doer *user_model.User) ([]*Comment, error) {
opts := FindCommentsOptions{
Type: CommentTypeCode,
- IssueID: issue.ID,
- TreePath: treePath,
- Line: line,
+ IssueID: comment.IssueID,
+ ReviewID: comment.ReviewID,
+ TreePath: comment.TreePath,
+ Line: comment.Line,
}
- return findCodeComments(ctx, opts, issue, currentUser, nil, showOutdatedComments)
+ return findCodeComments(ctx, opts, comment.Issue, doer, nil, true)
}
diff --git a/models/issues/comment_list.go b/models/issues/comment_list.go
index 93af45870e..30a437ea50 100644
--- a/models/issues/comment_list.go
+++ b/models/issues/comment_list.go
@@ -225,6 +225,10 @@ func (comments CommentList) loadAssignees(ctx context.Context) error {
for _, comment := range comments {
comment.Assignee = assignees[comment.AssigneeID]
+ if comment.Assignee == nil {
+ comment.AssigneeID = user_model.GhostUserID
+ comment.Assignee = user_model.NewGhostUser()
+ }
}
return nil
}
@@ -430,7 +434,8 @@ func (comments CommentList) loadReviews(ctx context.Context) error {
for _, comment := range comments {
comment.Review = reviews[comment.ReviewID]
if comment.Review == nil {
- if comment.ReviewID > 0 {
+ // review request which has been replaced by actual reviews doesn't exist in database anymore, so don't log errors for them.
+ if comment.ReviewID > 0 && comment.Type != CommentTypeReviewRequest {
log.Error("comment with review id [%d] but has no review record", comment.ReviewID)
}
continue
diff --git a/models/issues/comment_test.go b/models/issues/comment_test.go
index e08bd7fbf5..e7ceee4298 100644
--- a/models/issues/comment_test.go
+++ b/models/issues/comment_test.go
@@ -46,20 +46,20 @@ func TestCreateComment(t *testing.T) {
unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix))
}
-func TestFetchCodeComments(t *testing.T) {
+func TestFetchCodeConversations(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
- res, err := issues_model.FetchCodeComments(db.DefaultContext, issue, user, false)
+ res, err := issues_model.FetchCodeConversations(db.DefaultContext, issue, user, false)
assert.NoError(t, err)
assert.Contains(t, res, "README.md")
assert.Contains(t, res["README.md"], int64(4))
assert.Len(t, res["README.md"][4], 1)
- assert.Equal(t, int64(4), res["README.md"][4][0].ID)
+ assert.Equal(t, int64(4), res["README.md"][4][0][0].ID)
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- res, err = issues_model.FetchCodeComments(db.DefaultContext, issue, user2, false)
+ res, err = issues_model.FetchCodeConversations(db.DefaultContext, issue, user2, false)
assert.NoError(t, err)
assert.Len(t, res, 1)
}
diff --git a/models/issues/content_history.go b/models/issues/content_history.go
index 8c333bc6dd..cd3e217b21 100644
--- a/models/issues/content_history.go
+++ b/models/issues/content_history.go
@@ -161,22 +161,18 @@ func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int6
}
for _, item := range res {
- item.UserAvatarLink = avatars.GenerateUserAvatarFastLink(item.UserName, 0)
+ if item.UserID > 0 {
+ item.UserAvatarLink = avatars.GenerateUserAvatarFastLink(item.UserName, 0)
+ } else {
+ item.UserAvatarLink = avatars.DefaultAvatarLink()
+ }
}
return res, nil
}
// HasIssueContentHistory check if a ContentHistory entry exists
func HasIssueContentHistory(dbCtx context.Context, issueID, commentID int64) (bool, error) {
- exists, err := db.GetEngine(dbCtx).Cols("id").Exist(&ContentHistory{
- IssueID: issueID,
- CommentID: commentID,
- })
- if err != nil {
- log.Error("can not fetch issue content history. err=%v", err)
- return false, err
- }
- return exists, err
+ return db.GetEngine(dbCtx).Where("issue_id = ? AND comment_id = ?", issueID, commentID).Exist(new(ContentHistory))
}
// SoftDeleteIssueContentHistory soft delete
diff --git a/models/issues/content_history_test.go b/models/issues/content_history_test.go
index 0ea1d0f7b2..89d77a1df3 100644
--- a/models/issues/content_history_test.go
+++ b/models/issues/content_history_test.go
@@ -78,3 +78,16 @@ func TestContentHistory(t *testing.T) {
assert.EqualValues(t, 7, list2[1].HistoryID)
assert.EqualValues(t, 4, list2[2].HistoryID)
}
+
+func TestHasIssueContentHistory(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ // Ensures that comment_id is into taken account even if it's zero.
+ _ = issues_model.SaveIssueContentHistory(db.DefaultContext, 1, 11, 100, timeutil.TimeStampNow(), "c-a", true)
+ _ = issues_model.SaveIssueContentHistory(db.DefaultContext, 1, 11, 100, timeutil.TimeStampNow().Add(5), "c-b", false)
+
+ hasHistory1, _ := issues_model.HasIssueContentHistory(db.DefaultContext, 11, 0)
+ assert.False(t, hasHistory1)
+ hasHistory2, _ := issues_model.HasIssueContentHistory(db.DefaultContext, 11, 100)
+ assert.True(t, hasHistory2)
+}
diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go
index 2b20ede173..044666a3f0 100644
--- a/models/issues/issue_test.go
+++ b/models/issues/issue_test.go
@@ -381,7 +381,7 @@ func TestCountIssues(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
count, err := issues_model.CountIssues(db.DefaultContext, &issues_model.IssuesOptions{})
assert.NoError(t, err)
- assert.EqualValues(t, 20, count)
+ assert.EqualValues(t, 22, count)
}
func TestIssueLoadAttributes(t *testing.T) {
diff --git a/models/issues/issue_xref.go b/models/issues/issue_xref.go
index 9b1a447471..9c9d5d66cd 100644
--- a/models/issues/issue_xref.go
+++ b/models/issues/issue_xref.go
@@ -46,10 +46,10 @@ func neuterCrossReferences(ctx context.Context, issueID, commentID int64) error
for i, c := range active {
ids[i] = c.ID
}
- return neuterCrossReferencesIds(ctx, nil, ids)
+ return neuterCrossReferencesIDs(ctx, nil, ids)
}
-func neuterCrossReferencesIds(stdCtx context.Context, ctx *crossReferencesContext, ids []int64) error {
+func neuterCrossReferencesIDs(stdCtx context.Context, ctx *crossReferencesContext, ids []int64) error {
sess := db.GetEngine(stdCtx).In("id", ids).Cols("`ref_action`")
if ctx != nil && ctx.OrigIssue.NoAutoTime {
sess.SetExpr("updated_unix", ctx.OrigIssue.UpdatedUnix).NoAutoTime()
@@ -104,7 +104,7 @@ func (issue *Issue) createCrossReferences(stdCtx context.Context, ctx *crossRefe
}
}
if len(ids) > 0 {
- if err = neuterCrossReferencesIds(stdCtx, ctx, ids); err != nil {
+ if err = neuterCrossReferencesIDs(stdCtx, ctx, ids); err != nil {
return err
}
}
diff --git a/models/issues/pull.go b/models/issues/pull.go
index 2cb1e1b971..98d1617380 100644
--- a/models/issues/pull.go
+++ b/models/issues/pull.go
@@ -652,6 +652,35 @@ func GetPullRequestByIssueID(ctx context.Context, issueID int64) (*PullRequest,
return pr, pr.LoadAttributes(ctx)
}
+// GetPullRequestsByBaseHeadInfo returns the pull request by given base and head
+func GetPullRequestByBaseHeadInfo(ctx context.Context, baseID, headID int64, base, head string) (*PullRequest, error) {
+ pr := &PullRequest{}
+ sess := db.GetEngine(ctx).
+ Join("INNER", "issue", "issue.id = pull_request.issue_id").
+ Where("base_repo_id = ? AND base_branch = ? AND head_repo_id = ? AND head_branch = ?", baseID, base, headID, head)
+ has, err := sess.Get(pr)
+ if err != nil {
+ return nil, err
+ }
+ if !has {
+ return nil, ErrPullRequestNotExist{
+ HeadRepoID: headID,
+ BaseRepoID: baseID,
+ HeadBranch: head,
+ BaseBranch: base,
+ }
+ }
+
+ if err = pr.LoadAttributes(ctx); err != nil {
+ return nil, err
+ }
+ if err = pr.LoadIssue(ctx); err != nil {
+ return nil, err
+ }
+
+ return pr, nil
+}
+
// GetAllUnmergedAgitPullRequestByPoster get all unmerged agit flow pull request
// By poster id.
func GetAllUnmergedAgitPullRequestByPoster(ctx context.Context, uid int64) ([]*PullRequest, error) {
@@ -893,7 +922,14 @@ func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullReque
}
rules, _ := GetCodeOwnersFromContent(ctx, data)
- changedFiles, err := repo.GetFilesChangedBetween(git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
+
+ prInfo, err := repo.GetCompareInfo(repo.Path, git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false)
+ if err != nil {
+ return err
+ }
+ // 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)
if err != nil {
return err
}
@@ -1093,3 +1129,23 @@ func InsertPullRequests(ctx context.Context, prs ...*PullRequest) error {
}
return committer.Commit()
}
+
+// GetPullRequestByMergedCommit returns a merged pull request by the given commit
+func GetPullRequestByMergedCommit(ctx context.Context, repoID int64, sha string) (*PullRequest, error) {
+ pr := new(PullRequest)
+ has, err := db.GetEngine(ctx).Where("base_repo_id = ? AND merged_commit_id = ?", repoID, sha).Get(pr)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrPullRequestNotExist{0, 0, 0, repoID, "", ""}
+ }
+
+ if err = pr.LoadAttributes(ctx); err != nil {
+ return nil, err
+ }
+ if err = pr.LoadIssue(ctx); err != nil {
+ return nil, err
+ }
+
+ return pr, nil
+}
diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go
index 4702049af2..213838abec 100644
--- a/models/issues/pull_test.go
+++ b/models/issues/pull_test.go
@@ -425,6 +425,18 @@ func TestGetApprovers(t *testing.T) {
assert.EqualValues(t, expected, approvers)
}
+func TestGetPullRequestByMergedCommit(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+ pr, err := issues_model.GetPullRequestByMergedCommit(db.DefaultContext, 1, "1a8823cd1a9549fde083f992f6b9b87a7ab74fb3")
+ assert.NoError(t, err)
+ assert.EqualValues(t, 1, pr.ID)
+
+ _, err = issues_model.GetPullRequestByMergedCommit(db.DefaultContext, 0, "1a8823cd1a9549fde083f992f6b9b87a7ab74fb3")
+ assert.ErrorAs(t, err, &issues_model.ErrPullRequestNotExist{})
+ _, err = issues_model.GetPullRequestByMergedCommit(db.DefaultContext, 1, "")
+ assert.ErrorAs(t, err, &issues_model.ErrPullRequestNotExist{})
+}
+
func TestMigrate_InsertPullRequests(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
reponame := "repo1"
diff --git a/models/issues/review.go b/models/issues/review.go
index f2022ae0aa..fc110630e0 100644
--- a/models/issues/review.go
+++ b/models/issues/review.go
@@ -159,6 +159,14 @@ func (r *Review) LoadReviewer(ctx context.Context) (err error) {
return err
}
r.Reviewer, err = user_model.GetPossibleUserByID(ctx, r.ReviewerID)
+ if err != nil {
+ if !user_model.IsErrUserNotExist(err) {
+ return fmt.Errorf("GetPossibleUserByID [%d]: %w", r.ReviewerID, err)
+ }
+ r.ReviewerID = user_model.GhostUserID
+ r.Reviewer = user_model.NewGhostUser()
+ return nil
+ }
return err
}
@@ -284,8 +292,14 @@ func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organizatio
// CreateReview creates a new review based on opts
func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error) {
+ ctx, committer, err := db.TxContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ defer committer.Close()
+ sess := db.GetEngine(ctx)
+
review := &Review{
- Type: opts.Type,
Issue: opts.Issue,
IssueID: opts.Issue.ID,
Reviewer: opts.Reviewer,
@@ -295,15 +309,39 @@ func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error
CommitID: opts.CommitID,
Stale: opts.Stale,
}
+
if opts.Reviewer != nil {
+ review.Type = opts.Type
review.ReviewerID = opts.Reviewer.ID
- } else {
- if review.Type != ReviewTypeRequest {
- review.Type = ReviewTypeRequest
+
+ reviewCond := builder.Eq{"reviewer_id": opts.Reviewer.ID, "issue_id": opts.Issue.ID}
+ // make sure user review requests are cleared
+ if opts.Type != ReviewTypePending {
+ if _, err := sess.Where(reviewCond.And(builder.Eq{"type": ReviewTypeRequest})).Delete(new(Review)); err != nil {
+ return nil, err
+ }
}
+ // make sure if the created review gets dismissed no old review surface
+ // other types can be ignored, as they don't affect branch protection
+ if opts.Type == ReviewTypeApprove || opts.Type == ReviewTypeReject {
+ if _, err := sess.Where(reviewCond.And(builder.In("type", ReviewTypeApprove, ReviewTypeReject))).
+ Cols("dismissed").Update(&Review{Dismissed: true}); err != nil {
+ return nil, err
+ }
+ }
+
+ } else if opts.ReviewerTeam != nil {
+ review.Type = ReviewTypeRequest
review.ReviewerTeamID = opts.ReviewerTeam.ID
+
+ } else {
+ return nil, fmt.Errorf("provide either reviewer or reviewer team")
}
- return review, db.Insert(ctx, review)
+
+ if _, err := sess.Insert(review); err != nil {
+ return nil, err
+ }
+ return review, committer.Commit()
}
// GetCurrentReview returns the current pending review of reviewer for given issue
@@ -621,6 +659,9 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo
return nil, err
}
+ // func caller use the created comment to retrieve created review too.
+ comment.Review = review
+
return comment, committer.Commit()
}
diff --git a/models/issues/review_list.go b/models/issues/review_list.go
index ed3d0bd028..282f18b4f7 100644
--- a/models/issues/review_list.go
+++ b/models/issues/review_list.go
@@ -18,11 +18,11 @@ type ReviewList []*Review
// LoadReviewers loads reviewers
func (reviews ReviewList) LoadReviewers(ctx context.Context) error {
- reviewerIds := make([]int64, len(reviews))
+ reviewerIDs := make([]int64, len(reviews))
for i := 0; i < len(reviews); i++ {
- reviewerIds[i] = reviews[i].ReviewerID
+ reviewerIDs[i] = reviews[i].ReviewerID
}
- reviewers, err := user_model.GetPossibleUserByIDs(ctx, reviewerIds)
+ reviewers, err := user_model.GetPossibleUserByIDs(ctx, reviewerIDs)
if err != nil {
return err
}
@@ -38,12 +38,12 @@ func (reviews ReviewList) LoadReviewers(ctx context.Context) error {
}
func (reviews ReviewList) LoadIssues(ctx context.Context) error {
- issueIds := container.Set[int64]{}
+ issueIDs := container.Set[int64]{}
for i := 0; i < len(reviews); i++ {
- issueIds.Add(reviews[i].IssueID)
+ issueIDs.Add(reviews[i].IssueID)
}
- issues, err := GetIssuesByIDs(ctx, issueIds.Values())
+ issues, err := GetIssuesByIDs(ctx, issueIDs.Values())
if err != nil {
return err
}
diff --git a/models/organization/org.go b/models/organization/org.go
index 23a4e2f96a..b4919defb4 100644
--- a/models/organization/org.go
+++ b/models/organization/org.go
@@ -594,9 +594,7 @@ func GetOrgByID(ctx context.Context, id int64) (*Organization, error) {
return nil, err
} else if !has {
return nil, user_model.ErrUserNotExist{
- UID: id,
- Name: "",
- KeyID: 0,
+ UID: id,
}
}
return u, nil
diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go
index 0b66e62d7d..7e39627a75 100644
--- a/models/perm/access/repo_permission.go
+++ b/models/perm/access/repo_permission.go
@@ -356,7 +356,6 @@ func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.
// CanBeAssigned return true if user can be assigned to issue or pull requests in repo
// Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
-// FIXME: user could send PullRequest also could be assigned???
func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) {
if user.IsOrganization() {
return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
@@ -365,7 +364,8 @@ func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.
if err != nil {
return false, err
}
- return perm.CanAccessAny(perm_model.AccessModeWrite, unit.TypeCode, unit.TypeIssues, unit.TypePullRequests), nil
+ return perm.CanAccessAny(perm_model.AccessModeWrite, unit.AllRepoUnitTypes...) ||
+ perm.CanAccessAny(perm_model.AccessModeRead, unit.TypePullRequests), nil
}
// HasAccess returns true if user has access to repo
diff --git a/models/repo/git.go b/models/repo/git.go
index 610c554296..388bf86522 100644
--- a/models/repo/git.go
+++ b/models/repo/git.go
@@ -21,6 +21,8 @@ const (
MergeStyleRebaseMerge MergeStyle = "rebase-merge"
// MergeStyleSquash squash commits into single commit before merging
MergeStyleSquash MergeStyle = "squash"
+ // MergeStyleFastForwardOnly fast-forward merge if possible, otherwise fail
+ MergeStyleFastForwardOnly MergeStyle = "fast-forward-only"
// MergeStyleManuallyMerged pr has been merged manually, just mark it as merged directly
MergeStyleManuallyMerged MergeStyle = "manually-merged"
// MergeStyleRebaseUpdate not a merge style, used to update pull head by rebase
diff --git a/models/repo/repo.go b/models/repo/repo.go
index 037a79d504..f77454a94e 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -449,6 +449,31 @@ func (repo *Repository) GetUnit(ctx context.Context, tp unit.Type) (*RepoUnit, e
return nil, ErrUnitTypeNotExist{tp}
}
+// AllUnitsEnabled returns true if all units are enabled for the repo.
+func (repo *Repository) AllUnitsEnabled(ctx context.Context) bool {
+ hasAnyUnitEnabled := func(unitGroup []unit.Type) bool {
+ // Loop over the group of units
+ for _, unit := range unitGroup {
+ // If *any* of them is enabled, return true.
+ if repo.UnitEnabled(ctx, unit) {
+ return true
+ }
+ }
+
+ // If none are enabled, return false.
+ return false
+ }
+
+ for _, unitGroup := range unit.AllowedRepoUnitGroups {
+ // If any disabled unit is found, return false immediately.
+ if !hasAnyUnitEnabled(unitGroup) {
+ return false
+ }
+ }
+
+ return true
+}
+
// LoadOwner loads owner user
func (repo *Repository) LoadOwner(ctx context.Context) (err error) {
if repo.Owner != nil {
diff --git a/models/repo/repo_list_test.go b/models/repo/repo_list_test.go
index a8b958109c..8006289460 100644
--- a/models/repo/repo_list_test.go
+++ b/models/repo/repo_list_test.go
@@ -138,12 +138,12 @@ func getTestCases() []struct {
{
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse},
- count: 32,
+ 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},
- count: 37,
+ count: 39,
},
{
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
@@ -158,7 +158,7 @@ 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},
- count: 32,
+ count: 34,
},
{
name: "AllTemplates",
diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go
index 3df5236ea7..08058b0d45 100644
--- a/models/repo/repo_unit.go
+++ b/models/repo/repo_unit.go
@@ -153,6 +153,7 @@ type PullRequestsConfig struct {
AllowRebase bool
AllowRebaseMerge bool
AllowSquash bool
+ AllowFastForwardOnly bool
AllowManualMerge bool
AutodetectManualMerge bool
AllowRebaseUpdate bool
@@ -179,6 +180,7 @@ func (cfg *PullRequestsConfig) IsMergeStyleAllowed(mergeStyle MergeStyle) bool {
mergeStyle == MergeStyleRebase && cfg.AllowRebase ||
mergeStyle == MergeStyleRebaseMerge && cfg.AllowRebaseMerge ||
mergeStyle == MergeStyleSquash && cfg.AllowSquash ||
+ mergeStyle == MergeStyleFastForwardOnly && cfg.AllowFastForwardOnly ||
mergeStyle == MergeStyleManuallyMerged && cfg.AllowManualMerge
}
diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go
index 5d6e24e2a5..219f4ff25d 100644
--- a/models/repo/user_repo.go
+++ b/models/repo/user_repo.go
@@ -8,6 +8,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
+ "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
api "code.gitea.io/gitea/modules/structs"
@@ -78,7 +79,8 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
if err = e.Table("team_user").
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
- Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite).
+ Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
+ repo.ID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
Distinct("`team_user`.uid").
Select("`team_user`.uid").
Find(&additionalUserIDs); err != nil {
diff --git a/models/shared/types/ownertype.go b/models/shared/types/ownertype.go
index e6fe4e4cfd..a1d46c986f 100644
--- a/models/shared/types/ownertype.go
+++ b/models/shared/types/ownertype.go
@@ -17,13 +17,13 @@ const (
func (o OwnerType) LocaleString(locale translation.Locale) string {
switch o {
case OwnerTypeSystemGlobal:
- return locale.Tr("concept_system_global")
+ return locale.TrString("concept_system_global")
case OwnerTypeIndividual:
- return locale.Tr("concept_user_individual")
+ return locale.TrString("concept_user_individual")
case OwnerTypeRepository:
- return locale.Tr("concept_code_repository")
+ return locale.TrString("concept_code_repository")
case OwnerTypeOrganization:
- return locale.Tr("concept_user_organization")
+ return locale.TrString("concept_user_organization")
}
- return locale.Tr("unknown")
+ return locale.TrString("unknown")
}
diff --git a/models/unit/unit.go b/models/unit/unit.go
index b216712d37..e37adf995e 100644
--- a/models/unit/unit.go
+++ b/models/unit/unit.go
@@ -108,6 +108,10 @@ var (
// DisabledRepoUnits contains the units that have been globally disabled
DisabledRepoUnits = []Type{}
+
+ // AllowedRepoUnitGroups contains the units that have been globally enabled,
+ // with mutually exclusive units grouped together.
+ AllowedRepoUnitGroups = [][]Type{}
)
// Get valid set of default repository units from settings
@@ -162,6 +166,45 @@ func LoadUnitConfig() error {
if len(DefaultForkRepoUnits) == 0 {
return errors.New("no default fork repository units found")
}
+
+ // Collect the allowed repo unit groups. Mutually exclusive units are
+ // grouped together.
+ AllowedRepoUnitGroups = [][]Type{}
+ for _, unit := range []Type{
+ TypeCode,
+ TypePullRequests,
+ TypeProjects,
+ TypePackages,
+ TypeActions,
+ } {
+ // If unit is globally disabled, ignore it.
+ if unit.UnitGlobalDisabled() {
+ continue
+ }
+
+ // If it is allowed, add it to the group list.
+ AllowedRepoUnitGroups = append(AllowedRepoUnitGroups, []Type{unit})
+ }
+
+ addMutuallyExclusiveGroup := func(unit1, unit2 Type) {
+ var list []Type
+
+ if !unit1.UnitGlobalDisabled() {
+ list = append(list, unit1)
+ }
+
+ if !unit2.UnitGlobalDisabled() {
+ list = append(list, unit2)
+ }
+
+ if len(list) > 0 {
+ AllowedRepoUnitGroups = append(AllowedRepoUnitGroups, list)
+ }
+ }
+
+ addMutuallyExclusiveGroup(TypeIssues, TypeExternalTracker)
+ addMutuallyExclusiveGroup(TypeWiki, TypeExternalWiki)
+
return nil
}
diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go
index 69f3505c28..af5c31f157 100644
--- a/models/unittest/testdb.go
+++ b/models/unittest/testdb.go
@@ -44,12 +44,12 @@ func fatalTestError(fmtStr string, args ...any) {
}
// InitSettings initializes config provider and load common settings for tests
-func InitSettings(extraConfigs ...string) {
+func InitSettings() {
if setting.CustomConf == "" {
setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini")
_ = os.Remove(setting.CustomConf)
}
- setting.InitCfgProvider(setting.CustomConf, strings.Join(extraConfigs, "\n"))
+ setting.InitCfgProvider(setting.CustomConf)
setting.LoadCommonSettings()
if err := setting.PrepareAppDataPath(); err != nil {
diff --git a/models/unittest/unit_tests.go b/models/unittest/unit_tests.go
index d47bceea1e..75898436fc 100644
--- a/models/unittest/unit_tests.go
+++ b/models/unittest/unit_tests.go
@@ -131,8 +131,8 @@ func AssertSuccessfulInsert(t assert.TestingT, beans ...any) {
}
// AssertCount assert the count of a bean
-func AssertCount(t assert.TestingT, bean, expected any) {
- assert.EqualValues(t, expected, GetCount(t, bean))
+func AssertCount(t assert.TestingT, bean, expected any) bool {
+ return assert.EqualValues(t, expected, GetCount(t, bean))
}
// AssertInt64InRange assert value is in range [low, high]
@@ -150,7 +150,7 @@ func GetCountByCond(t assert.TestingT, tableName string, cond builder.Cond) int6
}
// AssertCountByCond test the count of database entries matching bean
-func AssertCountByCond(t assert.TestingT, tableName string, cond builder.Cond, expected int) {
- assert.EqualValues(t, expected, GetCountByCond(t, tableName, cond),
+func AssertCountByCond(t assert.TestingT, tableName string, cond builder.Cond, expected int) bool {
+ return assert.EqualValues(t, expected, GetCountByCond(t, tableName, cond),
"Failed consistency test, the counted bean (of table %s) was %+v", tableName, cond)
}
diff --git a/models/user/error.go b/models/user/error.go
index ef572c178a..cbf19998d1 100644
--- a/models/user/error.go
+++ b/models/user/error.go
@@ -31,9 +31,8 @@ func (err ErrUserAlreadyExist) Unwrap() error {
// ErrUserNotExist represents a "UserNotExist" kind of error.
type ErrUserNotExist struct {
- UID int64
- Name string
- KeyID int64
+ UID int64
+ Name string
}
// IsErrUserNotExist checks if an error is a ErrUserNotExist.
@@ -43,7 +42,7 @@ func IsErrUserNotExist(err error) bool {
}
func (err ErrUserNotExist) Error() string {
- return fmt.Sprintf("user does not exist [uid: %d, name: %s, keyid: %d]", err.UID, err.Name, err.KeyID)
+ return fmt.Sprintf("user does not exist [uid: %d, name: %s]", err.UID, err.Name)
}
// Unwrap unwraps this error as a ErrNotExist error
diff --git a/models/user/user.go b/models/user/user.go
index 1c305fff8e..975adc6e28 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -26,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
"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/timeutil"
@@ -442,14 +443,14 @@ func (u *User) GetDisplayName() string {
}
// GetCompleteName returns the the full name and username in the form of
-// "Full Name (@username)" if full name is not empty, otherwise it returns
-// "@username".
+// "Full Name (username)" if full name is not empty, otherwise it returns
+// "username".
func (u *User) GetCompleteName() string {
trimmedFullName := strings.TrimSpace(u.FullName)
if len(trimmedFullName) > 0 {
- return fmt.Sprintf("%s (@%s)", trimmedFullName, u.Name)
+ return fmt.Sprintf("%s (%s)", trimmedFullName, u.Name)
}
- return fmt.Sprintf("@%s", u.Name)
+ return u.Name
}
func gitSafeName(name string) string {
@@ -591,14 +592,14 @@ func IsUsableUsername(name string) error {
// CreateUserOverwriteOptions are an optional options who overwrite system defaults on user creation
type CreateUserOverwriteOptions struct {
- KeepEmailPrivate util.OptionalBool
+ KeepEmailPrivate optional.Option[bool]
Visibility *structs.VisibleType
- AllowCreateOrganization util.OptionalBool
+ AllowCreateOrganization optional.Option[bool]
EmailNotificationsPreference *string
MaxRepoCreation *int
Theme *string
- IsRestricted util.OptionalBool
- IsActive util.OptionalBool
+ IsRestricted optional.Option[bool]
+ IsActive optional.Option[bool]
}
// CreateUser creates record of a new user.
@@ -625,14 +626,14 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
// overwrite defaults if set
if len(overwriteDefault) != 0 && overwriteDefault[0] != nil {
overwrite := overwriteDefault[0]
- if !overwrite.KeepEmailPrivate.IsNone() {
- u.KeepEmailPrivate = overwrite.KeepEmailPrivate.IsTrue()
+ if overwrite.KeepEmailPrivate.Has() {
+ u.KeepEmailPrivate = overwrite.KeepEmailPrivate.Value()
}
if overwrite.Visibility != nil {
u.Visibility = *overwrite.Visibility
}
- if !overwrite.AllowCreateOrganization.IsNone() {
- u.AllowCreateOrganization = overwrite.AllowCreateOrganization.IsTrue()
+ if overwrite.AllowCreateOrganization.Has() {
+ u.AllowCreateOrganization = overwrite.AllowCreateOrganization.Value()
}
if overwrite.EmailNotificationsPreference != nil {
u.EmailNotificationsPreference = *overwrite.EmailNotificationsPreference
@@ -643,11 +644,11 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
if overwrite.Theme != nil {
u.Theme = *overwrite.Theme
}
- if !overwrite.IsRestricted.IsNone() {
- u.IsRestricted = overwrite.IsRestricted.IsTrue()
+ if overwrite.IsRestricted.Has() {
+ u.IsRestricted = overwrite.IsRestricted.Value()
}
- if !overwrite.IsActive.IsNone() {
- u.IsActive = overwrite.IsActive.IsTrue()
+ if overwrite.IsActive.Has() {
+ u.IsActive = overwrite.IsActive.Value()
}
}
@@ -864,7 +865,7 @@ func GetUserByID(ctx context.Context, id int64) (*User, error) {
if err != nil {
return nil, err
} else if !has {
- return nil, ErrUserNotExist{id, "", 0}
+ return nil, ErrUserNotExist{UID: id}
}
return u, nil
}
@@ -914,14 +915,14 @@ func GetPossibleUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
// GetUserByNameCtx returns user by given name.
func GetUserByName(ctx context.Context, name string) (*User, error) {
if len(name) == 0 {
- return nil, ErrUserNotExist{0, name, 0}
+ return nil, ErrUserNotExist{Name: name}
}
u := &User{LowerName: strings.ToLower(name), Type: UserTypeIndividual}
has, err := db.GetEngine(ctx).Get(u)
if err != nil {
return nil, err
} else if !has {
- return nil, ErrUserNotExist{0, name, 0}
+ return nil, ErrUserNotExist{Name: name}
}
return u, nil
}
@@ -1062,7 +1063,7 @@ func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) []
// GetUserByEmail returns the user object by given e-mail if exists.
func GetUserByEmail(ctx context.Context, email string) (*User, error) {
if len(email) == 0 {
- return nil, ErrUserNotExist{0, email, 0}
+ return nil, ErrUserNotExist{Name: email}
}
email = strings.ToLower(email)
@@ -1089,7 +1090,7 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) {
}
}
- return nil, ErrUserNotExist{0, email, 0}
+ return nil, ErrUserNotExist{Name: email}
}
// GetUser checks if a user already exists
@@ -1100,7 +1101,7 @@ func GetUser(ctx context.Context, user *User) (bool, error) {
// GetUserByOpenID returns the user object by given OpenID if exists.
func GetUserByOpenID(ctx context.Context, uri string) (*User, error) {
if len(uri) == 0 {
- return nil, ErrUserNotExist{0, uri, 0}
+ return nil, ErrUserNotExist{Name: uri}
}
uri, err := openid.Normalize(uri)
@@ -1120,7 +1121,7 @@ func GetUserByOpenID(ctx context.Context, uri string) (*User, error) {
return GetUserByID(ctx, oid.UID)
}
- return nil, ErrUserNotExist{0, uri, 0}
+ return nil, ErrUserNotExist{Name: uri}
}
// GetAdminUser returns the first administrator
diff --git a/models/user/user_test.go b/models/user/user_test.go
index 5105845394..52002e998d 100644
--- a/models/user/user_test.go
+++ b/models/user/user_test.go
@@ -98,7 +98,7 @@ func TestSearchUsers(t *testing.T) {
[]int64{19, 25})
testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}},
- []int64{26})
+ []int64{26, 41})
testOrgSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 5, PageSize: 2}},
[]int64{})
@@ -110,13 +110,13 @@ 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})
+ []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},
[]int64{9})
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue},
- []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37})
+ []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},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
diff --git a/modules/actions/github.go b/modules/actions/github.go
index a988b2a124..cc04ca5c5c 100644
--- a/modules/actions/github.go
+++ b/modules/actions/github.go
@@ -25,6 +25,45 @@ const (
GithubEventSchedule = "schedule"
)
+// IsDefaultBranchWorkflow returns true if the event only triggers workflows on the default branch
+func IsDefaultBranchWorkflow(triggedEvent webhook_module.HookEventType) bool {
+ switch triggedEvent {
+ case webhook_module.HookEventDelete:
+ // GitHub "delete" event
+ // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#delete
+ return true
+ case webhook_module.HookEventFork:
+ // GitHub "fork" event
+ // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#fork
+ return true
+ case webhook_module.HookEventIssueComment:
+ // GitHub "issue_comment" event
+ // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issue_comment
+ return true
+ case webhook_module.HookEventPullRequestComment:
+ // GitHub "pull_request_comment" event
+ // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment
+ return true
+ case webhook_module.HookEventWiki:
+ // GitHub "gollum" event
+ // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum
+ return true
+ case webhook_module.HookEventSchedule:
+ // GitHub "schedule" event
+ // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
+ return true
+ case webhook_module.HookEventIssues,
+ webhook_module.HookEventIssueAssign,
+ webhook_module.HookEventIssueLabel,
+ webhook_module.HookEventIssueMilestone:
+ // Github "issues" event
+ // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issues
+ return true
+ }
+
+ return false
+}
+
// canGithubEventMatch check if the input Github event can match any Gitea event.
func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEventType) bool {
switch eventName {
@@ -55,7 +94,9 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent
case webhook_module.HookEventPullRequest,
webhook_module.HookEventPullRequestSync,
webhook_module.HookEventPullRequestAssign,
- webhook_module.HookEventPullRequestLabel:
+ webhook_module.HookEventPullRequestLabel,
+ webhook_module.HookEventPullRequestReviewRequest,
+ webhook_module.HookEventPullRequestMilestone:
return true
default:
@@ -73,6 +114,11 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent
return false
}
+ case GithubEventIssueComment:
+ // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment
+ return triggedEvent == webhook_module.HookEventIssueComment ||
+ triggedEvent == webhook_module.HookEventPullRequestComment
+
default:
return eventName == string(triggedEvent)
}
diff --git a/modules/actions/github_test.go b/modules/actions/github_test.go
index 4bf55ae03f..6652ff6eac 100644
--- a/modules/actions/github_test.go
+++ b/modules/actions/github_test.go
@@ -103,6 +103,12 @@ func TestCanGithubEventMatch(t *testing.T) {
webhook_module.HookEventCreate,
true,
},
+ {
+ "create pull request comment",
+ GithubEventIssueComment,
+ webhook_module.HookEventPullRequestComment,
+ true,
+ },
}
for _, tc := range testCases {
diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go
index 00d83e06d7..81ab26bc27 100644
--- a/modules/actions/workflows.go
+++ b/modules/actions/workflows.go
@@ -186,7 +186,9 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
webhook_module.HookEventPullRequest,
webhook_module.HookEventPullRequestSync,
webhook_module.HookEventPullRequestAssign,
- webhook_module.HookEventPullRequestLabel:
+ webhook_module.HookEventPullRequestLabel,
+ webhook_module.HookEventPullRequestReviewRequest,
+ webhook_module.HookEventPullRequestMilestone:
return matchPullRequestEvent(gitRepo, commit, payload.(*api.PullRequestPayload), evt)
case // pull_request_review
@@ -362,13 +364,13 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
} else {
// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
// Actions with the same name:
- // opened, edited, closed, reopened, assigned, unassigned
+ // opened, edited, closed, reopened, assigned, unassigned, review_requested, review_request_removed, milestoned, demilestoned
// Actions need to be converted:
// synchronized -> synchronize
// label_updated -> labeled
// label_cleared -> unlabeled
// Unsupported activity types:
- // converted_to_draft, ready_for_review, locked, unlocked, review_requested, review_request_removed, auto_merge_enabled, auto_merge_disabled
+ // converted_to_draft, ready_for_review, locked, unlocked, auto_merge_enabled, auto_merge_disabled, enqueued, dequeued
action := prPayload.Action
switch action {
diff --git a/modules/auth/password/password.go b/modules/auth/password/password.go
index 2c7205b708..27074358a9 100644
--- a/modules/auth/password/password.go
+++ b/modules/auth/password/password.go
@@ -8,6 +8,7 @@ import (
"context"
"crypto/rand"
"errors"
+ "html/template"
"math/big"
"strings"
"sync"
@@ -121,15 +122,15 @@ func Generate(n int) (string, error) {
}
// BuildComplexityError builds the error message when password complexity checks fail
-func BuildComplexityError(locale translation.Locale) string {
+func BuildComplexityError(locale translation.Locale) template.HTML {
var buffer bytes.Buffer
- buffer.WriteString(locale.Tr("form.password_complexity"))
+ buffer.WriteString(locale.TrString("form.password_complexity"))
buffer.WriteString("")
for _, c := range requiredList {
buffer.WriteString("- ")
- buffer.WriteString(locale.Tr(c.TrNameOne))
+ buffer.WriteString(locale.TrString(c.TrNameOne))
buffer.WriteString("
")
}
buffer.WriteString("
")
- return buffer.String()
+ return template.HTML(buffer.String())
}
diff --git a/modules/base/natural_sort.go b/modules/base/natural_sort.go
index e920177f89..5b5febb906 100644
--- a/modules/base/natural_sort.go
+++ b/modules/base/natural_sort.go
@@ -5,11 +5,13 @@ package base
import (
"math/big"
+ "strings"
"unicode/utf8"
)
// NaturalSortLess compares two strings so that they could be sorted in natural order
func NaturalSortLess(s1, s2 string) bool {
+ s1, s2 = strings.ToLower(s1), strings.ToLower(s2)
var i1, i2 int
for {
rune1, j1, end1 := getNextRune(s1, i1)
diff --git a/modules/base/natural_sort_test.go b/modules/base/natural_sort_test.go
index 91e864ad2a..7378d9a643 100644
--- a/modules/base/natural_sort_test.go
+++ b/modules/base/natural_sort_test.go
@@ -20,4 +20,10 @@ func TestNaturalSortLess(t *testing.T) {
test("a-1-a", "a-1-b", true)
test("2", "12", true)
test("a", "ab", true)
+
+ // Test for case insensitive.
+ test("A", "ab", true)
+ test("B", "ab", false)
+ test("a", "AB", true)
+ test("b", "AB", false)
}
diff --git a/modules/base/tool.go b/modules/base/tool.go
index b72f3a1930..231507546d 100644
--- a/modules/base/tool.go
+++ b/modules/base/tool.go
@@ -115,7 +115,7 @@ func CreateTimeLimitCode(data string, minutes int, startInf any) string {
// create sha1 encode string
sh := sha1.New()
- _, _ = sh.Write([]byte(fmt.Sprintf("%s%s%s%s%d", data, setting.SecretKey, startStr, endStr, minutes)))
+ _, _ = sh.Write([]byte(fmt.Sprintf("%s%s%s%s%d", data, hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), startStr, endStr, minutes)))
encoded := hex.EncodeToString(sh.Sum(nil))
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
@@ -174,7 +174,7 @@ func Int64sToStrings(ints []int64) []string {
func EntryIcon(entry *git.TreeEntry) string {
switch {
case entry.IsLink():
- te, err := entry.FollowLink()
+ te, _, err := entry.FollowLink()
if err != nil {
log.Debug(err.Error())
return "file-symlink-file"
diff --git a/modules/charset/escape.go b/modules/charset/escape.go
index 92e417d1f7..ba0eb73a3a 100644
--- a/modules/charset/escape.go
+++ b/modules/charset/escape.go
@@ -10,6 +10,7 @@ package charset
import (
"html/template"
"io"
+ "slices"
"strings"
"code.gitea.io/gitea/modules/log"
@@ -20,16 +21,29 @@ import (
// RuneNBSP is the codepoint for NBSP
const RuneNBSP = 0xa0
+type escapeContext string
+
+// Keep this consistent with the documentation of [ui].SKIP_ESCAPE_CONTEXTS
+// Defines the different contexts that could be used to escape in.
+const (
+ // Wiki pages.
+ WikiContext escapeContext = "wiki"
+ // Rendered content (except markup), source code and blames.
+ FileviewContext escapeContext = "file-view"
+ // Commits or pull requet's diff.
+ DiffContext escapeContext = "diff"
+)
+
// EscapeControlHTML escapes the unicode control sequences in a provided html document
-func EscapeControlHTML(html template.HTML, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output template.HTML) {
+func EscapeControlHTML(html template.HTML, locale translation.Locale, context escapeContext, allowed ...rune) (escaped *EscapeStatus, output template.HTML) {
sb := &strings.Builder{}
- escaped, _ = EscapeControlReader(strings.NewReader(string(html)), sb, locale, allowed...) // err has been handled in EscapeControlReader
+ escaped, _ = EscapeControlReader(strings.NewReader(string(html)), sb, locale, context, allowed...) // err has been handled in EscapeControlReader
return escaped, template.HTML(sb.String())
}
// EscapeControlReader escapes the unicode control sequences in a provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus
-func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) {
- if !setting.UI.AmbiguousUnicodeDetection {
+func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.Locale, context escapeContext, allowed ...rune) (escaped *EscapeStatus, err error) {
+ if !setting.UI.AmbiguousUnicodeDetection || slices.Contains(setting.UI.SkipEscapeContexts, string(context)) {
_, err = io.Copy(writer, reader)
return &EscapeStatus{}, err
}
diff --git a/modules/charset/escape_stream.go b/modules/charset/escape_stream.go
index 3f08fd94a4..29943eb858 100644
--- a/modules/charset/escape_stream.go
+++ b/modules/charset/escape_stream.go
@@ -173,7 +173,7 @@ func (e *escapeStreamer) ambiguousRune(r, c rune) error {
Val: "ambiguous-code-point",
}, html.Attribute{
Key: "data-tooltip-content",
- Val: e.locale.Tr("repo.ambiguous_character", r, c),
+ Val: e.locale.TrString("repo.ambiguous_character", r, c),
}); err != nil {
return err
}
diff --git a/modules/charset/escape_test.go b/modules/charset/escape_test.go
index a353ced631..7442a80d7f 100644
--- a/modules/charset/escape_test.go
+++ b/modules/charset/escape_test.go
@@ -4,6 +4,7 @@
package charset
import (
+ "html/template"
"strings"
"testing"
@@ -14,6 +15,8 @@ import (
"github.com/stretchr/testify/assert"
)
+var testContext = escapeContext("test")
+
type escapeControlTest struct {
name string
text string
@@ -159,7 +162,7 @@ func TestEscapeControlReader(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output := &strings.Builder{}
- status, err := EscapeControlReader(strings.NewReader(tt.text), output, &translation.MockLocale{})
+ status, err := EscapeControlReader(strings.NewReader(tt.text), output, &translation.MockLocale{}, testContext)
assert.NoError(t, err)
assert.Equal(t, tt.status, *status)
assert.Equal(t, tt.result, output.String())
@@ -169,9 +172,22 @@ func TestEscapeControlReader(t *testing.T) {
func TestSettingAmbiguousUnicodeDetection(t *testing.T) {
defer test.MockVariableValue(&setting.UI.AmbiguousUnicodeDetection, true)()
- _, out := EscapeControlHTML("a test", &translation.MockLocale{})
+
+ _, out := EscapeControlHTML("a test", &translation.MockLocale{}, testContext)
assert.EqualValues(t, `a test`, out)
setting.UI.AmbiguousUnicodeDetection = false
- _, out = EscapeControlHTML("a test", &translation.MockLocale{})
+ _, out = EscapeControlHTML("a test", &translation.MockLocale{}, testContext)
assert.EqualValues(t, `a test`, out)
}
+
+func TestAmbiguousUnicodeDetectionContext(t *testing.T) {
+ defer test.MockVariableValue(&setting.UI.SkipEscapeContexts, []string{"test"})()
+
+ input := template.HTML("a test")
+
+ _, out := EscapeControlHTML(input, &translation.MockLocale{}, escapeContext("not-test"))
+ assert.EqualValues(t, `a test`, out)
+
+ _, out = EscapeControlHTML(input, &translation.MockLocale{}, testContext)
+ assert.EqualValues(t, input, out)
+}
diff --git a/modules/context/api.go b/modules/context/api.go
index 05b6a7a533..fafd49fd42 100644
--- a/modules/context/api.go
+++ b/modules/context/api.go
@@ -247,7 +247,7 @@ func APIContexter() func(http.Handler) http.Handler {
// NotFound handles 404s for APIContext
// String will replace message, errors will be added to a slice
func (ctx *APIContext) NotFound(objs ...any) {
- message := ctx.Tr("error.not_found")
+ message := ctx.Locale.TrString("error.not_found")
var errors []string
for _, obj := range objs {
// Ignore nil
@@ -309,12 +309,6 @@ func RepoRefForAPI(next http.Handler) http.Handler {
return
}
- objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat()
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetCommit", err)
- return
- }
-
if ref := ctx.FormTrim("ref"); len(ref) > 0 {
commit, err := ctx.Repo.GitRepo.GetCommit(ref)
if err != nil {
@@ -333,6 +327,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
}
refName := getRefName(ctx.Base, ctx.Repo, RepoRefAny)
+ var err error
if ctx.Repo.GitRepo.IsBranchExist(refName) {
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
@@ -348,7 +343,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if len(refName) == objectFormat.FullLength() {
+ } else if len(refName) == ctx.Repo.GetObjectFormat().FullLength() {
ctx.Repo.CommitID = refName
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
if err != nil {
diff --git a/modules/context/base.go b/modules/context/base.go
index 8df1dde866..fa05850a16 100644
--- a/modules/context/base.go
+++ b/modules/context/base.go
@@ -6,6 +6,7 @@ package context
import (
"context"
"fmt"
+ "html/template"
"io"
"net/http"
"net/url"
@@ -286,11 +287,11 @@ func (b *Base) cleanUp() {
}
}
-func (b *Base) Tr(msg string, args ...any) string {
+func (b *Base) Tr(msg string, args ...any) template.HTML {
return b.Locale.Tr(msg, args...)
}
-func (b *Base) TrN(cnt any, key1, keyN string, args ...any) string {
+func (b *Base) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
return b.Locale.TrN(cnt, key1, keyN, args...)
}
diff --git a/modules/context/context.go b/modules/context/context.go
index d19c5d1198..a06ebfb0dc 100644
--- a/modules/context/context.go
+++ b/modules/context/context.go
@@ -6,7 +6,8 @@ package context
import (
"context"
- "html"
+ "encoding/hex"
+ "fmt"
"html/template"
"io"
"net/http"
@@ -71,16 +72,6 @@ func init() {
})
}
-// TrHTMLEscapeArgs runs ".Locale.Tr()" but pre-escapes all arguments with html.EscapeString.
-// This is useful if the locale message is intended to only produce HTML content.
-func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string {
- trArgs := make([]any, len(args))
- for i, arg := range args {
- trArgs[i] = html.EscapeString(arg)
- }
- return ctx.Locale.Tr(msg, trArgs...)
-}
-
type webContextKeyType struct{}
var WebContextKey = webContextKeyType{}
@@ -134,7 +125,7 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context {
func Contexter() func(next http.Handler) http.Handler {
rnd := templates.HTMLRenderer()
csrfOpts := CsrfOptions{
- Secret: setting.SecretKey,
+ Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()),
Cookie: setting.CSRFCookieName,
SetCookie: true,
Secure: setting.SessionConfig.Secure,
@@ -207,6 +198,7 @@ func Contexter() func(next http.Handler) http.Handler {
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
ctx.Data["DisableStars"] = setting.Repository.DisableStars
+ ctx.Data["DisableForks"] = setting.Repository.DisableForks
ctx.Data["EnableActions"] = setting.Actions.Enabled
ctx.Data["ManifestData"] = setting.ManifestData
@@ -253,6 +245,13 @@ func (ctx *Context) JSONOK() {
ctx.JSON(http.StatusOK, map[string]any{"ok": true}) // this is only a dummy response, frontend seldom uses it
}
-func (ctx *Context) JSONError(msg string) {
- ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": msg})
+func (ctx *Context) JSONError(msg any) {
+ switch v := msg.(type) {
+ case string:
+ ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "text"})
+ case template.HTML:
+ ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "html"})
+ default:
+ panic(fmt.Sprintf("unsupported type: %T", msg))
+ }
}
diff --git a/modules/context/context_response.go b/modules/context/context_response.go
index 5729865561..829bca1f59 100644
--- a/modules/context/context_response.go
+++ b/modules/context/context_response.go
@@ -90,6 +90,20 @@ func (ctx *Context) HTML(status int, name base.TplName) {
}
}
+// JSONTemplate renders the template as JSON response
+// keep in mind that the template is processed in HTML context, so JSON-things should be handled carefully, eg: by JSEscape
+func (ctx *Context) JSONTemplate(tmpl base.TplName) {
+ t, err := ctx.Render.TemplateLookup(string(tmpl), nil)
+ if err != nil {
+ ctx.ServerError("unable to find template", err)
+ return
+ }
+ ctx.Resp.Header().Set("Content-Type", "application/json")
+ if err = t.Execute(ctx.Resp, ctx.Data); err != nil {
+ ctx.ServerError("unable to execute template", err)
+ }
+}
+
// RenderToString renders the template content to a string
func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (string, error) {
var buf strings.Builder
@@ -98,12 +112,11 @@ func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (stri
}
// RenderWithErr used for page has form validation but need to prompt error to users.
-func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form any) {
+func (ctx *Context) RenderWithErr(msg any, tpl base.TplName, form any) {
if form != nil {
middleware.AssignForm(form, ctx.Data)
}
- ctx.Flash.ErrorMsg = msg
- ctx.Data["Flash"] = ctx.Flash
+ ctx.Flash.Error(msg, true)
ctx.HTML(http.StatusOK, tpl)
}
diff --git a/modules/context/context_template.go b/modules/context/context_template.go
index ba90fc170a..7878d409ca 100644
--- a/modules/context/context_template.go
+++ b/modules/context/context_template.go
@@ -5,10 +5,7 @@ package context
import (
"context"
- "errors"
"time"
-
- "code.gitea.io/gitea/modules/log"
)
var _ context.Context = TemplateContext(nil)
@@ -36,14 +33,3 @@ func (c TemplateContext) Err() error {
func (c TemplateContext) Value(key any) any {
return c.parentContext().Value(key)
}
-
-// DataRaceCheck checks whether the template context function "ctx()" returns the consistent context
-// as the current template's rendering context (request context), to help to find data race issues as early as possible.
-// When the code is proven to be correct and stable, this function should be removed.
-func (c TemplateContext) DataRaceCheck(dataCtx context.Context) (string, error) {
- if c.parentContext() != dataCtx {
- log.Error("TemplateContext.DataRaceCheck: parent context mismatch\n%s", log.Stack(2))
- return "", errors.New("parent context mismatch")
- }
- return "", nil
-}
diff --git a/modules/context/org.go b/modules/context/org.go
index d068646577..018b76de43 100644
--- a/modules/context/org.go
+++ b/modules/context/org.go
@@ -11,6 +11,8 @@ import (
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
)
@@ -255,6 +257,19 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
ctx.Data["CanReadProjects"] = ctx.Org.CanReadUnit(ctx, unit.TypeProjects)
ctx.Data["CanReadPackages"] = ctx.Org.CanReadUnit(ctx, unit.TypePackages)
ctx.Data["CanReadCode"] = ctx.Org.CanReadUnit(ctx, unit.TypeCode)
+
+ ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
+ if len(ctx.ContextUser.Description) != 0 {
+ content, err := markdown.RenderString(&markup.RenderContext{
+ Metas: map[string]string{"mode": "document"},
+ Ctx: ctx,
+ }, ctx.ContextUser.Description)
+ if err != nil {
+ ctx.ServerError("RenderString", err)
+ return
+ }
+ ctx.Data["RenderedDescription"] = content
+ }
}
// OrgAssignment returns a middleware to handle organization assignment
diff --git a/modules/context/repo.go b/modules/context/repo.go
index 850edf666d..5e4f2718c4 100644
--- a/modules/context/repo.go
+++ b/modules/context/repo.go
@@ -6,6 +6,7 @@ package context
import (
"context"
+ "errors"
"fmt"
"html"
"net/http"
@@ -26,6 +27,7 @@ import (
"code.gitea.io/gitea/modules/gitrepo"
code_indexer "code.gitea.io/gitea/modules/indexer/code"
"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"
@@ -81,11 +83,15 @@ func (r *Repository) CanCreateBranch() bool {
return r.Permission.CanWrite(unit_model.TypeCode) && r.Repository.CanCreateBranch()
}
+func (r *Repository) GetObjectFormat() git.ObjectFormat {
+ return git.ObjectFormatFromName(r.Repository.ObjectFormatName)
+}
+
// RepoMustNotBeArchived checks if a repo is archived
func RepoMustNotBeArchived() func(ctx *Context) {
return func(ctx *Context) {
if ctx.Repo.Repository.IsArchived {
- ctx.NotFound("IsArchived", fmt.Errorf(ctx.Tr("repo.archive.title")))
+ ctx.NotFound("IsArchived", errors.New(ctx.Locale.TrString("repo.archive.title")))
}
}
}
@@ -687,7 +693,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
branchOpts := git_model.FindBranchOptions{
RepoID: ctx.Repo.Repository.ID,
- IsDeletedBranch: util.OptionalBoolFalse,
+ IsDeletedBranch: optional.Some(false),
ListOptions: db.ListOptionsAll,
}
branchesTotal, err := db.Count[git_model.Branch](ctx, branchOpts)
@@ -845,9 +851,8 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
}
// For legacy and API support only full commit sha
parts := strings.Split(path, "/")
- objectFormat, _ := repo.GitRepo.GetObjectFormat()
- if len(parts) > 0 && len(parts[0]) == objectFormat.FullLength() {
+ if len(parts) > 0 && len(parts[0]) == git.ObjectFormatFromName(repo.Repository.ObjectFormatName).FullLength() {
repo.TreePath = strings.Join(parts[1:], "/")
return parts[0]
}
@@ -891,9 +896,8 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
return getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsTagExist)
case RepoRefCommit:
parts := strings.Split(path, "/")
- objectFormat, _ := repo.GitRepo.GetObjectFormat()
- if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= objectFormat.FullLength() {
+ if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= repo.GetObjectFormat().FullLength() {
repo.TreePath = strings.Join(parts[1:], "/")
return parts[0]
}
@@ -952,12 +956,6 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
}
}
- objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat()
- if err != nil {
- log.Error("Cannot determine objectFormat for repository: %w", err)
- ctx.Repo.Repository.MarkAsBrokenEmpty()
- }
-
// Get default branch.
if len(ctx.Params("*")) == 0 {
refName = ctx.Repo.Repository.DefaultBranch
@@ -1024,7 +1022,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
return cancel
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if len(refName) >= 7 && len(refName) <= objectFormat.FullLength() {
+ } else if len(refName) >= 7 && len(refName) <= ctx.Repo.GetObjectFormat().FullLength() {
ctx.Repo.IsViewCommit = true
ctx.Repo.CommitID = refName
@@ -1034,7 +1032,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
return cancel
}
// If short commit ID add canonical link header
- if len(refName) < objectFormat.FullLength() {
+ if len(refName) < ctx.Repo.GetObjectFormat().FullLength() {
ctx.RespHeader().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))))
}
diff --git a/modules/csv/csv.go b/modules/csv/csv.go
index c5497befe7..35c5d6ab67 100644
--- a/modules/csv/csv.go
+++ b/modules/csv/csv.go
@@ -123,9 +123,9 @@ func guessDelimiter(data []byte) rune {
func FormatError(err error, locale translation.Locale) (string, error) {
if perr, ok := err.(*stdcsv.ParseError); ok {
if perr.Err == stdcsv.ErrFieldCount {
- return locale.Tr("repo.error.csv.invalid_field_count", perr.Line), nil
+ return locale.TrString("repo.error.csv.invalid_field_count", perr.Line), nil
}
- return locale.Tr("repo.error.csv.unexpected", perr.Line, perr.Column), nil
+ return locale.TrString("repo.error.csv.unexpected", perr.Line, perr.Column), nil
}
return "", err
diff --git a/modules/generate/generate.go b/modules/generate/generate.go
index df3e2474f9..41a6aa2815 100644
--- a/modules/generate/generate.go
+++ b/modules/generate/generate.go
@@ -7,6 +7,7 @@ package generate
import (
"crypto/rand"
"encoding/base64"
+ "fmt"
"io"
"time"
@@ -38,6 +39,20 @@ func NewInternalToken() (string, error) {
return internalToken, nil
}
+const defaultJwtSecretLen = 32
+
+// DecodeJwtSecret decodes a base64 encoded jwt secret into bytes, and check its length
+func DecodeJwtSecret(src string) ([]byte, error) {
+ encoding := base64.RawURLEncoding
+ decoded := make([]byte, encoding.DecodedLen(len(src))+3)
+ if n, err := encoding.Decode(decoded, []byte(src)); err != nil {
+ return nil, err
+ } else if n != defaultJwtSecretLen {
+ return nil, fmt.Errorf("invalid base64 decoded length: %d, expects: %d", n, defaultJwtSecretLen)
+ }
+ return decoded[:defaultJwtSecretLen], nil
+}
+
// NewJwtSecret generates a new base64 encoded value intended to be used for JWT secrets.
func NewJwtSecret() ([]byte, string, error) {
bytes := make([]byte, 32)
diff --git a/modules/generate/generate_test.go b/modules/generate/generate_test.go
new file mode 100644
index 0000000000..7d023b23ad
--- /dev/null
+++ b/modules/generate/generate_test.go
@@ -0,0 +1,34 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package generate
+
+import (
+ "encoding/base64"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestDecodeJwtSecret(t *testing.T) {
+ _, err := DecodeJwtSecret("abcd")
+ assert.ErrorContains(t, err, "invalid base64 decoded length")
+ _, err = DecodeJwtSecret(strings.Repeat("a", 64))
+ assert.ErrorContains(t, err, "invalid base64 decoded length")
+
+ str32 := strings.Repeat("x", 32)
+ encoded32 := base64.RawURLEncoding.EncodeToString([]byte(str32))
+ decoded32, err := DecodeJwtSecret(encoded32)
+ assert.NoError(t, err)
+ assert.Equal(t, str32, string(decoded32))
+}
+
+func TestNewJwtSecret(t *testing.T) {
+ secret, encoded, err := NewJwtSecret()
+ assert.NoError(t, err)
+ assert.Len(t, secret, 32)
+ decoded, err := DecodeJwtSecret(encoded)
+ assert.NoError(t, err)
+ assert.Equal(t, secret, decoded)
+}
diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go
index 53a9393d5f..043dbb44bd 100644
--- a/modules/git/batch_reader.go
+++ b/modules/git/batch_reader.go
@@ -203,16 +203,7 @@ headerLoop:
}
// Discard the rest of the tag
- discard := size - n + 1
- for discard > math.MaxInt32 {
- _, err := rd.Discard(math.MaxInt32)
- if err != nil {
- return id, err
- }
- discard -= math.MaxInt32
- }
- _, err := rd.Discard(int(discard))
- return id, err
+ return id, DiscardFull(rd, size-n+1)
}
// ReadTreeID reads a tree ID from a cat-file --batch stream, throwing away the rest of the stream.
@@ -238,16 +229,7 @@ headerLoop:
}
// Discard the rest of the commit
- discard := size - n + 1
- for discard > math.MaxInt32 {
- _, err := rd.Discard(math.MaxInt32)
- if err != nil {
- return id, err
- }
- discard -= math.MaxInt32
- }
- _, err := rd.Discard(int(discard))
- return id, err
+ return id, DiscardFull(rd, size-n+1)
}
// git tree files are a list:
@@ -345,3 +327,21 @@ func init() {
_, filename, _, _ := runtime.Caller(0)
callerPrefix = strings.TrimSuffix(filename, "modules/git/batch_reader.go")
}
+
+func DiscardFull(rd *bufio.Reader, discard int64) error {
+ if discard > math.MaxInt32 {
+ n, err := rd.Discard(math.MaxInt32)
+ discard -= int64(n)
+ if err != nil {
+ return err
+ }
+ }
+ for discard > 0 {
+ n, err := rd.Discard(int(discard))
+ discard -= int64(n)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/modules/git/blame.go b/modules/git/blame.go
index 64095a218a..69e1b08f93 100644
--- a/modules/git/blame.go
+++ b/modules/git/blame.go
@@ -115,6 +115,10 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
// Close BlameReader - don't run NextPart after invoking that
func (r *BlameReader) Close() error {
+ if r.bufferedReader == nil {
+ return nil
+ }
+
err := <-r.done
r.bufferedReader = nil
_ = r.reader.Close()
diff --git a/modules/git/blame_sha256_test.go b/modules/git/blame_sha256_test.go
index 01de0454a3..92f8b5b02a 100644
--- a/modules/git/blame_sha256_test.go
+++ b/modules/git/blame_sha256_test.go
@@ -11,6 +11,8 @@ import (
)
func TestReadingBlameOutputSha256(t *testing.T) {
+ skipIfSHA256NotSupported(t)
+
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
diff --git a/modules/git/blob_nogogit.go b/modules/git/blob_nogogit.go
index 6e8a48b1db..945a6bc432 100644
--- a/modules/git/blob_nogogit.go
+++ b/modules/git/blob_nogogit.go
@@ -9,7 +9,6 @@ import (
"bufio"
"bytes"
"io"
- "math"
"code.gitea.io/gitea/modules/log"
)
@@ -103,26 +102,17 @@ func (b *blobReader) Read(p []byte) (n int, err error) {
// Close implements io.Closer
func (b *blobReader) Close() error {
- defer b.cancel()
- if b.n > 0 {
- for b.n > math.MaxInt32 {
- n, err := b.rd.Discard(math.MaxInt32)
- b.n -= int64(n)
- if err != nil {
- return err
- }
- b.n -= math.MaxInt32
- }
- n, err := b.rd.Discard(int(b.n))
- b.n -= int64(n)
- if err != nil {
- return err
- }
+ if b.rd == nil {
+ return nil
}
- if b.n == 0 {
- _, err := b.rd.Discard(1)
- b.n--
+
+ defer b.cancel()
+
+ if err := DiscardFull(b.rd, b.n+1); err != nil {
return err
}
+
+ b.rd = nil
+
return nil
}
diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go
index e469d2cab6..a5d18694f7 100644
--- a/modules/git/commit_info_nogogit.go
+++ b/modules/git/commit_info_nogogit.go
@@ -151,6 +151,9 @@ func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string,
return nil, err
}
if typ != "commit" {
+ if err := DiscardFull(batchReader, size+1); err != nil {
+ return nil, err
+ }
return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID)
}
c, err = CommitFromReader(commit.repo, MustIDFromString(commitID), io.LimitReader(batchReader, size))
diff --git a/modules/git/commit_sha256_test.go b/modules/git/commit_sha256_test.go
index 82112cb409..916169f4e2 100644
--- a/modules/git/commit_sha256_test.go
+++ b/modules/git/commit_sha256_test.go
@@ -14,6 +14,8 @@ import (
)
func TestCommitsCountSha256(t *testing.T) {
+ skipIfSHA256NotSupported(t)
+
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
commitsCount, err := CommitsCount(DefaultContext,
@@ -27,6 +29,8 @@ func TestCommitsCountSha256(t *testing.T) {
}
func TestCommitsCountWithoutBaseSha256(t *testing.T) {
+ skipIfSHA256NotSupported(t)
+
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
commitsCount, err := CommitsCount(DefaultContext,
@@ -41,6 +45,8 @@ func TestCommitsCountWithoutBaseSha256(t *testing.T) {
}
func TestGetFullCommitIDSha256(t *testing.T) {
+ skipIfSHA256NotSupported(t)
+
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
id, err := GetFullCommitID(DefaultContext, bareRepo1Path, "f004f4")
@@ -49,6 +55,8 @@ func TestGetFullCommitIDSha256(t *testing.T) {
}
func TestGetFullCommitIDErrorSha256(t *testing.T) {
+ skipIfSHA256NotSupported(t)
+
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
id, err := GetFullCommitID(DefaultContext, bareRepo1Path, "unknown")
@@ -59,6 +67,8 @@ func TestGetFullCommitIDErrorSha256(t *testing.T) {
}
func TestCommitFromReaderSha256(t *testing.T) {
+ skipIfSHA256NotSupported(t)
+
commitString := `9433b2a62b964c17a4485ae180f45f595d3e69d31b786087775e28c6b6399df0 commit 1114
tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
@@ -131,6 +141,8 @@ signed commit`, commitFromReader.Signature.Payload)
}
func TestHasPreviousCommitSha256(t *testing.T) {
+ skipIfSHA256NotSupported(t)
+
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
repo, err := openRepositoryWithDefaultContext(bareRepo1Path)
@@ -159,6 +171,8 @@ func TestHasPreviousCommitSha256(t *testing.T) {
}
func TestGetCommitFileStatusMergesSha256(t *testing.T) {
+ skipIfSHA256NotSupported(t)
+
bareRepo1Path := filepath.Join(testReposDir, "repo6_merge_sha256")
commitFileStatus, err := GetCommitFileStatus(DefaultContext, bareRepo1Path, "d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1")
diff --git a/modules/git/error.go b/modules/git/error.go
index dc10d451b3..91d25eca69 100644
--- a/modules/git/error.go
+++ b/modules/git/error.go
@@ -96,7 +96,7 @@ func (err ErrBranchNotExist) Unwrap() error {
return util.ErrNotExist
}
-// ErrPushOutOfDate represents an error if merging fails due to unrelated histories
+// ErrPushOutOfDate represents an error if merging fails due to the base branch being updated
type ErrPushOutOfDate struct {
StdOut string
StdErr string
diff --git a/modules/git/git.go b/modules/git/git.go
index 89c23ff230..13a3127498 100644
--- a/modules/git/git.go
+++ b/modules/git/git.go
@@ -33,8 +33,9 @@ var (
// DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx
DefaultContext context.Context
- SupportProcReceive bool // >= 2.29
- SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
+ SupportProcReceive bool // >= 2.29
+ SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
+ InvertedGitFlushEnv bool // 2.43.1
gitVersion *version.Version
)
@@ -192,6 +193,8 @@ func InitFull(ctx context.Context) (err error) {
log.Warn("sha256 hash support is disabled - requires Git >= 2.42. Gogit is currently unsupported")
}
+ InvertedGitFlushEnv = CheckGitVersionEqual("2.43.1") == nil
+
if setting.LFS.StartServer {
if CheckGitVersionAtLeast("2.1.2") != nil {
return errors.New("LFS server support requires Git >= 2.1.2")
@@ -320,6 +323,21 @@ func CheckGitVersionAtLeast(atLeast string) error {
return nil
}
+// CheckGitVersionEqual checks if the git version is equal to the constraint version.
+func CheckGitVersionEqual(equal string) error {
+ if _, err := loadGitVersion(); err != nil {
+ return err
+ }
+ atLeastVersion, err := version.NewVersion(equal)
+ if err != nil {
+ return err
+ }
+ if !gitVersion.Equal(atLeastVersion) {
+ return fmt.Errorf("installed git binary version %s is not equal to %s", gitVersion.Original(), equal)
+ }
+ return nil
+}
+
func configSet(key, value string) error {
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
if err != nil && !err.IsExitCode(1) {
diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status.go
index 26a0d28098..9e345f3ee0 100644
--- a/modules/git/log_name_status.go
+++ b/modules/git/log_name_status.go
@@ -143,19 +143,19 @@ func (g *LogNameStatusRepoParser) Next(treepath string, paths2ids map[string]int
}
// Our "line" must look like: SP ( SP) * NUL
- commitIds := string(g.next)
+ commitIDs := string(g.next)
if g.buffull {
more, err := g.rd.ReadString('\x00')
if err != nil {
return nil, err
}
- commitIds += more
+ commitIDs += more
}
- commitIds = commitIds[:len(commitIds)-1]
- splitIds := strings.Split(commitIds, " ")
- ret.CommitID = splitIds[0]
- if len(splitIds) > 1 {
- ret.ParentIDs = splitIds[1:]
+ commitIDs = commitIDs[:len(commitIDs)-1]
+ splitIDs := strings.Split(commitIDs, " ")
+ ret.CommitID = splitIDs[0]
+ if len(splitIDs) > 1 {
+ ret.ParentIDs = splitIDs[1:]
}
// now read the next "line"
diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go
index a725f4799d..4c65249089 100644
--- a/modules/git/pipeline/lfs_nogogit.go
+++ b/modules/git/pipeline/lfs_nogogit.go
@@ -169,6 +169,10 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
} else {
break commitReadingLoop
}
+ default:
+ if err := git.DiscardFull(batchReader, size+1); err != nil {
+ return nil, err
+ }
}
}
}
diff --git a/modules/git/repo.go b/modules/git/repo.go
index db99d285a8..60078f3273 100644
--- a/modules/git/repo.go
+++ b/modules/git/repo.go
@@ -271,7 +271,7 @@ func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error
return time.Time{}, err
}
commitTime := strings.TrimSpace(stdout)
- return time.Parse(GitTimeLayout, commitTime)
+ return time.Parse("Mon Jan _2 15:04:05 2006 -0700", commitTime)
}
// DivergeObject represents commit count diverging commits
diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go
index 3c5a1429a9..197d626a4d 100644
--- a/modules/git/repo_attribute.go
+++ b/modules/git/repo_attribute.go
@@ -11,6 +11,7 @@ import (
"os"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
)
// CheckAttributeOpts represents the possible options to CheckAttribute
@@ -133,7 +134,13 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error {
c.env = append(c.env, "GIT_WORK_TREE="+c.WorkTree)
}
- c.env = append(c.env, "GIT_FLUSH=1")
+ // Version 2.43.1 has a bug where the behavior of `GIT_FLUSH` is flipped.
+ // Ref: https://lore.kernel.org/git/CABn0oJvg3M_kBW-u=j3QhKnO=6QOzk-YFTgonYw_UvFS1NTX4g@mail.gmail.com
+ if InvertedGitFlushEnv {
+ c.env = append(c.env, "GIT_FLUSH=0")
+ } else {
+ c.env = append(c.env, "GIT_FLUSH=1")
+ }
c.cmd.AddDynamicArguments(c.Attributes...)
@@ -316,3 +323,16 @@ func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeRe
return checker, deferable
}
+
+// true if "set"/"true", false if "unset"/"false", none otherwise
+func attributeToBool(attr map[string]string, name string) optional.Option[bool] {
+ if value, has := attr[name]; has && value != "unspecified" {
+ switch value {
+ case "set", "true":
+ return optional.Some(true)
+ case "unset", "false":
+ return optional.Some(false)
+ }
+ }
+ return optional.None[bool]()
+}
diff --git a/modules/git/repo_base_gogit.go b/modules/git/repo_base_gogit.go
index 9270bb70f0..3ca5eb36c6 100644
--- a/modules/git/repo_base_gogit.go
+++ b/modules/git/repo_base_gogit.go
@@ -88,16 +88,17 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
}
// Close this repository, in particular close the underlying gogitStorage if this is not nil
-func (repo *Repository) Close() (err error) {
+func (repo *Repository) Close() error {
if repo == nil || repo.gogitStorage == nil {
- return
+ return nil
}
if err := repo.gogitStorage.Close(); err != nil {
gitealog.Error("Error closing storage: %v", err)
}
+ repo.gogitStorage = nil
repo.LastCommitCache = nil
repo.tagCache = nil
- return
+ return nil
}
// GoGitRepo gets the go-git repo representation
diff --git a/modules/git/repo_base_nogogit.go b/modules/git/repo_base_nogogit.go
index d5a350a926..86b6a93567 100644
--- a/modules/git/repo_base_nogogit.go
+++ b/modules/git/repo_base_nogogit.go
@@ -27,10 +27,12 @@ type Repository struct {
gpgSettings *GPGSettings
+ batchInUse bool
batchCancel context.CancelFunc
batchReader *bufio.Reader
batchWriter WriteCloserError
+ checkInUse bool
checkCancel context.CancelFunc
checkReader *bufio.Reader
checkWriter WriteCloserError
@@ -79,24 +81,29 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
// CatFileBatch obtains a CatFileBatch for this repository
func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) {
- if repo.batchCancel == nil || repo.batchReader.Buffered() > 0 {
+ if repo.batchCancel == nil || repo.batchInUse {
log.Debug("Opening temporary cat file batch for: %s", repo.Path)
return CatFileBatch(ctx, repo.Path)
}
- return repo.batchWriter, repo.batchReader, func() {}
+ repo.batchInUse = true
+ return repo.batchWriter, repo.batchReader, func() {
+ repo.batchInUse = false
+ }
}
// CatFileBatchCheck obtains a CatFileBatchCheck for this repository
func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) {
- if repo.checkCancel == nil || repo.checkReader.Buffered() > 0 {
- log.Debug("Opening temporary cat file batch-check: %s", repo.Path)
+ if repo.checkCancel == nil || repo.checkInUse {
+ log.Debug("Opening temporary cat file batch-check for: %s", repo.Path)
return CatFileBatchCheck(ctx, repo.Path)
}
- return repo.checkWriter, repo.checkReader, func() {}
+ repo.checkInUse = true
+ return repo.checkWriter, repo.checkReader, func() {
+ repo.checkInUse = false
+ }
}
-// Close this repository, in particular close the underlying gogitStorage if this is not nil
-func (repo *Repository) Close() (err error) {
+func (repo *Repository) Close() error {
if repo == nil {
return nil
}
@@ -105,14 +112,16 @@ func (repo *Repository) Close() (err error) {
repo.batchReader = nil
repo.batchWriter = nil
repo.batchCancel = nil
+ repo.batchInUse = false
}
if repo.checkCancel != nil {
repo.checkCancel()
repo.checkCancel = nil
repo.checkReader = nil
repo.checkWriter = nil
+ repo.checkInUse = false
}
repo.LastCommitCache = nil
repo.tagCache = nil
- return err
+ return nil
}
diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go
index f0214e1ff8..a7031184e2 100644
--- a/modules/git/repo_commit_nogogit.go
+++ b/modules/git/repo_commit_nogogit.go
@@ -121,8 +121,7 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id ObjectID)
return commit, nil
default:
log.Debug("Unknown typ: %s", typ)
- _, err = rd.Discard(int(size) + 1)
- if err != nil {
+ if err := DiscardFull(rd, size+1); err != nil {
return nil, err
}
return nil, ErrNotExist{
diff --git a/modules/git/repo_language_stats.go b/modules/git/repo_language_stats.go
index 7ed2dc1587..c40d6937b5 100644
--- a/modules/git/repo_language_stats.go
+++ b/modules/git/repo_language_stats.go
@@ -13,18 +13,6 @@ const (
bigFileSize int64 = 1024 * 1024 // 1 MiB
)
-type LinguistBoolAttrib struct {
- Value string
-}
-
-func (attrib *LinguistBoolAttrib) IsTrue() bool {
- return attrib.Value == "set" || attrib.Value == "true"
-}
-
-func (attrib *LinguistBoolAttrib) IsFalse() bool {
- return attrib.Value == "unset" || attrib.Value == "false"
-}
-
// mergeLanguageStats mergers language names with different cases. The name with most upper case letters is used.
func mergeLanguageStats(stats map[string]int64) map[string]int64 {
names := map[string]struct {
diff --git a/modules/git/repo_language_stats_gogit.go b/modules/git/repo_language_stats_gogit.go
index 558a83af74..1276ce1a44 100644
--- a/modules/git/repo_language_stats_gogit.go
+++ b/modules/git/repo_language_stats_gogit.go
@@ -12,6 +12,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/analyze"
+ "code.gitea.io/gitea/modules/optional"
"github.com/go-enry/go-enry/v2"
"github.com/go-git/go-git/v5"
@@ -53,31 +54,30 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
firstExcludedLanguage := ""
firstExcludedLanguageSize := int64(0)
+ isTrue := func(v optional.Option[bool]) bool {
+ return v.ValueOrDefault(false)
+ }
+ isFalse := func(v optional.Option[bool]) bool {
+ return !v.ValueOrDefault(true)
+ }
+
err = tree.Files().ForEach(func(f *object.File) error {
if f.Size == 0 {
return nil
}
- isVendored := LinguistBoolAttrib{}
- isGenerated := LinguistBoolAttrib{}
- isDocumentation := LinguistBoolAttrib{}
- isDetectable := LinguistBoolAttrib{}
+ isVendored := optional.None[bool]()
+ isGenerated := optional.None[bool]()
+ isDocumentation := optional.None[bool]()
+ isDetectable := optional.None[bool]()
if checker != nil {
attrs, err := checker.CheckPath(f.Name)
if err == nil {
- if vendored, has := attrs["linguist-vendored"]; has {
- isVendored = LinguistBoolAttrib{Value: vendored}
- }
- if generated, has := attrs["linguist-generated"]; has {
- isGenerated = LinguistBoolAttrib{Value: generated}
- }
- if documentation, has := attrs["linguist-documentation"]; has {
- isDocumentation = LinguistBoolAttrib{Value: documentation}
- }
- if detectable, has := attrs["linguist-detectable"]; has {
- isDetectable = LinguistBoolAttrib{Value: detectable}
- }
+ isVendored = attributeToBool(attrs, "linguist-vendored")
+ isGenerated = attributeToBool(attrs, "linguist-generated")
+ isDocumentation = attributeToBool(attrs, "linguist-documentation")
+ isDetectable = attributeToBool(attrs, "linguist-detectable")
if language, has := attrs["linguist-language"]; has && language != "unspecified" && language != "" {
// group languages, such as Pug -> HTML; SCSS -> CSS
group := enry.GetLanguageGroup(language)
@@ -108,11 +108,11 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
}
}
- if isDetectable.IsFalse() || isVendored.IsTrue() || isDocumentation.IsTrue() ||
- (!isVendored.IsFalse() && analyze.IsVendor(f.Name)) ||
+ if isFalse(isDetectable) || isTrue(isVendored) || isTrue(isDocumentation) ||
+ (!isFalse(isVendored) && analyze.IsVendor(f.Name)) ||
enry.IsDotFile(f.Name) ||
enry.IsConfiguration(f.Name) ||
- (!isDocumentation.IsFalse() && enry.IsDocumentation(f.Name)) {
+ (!isFalse(isDocumentation) && enry.IsDocumentation(f.Name)) {
return nil
}
@@ -121,7 +121,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
if f.Size <= bigFileSize {
content, _ = readFile(f, fileSizeLimit)
}
- if !isGenerated.IsTrue() && enry.IsGenerated(f.Name, content) {
+ if !isTrue(isGenerated) && enry.IsGenerated(f.Name, content) {
return nil
}
@@ -138,21 +138,22 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
}
included, checked := includedLanguage[language]
+ langType := enry.GetLanguageType(language)
if !checked {
- langtype := enry.GetLanguageType(language)
- included = langtype == enry.Programming || langtype == enry.Markup
- if !included {
- if isDetectable.IsTrue() {
- included = true
- } else {
- return nil
- }
+ included = langType == enry.Programming || langType == enry.Markup
+ if !included && (isTrue(isDetectable) || (langType == enry.Prose && isFalse(isDocumentation))) {
+ included = true
}
includedLanguage[language] = included
}
if included {
sizes[language] += f.Size
} else if len(sizes) == 0 && (firstExcludedLanguage == "" || firstExcludedLanguage == language) {
+ // Only consider Programming or Markup languages as fallback
+ if !(langType == enry.Programming || langType == enry.Markup) {
+ return nil
+ }
+
firstExcludedLanguage = language
firstExcludedLanguageSize += f.Size
}
diff --git a/modules/git/repo_language_stats_nogogit.go b/modules/git/repo_language_stats_nogogit.go
index 13876094cc..1a3fdbb1f8 100644
--- a/modules/git/repo_language_stats_nogogit.go
+++ b/modules/git/repo_language_stats_nogogit.go
@@ -7,14 +7,13 @@
package git
import (
- "bufio"
"bytes"
"io"
- "math"
"strings"
"code.gitea.io/gitea/modules/analyze"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"github.com/go-enry/go-enry/v2"
)
@@ -77,6 +76,13 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
firstExcludedLanguage := ""
firstExcludedLanguageSize := int64(0)
+ isTrue := func(v optional.Option[bool]) bool {
+ return v.ValueOrDefault(false)
+ }
+ isFalse := func(v optional.Option[bool]) bool {
+ return !v.ValueOrDefault(true)
+ }
+
for _, f := range entries {
select {
case <-repo.Ctx.Done():
@@ -91,26 +97,18 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
continue
}
- isVendored := LinguistBoolAttrib{}
- isGenerated := LinguistBoolAttrib{}
- isDocumentation := LinguistBoolAttrib{}
- isDetectable := LinguistBoolAttrib{}
+ isVendored := optional.None[bool]()
+ isGenerated := optional.None[bool]()
+ isDocumentation := optional.None[bool]()
+ isDetectable := optional.None[bool]()
if checker != nil {
attrs, err := checker.CheckPath(f.Name())
if err == nil {
- if vendored, has := attrs["linguist-vendored"]; has {
- isVendored = LinguistBoolAttrib{Value: vendored}
- }
- if generated, has := attrs["linguist-generated"]; has {
- isGenerated = LinguistBoolAttrib{Value: generated}
- }
- if documentation, has := attrs["linguist-documentation"]; has {
- isDocumentation = LinguistBoolAttrib{Value: documentation}
- }
- if detectable, has := attrs["linguist-detectable"]; has {
- isDetectable = LinguistBoolAttrib{Value: detectable}
- }
+ isVendored = attributeToBool(attrs, "linguist-vendored")
+ isGenerated = attributeToBool(attrs, "linguist-generated")
+ isDocumentation = attributeToBool(attrs, "linguist-documentation")
+ isDetectable = attributeToBool(attrs, "linguist-detectable")
if language, has := attrs["linguist-language"]; has && language != "unspecified" && language != "" {
// group languages, such as Pug -> HTML; SCSS -> CSS
group := enry.GetLanguageGroup(language)
@@ -142,11 +140,11 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
}
}
- if isDetectable.IsFalse() || isVendored.IsTrue() || isDocumentation.IsTrue() ||
- (!isVendored.IsFalse() && analyze.IsVendor(f.Name())) ||
+ if isFalse(isDetectable) || isTrue(isVendored) || isTrue(isDocumentation) ||
+ (!isFalse(isVendored) && analyze.IsVendor(f.Name())) ||
enry.IsDotFile(f.Name()) ||
enry.IsConfiguration(f.Name()) ||
- (!isDocumentation.IsFalse() && enry.IsDocumentation(f.Name())) {
+ (!isFalse(isDocumentation) && enry.IsDocumentation(f.Name())) {
continue
}
@@ -174,12 +172,11 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
return nil, err
}
content = contentBuf.Bytes()
- err = discardFull(batchReader, discard)
- if err != nil {
+ if err := DiscardFull(batchReader, discard); err != nil {
return nil, err
}
}
- if !isGenerated.IsTrue() && enry.IsGenerated(f.Name(), content) {
+ if !isTrue(isGenerated) && enry.IsGenerated(f.Name(), content) {
continue
}
@@ -197,25 +194,24 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
}
included, checked := includedLanguage[language]
+ langType := enry.GetLanguageType(language)
if !checked {
- langType := enry.GetLanguageType(language)
included = langType == enry.Programming || langType == enry.Markup
- if !included {
- if isDetectable.IsTrue() {
- included = true
- } else {
- continue
- }
+ if !included && (isTrue(isDetectable) || (langType == enry.Prose && isFalse(isDocumentation))) {
+ included = true
}
includedLanguage[language] = included
}
if included {
sizes[language] += f.Size()
} else if len(sizes) == 0 && (firstExcludedLanguage == "" || firstExcludedLanguage == language) {
+ // Only consider Programming or Markup languages as fallback
+ if !(langType == enry.Programming || langType == enry.Markup) {
+ continue
+ }
firstExcludedLanguage = language
firstExcludedLanguageSize += f.Size()
}
- continue
}
// If there are no included languages add the first excluded language
@@ -225,21 +221,3 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
return mergeLanguageStats(sizes), nil
}
-
-func discardFull(rd *bufio.Reader, discard int64) error {
- if discard > math.MaxInt32 {
- n, err := rd.Discard(math.MaxInt32)
- discard -= int64(n)
- if err != nil {
- return err
- }
- }
- for discard > 0 {
- n, err := rd.Discard(int(discard))
- discard -= int64(n)
- if err != nil {
- return err
- }
- }
- return nil
-}
diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go
index af9a75b29c..ae5dbd171f 100644
--- a/modules/git/repo_tag.go
+++ b/modules/git/repo_tag.go
@@ -183,11 +183,7 @@ func parseTagRef(objectFormat ObjectFormat, ref map[string]string) (tag *Tag, er
}
}
- tag.Tagger, err = newSignatureFromCommitline([]byte(ref["creator"]))
- if err != nil {
- return nil, fmt.Errorf("parse tagger: %w", err)
- }
-
+ tag.Tagger = parseSignatureFromCommitLine(ref["creator"])
tag.Message = ref["contents"]
// strip PGP signature if present in contents field
pgpStart := strings.Index(tag.Message, beginpgp)
diff --git a/modules/git/repo_tag_nogogit.go b/modules/git/repo_tag_nogogit.go
index 5d98fadd54..cbab39f8c5 100644
--- a/modules/git/repo_tag_nogogit.go
+++ b/modules/git/repo_tag_nogogit.go
@@ -103,6 +103,9 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
return nil, err
}
if typ != "tag" {
+ if err := DiscardFull(rd, size+1); err != nil {
+ return nil, err
+ }
return nil, ErrNotExist{ID: tagID.String()}
}
diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go
index da7b1455a8..9816e311a8 100644
--- a/modules/git/repo_tag_test.go
+++ b/modules/git/repo_tag_test.go
@@ -227,7 +227,7 @@ func TestRepository_parseTagRef(t *testing.T) {
ID: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"),
Object: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"),
Type: "commit",
- Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"),
+ Tagger: parseSignatureFromCommitLine("Foo Bar 1565789218 +0300"),
Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n",
Signature: nil,
},
@@ -256,7 +256,7 @@ func TestRepository_parseTagRef(t *testing.T) {
ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"),
Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"),
Type: "tag",
- Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"),
+ Tagger: parseSignatureFromCommitLine("Foo Bar 1565789218 +0300"),
Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n",
Signature: nil,
},
@@ -314,7 +314,7 @@ qbHDASXl
ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"),
Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"),
Type: "tag",
- Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"),
+ Tagger: parseSignatureFromCommitLine("Foo Bar 1565789218 +0300"),
Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md",
Signature: &CommitGPGSignature{
Signature: `-----BEGIN PGP SIGNATURE-----
@@ -363,14 +363,3 @@ Add changelog of v1.9.1 (#7859)
})
}
}
-
-func parseAuthorLine(t *testing.T, committer string) *Signature {
- t.Helper()
-
- sig, err := newSignatureFromCommitline([]byte(committer))
- if err != nil {
- t.Fatalf("parse author line '%s': %v", committer, err)
- }
-
- return sig
-}
diff --git a/modules/git/repo_tree_nogogit.go b/modules/git/repo_tree_nogogit.go
index 20c92a79ed..582247b4a4 100644
--- a/modules/git/repo_tree_nogogit.go
+++ b/modules/git/repo_tree_nogogit.go
@@ -58,6 +58,9 @@ func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
tree.entriesParsed = true
return tree, nil
default:
+ if err := DiscardFull(rd, size+1); err != nil {
+ return nil, err
+ }
return nil, ErrNotExist{
ID: id.String(),
}
diff --git a/modules/git/signature.go b/modules/git/signature.go
index b5b17f23b0..f50a097758 100644
--- a/modules/git/signature.go
+++ b/modules/git/signature.go
@@ -4,7 +4,46 @@
package git
-const (
- // GitTimeLayout is the (default) time layout used by git.
- GitTimeLayout = "Mon Jan _2 15:04:05 2006 -0700"
+import (
+ "strconv"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/modules/log"
)
+
+// Helper to get a signature from the commit line, which looks like:
+//
+// full name 1378823654 +0200
+//
+// Haven't found the official reference for the standard format yet.
+// This function never fails, if the "line" can't be parsed, it returns a default Signature with "zero" time.
+func parseSignatureFromCommitLine(line string) *Signature {
+ sig := &Signature{}
+ s1, sx, ok1 := strings.Cut(line, " <")
+ s2, s3, ok2 := strings.Cut(sx, "> ")
+ if !ok1 || !ok2 {
+ sig.Name = line
+ return sig
+ }
+ sig.Name, sig.Email = s1, s2
+
+ if strings.Count(s3, " ") == 1 {
+ ts, tz, _ := strings.Cut(s3, " ")
+ seconds, _ := strconv.ParseInt(ts, 10, 64)
+ if tzTime, err := time.Parse("-0700", tz); err == nil {
+ sig.When = time.Unix(seconds, 0).In(tzTime.Location())
+ }
+ } else {
+ // the old gitea code tried to parse the date in a few different formats, but it's not clear why.
+ // according to public document, only the standard format "timestamp timezone" could be found, so drop other formats.
+ log.Error("suspicious commit line format: %q", line)
+ for _, fmt := range []string{ /*"Mon Jan _2 15:04:05 2006 -0700"*/ } {
+ if t, err := time.Parse(fmt, s3); err == nil {
+ sig.When = t
+ break
+ }
+ }
+ }
+ return sig
+}
diff --git a/modules/git/signature_gogit.go b/modules/git/signature_gogit.go
index c984ad6e20..1fc6aabceb 100644
--- a/modules/git/signature_gogit.go
+++ b/modules/git/signature_gogit.go
@@ -7,52 +7,8 @@
package git
import (
- "bytes"
- "strconv"
- "strings"
- "time"
-
"github.com/go-git/go-git/v5/plumbing/object"
)
// Signature represents the Author or Committer information.
type Signature = object.Signature
-
-// Helper to get a signature from the commit line, which looks like these:
-//
-// author Patrick Gundlach 1378823654 +0200
-// author Patrick Gundlach Thu, 07 Apr 2005 22:13:13 +0200
-//
-// but without the "author " at the beginning (this method should)
-// be used for author and committer.
-//
-// FIXME: include timezone for timestamp!
-func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
- sig := new(Signature)
- emailStart := bytes.IndexByte(line, '<')
- if emailStart > 0 { // Empty name has already occurred, even if it shouldn't
- sig.Name = strings.TrimSpace(string(line[:emailStart-1]))
- }
- emailEnd := bytes.IndexByte(line, '>')
- sig.Email = string(line[emailStart+1 : emailEnd])
-
- // Check date format.
- if len(line) > emailEnd+2 {
- firstChar := line[emailEnd+2]
- if firstChar >= 48 && firstChar <= 57 {
- timestop := bytes.IndexByte(line[emailEnd+2:], ' ')
- timestring := string(line[emailEnd+2 : emailEnd+2+timestop])
- seconds, _ := strconv.ParseInt(timestring, 10, 64)
- sig.When = time.Unix(seconds, 0)
- } else {
- sig.When, err = time.Parse(GitTimeLayout, string(line[emailEnd+2:]))
- if err != nil {
- return nil, err
- }
- }
- } else {
- // Fall back to unix 0 time
- sig.When = time.Unix(0, 0)
- }
- return sig, nil
-}
diff --git a/modules/git/signature_nogogit.go b/modules/git/signature_nogogit.go
index 25277f99d5..0d19c0abdc 100644
--- a/modules/git/signature_nogogit.go
+++ b/modules/git/signature_nogogit.go
@@ -7,21 +7,17 @@
package git
import (
- "bytes"
"fmt"
- "strconv"
- "strings"
"time"
+
+ "code.gitea.io/gitea/modules/util"
)
-// Signature represents the Author or Committer information.
+// Signature represents the Author, Committer or Tagger information.
type Signature struct {
- // Name represents a person name. It is an arbitrary string.
- Name string
- // Email is an email, but it cannot be assumed to be well-formed.
- Email string
- // When is the timestamp of the signature.
- When time.Time
+ Name string // the committer name, it can be anything
+ Email string // the committer email, it can be anything
+ When time.Time // the timestamp of the signature
}
func (s *Signature) String() string {
@@ -30,71 +26,5 @@ func (s *Signature) String() string {
// Decode decodes a byte array representing a signature to signature
func (s *Signature) Decode(b []byte) {
- sig, _ := newSignatureFromCommitline(b)
- s.Email = sig.Email
- s.Name = sig.Name
- s.When = sig.When
-}
-
-// Helper to get a signature from the commit line, which looks like these:
-//
-// author Patrick Gundlach 1378823654 +0200
-// author Patrick Gundlach Thu, 07 Apr 2005 22:13:13 +0200
-//
-// but without the "author " at the beginning (this method should)
-// be used for author and committer.
-// FIXME: there are a lot of "return sig, err" (but the err is also nil), that's the old behavior, to avoid breaking
-func newSignatureFromCommitline(line []byte) (sig *Signature, err error) {
- sig = new(Signature)
- emailStart := bytes.LastIndexByte(line, '<')
- emailEnd := bytes.LastIndexByte(line, '>')
- if emailStart == -1 || emailEnd == -1 || emailEnd < emailStart {
- return sig, err
- }
-
- if emailStart > 0 { // Empty name has already occurred, even if it shouldn't
- sig.Name = strings.TrimSpace(string(line[:emailStart-1]))
- }
- sig.Email = string(line[emailStart+1 : emailEnd])
-
- hasTime := emailEnd+2 < len(line)
- if !hasTime {
- return sig, err
- }
-
- // Check date format.
- firstChar := line[emailEnd+2]
- if firstChar >= 48 && firstChar <= 57 {
- idx := bytes.IndexByte(line[emailEnd+2:], ' ')
- if idx < 0 {
- return sig, err
- }
-
- timestring := string(line[emailEnd+2 : emailEnd+2+idx])
- seconds, _ := strconv.ParseInt(timestring, 10, 64)
- sig.When = time.Unix(seconds, 0)
-
- idx += emailEnd + 3
- if idx >= len(line) || idx+5 > len(line) {
- return sig, err
- }
-
- timezone := string(line[idx : idx+5])
- tzhours, err1 := strconv.ParseInt(timezone[0:3], 10, 64)
- tzmins, err2 := strconv.ParseInt(timezone[3:], 10, 64)
- if err1 != nil || err2 != nil {
- return sig, err
- }
- if tzhours < 0 {
- tzmins *= -1
- }
- tz := time.FixedZone("", int(tzhours*60*60+tzmins*60))
- sig.When = sig.When.In(tz)
- } else {
- sig.When, err = time.Parse(GitTimeLayout, string(line[emailEnd+2:]))
- if err != nil {
- return sig, err
- }
- }
- return sig, err
+ *s = *parseSignatureFromCommitLine(util.UnsafeBytesToString(b))
}
diff --git a/modules/git/signature_test.go b/modules/git/signature_test.go
new file mode 100644
index 0000000000..92681feea9
--- /dev/null
+++ b/modules/git/signature_test.go
@@ -0,0 +1,47 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParseSignatureFromCommitLine(t *testing.T) {
+ tests := []struct {
+ line string
+ want *Signature
+ }{
+ {
+ line: "a b 12345 +0100",
+ want: &Signature{
+ Name: "a b",
+ Email: "c@d.com",
+ When: time.Unix(12345, 0).In(time.FixedZone("", 3600)),
+ },
+ },
+ {
+ line: "bad line",
+ want: &Signature{Name: "bad line"},
+ },
+ {
+ line: "bad < line",
+ want: &Signature{Name: "bad < line"},
+ },
+ {
+ line: "bad > line",
+ want: &Signature{Name: "bad > line"},
+ },
+ {
+ line: "bad-line ",
+ want: &Signature{Name: "bad-line "},
+ },
+ }
+ for _, test := range tests {
+ got := parseSignatureFromCommitLine(test.line)
+ assert.EqualValues(t, test.want, got)
+ }
+}
diff --git a/modules/git/tag.go b/modules/git/tag.go
index 01a8d6f6a5..94e5cd7c63 100644
--- a/modules/git/tag.go
+++ b/modules/git/tag.go
@@ -7,6 +7,8 @@ import (
"bytes"
"sort"
"strings"
+
+ "code.gitea.io/gitea/modules/util"
)
const (
@@ -59,11 +61,7 @@ l:
// A commit can have one or more parents
tag.Type = string(line[spacepos+1:])
case "tagger":
- sig, err := newSignatureFromCommitline(line[spacepos+1:])
- if err != nil {
- return nil, err
- }
- tag.Tagger = sig
+ tag.Tagger = parseSignatureFromCommitLine(util.UnsafeBytesToString(line[spacepos+1:]))
}
nextline += eol + 1
case eol == 0:
diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go
index 9513121487..2c47c8858c 100644
--- a/modules/git/tree_entry.go
+++ b/modules/git/tree_entry.go
@@ -23,15 +23,15 @@ func (te *TreeEntry) Type() string {
}
// FollowLink returns the entry pointed to by a symlink
-func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
+func (te *TreeEntry) FollowLink() (*TreeEntry, string, error) {
if !te.IsLink() {
- return nil, ErrBadLink{te.Name(), "not a symlink"}
+ return nil, "", ErrBadLink{te.Name(), "not a symlink"}
}
// read the link
r, err := te.Blob().DataAsync()
if err != nil {
- return nil, err
+ return nil, "", err
}
closed := false
defer func() {
@@ -42,7 +42,7 @@ func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
buf := make([]byte, te.Size())
_, err = io.ReadFull(r, buf)
if err != nil {
- return nil, err
+ return nil, "", err
}
_ = r.Close()
closed = true
@@ -56,33 +56,35 @@ func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
}
if t == nil {
- return nil, ErrBadLink{te.Name(), "points outside of repo"}
+ return nil, "", ErrBadLink{te.Name(), "points outside of repo"}
}
target, err := t.GetTreeEntryByPath(lnk)
if err != nil {
if IsErrNotExist(err) {
- return nil, ErrBadLink{te.Name(), "broken link"}
+ return nil, "", ErrBadLink{te.Name(), "broken link"}
}
- return nil, err
+ return nil, "", err
}
- return target, nil
+ return target, lnk, nil
}
// FollowLinks returns the entry ultimately pointed to by a symlink
-func (te *TreeEntry) FollowLinks() (*TreeEntry, error) {
+func (te *TreeEntry) FollowLinks() (*TreeEntry, string, error) {
if !te.IsLink() {
- return nil, ErrBadLink{te.Name(), "not a symlink"}
+ return nil, "", ErrBadLink{te.Name(), "not a symlink"}
}
entry := te
+ entryLink := ""
for i := 0; i < 999; i++ {
if entry.IsLink() {
- next, err := entry.FollowLink()
+ next, link, err := entry.FollowLink()
+ entryLink = link
if err != nil {
- return nil, err
+ return nil, "", err
}
if next.ID == entry.ID {
- return nil, ErrBadLink{
+ return nil, "", ErrBadLink{
entry.Name(),
"recursive link",
}
@@ -93,12 +95,12 @@ func (te *TreeEntry) FollowLinks() (*TreeEntry, error) {
}
}
if entry.IsLink() {
- return nil, ErrBadLink{
+ return nil, "", ErrBadLink{
te.Name(),
"too many levels of symbolic links",
}
}
- return entry, nil
+ return entry, entryLink, nil
}
// returns the Tree pointed to by this TreeEntry, or nil if this is not a tree
diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go
index 89d3aebbc0..28d02c7e81 100644
--- a/modules/git/tree_nogogit.go
+++ b/modules/git/tree_nogogit.go
@@ -7,7 +7,6 @@ package git
import (
"io"
- "math"
"strings"
)
@@ -63,19 +62,8 @@ func (t *Tree) ListEntries() (Entries, error) {
}
// Not a tree just use ls-tree instead
- for sz > math.MaxInt32 {
- discarded, err := rd.Discard(math.MaxInt32)
- sz -= int64(discarded)
- if err != nil {
- return nil, err
- }
- }
- for sz > 0 {
- discarded, err := rd.Discard(int(sz))
- sz -= int64(discarded)
- if err != nil {
- return nil, err
- }
+ if err := DiscardFull(rd, sz+1); err != nil {
+ return nil, err
}
}
diff --git a/modules/git/tree_test.go b/modules/git/tree_test.go
new file mode 100644
index 0000000000..6d2b5c84d5
--- /dev/null
+++ b/modules/git/tree_test.go
@@ -0,0 +1,27 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSubTree_Issue29101(t *testing.T) {
+ repo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare"))
+ assert.NoError(t, err)
+ defer repo.Close()
+
+ commit, err := repo.GetCommit("ce064814f4a0d337b333e646ece456cd39fab612")
+ assert.NoError(t, err)
+
+ // old code could produce a different error if called multiple times
+ for i := 0; i < 10; i++ {
+ _, err = commit.SubTree("file1.txt")
+ assert.Error(t, err)
+ assert.True(t, IsErrNotExist(err))
+ }
+}
diff --git a/modules/git/utils_test.go b/modules/git/utils_test.go
new file mode 100644
index 0000000000..876a22924c
--- /dev/null
+++ b/modules/git/utils_test.go
@@ -0,0 +1,15 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import "testing"
+
+// This file contains utility functions that are used across multiple tests,
+// but not in production code.
+
+func skipIfSHA256NotSupported(t *testing.T) {
+ if isGogit || CheckGitVersionAtLeast("2.42") != nil {
+ t.Skip("skipping because installed Git version doesn't support SHA256")
+ }
+}
diff --git a/modules/indexer/code/elasticsearch/elasticsearch.go b/modules/indexer/code/elasticsearch/elasticsearch.go
index 2fadbfeb06..0f70f13485 100644
--- a/modules/indexer/code/elasticsearch/elasticsearch.go
+++ b/modules/indexer/code/elasticsearch/elasticsearch.go
@@ -180,11 +180,17 @@ func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha st
}
if len(reqs) > 0 {
- _, err := b.inner.Client.Bulk().
- Index(b.inner.VersionedIndexName()).
- Add(reqs...).
- Do(ctx)
- return err
+ esBatchSize := 50
+
+ for i := 0; i < len(reqs); i += esBatchSize {
+ _, err := b.inner.Client.Bulk().
+ Index(b.inner.VersionedIndexName()).
+ Add(reqs[i:min(i+esBatchSize, len(reqs))]...).
+ Do(ctx)
+ if err != nil {
+ return err
+ }
+ }
}
return nil
}
diff --git a/modules/indexer/internal/bleve/indexer.go b/modules/indexer/internal/bleve/indexer.go
index 531866ad0f..a168e0cb9b 100644
--- a/modules/indexer/internal/bleve/indexer.go
+++ b/modules/indexer/internal/bleve/indexer.go
@@ -91,7 +91,7 @@ func (i *Indexer) Ping(_ context.Context) error {
}
func (i *Indexer) Close() {
- if i == nil {
+ if i == nil || i.Indexer == nil {
return
}
diff --git a/modules/indexer/internal/meilisearch/indexer.go b/modules/indexer/internal/meilisearch/indexer.go
index b037249d43..f4004849c1 100644
--- a/modules/indexer/internal/meilisearch/indexer.go
+++ b/modules/indexer/internal/meilisearch/indexer.go
@@ -87,8 +87,5 @@ func (i *Indexer) Close() {
if i == nil {
return
}
- if i.Client == nil {
- return
- }
i.Client = nil
}
diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go
index da4fc9b878..3b96686d98 100644
--- a/modules/indexer/issues/indexer_test.go
+++ b/modules/indexer/issues/indexer_test.go
@@ -218,7 +218,7 @@ func searchIssueIsPull(t *testing.T) {
SearchOptions{
IsPull: util.OptionalBoolTrue,
},
- []int64{12, 11, 20, 19, 9, 8, 3, 2},
+ []int64{22, 21, 12, 11, 20, 19, 9, 8, 3, 2},
},
}
for _, test := range tests {
@@ -239,7 +239,7 @@ func searchIssueIsClosed(t *testing.T) {
SearchOptions{
IsClosed: util.OptionalBoolFalse,
},
- []int64{17, 16, 15, 14, 13, 12, 11, 20, 6, 19, 18, 10, 7, 9, 8, 3, 2, 1},
+ []int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 19, 18, 10, 7, 9, 8, 3, 2, 1},
},
{
SearchOptions{
@@ -305,7 +305,7 @@ func searchIssueByLabelID(t *testing.T) {
SearchOptions{
ExcludedLabelIDs: []int64{1},
},
- []int64{17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3},
+ []int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3},
},
}
for _, test := range tests {
@@ -329,7 +329,7 @@ func searchIssueByTime(t *testing.T) {
SearchOptions{
UpdatedAfterUnix: int64Pointer(0),
},
- []int64{17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1},
+ []int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1},
},
}
for _, test := range tests {
@@ -350,7 +350,7 @@ func searchIssueWithOrder(t *testing.T) {
SearchOptions{
SortBy: internal.SortByCreatedAsc,
},
- []int64{1, 2, 3, 8, 9, 4, 7, 10, 18, 19, 5, 6, 20, 11, 12, 13, 14, 15, 16, 17},
+ []int64{1, 2, 3, 8, 9, 4, 7, 10, 18, 19, 5, 6, 20, 11, 12, 13, 14, 15, 16, 17, 21, 22},
},
}
for _, test := range tests {
@@ -410,8 +410,8 @@ func searchIssueWithPaginator(t *testing.T) {
PageSize: 5,
},
},
- []int64{17, 16, 15, 14, 13},
- 20,
+ []int64{22, 21, 17, 16, 15},
+ 22,
},
}
for _, test := range tests {
diff --git a/modules/markup/html.go b/modules/markup/html.go
index 33dc1e9086..b7291823b5 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -804,7 +804,7 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
// indicate that in the text by appending (comment)
if m[4] != -1 && m[5] != -1 {
if locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale); ok {
- text += " " + locale.Tr("repo.from_comment")
+ text += " " + locale.TrString("repo.from_comment")
} else {
text += " (comment)"
}
diff --git a/modules/markup/markdown/ast.go b/modules/markup/markdown/ast.go
index 3e6e291ab2..72d32600f5 100644
--- a/modules/markup/markdown/ast.go
+++ b/modules/markup/markdown/ast.go
@@ -181,37 +181,3 @@ func IsColorPreview(node ast.Node) bool {
_, ok := node.(*ColorPreview)
return ok
}
-
-const (
- AttentionNote string = "Note"
- AttentionWarning string = "Warning"
-)
-
-// Attention is an inline for a color preview
-type Attention struct {
- ast.BaseInline
- AttentionType string
-}
-
-// Dump implements Node.Dump.
-func (n *Attention) Dump(source []byte, level int) {
- m := map[string]string{}
- m["AttentionType"] = n.AttentionType
- ast.DumpHelper(n, source, level, m, nil)
-}
-
-// KindAttention is the NodeKind for Attention
-var KindAttention = ast.NewNodeKind("Attention")
-
-// Kind implements Node.Kind.
-func (n *Attention) Kind() ast.NodeKind {
- return KindAttention
-}
-
-// NewAttention returns a new Attention node.
-func NewAttention(attentionType string) *Attention {
- return &Attention{
- BaseInline: ast.BaseInline{},
- AttentionType: attentionType,
- }
-}
diff --git a/modules/markup/markdown/callout/ast.go b/modules/markup/markdown/callout/ast.go
new file mode 100644
index 0000000000..a5b1bbc2a0
--- /dev/null
+++ b/modules/markup/markdown/callout/ast.go
@@ -0,0 +1,37 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package callout
+
+import (
+ "github.com/yuin/goldmark/ast"
+)
+
+// Attention is an inline for an attention
+type Attention struct {
+ ast.BaseInline
+ AttentionType string
+}
+
+// Dump implements Node.Dump.
+func (n *Attention) Dump(source []byte, level int) {
+ m := map[string]string{}
+ m["AttentionType"] = n.AttentionType
+ ast.DumpHelper(n, source, level, m, nil)
+}
+
+// KindAttention is the NodeKind for Attention
+var KindAttention = ast.NewNodeKind("Attention")
+
+// Kind implements Node.Kind.
+func (n *Attention) Kind() ast.NodeKind {
+ return KindAttention
+}
+
+// NewAttention returns a new Attention node.
+func NewAttention(attentionType string) *Attention {
+ return &Attention{
+ BaseInline: ast.BaseInline{},
+ AttentionType: attentionType,
+ }
+}
diff --git a/modules/markup/markdown/callout/github.go b/modules/markup/markdown/callout/github.go
new file mode 100644
index 0000000000..58f1fc960f
--- /dev/null
+++ b/modules/markup/markdown/callout/github.go
@@ -0,0 +1,142 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package callout
+
+import (
+ "strings"
+
+ "code.gitea.io/gitea/modules/svg"
+
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/renderer/html"
+ "github.com/yuin/goldmark/text"
+ "github.com/yuin/goldmark/util"
+)
+
+type GitHubCalloutTransformer struct{}
+
+// Transform transforms the given AST tree.
+func (g *GitHubCalloutTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
+ supportedAttentionTypes := map[string]bool{
+ "note": true,
+ "tip": true,
+ "important": true,
+ "warning": true,
+ "caution": true,
+ }
+
+ _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
+ }
+
+ switch v := n.(type) {
+ case *ast.Blockquote:
+ // We only want attention blockquotes when the AST looks like:
+ // Text: "["
+ // Text: "!TYPE"
+ // Text(SoftLineBreak): "]"
+
+ // grab these nodes and make sure we adhere to the attention blockquote structure
+ firstParagraph := v.FirstChild()
+ if firstParagraph.ChildCount() < 3 {
+ return ast.WalkContinue, nil
+ }
+ firstTextNode, ok := firstParagraph.FirstChild().(*ast.Text)
+ if !ok || string(firstTextNode.Text(reader.Source())) != "[" {
+ return ast.WalkContinue, nil
+ }
+ secondTextNode, ok := firstTextNode.NextSibling().(*ast.Text)
+ if !ok {
+ return ast.WalkContinue, nil
+ }
+ // If the second node's text isn't one of the supported attention
+ // types, continue walking.
+ secondTextNodeText := secondTextNode.Text(reader.Source())
+ attentionType := strings.ToLower(strings.TrimPrefix(string(secondTextNodeText), "!"))
+ if _, has := supportedAttentionTypes[attentionType]; !has {
+ return ast.WalkContinue, nil
+ }
+
+ thirdTextNode, ok := secondTextNode.NextSibling().(*ast.Text)
+ if !ok || string(thirdTextNode.Text(reader.Source())) != "]" {
+ return ast.WalkContinue, nil
+ }
+
+ // color the blockquote
+ v.SetAttributeString("class", []byte("gt-py-3 attention attention-"+attentionType))
+
+ // create an emphasis to make it bold
+ emphasis := ast.NewEmphasis(2)
+ emphasis.SetAttributeString("class", []byte("attention-"+attentionType))
+ firstParagraph.InsertBefore(firstParagraph, firstTextNode, emphasis)
+
+ // capitalize first letter
+ attentionText := ast.NewString([]byte(strings.ToUpper(string(attentionType[0])) + attentionType[1:]))
+
+ // replace the ![TYPE] with icon+Type
+ emphasis.AppendChild(emphasis, attentionText)
+ for i := 0; i < 2; i++ {
+ lineBreak := ast.NewText()
+ lineBreak.SetSoftLineBreak(true)
+ firstParagraph.InsertAfter(firstParagraph, emphasis, lineBreak)
+ }
+ firstParagraph.InsertBefore(firstParagraph, emphasis, NewAttention(attentionType))
+ firstParagraph.RemoveChild(firstParagraph, firstTextNode)
+ firstParagraph.RemoveChild(firstParagraph, secondTextNode)
+ firstParagraph.RemoveChild(firstParagraph, thirdTextNode)
+ }
+ return ast.WalkContinue, nil
+ })
+}
+
+type GitHubCalloutHTMLRenderer struct {
+ html.Config
+}
+
+// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
+func (r *GitHubCalloutHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(KindAttention, r.renderAttention)
+}
+
+// renderAttention renders a quote marked with i.e. "> **Note**" or "> **Warning**" with a corresponding svg
+func (r *GitHubCalloutHTMLRenderer) renderAttention(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+ if entering {
+ _, _ = w.WriteString(``)
+
+ var octiconType string
+ switch n.AttentionType {
+ case "note":
+ octiconType = "info"
+ case "tip":
+ octiconType = "light-bulb"
+ case "important":
+ octiconType = "report"
+ case "warning":
+ octiconType = "alert"
+ case "caution":
+ octiconType = "stop"
+ }
+ _, _ = w.WriteString(string(svg.RenderHTML("octicon-" + octiconType)))
+ } else {
+ _, _ = w.WriteString("\n")
+ }
+ return ast.WalkContinue, nil
+}
+
+func NewGitHubCalloutHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
+ r := &GitHubCalloutHTMLRenderer{
+ Config: html.NewConfig(),
+ }
+ for _, opt := range opts {
+ opt.SetHTMLOption(&r.Config)
+ }
+ return r
+}
diff --git a/modules/markup/markdown/callout/github_legacy.go b/modules/markup/markdown/callout/github_legacy.go
new file mode 100644
index 0000000000..add6b0a847
--- /dev/null
+++ b/modules/markup/markdown/callout/github_legacy.go
@@ -0,0 +1,60 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package callout
+
+import (
+ "strings"
+
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/text"
+)
+
+// Transformer for GitHub's legacy callout markup.
+type GitHubLegacyCalloutTransformer struct{}
+
+func (g *GitHubLegacyCalloutTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
+ supportedCalloutTypes := map[string]bool{"Note": true, "Warning": true}
+
+ _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
+ }
+
+ switch v := n.(type) {
+ case *ast.Blockquote:
+ // The first paragraph contains the callout type.
+ firstParagraph := v.FirstChild()
+ if firstParagraph.ChildCount() < 1 {
+ return ast.WalkContinue, nil
+ }
+
+ // In the legacy GitHub callout markup, the first node of the first
+ // paragraph should be an emphasis.
+ calloutNode, ok := firstParagraph.FirstChild().(*ast.Emphasis)
+ if !ok {
+ return ast.WalkContinue, nil
+ }
+ calloutText := string(calloutNode.Text(reader.Source()))
+ calloutType := strings.ToLower(calloutText)
+ // We only support "Note" and "Warning" callouts in legacy mode,
+ // match only those.
+ if _, has := supportedCalloutTypes[calloutText]; !has {
+ return ast.WalkContinue, nil
+ }
+
+ // Set the attention attribute on the emphasis
+ calloutNode.SetAttributeString("class", []byte("attention-"+calloutType))
+
+ // color the blockquote
+ v.SetAttributeString("class", []byte("gt-py-3 attention attention-"+calloutType))
+
+ // Prepend callout icon before the callout node itself
+ firstParagraph.InsertBefore(firstParagraph, calloutNode, NewAttention(calloutType))
+ }
+
+ return ast.WalkContinue, nil
+ })
+}
diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go
index 178e3d2fdd..7ada8b5548 100644
--- a/modules/markup/markdown/goldmark.go
+++ b/modules/markup/markdown/goldmark.go
@@ -7,13 +7,13 @@ import (
"bytes"
"fmt"
"regexp"
+ "slices"
"strings"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/common"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/svg"
giteautil "code.gitea.io/gitea/modules/util"
"github.com/microcosm-cc/bluemonday/css"
@@ -53,7 +53,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
}
}
- attentionMarkedBlockquotes := make(container.Set[*ast.Blockquote])
_ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
@@ -131,11 +130,17 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
case *ast.Link:
// Links need their href to munged to be a real value
link := v.Destination
- if len(link) > 0 && !markup.IsLink(link) &&
- link[0] != '#' && !bytes.HasPrefix(link, byteMailto) {
- // special case: this is not a link, a hash link or a mailto:, so it's a
- // relative URL
+ // Do not process the link if it's not a link, starts with an hashtag
+ // (indicating it's an anchor link), starts with `mailto:` or any of the
+ // custom markdown URLs.
+ processLink := len(link) > 0 && !markup.IsLink(link) &&
+ link[0] != '#' && !bytes.HasPrefix(link, byteMailto) &&
+ !slices.ContainsFunc(setting.Markdown.CustomURLSchemes, func(s string) bool {
+ return bytes.HasPrefix(link, []byte(s+":"))
+ })
+
+ if processLink {
var base string
if ctx.IsWiki {
base = ctx.Links.WikiLink()
@@ -197,18 +202,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
if css.ColorHandler(strings.ToLower(string(colorContent))) {
v.AppendChild(v, NewColorPreview(colorContent))
}
- case *ast.Emphasis:
- // check if inside blockquote for attention, expected hierarchy is
- // Emphasis < Paragraph < Blockquote
- blockquote, isInBlockquote := n.Parent().Parent().(*ast.Blockquote)
- if isInBlockquote && !attentionMarkedBlockquotes.Contains(blockquote) {
- fullText := string(n.Text(reader.Source()))
- if fullText == AttentionNote || fullText == AttentionWarning {
- v.SetAttributeString("class", []byte("attention-"+strings.ToLower(fullText)))
- v.Parent().InsertBefore(v.Parent(), v, NewAttention(fullText))
- attentionMarkedBlockquotes.Add(blockquote)
- }
- }
}
return ast.WalkContinue, nil
})
@@ -299,7 +292,6 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(KindSummary, r.renderSummary)
reg.Register(KindIcon, r.renderIcon)
reg.Register(ast.KindCodeSpan, r.renderCodeSpan)
- reg.Register(KindAttention, r.renderAttention)
reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
}
@@ -336,28 +328,6 @@ func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Nod
return ast.WalkContinue, nil
}
-// renderAttention renders a quote marked with i.e. "> **Note**" or "> **Warning**" with a corresponding svg
-func (r *HTMLRenderer) renderAttention(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
- if entering {
- _, _ = w.WriteString(``)
-
- var octiconType string
- switch n.AttentionType {
- case AttentionNote:
- octiconType = "info"
- case AttentionWarning:
- octiconType = "alert"
- }
- _, _ = w.WriteString(string(svg.RenderHTML("octicon-" + octiconType)))
- } else {
- _, _ = w.WriteString("\n")
- }
- return ast.WalkContinue, nil
-}
-
func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*ast.Document)
diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go
index 771162b9a3..00d01a2f55 100644
--- a/modules/markup/markdown/markdown.go
+++ b/modules/markup/markdown/markdown.go
@@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/common"
+ "code.gitea.io/gitea/modules/markup/markdown/callout"
"code.gitea.io/gitea/modules/markup/markdown/math"
"code.gitea.io/gitea/modules/setting"
giteautil "code.gitea.io/gitea/modules/util"
@@ -124,6 +125,8 @@ func SpecializedMarkdown() goldmark.Markdown {
parser.WithAttribute(),
parser.WithAutoHeadingID(),
parser.WithASTTransformers(
+ util.Prioritized(&callout.GitHubLegacyCalloutTransformer{}, 8000),
+ util.Prioritized(&callout.GitHubCalloutTransformer{}, 9000),
util.Prioritized(&ASTTransformer{}, 10000),
),
),
@@ -135,6 +138,7 @@ func SpecializedMarkdown() goldmark.Markdown {
// Override the original Tasklist renderer!
specMarkdown.Renderer().AddOptions(
renderer.WithNodeRenderers(
+ util.Prioritized(callout.NewGitHubCalloutHTMLRenderer(), 10),
util.Prioritized(NewHTMLRenderer(), 10),
),
)
diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
index 61167dd0b7..1be8c6a273 100644
--- a/modules/markup/markdown/markdown_test.go
+++ b/modules/markup/markdown/markdown_test.go
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
@@ -1170,3 +1171,29 @@ space
assert.Equal(t, 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,
+ BranchPath: "branch/main",
+ },
+ }, input)
+ assert.NoError(t, err)
+ assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
+ }
+
+ test("[test](abp:subscribe?location=https://codeberg.org/filters.txt&title=joy)",
+ `test
`)
+
+ // Ensure that the schema itself without `:` is still made absolute.
+ test("[test](abp)",
+ `test
`)
+}
diff --git a/modules/markup/markdown/toc.go b/modules/markup/markdown/toc.go
index 9602040931..38f744a25f 100644
--- a/modules/markup/markdown/toc.go
+++ b/modules/markup/markdown/toc.go
@@ -21,7 +21,7 @@ func createTOCNode(toc []markup.Header, lang string, detailsAttrs map[string]str
details.SetAttributeString(k, []byte(v))
}
- summary.AppendChild(summary, ast.NewString([]byte(translation.NewLocale(lang).Tr("toc"))))
+ summary.AppendChild(summary, ast.NewString([]byte(translation.NewLocale(lang).TrString("toc"))))
details.AppendChild(details, summary)
ul := ast.NewList('-')
details.AppendChild(details, ul)
diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go
index e0c9b13a9e..8efe4e395d 100644
--- a/modules/markup/orgmode/orgmode.go
+++ b/modules/markup/orgmode/orgmode.go
@@ -147,11 +147,21 @@ func (r *Writer) resolveLink(node org.Node) string {
}
if len(link) > 0 && !markup.IsLinkStr(link) &&
link[0] != '#' && !strings.HasPrefix(link, mailto) {
- base := r.Ctx.Links.Base
+
+ var base string
+ if r.Ctx.IsWiki {
+ base = r.Ctx.Links.WikiLink()
+ } else if r.Ctx.Links.HasBranchInfo() {
+ base = r.Ctx.Links.SrcLink()
+ } else {
+ base = r.Ctx.Links.Base
+ }
+
switch l.Kind() {
case "image", "video":
base = r.Ctx.Links.ResolveMediaLink(r.Ctx.IsWiki)
}
+
link = util.URLJoin(base, link)
}
return link
diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go
index e963718883..5ced819984 100644
--- a/modules/markup/orgmode/orgmode_test.go
+++ b/modules/markup/orgmode/orgmode_test.go
@@ -36,6 +36,10 @@ func TestRender_StandardLinks(t *testing.T) {
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
}
+ // No BranchPath or TreePath set.
+ test("[[file:comfy][comfy]]",
+ `comfy
`)
+
test("[[https://google.com/]]",
`https://google.com/
`)
@@ -44,6 +48,46 @@ func TestRender_StandardLinks(t *testing.T) {
`WikiPage
`)
}
+func TestRender_BaseLinks(t *testing.T) {
+ setting.AppURL = AppURL
+ setting.AppSubURL = AppSubURL
+
+ testBranch := func(input, expected string) {
+ buffer, err := RenderString(&markup.RenderContext{
+ Ctx: git.DefaultContext,
+ Links: markup.Links{
+ Base: setting.AppSubURL,
+ BranchPath: "branch/main",
+ },
+ }, input)
+ assert.NoError(t, err)
+ assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
+ }
+
+ testBranchTree := func(input, expected string) {
+ buffer, err := RenderString(&markup.RenderContext{
+ Ctx: git.DefaultContext,
+ Links: markup.Links{
+ Base: setting.AppSubURL,
+ BranchPath: "branch/main",
+ TreePath: "deep/nested/folder",
+ },
+ }, input)
+ assert.NoError(t, err)
+ assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
+ }
+
+ testBranch("[[file:comfy][comfy]]",
+ `comfy
`)
+ testBranchTree("[[file:comfy][comfy]]",
+ `comfy
`)
+
+ testBranch("[[file:./src][./src/]]",
+ `./src/
`)
+ testBranchTree("[[file:./src][./src/]]",
+ `./src/
`)
+}
+
func TestRender_Media(t *testing.T) {
setting.AppURL = AppURL
setting.AppSubURL = AppSubURL
diff --git a/modules/markup/sanitizer.go b/modules/markup/sanitizer.go
index 992e85b989..ffc33c3b8e 100644
--- a/modules/markup/sanitizer.go
+++ b/modules/markup/sanitizer.go
@@ -64,9 +64,10 @@ func createDefaultPolicy() *bluemonday.Policy {
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^color-preview$`)).OnElements("span")
// For attention
+ policy.AllowAttrs("class").Matching(regexp.MustCompile(`^gt-py-3 attention attention-\w+$`)).OnElements("blockquote")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^attention-\w+$`)).OnElements("strong")
- policy.AllowAttrs("class").Matching(regexp.MustCompile(`^attention-icon attention-\w+$`)).OnElements("span", "strong")
- policy.AllowAttrs("class").Matching(regexp.MustCompile(`^svg octicon-\w+$`)).OnElements("svg")
+ policy.AllowAttrs("class").Matching(regexp.MustCompile(`^gt-mr-2 gt-vm attention-\w+$`)).OnElements("span", "strong")
+ policy.AllowAttrs("class").Matching(regexp.MustCompile(`^svg octicon-(\w|-)+$`)).OnElements("svg")
policy.AllowAttrs("viewBox", "width", "height", "aria-hidden").OnElements("svg")
policy.AllowAttrs("fill-rule", "d").OnElements("path")
diff --git a/modules/migration/messenger.go b/modules/migration/messenger.go
index 924aac9769..6f9cad3f10 100644
--- a/modules/migration/messenger.go
+++ b/modules/migration/messenger.go
@@ -3,7 +3,7 @@
package migration
-// Messenger is a formatting function similar to i18n.Tr
+// Messenger is a formatting function similar to i18n.TrString
type Messenger func(key string, args ...any)
// NilMessenger represents an empty formatting function
diff --git a/modules/optional/option_test.go b/modules/optional/option_test.go
index 7ec345b6ba..410fd73577 100644
--- a/modules/optional/option_test.go
+++ b/modules/optional/option_test.go
@@ -1,48 +1,49 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package optional
+package optional_test
import (
"testing"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert"
)
func TestOption(t *testing.T) {
- var uninitialized Option[int]
+ var uninitialized optional.Option[int]
assert.False(t, uninitialized.Has())
assert.Equal(t, int(0), uninitialized.Value())
assert.Equal(t, int(1), uninitialized.ValueOrDefault(1))
- none := None[int]()
+ none := optional.None[int]()
assert.False(t, none.Has())
assert.Equal(t, int(0), none.Value())
assert.Equal(t, int(1), none.ValueOrDefault(1))
- some := Some[int](1)
+ some := optional.Some[int](1)
assert.True(t, some.Has())
assert.Equal(t, int(1), some.Value())
assert.Equal(t, int(1), some.ValueOrDefault(2))
var ptr *int
- assert.False(t, FromPtr(ptr).Has())
+ assert.False(t, optional.FromPtr(ptr).Has())
- opt1 := FromPtr(util.ToPointer(1))
+ int1 := 1
+ opt1 := optional.FromPtr(&int1)
assert.True(t, opt1.Has())
assert.Equal(t, int(1), opt1.Value())
- assert.False(t, FromNonDefault("").Has())
+ assert.False(t, optional.FromNonDefault("").Has())
- opt2 := FromNonDefault("test")
+ opt2 := optional.FromNonDefault("test")
assert.True(t, opt2.Has())
assert.Equal(t, "test", opt2.Value())
- assert.False(t, FromNonDefault(0).Has())
+ assert.False(t, optional.FromNonDefault(0).Has())
- opt3 := FromNonDefault(1)
+ opt3 := optional.FromNonDefault(1)
assert.True(t, opt3.Has())
assert.Equal(t, int(1), opt3.Value())
}
diff --git a/modules/optional/serialization.go b/modules/optional/serialization.go
new file mode 100644
index 0000000000..6688e78cd1
--- /dev/null
+++ b/modules/optional/serialization.go
@@ -0,0 +1,46 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package optional
+
+import (
+ "code.gitea.io/gitea/modules/json"
+
+ "gopkg.in/yaml.v3"
+)
+
+func (o *Option[T]) UnmarshalJSON(data []byte) error {
+ var v *T
+ if err := json.Unmarshal(data, &v); err != nil {
+ return err
+ }
+ *o = FromPtr(v)
+ return nil
+}
+
+func (o Option[T]) MarshalJSON() ([]byte, error) {
+ if !o.Has() {
+ return []byte("null"), nil
+ }
+
+ return json.Marshal(o.Value())
+}
+
+func (o *Option[T]) UnmarshalYAML(value *yaml.Node) error {
+ var v *T
+ if err := value.Decode(&v); err != nil {
+ return err
+ }
+ *o = FromPtr(v)
+ return nil
+}
+
+func (o Option[T]) MarshalYAML() (interface{}, error) {
+ if !o.Has() {
+ return nil, nil
+ }
+
+ value := new(yaml.Node)
+ err := value.Encode(o.Value())
+ return value, err
+}
diff --git a/modules/optional/serialization_test.go b/modules/optional/serialization_test.go
new file mode 100644
index 0000000000..09a4bddea0
--- /dev/null
+++ b/modules/optional/serialization_test.go
@@ -0,0 +1,190 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package optional_test
+
+import (
+ std_json "encoding/json" //nolint:depguard
+ "testing"
+
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/optional"
+
+ "github.com/stretchr/testify/assert"
+ "gopkg.in/yaml.v3"
+)
+
+type testSerializationStruct struct {
+ NormalString string `json:"normal_string" yaml:"normal_string"`
+ NormalBool bool `json:"normal_bool" yaml:"normal_bool"`
+ OptBool optional.Option[bool] `json:"optional_bool,omitempty" yaml:"optional_bool,omitempty"`
+ OptString optional.Option[string] `json:"optional_string,omitempty" yaml:"optional_string,omitempty"`
+ OptTwoBool optional.Option[bool] `json:"optional_two_bool" yaml:"optional_two_bool"`
+ OptTwoString optional.Option[string] `json:"optional_twostring" yaml:"optional_two_string"`
+}
+
+func TestOptionalToJson(t *testing.T) {
+ tests := []struct {
+ name string
+ obj *testSerializationStruct
+ want string
+ }{
+ {
+ name: "empty",
+ obj: new(testSerializationStruct),
+ want: `{"normal_string":"","normal_bool":false,"optional_two_bool":null,"optional_twostring":null}`,
+ },
+ {
+ name: "some",
+ obj: &testSerializationStruct{
+ NormalString: "a string",
+ NormalBool: true,
+ OptBool: optional.Some(false),
+ OptString: optional.Some(""),
+ OptTwoBool: optional.None[bool](),
+ OptTwoString: optional.None[string](),
+ },
+ want: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_twostring":null}`,
+ },
+ }
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ b, err := json.Marshal(tc.obj)
+ assert.NoError(t, err)
+ assert.EqualValues(t, tc.want, string(b), "gitea json module returned unexpected")
+
+ b, err = std_json.Marshal(tc.obj)
+ assert.NoError(t, err)
+ assert.EqualValues(t, tc.want, string(b), "std json module returned unexpected")
+ })
+ }
+}
+
+func TestOptionalFromJson(t *testing.T) {
+ tests := []struct {
+ name string
+ data string
+ want testSerializationStruct
+ }{
+ {
+ name: "empty",
+ data: `{}`,
+ want: testSerializationStruct{
+ NormalString: "",
+ },
+ },
+ {
+ name: "some",
+ data: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_twostring":null}`,
+ want: testSerializationStruct{
+ NormalString: "a string",
+ NormalBool: true,
+ OptBool: optional.Some(false),
+ OptString: optional.Some(""),
+ },
+ },
+ }
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ var obj1 testSerializationStruct
+ err := json.Unmarshal([]byte(tc.data), &obj1)
+ assert.NoError(t, err)
+ assert.EqualValues(t, tc.want, obj1, "gitea json module returned unexpected")
+
+ var obj2 testSerializationStruct
+ err = std_json.Unmarshal([]byte(tc.data), &obj2)
+ assert.NoError(t, err)
+ assert.EqualValues(t, tc.want, obj2, "std json module returned unexpected")
+ })
+ }
+}
+
+func TestOptionalToYaml(t *testing.T) {
+ tests := []struct {
+ name string
+ obj *testSerializationStruct
+ want string
+ }{
+ {
+ name: "empty",
+ obj: new(testSerializationStruct),
+ want: `normal_string: ""
+normal_bool: false
+optional_two_bool: null
+optional_two_string: null
+`,
+ },
+ {
+ name: "some",
+ obj: &testSerializationStruct{
+ NormalString: "a string",
+ NormalBool: true,
+ OptBool: optional.Some(false),
+ OptString: optional.Some(""),
+ },
+ want: `normal_string: a string
+normal_bool: true
+optional_bool: false
+optional_string: ""
+optional_two_bool: null
+optional_two_string: null
+`,
+ },
+ }
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ b, err := yaml.Marshal(tc.obj)
+ assert.NoError(t, err)
+ assert.EqualValues(t, tc.want, string(b), "yaml module returned unexpected")
+ })
+ }
+}
+
+func TestOptionalFromYaml(t *testing.T) {
+ tests := []struct {
+ name string
+ data string
+ want testSerializationStruct
+ }{
+ {
+ name: "empty",
+ data: ``,
+ want: testSerializationStruct{},
+ },
+ {
+ name: "empty but init",
+ data: `normal_string: ""
+normal_bool: false
+optional_bool:
+optional_two_bool:
+optional_two_string:
+`,
+ want: testSerializationStruct{},
+ },
+ {
+ name: "some",
+ data: `
+normal_string: a string
+normal_bool: true
+optional_bool: false
+optional_string: ""
+optional_two_bool: null
+optional_twostring: null
+`,
+ want: testSerializationStruct{
+ NormalString: "a string",
+ NormalBool: true,
+ OptBool: optional.Some(false),
+ OptString: optional.Some(""),
+ },
+ },
+ }
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ var obj testSerializationStruct
+ err := yaml.Unmarshal([]byte(tc.data), &obj)
+ assert.NoError(t, err)
+ assert.EqualValues(t, tc.want, obj, "yaml module returned unexpected")
+ })
+ }
+}
diff --git a/modules/references/references.go b/modules/references/references.go
index 7758312564..fce893cf5b 100644
--- a/modules/references/references.go
+++ b/modules/references/references.go
@@ -29,7 +29,7 @@ var (
// TODO: fix invalid linking issue
// 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|[:,;.?!]?$|\)|\])`)
+ 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|[:;,.?!]$)`)
// issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234
diff --git a/modules/references/references_test.go b/modules/references/references_test.go
index ba7dda80cc..3f1f52401a 100644
--- a/modules/references/references_test.go
+++ b/modules/references/references_test.go
@@ -392,6 +392,9 @@ func TestRegExp_mentionPattern(t *testing.T) {
{"@gitea,", "@gitea"},
{"@gitea;", "@gitea"},
{"@gitea/team1;", "@gitea/team1"},
+ {"@jess'", "@jess"},
+ {"@forgejo's", "@forgejo"},
+ {"Оно сломалось из-за коммитов от @jopik'а", "@jopik"},
}
falseTestCases := []string{
"@ 0",
diff --git a/modules/repository/create.go b/modules/repository/create.go
index 7c954a1412..ca2150b972 100644
--- a/modules/repository/create.go
+++ b/modules/repository/create.go
@@ -87,7 +87,11 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re
units = append(units, repo_model.RepoUnit{
RepoID: repo.ID,
Type: tp,
- Config: &repo_model.PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle), AllowRebaseUpdate: true},
+ Config: &repo_model.PullRequestsConfig{
+ AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, AllowFastForwardOnly: true,
+ DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle),
+ AllowRebaseUpdate: true,
+ },
})
} else {
units = append(units, repo_model.RepoUnit{
diff --git a/modules/repository/generate.go b/modules/repository/generate.go
index 013dd8f76f..f622383bb5 100644
--- a/modules/repository/generate.go
+++ b/modules/repository/generate.go
@@ -94,7 +94,7 @@ type GiteaTemplate struct {
}
// Globs parses the .gitea/template globs or returns them if they were already parsed
-func (gt GiteaTemplate) Globs() []glob.Glob {
+func (gt *GiteaTemplate) Globs() []glob.Glob {
if gt.globs != nil {
return gt.globs
}
diff --git a/modules/repository/hooks.go b/modules/repository/hooks.go
index daab7c3091..95849789ab 100644
--- a/modules/repository/hooks.go
+++ b/modules/repository/hooks.go
@@ -9,7 +9,6 @@ import (
"path/filepath"
"runtime"
- "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
@@ -94,15 +93,14 @@ done
`, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),
}
- if git.SupportProcReceive {
- hookNames = append(hookNames, "proc-receive")
- hookTpls = append(hookTpls,
- fmt.Sprintf(`#!/usr/bin/env %s
+ // although only new git (>=2.29) supports proc-receive, it's still good to create its hook, in case the user upgrades git
+ hookNames = append(hookNames, "proc-receive")
+ hookTpls = append(hookTpls,
+ fmt.Sprintf(`#!/usr/bin/env %s
# AUTO GENERATED BY GITEA, DO NOT MODIFY
%s hook --config=%s proc-receive
`, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)))
- giteaHookTpls = append(giteaHookTpls, "")
- }
+ giteaHookTpls = append(giteaHookTpls, "")
return hookNames, hookTpls, giteaHookTpls
}
diff --git a/modules/repository/repo.go b/modules/repository/repo.go
index 65b50b2e45..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)
@@ -376,7 +110,9 @@ func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitR
}
if err := PushUpdateAddTag(ctx, repo, gitRepo, tagName, sha1, refname); err != nil {
- return fmt.Errorf("unable to PushUpdateAddTag: %q to Repo[%d:%s/%s]: %w", tagName, repo.ID, repo.OwnerName, repo.Name, err)
+ // sometimes, some tags will be sync failed. i.e. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tag/?h=v2.6.11
+ // this is a tree object, not a tag object which created before git
+ log.Error("unable to PushUpdateAddTag: %q to Repo[%d:%s/%s]: %v", tagName, repo.ID, repo.OwnerName, repo.Name, err)
}
return nil
diff --git a/modules/setting/admin.go b/modules/setting/admin.go
index d7f0ee827d..502efd0eb9 100644
--- a/modules/setting/admin.go
+++ b/modules/setting/admin.go
@@ -3,15 +3,23 @@
package setting
+import "code.gitea.io/gitea/modules/container"
+
// Admin settings
var Admin struct {
DisableRegularOrgCreation bool
DefaultEmailNotification string
SendNotificationEmailOnNewUser bool
+ UserDisabledFeatures container.Set[string]
}
func loadAdminFrom(rootCfg ConfigProvider) {
- mustMapSetting(rootCfg, "admin", &Admin)
sec := rootCfg.Section("admin")
+ Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false)
Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled")
+ Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...)
}
+
+const (
+ UserFeatureDeletion = "deletion"
+)
diff --git a/modules/setting/config_provider.go b/modules/setting/config_provider.go
index e1c25ed9c7..12cf36aa59 100644
--- a/modules/setting/config_provider.go
+++ b/modules/setting/config_provider.go
@@ -197,7 +197,7 @@ func NewConfigProviderFromData(configContent string) (ConfigProvider, error) {
// NewConfigProviderFromFile load configuration from file.
// NOTE: do not print any log except error.
-func NewConfigProviderFromFile(file string, extraConfigs ...string) (ConfigProvider, error) {
+func NewConfigProviderFromFile(file string) (ConfigProvider, error) {
cfg := ini.Empty(configProviderLoadOptions())
loadedFromEmpty := true
@@ -214,12 +214,6 @@ func NewConfigProviderFromFile(file string, extraConfigs ...string) (ConfigProvi
}
}
- for _, s := range extraConfigs {
- if err := cfg.Append([]byte(s)); err != nil {
- return nil, fmt.Errorf("unable to append more config: %v", err)
- }
- }
-
cfg.NameMapper = ini.SnackCase
return &iniConfigProvider{
file: file,
diff --git a/modules/setting/database.go b/modules/setting/database.go
index c7bc92e673..47d79d0de9 100644
--- a/modules/setting/database.go
+++ b/modules/setting/database.go
@@ -42,6 +42,7 @@ var (
DBConnectBackoff time.Duration
MaxIdleConns int
MaxOpenConns int
+ ConnMaxIdleTime time.Duration
ConnMaxLifetime time.Duration
IterateBufferSize int
AutoMigration bool
@@ -81,6 +82,7 @@ func loadDBSetting(rootCfg ConfigProvider) {
} else {
Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFETIME").MustDuration(0)
}
+ Database.ConnMaxIdleTime = sec.Key("CONN_MAX_IDLETIME").MustDuration(0)
Database.MaxOpenConns = sec.Key("MAX_OPEN_CONNS").MustInt(0)
Database.IterateBufferSize = sec.Key("ITERATE_BUFFER_SIZE").MustInt(50)
diff --git a/modules/setting/lfs.go b/modules/setting/lfs.go
index 7ab90669e7..750101747f 100644
--- a/modules/setting/lfs.go
+++ b/modules/setting/lfs.go
@@ -4,22 +4,19 @@
package setting
import (
- "encoding/base64"
"fmt"
"time"
"code.gitea.io/gitea/modules/generate"
- "code.gitea.io/gitea/modules/util"
)
// LFS represents the configuration for Git LFS
var LFS = struct {
- StartServer bool `ini:"LFS_START_SERVER"`
- JWTSecretBase64 string `ini:"LFS_JWT_SECRET"`
- JWTSecretBytes []byte `ini:"-"`
- HTTPAuthExpiry time.Duration `ini:"LFS_HTTP_AUTH_EXPIRY"`
- MaxFileSize int64 `ini:"LFS_MAX_FILE_SIZE"`
- LocksPagingNum int `ini:"LFS_LOCKS_PAGING_NUM"`
+ StartServer bool `ini:"LFS_START_SERVER"`
+ JWTSecretBytes []byte `ini:"-"`
+ HTTPAuthExpiry time.Duration `ini:"LFS_HTTP_AUTH_EXPIRY"`
+ MaxFileSize int64 `ini:"LFS_MAX_FILE_SIZE"`
+ LocksPagingNum int `ini:"LFS_LOCKS_PAGING_NUM"`
Storage *Storage
}{}
@@ -61,10 +58,10 @@ func loadLFSFrom(rootCfg ConfigProvider) error {
return nil
}
- LFS.JWTSecretBase64 = loadSecret(rootCfg.Section("server"), "LFS_JWT_SECRET_URI", "LFS_JWT_SECRET")
- LFS.JWTSecretBytes, err = util.Base64FixedDecode(base64.RawURLEncoding, []byte(LFS.JWTSecretBase64), 32)
+ jwtSecretBase64 := loadSecret(rootCfg.Section("server"), "LFS_JWT_SECRET_URI", "LFS_JWT_SECRET")
+ LFS.JWTSecretBytes, err = generate.DecodeJwtSecret(jwtSecretBase64)
if err != nil {
- LFS.JWTSecretBytes, LFS.JWTSecretBase64, err = generate.NewJwtSecret()
+ LFS.JWTSecretBytes, jwtSecretBase64, err = generate.NewJwtSecret()
if err != nil {
return fmt.Errorf("error generating JWT Secret for custom config: %v", err)
}
@@ -74,8 +71,8 @@ func loadLFSFrom(rootCfg ConfigProvider) error {
if err != nil {
return fmt.Errorf("error saving JWT Secret for custom config: %v", err)
}
- rootCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
- saveCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
+ rootCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(jwtSecretBase64)
+ saveCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(jwtSecretBase64)
if err := saveCfg.Save(); err != nil {
return fmt.Errorf("error saving JWT Secret for custom config: %v", err)
}
diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go
index e93ce188df..d3c4d5c387 100644
--- a/modules/setting/oauth2.go
+++ b/modules/setting/oauth2.go
@@ -4,13 +4,12 @@
package setting
import (
- "encoding/base64"
"math"
"path/filepath"
+ "sync/atomic"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/util"
)
// OAuth2UsernameType is enum describing the way gitea 'name' should be generated from oauth2 data
@@ -98,7 +97,6 @@ var OAuth2 = struct {
RefreshTokenExpirationTime int64
InvalidateRefreshTokens bool
JWTSigningAlgorithm string `ini:"JWT_SIGNING_ALGORITHM"`
- JWTSecretBase64 string `ini:"JWT_SECRET"`
JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"`
MaxTokenLength int
DefaultApplications []string
@@ -130,28 +128,50 @@ func loadOAuth2From(rootCfg ConfigProvider) {
return
}
- OAuth2.JWTSecretBase64 = loadSecret(sec, "JWT_SECRET_URI", "JWT_SECRET")
+ jwtSecretBase64 := loadSecret(sec, "JWT_SECRET_URI", "JWT_SECRET")
if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) {
OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile)
}
if InstallLock {
- if _, err := util.Base64FixedDecode(base64.RawURLEncoding, []byte(OAuth2.JWTSecretBase64), 32); err != nil {
- _, OAuth2.JWTSecretBase64, err = generate.NewJwtSecret()
+ jwtSecretBytes, err := generate.DecodeJwtSecret(jwtSecretBase64)
+ if err != nil {
+ jwtSecretBytes, jwtSecretBase64, err = generate.NewJwtSecret()
if err != nil {
log.Fatal("error generating JWT secret: %v", err)
}
-
saveCfg, err := rootCfg.PrepareSaving()
if err != nil {
log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
}
- rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(OAuth2.JWTSecretBase64)
- saveCfg.Section("oauth2").Key("JWT_SECRET").SetValue(OAuth2.JWTSecretBase64)
+ rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(jwtSecretBase64)
+ saveCfg.Section("oauth2").Key("JWT_SECRET").SetValue(jwtSecretBase64)
if err := saveCfg.Save(); err != nil {
log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
}
}
+ generalSigningSecret.Store(&jwtSecretBytes)
}
}
+
+// generalSigningSecret is used as container for a []byte value
+// instead of an additional mutex, we use CompareAndSwap func to change the value thread save
+var generalSigningSecret atomic.Pointer[[]byte]
+
+func GetGeneralTokenSigningSecret() []byte {
+ old := generalSigningSecret.Load()
+ if old == nil || len(*old) == 0 {
+ jwtSecret, _, err := generate.NewJwtSecret()
+ if err != nil {
+ log.Fatal("Unable to generate general JWT secret: %s", err.Error())
+ }
+ if generalSigningSecret.CompareAndSwap(old, &jwtSecret) {
+ // FIXME: in main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...)
+ log.Warn("OAuth2 is not enabled, unable to use a persistent signing secret, a new one is generated, which is not persistent between restarts and cluster nodes")
+ return jwtSecret
+ }
+ return *generalSigningSecret.Load()
+ }
+ return *old
+}
diff --git a/modules/setting/oauth2_test.go b/modules/setting/oauth2_test.go
new file mode 100644
index 0000000000..da36d100aa
--- /dev/null
+++ b/modules/setting/oauth2_test.go
@@ -0,0 +1,34 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package setting
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/modules/generate"
+ "code.gitea.io/gitea/modules/test"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGetGeneralSigningSecret(t *testing.T) {
+ // when there is no general signing secret, it should be generated, and keep the same value
+ assert.Nil(t, generalSigningSecret.Load())
+ s1 := GetGeneralTokenSigningSecret()
+ assert.NotNil(t, s1)
+ s2 := GetGeneralTokenSigningSecret()
+ assert.Equal(t, s1, s2)
+
+ // the config value should always override any pre-generated value
+ cfg, _ := NewConfigProviderFromData(`
+[oauth2]
+JWT_SECRET = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
+`)
+ defer test.MockVariableValue(&InstallLock, true)()
+ loadOAuth2From(cfg)
+ actual := GetGeneralTokenSigningSecret()
+ expected, _ := generate.DecodeJwtSecret("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB")
+ assert.Len(t, actual, 32)
+ assert.EqualValues(t, expected, actual)
+}
diff --git a/modules/setting/repository.go b/modules/setting/repository.go
index 4ab566b7ff..7a07fec85c 100644
--- a/modules/setting/repository.go
+++ b/modules/setting/repository.go
@@ -49,7 +49,8 @@ var (
DownloadOrCloneMethods []string
PrefixArchiveFiles bool
DisableMigrations bool
- DisableStars bool `ini:"DISABLE_STARS"`
+ DisableStars bool
+ DisableForks bool
DefaultBranch string
AllowAdoptionOfUnadoptedRepositories bool
AllowDeleteOfUnadoptedRepositories bool
@@ -172,6 +173,7 @@ var (
PrefixArchiveFiles: true,
DisableMigrations: false,
DisableStars: false,
+ DisableForks: false,
DefaultBranch: "main",
AllowForkWithoutMaximumLimit: true,
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index c0d8d0ee23..87e7b08c6e 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -92,9 +92,9 @@ func PrepareAppDataPath() error {
return nil
}
-func InitCfgProvider(file string, extraConfigs ...string) {
+func InitCfgProvider(file string) {
var err error
- if CfgProvider, err = NewConfigProviderFromFile(file, extraConfigs...); err != nil {
+ if CfgProvider, err = NewConfigProviderFromFile(file); err != nil {
log.Fatal("Unable to init config provider from %q: %v", file, err)
}
CfgProvider.DisableSaving() // do not allow saving the CfgProvider into file, it will be polluted by the "MustXxx" calls
diff --git a/modules/setting/ui.go b/modules/setting/ui.go
index 02a213d478..47e1393ef3 100644
--- a/modules/setting/ui.go
+++ b/modules/setting/ui.go
@@ -38,6 +38,7 @@ var UI = struct {
PreferredTimestampTense string
AmbiguousUnicodeDetection bool
+ SkipEscapeContexts []string
Notification struct {
MinTimeout time.Duration
@@ -89,6 +90,7 @@ var UI = struct {
PreferredTimestampTense: "mixed",
AmbiguousUnicodeDetection: true,
+ SkipEscapeContexts: []string{},
Notification: struct {
MinTimeout time.Duration
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index e20b6bc26e..a50cddaf7e 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -99,6 +99,7 @@ type Repository struct {
AllowRebase bool `json:"allow_rebase"`
AllowRebaseMerge bool `json:"allow_rebase_explicit"`
AllowSquash bool `json:"allow_squash_merge"`
+ AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge"`
AllowRebaseUpdate bool `json:"allow_rebase_update"`
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"`
DefaultMergeStyle string `json:"default_merge_style"`
@@ -198,6 +199,8 @@ type EditRepoOption struct {
AllowRebaseMerge *bool `json:"allow_rebase_explicit,omitempty"`
// either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging.
AllowSquash *bool `json:"allow_squash_merge,omitempty"`
+ // either `true` to allow fast-forward-only merging pull requests, or `false` to prevent fast-forward-only merging.
+ AllowFastForwardOnly *bool `json:"allow_fast_forward_only_merge,omitempty"`
// either `true` to allow mark pr as merged manually, or `false` to prevent it.
AllowManualMerge *bool `json:"allow_manual_merge,omitempty"`
// either `true` to enable AutodetectManualMerge, or `false` to prevent it. Note: In some special cases, misjudgments can occur.
@@ -206,7 +209,7 @@ type EditRepoOption struct {
AllowRebaseUpdate *bool `json:"allow_rebase_update,omitempty"`
// set to `true` to delete pr branch after merge by default
DefaultDeleteBranchAfterMerge *bool `json:"default_delete_branch_after_merge,omitempty"`
- // set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", or "squash".
+ // set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", "squash", or "fast-forward-only".
DefaultMergeStyle *string `json:"default_merge_style,omitempty"`
// set to `true` to allow edits from maintainers by default
DefaultAllowMaintainerEdit *bool `json:"default_allow_maintainer_edit,omitempty"`
diff --git a/modules/structs/settings.go b/modules/structs/settings.go
index e48b1a493d..b127b58462 100644
--- a/modules/structs/settings.go
+++ b/modules/structs/settings.go
@@ -9,6 +9,7 @@ type GeneralRepoSettings struct {
HTTPGitDisabled bool `json:"http_git_disabled"`
MigrationsDisabled bool `json:"migrations_disabled"`
StarsDisabled bool `json:"stars_disabled"`
+ ForksDisabled bool `json:"forks_disabled"`
TimeTrackingDisabled bool `json:"time_tracking_disabled"`
LFSDisabled bool `json:"lfs_disabled"`
}
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 9b2668d318..7f87c032d5 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -9,12 +9,12 @@ import (
"html"
"html/template"
"net/url"
+ "slices"
"strings"
"time"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/svg"
@@ -35,10 +35,11 @@ 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,
- "Safe": Safe,
- "Escape": html.EscapeString,
+ "SafeHTML": SafeHTML,
+ "HTMLFormat": HTMLFormat,
+ "HTMLEscape": HTMLEscape,
"QueryEscape": url.QueryEscape,
- "JSEscape": template.JSEscapeString,
+ "JSEscape": JSEscapeSafe,
"Str2html": Str2html, // TODO: rename it to SanitizeHTML
"URLJoin": util.URLJoin,
"DotEscape": DotEscape,
@@ -165,7 +166,6 @@ func NewFuncMap() template.FuncMap {
"RenderCodeBlock": RenderCodeBlock,
"RenderIssueTitle": RenderIssueTitle,
"RenderEmoji": RenderEmoji,
- "RenderEmojiPlain": emoji.ReplaceAliases,
"ReactionToEmoji": ReactionToEmoji,
"RenderMarkdownToHtml": RenderMarkdownToHtml,
@@ -185,14 +185,57 @@ func NewFuncMap() template.FuncMap {
}
}
-// Safe render raw as HTML
-func Safe(raw string) template.HTML {
- return template.HTML(raw)
+func HTMLFormat(s string, rawArgs ...any) template.HTML {
+ args := slices.Clone(rawArgs)
+ for i, v := range args {
+ switch v := v.(type) {
+ case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
+ // for most basic types (including template.HTML which is safe), just do nothing and use it
+ case string:
+ args[i] = template.HTMLEscapeString(v)
+ case fmt.Stringer:
+ args[i] = template.HTMLEscapeString(v.String())
+ default:
+ args[i] = template.HTMLEscapeString(fmt.Sprint(v))
+ }
+ }
+ return template.HTML(fmt.Sprintf(s, args...))
}
-// Str2html render Markdown text to HTML
-func Str2html(raw string) template.HTML {
- return template.HTML(markup.Sanitize(raw))
+// SafeHTML render raw as HTML
+func SafeHTML(s any) template.HTML {
+ switch v := s.(type) {
+ case string:
+ return template.HTML(v)
+ case template.HTML:
+ return v
+ }
+ 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))
+}
+
+func HTMLEscape(s any) template.HTML {
+ switch v := s.(type) {
+ case string:
+ return template.HTML(html.EscapeString(v))
+ case template.HTML:
+ return v
+ }
+ panic(fmt.Sprintf("unexpected type %T", s))
+}
+
+func JSEscapeSafe(s string) template.HTML {
+ return template.HTML(template.JSEscapeString(s))
}
// DotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent autolinkers from detecting these as urls
diff --git a/modules/templates/helper_test.go b/modules/templates/helper_test.go
index ec83e9ac33..8f5d633d4f 100644
--- a/modules/templates/helper_test.go
+++ b/modules/templates/helper_test.go
@@ -4,6 +4,7 @@
package templates
import (
+ "html/template"
"testing"
"github.com/stretchr/testify/assert"
@@ -52,3 +53,11 @@ func TestSubjectBodySeparator(t *testing.T) {
"",
"Insuficient\n--\nSeparators")
}
+
+func TestJSEscapeSafe(t *testing.T) {
+ assert.EqualValues(t, `\u0026\u003C\u003E\'\"`, JSEscapeSafe(`&<>'"`))
+}
+
+func TestHTMLFormat(t *testing.T) {
+ assert.Equal(t, template.HTML("< < 1"), HTMLFormat("%s %s %d", "<", template.HTML("<"), 1))
+}
diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go
index 8648967d38..465640e08d 100644
--- a/modules/templates/util_render_test.go
+++ b/modules/templates/util_render_test.go
@@ -59,6 +59,11 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
+func TestApostrophesInMentions(t *testing.T) {
+ rendered := RenderMarkdownToHtml(context.Background(), "@mention-user's comment")
+ assert.EqualValues(t, "@mention-user's comment
\n", rendered)
+}
+
func TestRenderCommitBody(t *testing.T) {
type args struct {
ctx context.Context
diff --git a/modules/templates/util_string.go b/modules/templates/util_string.go
index 2771b1e223..3f51c122b1 100644
--- a/modules/templates/util_string.go
+++ b/modules/templates/util_string.go
@@ -4,6 +4,7 @@
package templates
import (
+ "html/template"
"strings"
"code.gitea.io/gitea/modules/base"
@@ -17,8 +18,14 @@ func NewStringUtils() *StringUtils {
return &stringUtils
}
-func (su *StringUtils) HasPrefix(s, prefix string) bool {
- return strings.HasPrefix(s, prefix)
+func (su *StringUtils) HasPrefix(s any, prefix string) bool {
+ switch v := s.(type) {
+ case string:
+ return strings.HasPrefix(v, prefix)
+ case template.HTML:
+ return strings.HasPrefix(string(v), prefix)
+ }
+ return false
}
func (su *StringUtils) Contains(s, substr string) bool {
diff --git a/modules/templates/util_string_test.go b/modules/templates/util_string_test.go
new file mode 100644
index 0000000000..5844cf201d
--- /dev/null
+++ b/modules/templates/util_string_test.go
@@ -0,0 +1,20 @@
+// Copyright Earl Warren
+// SPDX-License-Identifier: MIT
+
+package templates
+
+import (
+ "html/template"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_StringUtils_HasPrefix(t *testing.T) {
+ su := &StringUtils{}
+ assert.True(t, su.HasPrefix("ABC", "A"))
+ assert.False(t, su.HasPrefix("ABC", "B"))
+ assert.True(t, su.HasPrefix(template.HTML("ABC"), "A"))
+ assert.False(t, su.HasPrefix(template.HTML("ABC"), "B"))
+ assert.False(t, su.HasPrefix(123, "B"))
+}
diff --git a/modules/timeutil/since.go b/modules/timeutil/since.go
index 1cb3c4f288..dfaa0e3e3a 100644
--- a/modules/timeutil/since.go
+++ b/modules/timeutil/since.go
@@ -28,54 +28,54 @@ func computeTimeDiffFloor(diff int64, lang translation.Locale) (int64, string) {
switch {
case diff <= 0:
diff = 0
- diffStr = lang.Tr("tool.now")
+ diffStr = lang.TrString("tool.now")
case diff < 2:
diff = 0
- diffStr = lang.Tr("tool.1s")
+ diffStr = lang.TrString("tool.1s")
case diff < 1*Minute:
- diffStr = lang.Tr("tool.seconds", diff)
+ diffStr = lang.TrString("tool.seconds", diff)
diff = 0
case diff < 2*Minute:
diff -= 1 * Minute
- diffStr = lang.Tr("tool.1m")
+ diffStr = lang.TrString("tool.1m")
case diff < 1*Hour:
- diffStr = lang.Tr("tool.minutes", diff/Minute)
+ diffStr = lang.TrString("tool.minutes", diff/Minute)
diff -= diff / Minute * Minute
case diff < 2*Hour:
diff -= 1 * Hour
- diffStr = lang.Tr("tool.1h")
+ diffStr = lang.TrString("tool.1h")
case diff < 1*Day:
- diffStr = lang.Tr("tool.hours", diff/Hour)
+ diffStr = lang.TrString("tool.hours", diff/Hour)
diff -= diff / Hour * Hour
case diff < 2*Day:
diff -= 1 * Day
- diffStr = lang.Tr("tool.1d")
+ diffStr = lang.TrString("tool.1d")
case diff < 1*Week:
- diffStr = lang.Tr("tool.days", diff/Day)
+ diffStr = lang.TrString("tool.days", diff/Day)
diff -= diff / Day * Day
case diff < 2*Week:
diff -= 1 * Week
- diffStr = lang.Tr("tool.1w")
+ diffStr = lang.TrString("tool.1w")
case diff < 1*Month:
- diffStr = lang.Tr("tool.weeks", diff/Week)
+ diffStr = lang.TrString("tool.weeks", diff/Week)
diff -= diff / Week * Week
case diff < 2*Month:
diff -= 1 * Month
- diffStr = lang.Tr("tool.1mon")
+ diffStr = lang.TrString("tool.1mon")
case diff < 1*Year:
- diffStr = lang.Tr("tool.months", diff/Month)
+ diffStr = lang.TrString("tool.months", diff/Month)
diff -= diff / Month * Month
case diff < 2*Year:
diff -= 1 * Year
- diffStr = lang.Tr("tool.1y")
+ diffStr = lang.TrString("tool.1y")
default:
- diffStr = lang.Tr("tool.years", diff/Year)
+ diffStr = lang.TrString("tool.years", diff/Year)
diff -= (diff / Year) * Year
}
return diff, diffStr
@@ -97,10 +97,10 @@ func timeSincePro(then, now time.Time, lang translation.Locale) string {
diff := now.Unix() - then.Unix()
if then.After(now) {
- return lang.Tr("tool.future")
+ return lang.TrString("tool.future")
}
if diff == 0 {
- return lang.Tr("tool.now")
+ return lang.TrString("tool.now")
}
var timeStr, diffStr string
@@ -115,7 +115,7 @@ func timeSincePro(then, now time.Time, lang translation.Locale) string {
return strings.TrimPrefix(timeStr, ", ")
}
-func timeSinceUnix(then, now time.Time, lang translation.Locale) template.HTML {
+func timeSinceUnix(then, now time.Time, _ translation.Locale) template.HTML {
friendlyText := then.Format("2006-01-02 15:04:05 -07:00")
// document: https://github.com/github/relative-time-element
diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go
index 42475545b3..1555cd961e 100644
--- a/modules/translation/i18n/i18n.go
+++ b/modules/translation/i18n/i18n.go
@@ -4,26 +4,25 @@
package i18n
import (
+ "html/template"
"io"
)
var DefaultLocales = NewLocaleStore()
type Locale interface {
- // Tr translates a given key and arguments for a language
- Tr(trKey string, trArgs ...any) string
- // Has reports if a locale has a translation for a given key
- Has(trKey string) bool
+ // TrString translates a given key and arguments for a language
+ TrString(trKey string, trArgs ...any) string
+ // TrHTML translates a given key and arguments for a language, string arguments are escaped to HTML
+ TrHTML(trKey string, trArgs ...any) template.HTML
+ // HasKey reports if a locale has a translation for a given key
+ HasKey(trKey string) bool
}
// LocaleStore provides the functions common to all locale stores
type LocaleStore interface {
io.Closer
- // Tr translates a given key and arguments for a language
- Tr(lang, trKey string, trArgs ...any) string
- // Has reports if a locale has a translation for a given key
- Has(lang, trKey string) bool
// SetDefaultLang sets the default language to fall back to
SetDefaultLang(lang string)
// ListLangNameDesc provides paired slices of language names to descriptors
@@ -45,7 +44,7 @@ func ResetDefaultLocales() {
DefaultLocales = NewLocaleStore()
}
-// GetLocales returns the locale from the default locales
+// GetLocale returns the locale from the default locales
func GetLocale(lang string) (Locale, bool) {
return DefaultLocales.Locale(lang)
}
diff --git a/modules/translation/i18n/i18n_test.go b/modules/translation/i18n/i18n_test.go
index 1d1be43318..b364992dfe 100644
--- a/modules/translation/i18n/i18n_test.go
+++ b/modules/translation/i18n/i18n_test.go
@@ -4,6 +4,7 @@
package i18n
import (
+ "html/template"
"strings"
"testing"
@@ -17,7 +18,7 @@ fmt = %[1]s %[2]s
[section]
sub = Sub String
-mixed = test value; more text
+mixed = test value; %s
`)
testData2 := []byte(`
@@ -32,29 +33,33 @@ sub = Changed Sub String
assert.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", testData2, nil))
ls.SetDefaultLang("lang1")
- result := ls.Tr("lang1", "fmt", "a", "b")
+ lang1, _ := ls.Locale("lang1")
+ lang2, _ := ls.Locale("lang2")
+
+ result := lang1.TrString("fmt", "a", "b")
assert.Equal(t, "a b", result)
- result = ls.Tr("lang2", "fmt", "a", "b")
+ result = lang2.TrString("fmt", "a", "b")
assert.Equal(t, "b a", result)
- result = ls.Tr("lang1", "section.sub")
+ result = lang1.TrString("section.sub")
assert.Equal(t, "Sub String", result)
- result = ls.Tr("lang2", "section.sub")
+ result = lang2.TrString("section.sub")
assert.Equal(t, "Changed Sub String", result)
- result = ls.Tr("", ".dot.name")
+ langNone, _ := ls.Locale("none")
+ result = langNone.TrString(".dot.name")
assert.Equal(t, "Dot Name", result)
- result = ls.Tr("lang2", "section.mixed")
- assert.Equal(t, `test value; more text`, result)
+ result2 := lang2.TrHTML("section.mixed", "a&b")
+ assert.EqualValues(t, `test value; a&b`, result2)
langs, descs := ls.ListLangNameDesc()
assert.ElementsMatch(t, []string{"lang1", "lang2"}, langs)
assert.ElementsMatch(t, []string{"Lang1", "Lang2"}, descs)
- found := ls.Has("lang1", "no-such")
+ found := lang1.HasKey("no-such")
assert.False(t, found)
assert.NoError(t, ls.Close())
}
@@ -72,9 +77,75 @@ c=22
ls := NewLocaleStore()
assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1, testData2))
- assert.Equal(t, "11", ls.Tr("lang1", "a"))
- assert.Equal(t, "21", ls.Tr("lang1", "b"))
- assert.Equal(t, "22", ls.Tr("lang1", "c"))
+ lang1, _ := ls.Locale("lang1")
+ assert.Equal(t, "11", lang1.TrString("a"))
+ assert.Equal(t, "21", lang1.TrString("b"))
+ assert.Equal(t, "22", lang1.TrString("c"))
+}
+
+type stringerPointerReceiver struct {
+ s string
+}
+
+func (s *stringerPointerReceiver) String() string {
+ return s.s
+}
+
+type stringerStructReceiver struct {
+ s string
+}
+
+func (s stringerStructReceiver) String() string {
+ return s.s
+}
+
+type errorStructReceiver struct {
+ s string
+}
+
+func (e errorStructReceiver) Error() string {
+ return e.s
+}
+
+type errorPointerReceiver struct {
+ s string
+}
+
+func (e *errorPointerReceiver) Error() string {
+ return e.s
+}
+
+func TestLocaleWithTemplate(t *testing.T) {
+ ls := NewLocaleStore()
+ assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", []byte(`key=%s`), nil))
+ lang1, _ := ls.Locale("lang1")
+
+ tmpl := template.New("test").Funcs(template.FuncMap{"tr": lang1.TrHTML})
+ tmpl = template.Must(tmpl.Parse(`{{tr "key" .var}}`))
+
+ cases := []struct {
+ in any
+ want string
+ }{
+ {"", "<str>"},
+ {[]byte(""), "[60 98 121 116 101 115 62]"},
+ {template.HTML(""), ""},
+ {stringerPointerReceiver{""}, "{<stringerPointerReceiver>}"},
+ {&stringerPointerReceiver{""}, "<stringerPointerReceiver ptr>"},
+ {stringerStructReceiver{""}, "<stringerStructReceiver>"},
+ {&stringerStructReceiver{""}, "<stringerStructReceiver ptr>"},
+ {errorStructReceiver{""}, "<errorStructReceiver>"},
+ {&errorStructReceiver{""}, "<errorStructReceiver ptr>"},
+ {errorPointerReceiver{""}, "{<errorPointerReceiver>}"},
+ {&errorPointerReceiver{""}, "<errorPointerReceiver ptr>"},
+ }
+
+ buf := &strings.Builder{}
+ for _, c := range cases {
+ buf.Reset()
+ assert.NoError(t, tmpl.Execute(buf, map[string]any{"var": c.in}))
+ assert.Equal(t, c.want, buf.String())
+ }
}
func TestLocaleStoreQuirks(t *testing.T) {
@@ -110,8 +181,9 @@ func TestLocaleStoreQuirks(t *testing.T) {
for _, testData := range testDataList {
ls := NewLocaleStore()
err := ls.AddLocaleByIni("lang1", "Lang1", []byte("a="+testData.in), nil)
+ lang1, _ := ls.Locale("lang1")
assert.NoError(t, err, testData.hint)
- assert.Equal(t, testData.out, ls.Tr("lang1", "a"), testData.hint)
+ assert.Equal(t, testData.out, lang1.TrString("a"), testData.hint)
assert.NoError(t, ls.Close())
}
diff --git a/modules/translation/i18n/localestore.go b/modules/translation/i18n/localestore.go
index 42b95dda54..44c3fb0c88 100644
--- a/modules/translation/i18n/localestore.go
+++ b/modules/translation/i18n/localestore.go
@@ -5,6 +5,8 @@ package i18n
import (
"fmt"
+ "html/template"
+ "slices"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -18,6 +20,8 @@ type locale struct {
idxToMsgMap map[int]string // the map idx is generated by store's trKeyToIdxMap
}
+var _ Locale = (*locale)(nil)
+
type localeStore struct {
// After initializing has finished, these fields are read-only.
langNames []string
@@ -88,20 +92,6 @@ func (store *localeStore) SetDefaultLang(lang string) {
store.defaultLang = lang
}
-// Tr translates content to target language. fall back to default language.
-func (store *localeStore) Tr(lang, trKey string, trArgs ...any) string {
- l, _ := store.Locale(lang)
-
- return l.Tr(trKey, trArgs...)
-}
-
-// Has returns whether the given language has a translation for the provided key
-func (store *localeStore) Has(lang, trKey string) bool {
- l, _ := store.Locale(lang)
-
- return l.Has(trKey)
-}
-
// Locale returns the locale for the lang or the default language
func (store *localeStore) Locale(lang string) (Locale, bool) {
l, found := store.localeMap[lang]
@@ -116,13 +106,11 @@ func (store *localeStore) Locale(lang string) (Locale, bool) {
return l, found
}
-// Close implements io.Closer
func (store *localeStore) Close() error {
return nil
}
-// Tr translates content to locale language. fall back to default language.
-func (l *locale) Tr(trKey string, trArgs ...any) string {
+func (l *locale) TrString(trKey string, trArgs ...any) string {
format := trKey
idx, ok := l.store.trKeyToIdxMap[trKey]
@@ -144,8 +132,25 @@ func (l *locale) Tr(trKey string, trArgs ...any) string {
return msg
}
-// Has returns whether a key is present in this locale or not
-func (l *locale) Has(trKey string) bool {
+func (l *locale) TrHTML(trKey string, trArgs ...any) template.HTML {
+ args := slices.Clone(trArgs)
+ for i, v := range args {
+ switch v := v.(type) {
+ case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
+ // for most basic types (including template.HTML which is safe), just do nothing and use it
+ case string:
+ args[i] = template.HTMLEscapeString(v)
+ case fmt.Stringer:
+ args[i] = template.HTMLEscapeString(v.String())
+ default:
+ args[i] = template.HTMLEscapeString(fmt.Sprint(v))
+ }
+ }
+ return template.HTML(l.TrString(trKey, args...))
+}
+
+// HasKey returns whether a key is present in this locale or not
+func (l *locale) HasKey(trKey string) bool {
idx, ok := l.store.trKeyToIdxMap[trKey]
if !ok {
return false
diff --git a/modules/translation/mock.go b/modules/translation/mock.go
index 2d0cb17324..1f0559f38d 100644
--- a/modules/translation/mock.go
+++ b/modules/translation/mock.go
@@ -3,7 +3,10 @@
package translation
-import "fmt"
+import (
+ "fmt"
+ "html/template"
+)
// MockLocale provides a mocked locale without any translations
type MockLocale struct{}
@@ -14,12 +17,16 @@ func (l MockLocale) Language() string {
return "en"
}
-func (l MockLocale) Tr(s string, _ ...any) string {
+func (l MockLocale) TrString(s string, _ ...any) string {
return s
}
-func (l MockLocale) TrN(_cnt any, key1, _keyN string, _args ...any) string {
- return key1
+func (l MockLocale) Tr(s string, a ...any) template.HTML {
+ return template.HTML(s)
+}
+
+func (l MockLocale) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
+ return template.HTML(key1)
}
func (l MockLocale) PrettyNumber(v any) string {
diff --git a/modules/translation/translation.go b/modules/translation/translation.go
index dba4de6607..b7c18f610a 100644
--- a/modules/translation/translation.go
+++ b/modules/translation/translation.go
@@ -5,6 +5,7 @@ package translation
import (
"context"
+ "html/template"
"sort"
"strings"
"sync"
@@ -27,8 +28,11 @@ var ContextKey any = &contextKey{}
// Locale represents an interface to translation
type Locale interface {
Language() string
- Tr(string, ...any) string
- TrN(cnt any, key1, keyN string, args ...any) string
+ TrString(string, ...any) string
+
+ Tr(key string, args ...any) template.HTML
+ TrN(cnt any, key1, keyN string, args ...any) template.HTML
+
PrettyNumber(v any) string
}
@@ -144,6 +148,8 @@ type locale struct {
msgPrinter *message.Printer
}
+var _ Locale = (*locale)(nil)
+
// NewLocale return a locale
func NewLocale(lang string) Locale {
if lock != nil {
@@ -216,8 +222,12 @@ var trNLangRules = map[string]func(int64) int{
},
}
+func (l *locale) Tr(s string, args ...any) template.HTML {
+ return l.TrHTML(s, args...)
+}
+
// TrN returns translated message for plural text translation
-func (l *locale) TrN(cnt any, key1, keyN string, args ...any) string {
+func (l *locale) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
var c int64
if t, ok := cnt.(int); ok {
c = int64(t)
diff --git a/modules/util/filebuffer/file_backed_buffer.go b/modules/util/filebuffer/file_backed_buffer.go
index 6b07bd0413..739543e297 100644
--- a/modules/util/filebuffer/file_backed_buffer.go
+++ b/modules/util/filebuffer/file_backed_buffer.go
@@ -149,6 +149,7 @@ func (b *FileBackedBuffer) Close() error {
if b.file != nil {
err := b.file.Close()
os.Remove(b.file.Name())
+ b.file = nil
return err
}
return nil
diff --git a/modules/util/util.go b/modules/util/util.go
index c47931f6c9..28b549f405 100644
--- a/modules/util/util.go
+++ b/modules/util/util.go
@@ -6,12 +6,13 @@ package util
import (
"bytes"
"crypto/rand"
- "encoding/base64"
"fmt"
"math/big"
"strconv"
"strings"
+ "code.gitea.io/gitea/modules/optional"
+
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
@@ -43,6 +44,22 @@ 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() {
+ 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 {
@@ -246,13 +263,3 @@ func ToFloat64(number any) (float64, error) {
func ToPointer[T any](val T) *T {
return &val
}
-
-func Base64FixedDecode(encoding *base64.Encoding, src []byte, length int) ([]byte, error) {
- decoded := make([]byte, encoding.DecodedLen(len(src))+3)
- if n, err := encoding.Decode(decoded, src); err != nil {
- return nil, err
- } else if n != length {
- return nil, fmt.Errorf("invalid base64 decoded length: %d, expects: %d", n, length)
- }
- return decoded[:length], nil
-}
diff --git a/modules/util/util_test.go b/modules/util/util_test.go
index 8509d8aced..c5830ce01c 100644
--- a/modules/util/util_test.go
+++ b/modules/util/util_test.go
@@ -4,7 +4,6 @@
package util
import (
- "encoding/base64"
"regexp"
"strings"
"testing"
@@ -234,16 +233,3 @@ func TestToPointer(t *testing.T) {
val123 := 123
assert.False(t, &val123 == ToPointer(val123))
}
-
-func TestBase64FixedDecode(t *testing.T) {
- _, err := Base64FixedDecode(base64.RawURLEncoding, []byte("abcd"), 32)
- assert.ErrorContains(t, err, "invalid base64 decoded length")
- _, err = Base64FixedDecode(base64.RawURLEncoding, []byte(strings.Repeat("a", 64)), 32)
- assert.ErrorContains(t, err, "invalid base64 decoded length")
-
- str32 := strings.Repeat("x", 32)
- encoded32 := base64.RawURLEncoding.EncodeToString([]byte(str32))
- decoded32, err := Base64FixedDecode(base64.RawURLEncoding, []byte(encoded32), 32)
- assert.NoError(t, err)
- assert.Equal(t, str32, string(decoded32))
-}
diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go
index 4e7fca80e2..4891e43f27 100644
--- a/modules/web/middleware/binding.go
+++ b/modules/web/middleware/binding.go
@@ -105,44 +105,44 @@ func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Lo
trName := field.Tag.Get("locale")
if len(trName) == 0 {
- trName = l.Tr("form." + field.Name)
+ trName = l.TrString("form." + field.Name)
} else {
- trName = l.Tr(trName)
+ trName = l.TrString(trName)
}
switch errs[0].Classification {
case binding.ERR_REQUIRED:
- data["ErrorMsg"] = trName + l.Tr("form.require_error")
+ data["ErrorMsg"] = trName + l.TrString("form.require_error")
case binding.ERR_ALPHA_DASH:
- data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_error")
+ data["ErrorMsg"] = trName + l.TrString("form.alpha_dash_error")
case binding.ERR_ALPHA_DASH_DOT:
- data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_error")
+ data["ErrorMsg"] = trName + l.TrString("form.alpha_dash_dot_error")
case validation.ErrGitRefName:
- data["ErrorMsg"] = trName + l.Tr("form.git_ref_name_error")
+ data["ErrorMsg"] = trName + l.TrString("form.git_ref_name_error")
case binding.ERR_SIZE:
- data["ErrorMsg"] = trName + l.Tr("form.size_error", GetSize(field))
+ data["ErrorMsg"] = trName + l.TrString("form.size_error", GetSize(field))
case binding.ERR_MIN_SIZE:
- data["ErrorMsg"] = trName + l.Tr("form.min_size_error", GetMinSize(field))
+ data["ErrorMsg"] = trName + l.TrString("form.min_size_error", GetMinSize(field))
case binding.ERR_MAX_SIZE:
- data["ErrorMsg"] = trName + l.Tr("form.max_size_error", GetMaxSize(field))
+ data["ErrorMsg"] = trName + l.TrString("form.max_size_error", GetMaxSize(field))
case binding.ERR_EMAIL:
- data["ErrorMsg"] = trName + l.Tr("form.email_error")
+ data["ErrorMsg"] = trName + l.TrString("form.email_error")
case binding.ERR_URL:
- data["ErrorMsg"] = trName + l.Tr("form.url_error", errs[0].Message)
+ data["ErrorMsg"] = trName + l.TrString("form.url_error", errs[0].Message)
case binding.ERR_INCLUDE:
- data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field))
+ data["ErrorMsg"] = trName + l.TrString("form.include_error", GetInclude(field))
case validation.ErrGlobPattern:
- data["ErrorMsg"] = trName + l.Tr("form.glob_pattern_error", errs[0].Message)
+ data["ErrorMsg"] = trName + l.TrString("form.glob_pattern_error", errs[0].Message)
case validation.ErrRegexPattern:
- data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message)
+ data["ErrorMsg"] = trName + l.TrString("form.regex_pattern_error", errs[0].Message)
case validation.ErrUsername:
if setting.Service.AllowDotsInUsernames {
- data["ErrorMsg"] = trName + l.Tr("form.username_error")
+ data["ErrorMsg"] = trName + l.TrString("form.username_error")
} else {
- data["ErrorMsg"] = trName + l.Tr("form.username_error_no_dots")
+ data["ErrorMsg"] = trName + l.TrString("form.username_error_no_dots")
}
case validation.ErrInvalidGroupTeamMap:
- data["ErrorMsg"] = trName + l.Tr("form.invalid_group_team_map_error", errs[0].Message)
+ data["ErrorMsg"] = trName + l.TrString("form.invalid_group_team_map_error", errs[0].Message)
default:
msg := errs[0].Classification
if msg != "" && errs[0].Message != "" {
@@ -151,7 +151,7 @@ func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Lo
msg += errs[0].Message
if msg == "" {
- msg = l.Tr("form.unknown_error")
+ msg = l.TrString("form.unknown_error")
}
data["ErrorMsg"] = trName + ": " + msg
}
diff --git a/modules/web/middleware/flash.go b/modules/web/middleware/flash.go
index 41f3aac27c..88da2049a4 100644
--- a/modules/web/middleware/flash.go
+++ b/modules/web/middleware/flash.go
@@ -3,7 +3,11 @@
package middleware
-import "net/url"
+import (
+ "fmt"
+ "html/template"
+ "net/url"
+)
// Flash represents a one time data transfer between two requests.
type Flash struct {
@@ -26,26 +30,36 @@ func (f *Flash) set(name, msg string, current ...bool) {
}
}
+func flashMsgStringOrHTML(msg any) string {
+ switch v := msg.(type) {
+ case string:
+ return v
+ case template.HTML:
+ return string(v)
+ }
+ panic(fmt.Sprintf("unknown type: %T", msg))
+}
+
// Error sets error message
-func (f *Flash) Error(msg string, current ...bool) {
- f.ErrorMsg = msg
- f.set("error", msg, current...)
+func (f *Flash) Error(msg any, current ...bool) {
+ f.ErrorMsg = flashMsgStringOrHTML(msg)
+ f.set("error", f.ErrorMsg, current...)
}
// Warning sets warning message
-func (f *Flash) Warning(msg string, current ...bool) {
- f.WarningMsg = msg
- f.set("warning", msg, current...)
+func (f *Flash) Warning(msg any, current ...bool) {
+ f.WarningMsg = flashMsgStringOrHTML(msg)
+ f.set("warning", f.WarningMsg, current...)
}
// Info sets info message
-func (f *Flash) Info(msg string, current ...bool) {
- f.InfoMsg = msg
- f.set("info", msg, current...)
+func (f *Flash) Info(msg any, current ...bool) {
+ f.InfoMsg = flashMsgStringOrHTML(msg)
+ f.set("info", f.InfoMsg, current...)
}
// Success sets success message
-func (f *Flash) Success(msg string, current ...bool) {
- f.SuccessMsg = msg
- f.set("success", msg, current...)
+func (f *Flash) Success(msg any, current ...bool) {
+ f.SuccessMsg = flashMsgStringOrHTML(msg)
+ f.set("success", f.SuccessMsg, current...)
}
diff --git a/options/gitignore/Zig b/options/gitignore/Zig
new file mode 100644
index 0000000000..236ae6be8c
--- /dev/null
+++ b/options/gitignore/Zig
@@ -0,0 +1,5 @@
+zig-cache/
+zig-out/
+build/
+build-*/
+docgen_tmp/
\ No newline at end of file
diff --git a/options/license/Brian-Gladman-2-Clause b/options/license/Brian-Gladman-2-Clause
new file mode 100644
index 0000000000..7276f63e9e
--- /dev/null
+++ b/options/license/Brian-Gladman-2-Clause
@@ -0,0 +1,17 @@
+Copyright (C) 1998-2013, Brian Gladman, Worcester, UK. All
+ rights reserved.
+
+The redistribution and use of this software (with or without
+changes) is allowed without the payment of fees or royalties
+provided that:
+
+ source code distributions include the above copyright notice,
+ this list of conditions and the following disclaimer;
+
+ binary distributions include the above copyright notice, this
+ list of conditions and the following disclaimer in their
+ documentation.
+
+This software is provided 'as is' with no explicit or implied
+warranties in respect of its operation, including, but not limited
+to, correctness and fitness for purpose.
diff --git a/options/license/CMU-Mach-nodoc b/options/license/CMU-Mach-nodoc
new file mode 100644
index 0000000000..c81d74fee7
--- /dev/null
+++ b/options/license/CMU-Mach-nodoc
@@ -0,0 +1,11 @@
+Copyright (C) 2002 Naval Research Laboratory (NRL/CCS)
+
+Permission to use, copy, modify and distribute this software and
+its documentation is hereby granted, provided that both the
+copyright notice and this permission notice appear in all copies of
+the software, derivative works or modified versions, and any
+portions thereof.
+
+NRL ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION AND
+DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER
+RESULTING FROM THE USE OF THIS SOFTWARE.
diff --git a/options/license/GNOME-examples-exception b/options/license/GNOME-examples-exception
new file mode 100644
index 0000000000..0f0cd53b50
--- /dev/null
+++ b/options/license/GNOME-examples-exception
@@ -0,0 +1 @@
+As a special exception, the copyright holders give you permission to copy, modify, and distribute the example code contained in this document under the terms of your choosing, without restriction.
diff --git a/options/license/Gmsh-exception b/options/license/Gmsh-exception
new file mode 100644
index 0000000000..6d28f704e4
--- /dev/null
+++ b/options/license/Gmsh-exception
@@ -0,0 +1,16 @@
+The copyright holders of Gmsh give you permission to combine Gmsh
+ with code included in the standard release of Netgen (from Joachim
+ Sch"oberl), METIS (from George Karypis at the University of
+ Minnesota), OpenCASCADE (from Open CASCADE S.A.S) and ParaView
+ (from Kitware, Inc.) under their respective licenses. You may copy
+ and distribute such a system following the terms of the GNU GPL for
+ Gmsh and the licenses of the other code concerned, provided that
+ you include the source code of that other code when and as the GNU
+ GPL requires distribution of source code.
+
+ Note that people who make modified versions of Gmsh are not
+ obligated to grant this special exception for their modified
+ versions; it is their choice whether to do so. The GNU General
+ Public License gives permission to release a modified version
+ without this exception; this exception also makes it possible to
+ release a modified version which carries forward this exception.
diff --git a/options/license/HPND-Fenneberg-Livingston b/options/license/HPND-Fenneberg-Livingston
new file mode 100644
index 0000000000..aaf524f3aa
--- /dev/null
+++ b/options/license/HPND-Fenneberg-Livingston
@@ -0,0 +1,13 @@
+Copyright (C) 1995,1996,1997,1998 Lars Fenneberg
+
+Permission to use, copy, modify, and distribute this software for any
+purpose and without fee is hereby granted, provided that this copyright and
+permission notice appear on all copies and supporting documentation, the
+name of Lars Fenneberg not be used in advertising or publicity pertaining to
+distribution of the program without specific prior permission, and notice be
+given in supporting documentation that copying and distribution is by
+permission of Lars Fenneberg.
+
+Lars Fenneberg makes no representations about the suitability of this
+software for any purpose. It is provided "as is" without express or implied
+warranty.
diff --git a/options/license/HPND-INRIA-IMAG b/options/license/HPND-INRIA-IMAG
new file mode 100644
index 0000000000..87d09d92cb
--- /dev/null
+++ b/options/license/HPND-INRIA-IMAG
@@ -0,0 +1,9 @@
+This software is available with usual "research" terms with
+the aim of retain credits of the software. Permission to use,
+copy, modify and distribute this software for any purpose and
+without fee is hereby granted, provided that the above copyright
+notice and this permission notice appear in all copies, and
+the name of INRIA, IMAG, or any contributor not be used in
+advertising or publicity pertaining to this material without
+the prior explicit permission. The software is provided "as
+is" without any warranties, support or liabilities of any kind.
diff --git a/options/license/Mackerras-3-Clause b/options/license/Mackerras-3-Clause
new file mode 100644
index 0000000000..6467f0c98e
--- /dev/null
+++ b/options/license/Mackerras-3-Clause
@@ -0,0 +1,25 @@
+Copyright (c) 1995 Eric Rosenquist. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ 3. The name(s) of the authors of this software must not be used to
+ endorse or promote products derived from this software without
+ prior written permission.
+
+ THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
+ THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/options/license/Mackerras-3-Clause-acknowledgment b/options/license/Mackerras-3-Clause-acknowledgment
new file mode 100644
index 0000000000..5f0187add7
--- /dev/null
+++ b/options/license/Mackerras-3-Clause-acknowledgment
@@ -0,0 +1,25 @@
+Copyright (c) 1993-2002 Paul Mackerras. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. The name(s) of the authors of this software must not be used to
+ endorse or promote products derived from this software without
+ prior written permission.
+
+3. Redistributions of any form whatsoever must retain the following
+ acknowledgment:
+ "This product includes software developed by Paul Mackerras
+ ".
+
+THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/options/license/OpenVision b/options/license/OpenVision
new file mode 100644
index 0000000000..983505389e
--- /dev/null
+++ b/options/license/OpenVision
@@ -0,0 +1,33 @@
+Copyright, OpenVision Technologies, Inc., 1993-1996, All Rights
+Reserved
+
+WARNING: Retrieving the OpenVision Kerberos Administration system
+source code, as described below, indicates your acceptance of the
+following terms. If you do not agree to the following terms, do
+not retrieve the OpenVision Kerberos administration system.
+
+You may freely use and distribute the Source Code and Object Code
+compiled from it, with or without modification, but this Source
+Code is provided to you "AS IS" EXCLUSIVE OF ANY WARRANTY,
+INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY OR
+FITNESS FOR A PARTICULAR PURPOSE, OR ANY OTHER WARRANTY, WHETHER
+EXPRESS OR IMPLIED. IN NO EVENT WILL OPENVISION HAVE ANY LIABILITY
+FOR ANY LOST PROFITS, LOSS OF DATA OR COSTS OF PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES, OR FOR ANY SPECIAL, INDIRECT, OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, INCLUDING,
+WITHOUT LIMITATION, THOSE RESULTING FROM THE USE OF THE SOURCE
+CODE, OR THE FAILURE OF THE SOURCE CODE TO PERFORM, OR FOR ANY
+OTHER REASON.
+
+OpenVision retains all copyrights in the donated Source Code.
+OpenVision also retains copyright to derivative works of the Source
+Code, whether created by OpenVision or by a third party. The
+OpenVision copyright notice must be preserved if derivative works
+are made based on the donated Source Code.
+
+OpenVision Technologies, Inc. has donated this Kerberos
+Administration system to MIT for inclusion in the standard Kerberos
+5 distribution. This donation underscores our commitment to
+continuing Kerberos technology development and our gratitude for
+the valuable work which has been performed by MIT and the Kerberos
+community.
diff --git a/options/license/Sun-PPP b/options/license/Sun-PPP
new file mode 100644
index 0000000000..5f94a13437
--- /dev/null
+++ b/options/license/Sun-PPP
@@ -0,0 +1,13 @@
+Copyright (c) 2001 by Sun Microsystems, Inc.
+All rights reserved.
+
+Non-exclusive rights to redistribute, modify, translate, and use
+this software in source and binary forms, in whole or in part, is
+hereby granted, provided that the above copyright notice is
+duplicated in any source form, and that neither the name of the
+copyright holder nor the author is used to endorse or promote
+products derived from this software.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/options/license/UMich-Merit b/options/license/UMich-Merit
new file mode 100644
index 0000000000..93e304b90e
--- /dev/null
+++ b/options/license/UMich-Merit
@@ -0,0 +1,19 @@
+[C] The Regents of the University of Michigan and Merit Network, Inc. 1992,
+1993, 1994, 1995 All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted, provided
+that the above copyright notice and this permission notice appear in all
+copies of the software and derivative works or modified versions thereof,
+and that both the copyright notice and this permission and disclaimer
+notice appear in supporting documentation.
+
+THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
+EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE REGENTS OF THE
+UNIVERSITY OF MICHIGAN AND MERIT NETWORK, INC. DO NOT WARRANT THAT THE
+FUNCTIONS CONTAINED IN THE SOFTWARE WILL MEET LICENSEE'S REQUIREMENTS OR
+THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. The Regents of the
+University of Michigan and Merit Network, Inc. shall not be liable for any
+special, indirect, incidental or consequential damages with respect to any
+claim by Licensee or any third party arising from use of the software.
diff --git a/options/license/bcrypt-Solar-Designer b/options/license/bcrypt-Solar-Designer
new file mode 100644
index 0000000000..8cb05017fc
--- /dev/null
+++ b/options/license/bcrypt-Solar-Designer
@@ -0,0 +1,11 @@
+Written by Solar Designer in 1998-2014.
+No copyright is claimed, and the software is hereby placed in the public
+domain. In case this attempt to disclaim copyright and place the software
+in the public domain is deemed null and void, then the software is
+Copyright (c) 1998-2014 Solar Designer and it is hereby released to the
+general public under the following terms:
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted.
+
+There's ABSOLUTELY NO WARRANTY, express or implied.
diff --git a/options/license/gtkbook b/options/license/gtkbook
new file mode 100644
index 0000000000..91215e80d6
--- /dev/null
+++ b/options/license/gtkbook
@@ -0,0 +1,6 @@
+Copyright 2005 Syd Logan, All Rights Reserved
+
+This code is distributed without warranty. You are free to use
+this code for any purpose, however, if this code is republished or
+redistributed in its original form, as hardcopy or electronically,
+then you must include this copyright notice along with the code.
diff --git a/options/license/softSurfer b/options/license/softSurfer
new file mode 100644
index 0000000000..1bbc88c34c
--- /dev/null
+++ b/options/license/softSurfer
@@ -0,0 +1,6 @@
+Copyright 2001, softSurfer (www.softsurfer.com)
+This code may be freely used and modified for any purpose
+providing that this copyright notice is included with it.
+SoftSurfer makes no warranty for this code, and cannot be held
+liable for any real or imagined damage resulting from its use.
+Users of this code must verify correctness for their application.
diff --git a/options/locale/locale_ar.ini b/options/locale/locale_ar.ini
index 9f0dde5c94..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 = مُفَعَّل
@@ -122,6 +122,10 @@ powered_by = مدعوم بواسطة %s
retry = أعد المحاولة
tracked_time_summary = ملخص للتتبع الزمني وفقًا لنتائج تصفية قائمة المسائل
copy_hash = انسخ البصمة
+remove = أزل
+remove_all = أزل الكل
+remove_label_str = أزل العنصر "%s"
+confirm_delete_artifact = هل أنت متأكد أنك تريد حذف العنصر '%s'؟
[install]
db_name = اسم قاعدة البيانات
@@ -206,34 +210,34 @@ 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
-default_enable_timetracking = فعل تتبع الوقت افتراضياً
+default_enable_timetracking = فعّل تتبع الوقت مبدئيا
env_config_keys_prompt = ستطبق المتغيرات البيئية التالية أيضاً على ملف الإعدادات:
admin_title = إعدادات حساب المدير
no_reply_address_helper = النطاق للمستخدمين بعنوان بريد إلكتروني مخفي. مثلاً، اسم المستخدم "sarah" سوف يسجل في جِت كـ"sarah@noreply.example.org" لو كان نطاق البريد الإلكتروني الخفي مدخل كـ"noreply.example.org".
enable_update_checker = فعل فحص التحديثات
-default_enable_timetracking_popup = فعل تتبع الوقت للمستودعات الجديدة افتراضياً.
+default_enable_timetracking_popup = فعل تتبع الوقت للمستودعات الجديدة مبدئيا.
run_user_not_match = مستخدم التشغيل غير مطابق لأسم المستخدم الحالي: %s -> %s
invalid_db_setting = إعدادات قاعدة البيانات غير صالحة: %v
invalid_db_table = جدول قاعدة البيانات "%s" غير صالح: %v
-default_keep_email_private_popup = اخفي عناوين البريد الإلكتروني للحسابات الجديدة افتراضياً.
+default_keep_email_private_popup = أخفِ عناوين البريد الإلكتروني للحسابات الجديدة مبدئيا.
env_config_keys = إعدادات بيئية
-default_allow_create_organization = اسمح بإنشاء المنظمات افتراضياً
+default_allow_create_organization = اسمح بإنشاء المنظمات مبدئيا
invalid_app_data_path = مسار بيانات التطبيق غير صالح: %v
enable_update_checker_helper = يفحص لإيجاد اصدارات جديدة عن طريق الإتصال بسيرفرات فورجيو.
invalid_repo_path = المسار الجزري للمستودع غير صالح: %v
internal_token_failed = فشل توليد الرمز الداخلي: %v
no_reply_address = نطاقات البريد الإلكتروني المخفية
-default_keep_email_private = اخفي عناوين البريد الإلكتروني افتراضياً
+default_keep_email_private = أخفِ عناوين البريد الإلكتروني مبدئيا
admin_name = اسم مستخدم المدير
-default_allow_create_organization_popup = اسمح بحسابات المستخدمين الجديدة بإنشاء المنظمات افتراضياً.
+default_allow_create_organization_popup = اسمح بحسابات المستخدمين الجديدة بإنشاء المنظمات مبدئيا.
password_algorithm = خوارزمية تجزئة كلمة المرور
-invalid_password_algorithm = خوارزمية تجزئة كلمة المرور غير صالحة
-password_algorithm_helper = اختر خوارزمية تجزئة كلمة المرور. الخوارزميات لديهم متطلبات وقوى مختلفة. خوارزمية argon2 هي آمنة ولكن تتطلب الكثير من الذاكرة ولذلك قد تكون غير ملائمة للأنظمة الصغيرة.
+invalid_password_algorithm = خوارزمية بصمة كلمة المرور غير صالحة
+password_algorithm_helper = اختر خوارزمية بصمة كلمة المرور. تختلف الخوارزميات في متطلباتها وقوتها. خوارزمية argon2 آمنة لكن تتطلب الكثير من الذاكرة ولذلك قد تكون غير ملائمة للأنظمة الصغيرة.
[editor]
buttons.list.ordered.tooltip = أضف قائمة مرقمة
@@ -321,7 +325,7 @@ twofa_disable = تعطيل الاستيثاق الثنائي
retype_new_password = تأكيد كلمة المرور الجديدة
manage_emails = أدر عناوين البريد الإلكتروني
then_enter_passcode = وأدخل رمز الدخول الظاهر في التطبيق:
-change_password = حدّث كلمة المرور
+update_password = حدّث كلمة المرور
continue = استمر
emails = عناوين البريد الإلكتروني
confirm_delete_account = أكُد الحذف
@@ -381,7 +385,7 @@ key_state_desc = هذا المفتاح أستُعمل خلال آخر 7 أيام
webauthn_delete_key = أزِل مفتاح الأمان
valid_forever = صالح للأبد
can_read_info = قراءة
-create_oauth2_application_button = أنشئ تطبيق
+create_oauth2_application_button = أنشئ تطبيقا
save_application = احفظ
permissions_access_all = الكل (عام، خاص، ومحدود)
valid_until_date = صالح حتى %s
@@ -405,7 +409,7 @@ comment_type_group_deadline = الموعد النهائي
add_key = أضف مفتاح
gpg_no_key_email_found = هذا المفتاح الـGPG لا يُطابق أي بريد إلكتروني مُفعل ومربوط بحسابك. لا زال يمكن إضافته إن وقعت الرمز المقدم.
keep_activity_private = اخف النشاط من صفحة الملف الشخصي
-profile_desc = تحكم في كيفية ظهور ملفك الشخصي للمستخدمين الآخرين. سيتم استخدام عنوان بريدك الإلكتروني الأساسي للإشعارات واستعادة كلمة المرور وعمليات Git المستندة إلى الويب.
+profile_desc = تحكم في كيفية ظهور ملفك الشخصي للمستخدمين الآخرين. سيتم استخدام عنوان بريدك الإلكتروني الأساسي للإشعارات واستعادة كلمة المرور وعمليات Git المعتمدة على الويب.
can_not_add_email_activations_pending = هناك تفعيل قيد الانتظار، حاول مجدداً خلال بضع دقائق إن أردت أضافه بريد إلكتروني جديد.
gpg_key_id_used = هناك مفتاح GPG عام بنفس المعرف موجود بالفعل.
add_new_gpg_key = أضف مفتاح GPG
@@ -420,7 +424,7 @@ add_new_key = أضف مفتاح SSH
hidden_comment_types_description = أنواع التعليق المُختارة هنا لن تظهر داخل صفحات المسائل. أختيار "تصنيف" مثلاً يمسح كل تعليقات "<مستخدم> أضاف/مسح <تصنيف>".
key_content_gpg_placeholder = يبدأ بـ '-----BEGIN PGP PUBLIC KEY BLOCK-----'
add_email_confirmation_sent = بريد تفعيل جديد تم إرساله إلى "%s". يُرجى التحقق من البريد الوارد خلال %s لتأكيد عنوان البريد الإلكتروني.
-ssh_desc = ترتبط هذه المفاتيح الـSSH العامة بحسابك. تسمح المفاتيح الخاصة المطابقة بالوصول الكامل إلى مستودعاتك.
+ssh_desc = مفاتيح SSH العمومية هذه مرتبطة بحسابك. وتسمح المفاتيح الخصوصية المرافقة بالوصول الكامل إلى مستودعاتك. ويمكن استعمال مفاتيح SSH الموثَّقة لتوثيق إيداعات جت الموقَّعة بمفاتيح SSH.
ssh_gpg_keys = مفاتيح SSH / GPG
authorized_oauth2_applications_description = لقد منحتَ إمكانية الوصول إلى حسابك الشخصي على فورجيو لهذه التطبيقات من تطبيقات خارجية. الرجاء إلغاء وصول التطبيقات التي لم تعد بحاجة إليها.
ssh_key_been_used = هذا المفتاح الـSSH تم إضافته بالفعل إلى هذا الخادم.
@@ -449,7 +453,7 @@ gpg_key_verified_long = تم التحقق من المفتاح بواسطة رم
change_username_redirect_prompt = اسم المستخدم القديم سوف يعاد توجيهه حتى يطالب به شخص آخر.
add_key_success = تم إضافة مفتاح SSH "%s".
key_name = اسم المفتاح
-comment_type_group_time_tracking = تعقب الوقت
+comment_type_group_time_tracking = تتبع الوقت
gpg_invalid_token_signature = مفتاح GPG المزود، والتوقيع والرمز لا يتطابقوا أو الرمز قديم.
ssh_key_verified = مفتاح مُتحقق منه
ssh_key_deletion_success = تم إزالة مفتاح SSH.
@@ -470,6 +474,16 @@ gpg_token = رمز
gpg_key_matched_identities_long = الهويات المدمجة في هذا المفتاح تطابق عناوين البريد الإلكتروني المفعلة التالية لهذا المستخدم. ويمكن التحقق من صحة الإيداعات المطابقة لهذه العناوين البريدية مع هذا المفتاح.
key_content = المحتوى
key_signature_gpg_placeholder = يبدأ بـ'-----BEGIN PGP SIGNATURE-----'
+manage_oauth2_applications = إدارة تطبيقات OAuth2
+edit_oauth2_application = تعديل تطبيق OAuth2
+remove_oauth2_application = إزالة تطبيق OAuth2
+create_oauth2_application = أنشئ تطبيق OAuth2 جديدا
+create_oauth2_application_success = لقد أنشأت بنجاح تطبيق OAuth2 جديدا.
+remove_oauth2_application_success = أُزيل التطبيق.
+update_oauth2_application_success = لقد حدّثت بنجاح تطبيق OAuth2.
+oauth2_redirect_uris = روابط إعادة التوجيه. نرجو وضع كل رابط في سطر وحده.
+remove_account_link = أزل الحساب المربوط
+remove_account_link_success = أُزيل الحساب المربوط.
[org]
follow_blocked_user = لا يمكنك إتباع هذه المنظمة لأن هذه المنظمة حظرتك.
@@ -560,7 +574,7 @@ settings.add_collaborator_blocked_our = لا يمكن إضافة المشترك
commits.browse_further = تصفح أكثر
settings.ignore_stale_approvals = تجاهل الطلبات الراكدة
rss.must_be_on_branch = يجب أن تكون على فرع لتحصل على موجز RSS.
-clone_in_vscodium = إستنسخ في VS Codium
+clone_in_vscodium = إستنسخ في VSCodium
admin.enabled_flags = العلامات المفعلة لهذا المستودع:
settings.new_owner_blocked_doer = المالك الجديد حظرك.
issues.comment.blocked_by_user = لا يمكنك أن ترسل تعليقاً على هذه المسألة لأنك محظور من قبل مالك المستودع أو مرسل المسألة.
@@ -710,9 +724,9 @@ issues.role.first_time_contributor = مساهم لأول مرة
issues.label_title = الاسم
issues.filter_projects = تصفية المشروعات
issues.role.collaborator = مشترك
-issues.action_open = حالية
+issues.action_open = افتح
issues.filter_sort.latest = الأحدث
-issues.action_close = تامة
+issues.action_close = أغلق
issues.is_stale = تغيّر طلب السحب هذا بعد المراجعة
issues.dependency.remove = أزل
issues.label_templates.info = لا توجد تصنيفات بعد. أنشئ تصنيفا بزر «تصنيف جديد» أو استخدم باقة تصنيفات معرّفة سابقا:
@@ -797,7 +811,7 @@ branch.rename_branch_to = غيّر اسم "%s" إلى:
issues.reopen_comment_issue = علّق وأعد فتحها
issues.dependency.add = أضف اعتمادية…
issues.label_deletion_desc = حذف تصنيف يحذفه من كل المسائل، أتريد الاستمرار؟
-labels = تصنيفات
+labels = التصنيفات
delete_preexisting_content = احذف الملفات في %s
milestones.deletion = احذف الهدف
issues.comment_pull_merged_at = دمج الإيداع %[1]s إلى %[2]s %[3]s
@@ -1003,7 +1017,7 @@ settings.web_hook_name_discord = دسكورد
settings.web_hook_name_telegram = تيليجرام
editor.commit_signed_changes = أودع التعديلات الموقّعة
editor.filename_is_invalid = اسم الملف غير صالح: "%s".
-pulls.no_merge_access = أنت غير مأذون بدمج طلب السحب.
+pulls.no_merge_access = ليس مسموحا لك دمج هذا الطلب.
visibility_helper = اجعل المستودع خاصًا
visibility_helper_forced = يفرض مدير موقعك أن تكون المستودعات الجديدة خاصة.
wiki.page_content = محتوى الصفحة
@@ -1107,9 +1121,197 @@ settings.collaborator_deletion = أزل مشتركا
settings.event_wiki = الموسوعة
settings.event_wiki_desc = إنشاء صفحة موسوعة أو تغيير اسمها أو تعديلها أو حذفها.
settings.transfer_desc = انقل ملكية هذا المستودع إلى مستخدم أو إلى منظمة تديرها.
+settings.transfer.rejected = رُفض نقل ملكية المستودع.
+pulls.cannot_auto_merge_desc = لا يمكن دمج هذا الطلب آليا بسبب النزاعات.
+pulls.auto_merge_newly_scheduled = هذا الطلب مجدول للدمج عند نجاح جميع الفحوص.
+pulls.approve_count_n = "%d موافقة"
+pulls.cannot_auto_merge_helper = ادمجه آليا لحل النزاعات.
+pulls.num_conflicting_files_n = "%d ملفا متنازع عليها"
+pulls.approve_count_1 = "%d موافقة"
+pulls.reject_count_1 = "%d طلب تغيير"
+pulls.reject_count_n = "%d طلب تغيير"
+pulls.waiting_count_1 = "%d انتظار مراجعة"
+pulls.waiting_count_n = "%d انتظار مراجعة"
+pulls.no_merge_desc = لا يمكن دمج هذا الطلب لأن كل خيارات الدمج في هذا المستودع معطّلة.
+pulls.no_merge_helper = فعّل خيارات الدمج في إعدادات المستودع أو ادمج الطلب يدويا.
+pulls.no_merge_not_ready = هذا الطلب ليس جاهزا للدمج؛ انظر حالة المراجعة وفحوص الحالة.
+pulls.merge_pull_request = أنشئ إيداع دمج
+pulls.rebase_merge_pull_request = أعد التأسيس ثم قم بالتسريع
+pulls.rebase_merge_commit_pull_request = أعد التأسيس ثم أنشئ إيداع دمج
+pulls.merge_manually = دُمِج يدويا
+pulls.merge_commit_id = معرّف إيداع الدمج
+pulls.invalid_merge_option = لا يمكنك استخدام خيار الدمج هذا مع هذا الطلب.
+pulls.merge_conflict_summary = رسالة خطأ
+pulls.rebase_conflict_summary = رسالة خطأ
+pulls.push_rejected_summary = رسالة الرفض الكاملة
+pulls.status_checking = في انتظار بعض الفحوص
+pulls.status_checks_failure = بعض الفحوص فشلت
+pulls.status_checks_success = جميع الفحوص ناجحة
+pulls.status_checks_warning = بعض الفحوص تعطي تحذيرات
+pulls.commit_ref_at = `أشار إلى طلب الدمج من إيداع %[2]s`
+pulls.cmd_instruction_hint = `أظهر شرح استخدام سطر الأوامر.`
+pulls.cmd_instruction_checkout_title = اسحب
+pulls.cmd_instruction_checkout_desc = من مستودع مشروعك، اسحب (check out) فرعا جديدا واختبر التغييرات.
+pulls.cmd_instruction_merge_title = ادمج
+pulls.cmd_instruction_merge_desc = ادمج التغييرات وحدّث المستودع في فورجيو.
+pulls.clear_merge_message = نظّف رسالة الدمج
+pulls.clear_merge_message_hint = تنظيف رسالة الدمج إنما يزيل محتوى رسالة الإيداع ويُبقى التذييلات التي يضيفها جت، مثل "Co-Authored-By …".
+pulls.auto_merge_when_succeed = ادمج آليا عند نجاح جميع الفحوص
+pulls.auto_merge_has_pending_schedule = %[1]s أمر بجدولة هذا الطلب للدمج عند نجاح جميع الفحوص %[2]s.
+pulls.auto_merge_cancel_schedule = ألغِ الدمج الآلي
+pulls.auto_merge_not_scheduled = لم يعد هذا الطلب مجدولا للدمج الآلي.
+pulls.auto_merge_canceled_schedule = لقد أُلغي الدمج الآلي لهذا الطلب.
+pulls.delete.title = حذف هذا الطلب؟
+pulls.delete.text = هل تريد حقا حذف هذا الطلب؟ (هذا سيحذف كل محتواه إلى الأبد. فكّر في إغلاقه بدلا من ذلك، إذا أردت الاحتفاظ به مؤرشفا)
+pulls.recently_pushed_new_branches = لقد دفعت إلى الفرع %[1]s %[2]s
+milestones.new = هدف جديد
+milestones.closed = أُغلق %s
+milestones.update_ago = حُدِّث %s
+milestones.open = حالي
+milestones.close = تام
+milestones.new_subheader = تساعدك الأهداف في تنظيم المسائل وتتبع سيرها.
+milestones.completeness = %d%% مكتمل
+milestones.create = أنشئ هدفا
+search.match.tooltip = لا تأت إلا بالنتائج التي تطابق كلمة البحث تماما
+search.results = نتائج البحث عن "%s" في %s
+settings.collaboration = المشتركون
+settings.collaboration.admin = مدير
+settings.collaboration.write = تحرير
+settings.collaboration.read = اطلاع
+settings.basic_settings = إعدادات أساسية
+settings.sync_mirror = زامن الآن
+settings.pull_mirror_sync_in_progress = يجذب التغييرات من المستودع البعيد %s الآن.
+settings.push_mirror_sync_in_progress = يدفع التغييرات إلى المستودع البعيد %s الآن.
+settings.site = موقع الويب
+settings.update_settings = حدّث الإعدادات
+settings.branches.update_default_branch = حدّث الفرع المبدئي
+settings.branches.add_new_rule = أضف قاعدة جديدة
+settings.advanced_settings = إعدادات متقدمة
+settings.wiki_desc = فعّل موسوعة المستودع
+settings.use_internal_wiki = استعمل الموسوعة المدمجة
+settings.use_external_wiki = استعمل موسوعة خارجية
+settings.external_wiki_url = رابط الموسوعة الخارجية
+settings.external_wiki_url_error = رابط الموسوعة الخارجية ليس رابطا صالحا.
+settings.external_wiki_url_desc = سيُوّجه الزائرين إلى الموسوعة الخارجية عندما يضغطون على زر الموسوعة.
+settings.issues_desc = فعّل متتبع المسائل في المستودع
+settings.use_internal_issue_tracker = استعمل متتبع المسائل المدمج
+settings.use_external_issue_tracker = استعمل متتبع مسائل خارجي
+settings.external_tracker_url = رابط متتبع المسائل الخارجي
+settings.external_tracker_url_error = رابط متتبع المسائل الخارجي ليس رابطا صالحا.
+settings.external_tracker_url_desc = سيُوّجه الزائرين إلى متتبع المسائل الخارجي عندما يضغطون على زر المسائل.
+settings.tracker_url_format = صيغة رابط متتبع المسائل الخارجي
+settings.tracker_url_format_error = صيغة رابط متتبع المسائل الخارجي ليست رابطا صالحا.
+settings.tracker_issue_style = صيغة رقم متتبع المسائل الخارجي
+settings.tracker_issue_style.numeric = أرقام
+settings.tracker_issue_style.alphanumeric = أرقام وحروف رومية
+settings.tracker_issue_style.regexp = تعبير نمطي
+settings.tracker_issue_style.regexp_pattern = نمط التعبير
+settings.enable_timetracker = فعّل تتبع الوقت
+settings.allow_only_contributors_to_track_time = لا تجعل إلا المشتركين في المستودع يتتبعون الوقت
+settings.pulls_desc = فعّل طلب الدمج في المستودع
+settings.pulls.ignore_whitespace = تجاهل المسافات في النزاعات
+settings.danger_zone = منطقة الخطر
+settings.new_owner_has_same_repo = المالك الجديد لديه مستودع بالاسم نفسه؛ برجاء اختيار اسم آخر.
+settings.transfer = نقل الملكية
+settings.transfer_abort = ألغِ نقل الملكية
+settings.transfer_abort_invalid = لا يمكنك إلغاء عملية غير موجودة لنقل ملكية مستودع.
+settings.transfer.success = نجح نقل ملكية المستودع.
+settings.transfer_abort_success = أُلغي بنجاح نقل ملكية المستودع إلى %s.
+settings.transfer_owner = المالك الجديد
+settings.transfer_perform = أتم نقل الملكية
+settings.transfer_succeed = تم نقل ملكية المستودع.
+pulls.auto_merge_newly_scheduled_comment = `أمر بجدولة هذا الطلب للدمج آليا عند نجاح جميع الفحوص %[1]s
+pulls.auto_merge_canceled_schedule_comment = `ألغى الدمج الآلي لهذا الطلب عند نجاح جميع الفحوص %[1]s
+ext_issues.desc = رابط متتبع المسائل الخارجي.
+projects.edit_subheader = تساعد المشروعات في تنظيم المسائل وتتبع سيرها.
+issues.tracker = متتبع الوقت
+issues.start_tracking_short = شغّل المؤقت
+issues.start_tracking = ابدأ تتبع الوقت
+issues.start_tracking_history = `بدأ العمل %s`
+issues.tracker_auto_close = سيتوقف المؤقت تلقائيا عندما تُغلق هذه المسألة
+issues.stop_tracking = أوقف المؤقت
+issues.stop_tracking_history = `توقف عن العمل %s`
+pulls.can_auto_merge_desc = يمكن دمج هذا الطلب آليا.
+pulls.num_conflicting_files_1 = "%d ملف متنازع عليه"
+pulls.status_checks_error = بعض الفحوص تعطي أخطاء
+pulls.status_checks_requested = مطلوب
+pulls.status_checks_details = تفاصيل
+pulls.status_checks_hide_all = أخفِ كل الفحوص
+pulls.status_checks_show_all = أظهر كل الفحوص
+pulls.close = أغلق طلب الدمج
+pulls.closed_at = `أغلق طلب الدمج %[2]s`
+pulls.reopened_at = `أعاد فتح طلب الدمج %[2]s`
+milestones.title = العنوان
+milestones.desc = الوصف
+milestones.edit = عدّل الهدف
+milestones.edit_subheader = تساعد الأهداف في تنظيم المسائل وتتبع سيرها.
+settings.options = المستودع
+settings.branches.switch_default_branch = بدّل الفرع المبدئي
+settings.admin_settings = إعدادات المدير
+settings.trust_model.default = نموذج الثقة المبدئي
+settings.trust_model.collaborator = مشترك
+settings.trust_model.collaborator.long = مشترك: ثق بتوقيعات المشترِكين
+wiki.file_revision = مراجعة الصفحة
+wiki.wiki_page_revisions = مراجعات صفحة الموسوعة
+wiki.back_to_wiki = عد إلى صفحة الموسوعة
+search.type.tooltip = نوع البحث
+search.fuzzy = تقريبي
+search.fuzzy.tooltip = ائت بالنتائج القريبة من كلمة البحث
+search.match = مطابق
+settings = الإعدادات
+settings.mirror_settings.push_mirror.remote_url = رابط مستودع جت البعيد
+settings.releases_desc = فعّل الإصدارات في المستودع
+settings.projects_desc = فعّل المشروعات في المستودع
+settings.signing_settings = إعدادات التحقق من التوقيعات
+settings.trust_model = نموذج الثقة في التوقيعات
+settings.trust_model.committer = مودِع
+settings.trust_model.collaboratorcommitter = مشترك+مودع
+settings.trust_model.collaboratorcommitter.long = مشترك+مودع: ثق بتوقيعات المشتركين التي تطابق المودع
+tagged_this = وسم هذا
+branches = الفروع
+tags = الوسوم
+issues = المسائل
+pulls = طلبات الدمج
+project_board = المشروعات
+packages = الحزم
+actions = الإجراءات
+released_this = أصدر هذا
+commit.load_referencing_branches_and_tags = حمّل الفروع والوسوم التي تشير إلى هذا الإيداع
+issues.opened_by = أنشأها %[1]s %[3]s
+issues.closed_by = من %[3]s أُغلقت %[1]s
+issues.opened_by_fake = أنشأها %[1]s %[2]s
+issues.closed_by_fake = من %[2]s أُغلقت %[1]s
+issues.num_comments_1 = %d تعليق
+issues.num_comments = %d تعليقا
+issues.commented_at = `علّق %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.reference_issue.body = المحتوى
+issues.reference_link = للإشارة: %s
+settings.actions_desc = فعّل الإجراءات في المستودع
+pulls.push_rejected = تعذر الدمج: تم رفض الدفع. راجع خطاطيف جت لهذا المستودع.
+contributors.contribution_type.additions = الإضافات
+search = بحث
+pulls.require_signed_wont_sign = يطلب الفرع إيداعات موقّعة، لكن لن يكون هذا الدمج موقّعًا
+pulls.update_branch = تحديث الفرع بالدمج
+pulls.update_branch_rebase = تحديث الفرع بإعادة التأسيس
+pulls.update_branch_success = نجح تحديث الفرع
+pulls.update_not_allowed = ليس مسموحا لك تحديث الفرع
+contributors.contribution_type.commits = الإيداعات
+contributors.contribution_type.deletions = الإزالات
+unit_disabled = لقد عطّل مدير الموقع قسم المستودع هذا.
+pulls.fast_forward_only_merge_pull_request = تسريع وحسب
+pulls.merge_conflict = تعذر الدمج: حدث نزاع خلال الدمج. مساعدة: جرب طريقة أخرى
+pulls.rebase_conflict = تعذر الدمج: حدث نزاع خلال إعادة تأسيس الإيداع: %[1]s. مساعدة: جرب طريقة أخرى
+pulls.has_merged = فشل: لقد تم دمج هذا الطلب، فلا يمكنك دمجه مجددا أو تغيير الفرع الهدف.
[mail]
-admin.new_user.text = من فضلك اضغط هنا لإدارة المستخدم من لوحة الإدارة.
+admin.new_user.text = من فضلك اضغط هنا لإدارة هذا المستخدم من لوحة الإدارة.
admin.new_user.subject = مستخدم جديد: %s سجل حالاً
admin.new_user.user_info = معلومات المستخدم
activate_account.text_1 = أهلا يا %[1]s، شكرا لك للتسجيل في %[2]s!
@@ -1145,23 +1347,23 @@ activate_email.title = %s، يرجى تأكيد عنوان بريدك الإلك
release.note = ملاحظة:
issue.action.close = @%[1]s أغلق #%[2]d.
issue.action.merge = @%[1]s دمج #%[2]d مع %[3]s.
-issue.action.force_push = %[1]s فرض الدفع بـ%[2]s من %[3]s إلى %[4]s.
+issue.action.force_push = %[1]s فرض دفع %[2]s من %[3]s إلى %[4]s.
repo.transfer.body = للقبول أو الرفض، زر %s أو تجاهل.
team_invite.text_3 = ملاحظة: هذه الدعوة معنيه إلى %[1]s. إن لم تكن مترقب هذه الدعوة، يمكنك تجاهل هذه الرسالة.
issue.action.reopen = @%[1]s أعاد فتح #%[2]d.
issue.action.approve = @%[1]s وافق على هذا الطلب للسحب.
-issue.action.push_1 = @%[1]s دفع بـ %[3]d إيداع إلى %[2]s
-release.new.text = @%[1]s اصدر %[2]s في %[3]s
+issue.action.push_1 = @%[1]s دفع %[3]d إيداع إلى %[2]s
+release.new.text = @%[1]s أصدر %[2]s في %[3]s
issue.action.reject = @%[1]s طلب تغييرات في هذا الطلب للسحب.
release.download.zip = البرمجية (ZIP)
release.download.targz = البرمجية (TAR.GZ)
issue.action.review = @%[1]s علّق على هذا الطلب للسحب.
issue.action.new = @%[1]s انشأ #%[2]d.
issue_assigned.issue = @%[1]s عيّنك إلى مسألة %[2]s في مستودع %[3]s.
-issue.action.push_n = @%[1]s دفع بـ %[3]d إيداعات إلى %[2]s
-release.new.subject = صُدر %s في %s
-repo.transfer.subject_to_you = %s يود نقل "%s" إليك
-repo.transfer.subject_to = %s يود نقل "%s" إلى %s
+issue.action.push_n = @%[1]s دفع %[3]d إيداعات إلى %[2]s
+release.new.subject = أُصدر %s في %s
+repo.transfer.subject_to_you = %s يود نقل ملكية "%s" إليك
+repo.transfer.subject_to = %s يود نقل ملكية "%s" إلى %s
issue.action.ready_for_review = @%[1]s علّم هذا الطلب للسحب كجاهز للمراجعة.
issue_assigned.pull = @%[1]s عيّنك إلى طلب سحب %[2]s في مستودع %[3]s.
issue.action.review_dismissed = @%[1]s أستبعد آخر مراجعة من %[2]s لهذا الطلب للسحب.
@@ -1173,6 +1375,7 @@ network_error = خطأ في الشبكة
invalid_csrf = طلب سيئ: رمز CSRF غير صالح
occurred = حدث خطأ
missing_csrf = طلب سيئ: لا يوجد رمز CSRF
+server_internal = خطأ داخلي في الخادم
[startpage]
install = سهلة التثبيت
@@ -1288,6 +1491,9 @@ sspi_auth_failed = فشلت عملية استيثاق SSPI
openid_connect_desc = مسار الـOpenID المختار مجهول. اربطه مع حساب جديد هنا.
openid_signin_desc = أدخل مسار الـOpenID الخاص بك. مثلاً: alice.openid.example.org أو https://openid.example.org/alice.
openid_register_desc = مسار الـOpenID المختار مجهول. اربطه مع حساب جديد هنا.
+remember_me = تذكر هذا الجهاز
+remember_me.compromised = رمز الاحتفاظ بتسجيل الدخول لم يعد صالحا، مما قد يعني اختراق الحساب. نرجو مراجعة حسابك لرؤية أي نشاط غير مألوف.
+authorization_failed_desc = فشل التفويض لأننا اكتشفنا طلبًا غير صالح. يرجى الاتصال بمشرف التطبيق الذي حاولت ترخيصه.
[packages]
rpm.repository.multiple_groups = هذه الحزمة متوفرة في مجموعات متعددة.
@@ -1445,9 +1651,14 @@ repos.issues = المسائل
users.created = أُنشئ
auths.oauth2_authURL = رابط الأذن
dashboard.resync_all_sshkeys = حدّث ملف '.ssh/authorized_keys' بمفاتيح SSH لفورجيو.
+config.default_keep_email_private = أخفِ عناوين البريد الإلكتروني مبدئيا
+config.default_allow_create_organization = اسمح بإنشاء المنظمات مبدئيا
+config.enable_timetracking = فعّل تتبع الوقت
+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 = اسم المستودع الذي أدخلته خطأ.
@@ -1475,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
@@ -1503,7 +1714,7 @@ username_has_not_been_changed = لم يتم تغيير اسم المستخدم
username_change_not_local_user = المستخدمين غير المحليين غير مسموح لهم بتغيير أسماؤهم.
captcha_incorrect = الكابتشا خاطئة.
AdminEmail = عنوان البريد الإلكتروني للمدير
-team_no_units_error = اسمح الوصول بعلى الأقل قسم مستودع واحد.
+team_no_units_error = اسمح بالوصول إلى قسم واحد على الأقل في المستودعات.
must_use_public_key = المفتاح الذي قدمته هو مفتاح خاص. من فضلك لا ترفع مفتاحك الخاص في أي مكان. استخدم مفتاحك العام بدلاً من ذلك.
unable_verify_ssh_key = "تعذر التحقق من مفتاح الـSSH، تأكد منه مجدداً."
invalid_gpg_key = فشل تحقق مفتاح الـGPG: %s
@@ -1520,6 +1731,13 @@ invalid_ssh_key = فشل تحقق مفتاح الـSSH: %s
AuthName = اسم الأذن
SSPISeparatorReplacement = الفاصلة
openid_been_used = عنوان الـOpenID "%s" مُستخدم بالفعل.
+git_ref_name_error = `يجب أن يكون اسمًا مرجعيًا جيدًا لـ Git.`
+include_error = ` يجب أن يحتوي على سلسلة فرعية "%s".`
+size_error = `يجب أن يكون بالحجم %s.'
+glob_pattern_error = `النمط الشامل غير صالح: %s.`
+CommitChoice = إختيار الإداع
+regex_pattern_error = ` نمط التعبير النمطي غير صالح: %s.`
+username_error = ` يُمكنه أن يحتوي على حروف إنجليزية وأرقام وشرطة ("-") وشرطة سفلية ("_") و نقطة (".") فقط. ويمكنه ان يبدأ وينتهي بحرف او برقم.`
[home]
filter = تصفيات أخرى
@@ -1589,29 +1807,50 @@ runners.status.active = نشيط
runners.status = الحالة
runners.description = الوصف
runners.update_runner = حدّث التغييرات
-runners.name = الأسم
-runners.version = الأصدار
+runners.name = الاسم
+runners.version = النسخة
runs.status = الحالة
status.unknown = "مجهول"
runners.owner_type = النوع
status.waiting = "ينتظر"
-runners.labels = التسميات
+runners.labels = التصنيفات
runners.status.unspecified = مجهول
runs.commit = إيداع
status.success = "نجح"
-runs.no_workflows.documentation =
+runs.no_workflows.documentation =لمعرفة المزيد عن إجراءات فورجيو، برجاء رؤية التوثيق.
runs.empty_commit_message = (رسالة إيداع فارغة)
status.cancelled = "ملغي"
runs.status_no_select = كل الحالات
runs.scheduled = مُجدوَل
-variables.edit = تعديل المتغير
-variables.update.success = تم تحرير المتغير.
+variables.edit = عدّل المتغير
+variables.update.success = عُدِّل المتغير.
variables.update.failed = فشل تعديل المتغير.
variables.deletion.failed = فشل حذف المتغير.
variables.creation.failed = فشل إضافة المتغير.
variables.creation.success = تم إضافة المتغير "%s".
variables.deletion.success = تم حذف المتغير.
-variables.id_not_exist = المتغير ذو المعرّف %d ليس موجود.
+variables.id_not_exist = المتغير ذو المعرّف %d ليس موجودا.
+actions = الإجراءات
+unit.desc = أدر الإجراءات
+status.skipped = متخطى
+runners = المشغلون
+runners.runner_manage_panel = إدارة المشغلين
+runners.new = أنشئ مشغلا جديدا
+runners.new_notice = كيف تبدأ مشغلا (بالإنجليزية)
+runners.id = المعرّف
+runners.last_online = آخر مرة كان متصلا
+runners.none = لا مشغّل متاح
+runners.status.offline = غير متصل
+runs.pushed_by = دفعه
+runs.no_matching_online_runner_helper = لا يوجد
+runners.edit_runner = عدّل المشغّل
+runners.update_runner_success = نجح تحديث المشغّل
+runners.update_runner_failed = تعذر تحديث المشغّل
+runners.delete_runner = احذف هذا المشغّل
+runners.delete_runner_success = نجح حذف المشغّل
+runners.delete_runner_failed = تعذر حذف المشغّل
+runners.delete_runner_header = تأكيد حذف هذا المشغّل
+variables.description = تمرر المتغيرات إلى إجراءات معينة ولا يمكن قراءتها بطريقة أخرى.
[modal]
no = لا
@@ -1636,6 +1875,7 @@ symbolic_link = رابط رمزي
invalid_input_type = لا يمكنك رفع ملفات من هذا النوع.
default_message = اسحب الملفات أو اضغط هنا لرفعها.
file_too_big = حجم الملف ({{filesize}} مب) يتعدى الحد الأقصى ({{maxFilesize}} مب).
+remove_file = أزل الملف
[notification]
notifications = الإشعارات
@@ -1673,6 +1913,42 @@ compare_commits_general = قارن الإيداعات
review_dismissed_reason = السبب:
compare_branch = قارن
compare_commits = قارن %d إيداع
+create_repo = أنشأ المستودع %s
+rename_repo = غيّر اسم المستودع %[1]s
إلى %[3]s
+commit_repo = دفع إلى %[3]s في %[4]s
+create_issue = `أنشأ المسألة %[3]s#%[2]s`
+close_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`
+comment_issue = `علّق على المسألة %[3]s#%[2]s`
+comment_pull = `علّق على طلب الدمج %[3]s#%[2]s`
+merge_pull_request = `دمج الطلب %[3]s#%[2]s`
+create_branch = أنشأ الفرع %[3]s في %[4]s
+transfer_repo = نقل ملكية المستودع %s
إلى %s
+push_tag = دفع الوسم %[3]s إلى %[4]s
+delete_tag = حذف الوسم %[2]s من %[3]s
+delete_branch = حذف الفرع %[2]s من %[3]s
+approve_pull_request = `قبِل %[3]s#%[2]s`
+reject_pull_request = `اقترح تغييرات في %[3]s#%[2]s`
+publish_release = `أصدر "%[4]s" في %[3]s`
[units]
-unit = وحدة
\ No newline at end of file
+unit = وحدة
+error.no_unit_allowed_repo = ليس مسموحا لك الوصول إلى أي قسم في هذا المستودع.
+error.unit_not_allowed = ليس مسموحا لك الوصول إلى هذا القسم في المستودع.
+
+[gpg]
+default_key = موقّع بالمفتاح المبدئي
+error.extract_sign = تعذّر استخراج التوقيع
+error.generate_hash = تعذّر إنشاء بصمة الإيداع
+error.no_committer_account = لا حساب مرتبط ببريد المودِع
+error.not_signed_commit = "ليس إيداعًا موقّعًا"
+error.failed_retrieval_gpg_keys = "تعذّر جلب مفتاح مرتبط بحساب المودِع"
+
+[graphs]
+component_loading = يحمّل %s...
+component_loading_failed = تعذر تحميل %s
+component_loading_info = قد يحتاج هذا وقتا…
+component_failed_to_load = حدث خطأ غير متوقع.
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
new file mode 100644
index 0000000000..3eca60f551
--- /dev/null
+++ b/options/locale/locale_bg.ini
@@ -0,0 +1,1385 @@
+
+
+
+[settings]
+ui = Тема
+delete_key = Премахване
+applications = Приложения
+visibility = Видимост на потребителя
+location = Местоположение
+password = Парола
+appearance = Облик
+new_password = Нова парола
+oauth2_application_edit = Редактиране
+repos = Хранилища
+can_write_info = Писане
+delete = Изтриване на акаунта
+social = Социални акаунти
+twofa = Двуфакторно удостоверяване (TOTP)
+update_theme = Обновяване на темата
+can_read_info = Четене
+access_token_deletion_confirm_action = Изтриване
+website = Уебсайт
+cancel = Отказ
+delete_token = Изтриване
+uid = UID
+language = Език
+save_application = Запазване
+privacy = Поверителност
+avatar = Профилна снимка
+add_key = Добавяне на ключ
+account_link = Свързани акаунти
+delete_email = Премахване
+update_language = Обновяване на езика
+organization = Организации
+link_account = Свързване на акаунт
+add_new_gpg_key = Добавяне на GPG ключ
+manage_gpg_keys = Управление на GPG ключовете
+manage_ssh_keys = Управление на SSH ключовете
+old_password = Текуща парола
+public_profile = Публичен профил
+full_name = Пълно име
+security = Сигурност
+add_new_key = Добавяне на SSH ключ
+account = Акаунт
+update_avatar = Обновяване на профилната снимка
+ssh_gpg_keys = SSH / GPG Ключове
+comment_type_group_milestone = Етап
+manage_emails = Управление на адресите на ел. поща
+permission_read = Четене
+update_password = Обновяване на паролата
+biography_placeholder = Разкажете ни малко за себе си! (Можете да използвате Markdown)
+orgs = Управление на организациите
+continue = Продължаване
+blocked_users = Блокирани потребители
+emails = Адреси на ел. поща
+update_profile = Обновяване на профила
+profile = Профил
+change_password = Промяна на паролата
+retype_new_password = Потвърдете новата парола
+choose_new_avatar = Изберете нова профилна снимка
+delete_current_avatar = Изтриване на текущата профилна снимка
+gpg_key_deletion_success = GPG ключът е премахнат.
+permission_no_access = Без достъп
+ssh_key_deletion_success = SSH ключът е премахнат.
+comment_type_group_project = Проект
+update_language_success = Езикът е обновен.
+add_key_success = SSH ключът "%s" е добавен.
+add_gpg_key_success = GPG ключът "%s" е добавен.
+user_unblock_success = Потребителят е отблокиран успешно.
+user_block_success = Потребителят е блокиран успешно.
+update_profile_success = Профилът ви е обновен.
+update_user_avatar_success = Профилната снимка на потребителя е обновена.
+remove_oauth2_application_success = Приложението е изтрито.
+email_deletion_success = Адресът на ел. поща е премахнат.
+update_avatar_success = Профилната ви снимка е обновена.
+change_username = Потребителското ви име е променено.
+comment_type_group_assignee = Изпълнител
+enable_custom_avatar = Използване на персонализирана профилна снимка
+requires_activation = Изисква активиране
+activated = Активиран
+primary = Основен
+email_deletion = Премахване на адреса на ел. поща
+add_new_email = Добавяне на нов адрес на ел. поща
+add_email = Добавяне на адрес на ел. поща
+key_content_gpg_placeholder = Започва с "-----BEGIN PGP PUBLIC KEY BLOCK-----"
+comment_type_group_title = Заглавие
+comment_type_group_label = Етикет
+change_username_prompt = Забележка: Промяната на потребителското ви име променя също URL на вашия акаунт.
+update_language_not_found = Езикът "%s" не е наличен.
+keep_activity_private_popup = Прави дейността видима само за вас и администраторите
+uploaded_avatar_not_a_image = Каченият файл не е изображение.
+uploaded_avatar_is_too_big = Размерът на качения файл (%d KiB) надвишава максималния размер (%d KiB).
+change_password_success = Паролата ви е обновена. Влизайте с новата си парола от сега нататък.
+manage_themes = Избор на тема по подразбиране
+manage_openid = Управление на OpenID адресите
+primary_email = Да е основен
+keep_email_private = Скриване на адреса на ел. поща
+theme_update_error = Избраната тема не съществува.
+theme_update_success = Темата ви е обновена.
+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"
+hide_openid = Скриване от профила
+key_content = Съдържание
+ssh_key_deletion = Премахване на SSH ключ
+gpg_key_deletion = Премахване на GPG ключ
+key_name = Име на ключа
+key_id = ID на ключа
+show_openid = Показване в профила
+visibility.public = Публична
+visibility.limited = Ограничена
+visibility.private = Частна
+location_placeholder = Споделете приблизителното си местоположение с другите
+key_signature_gpg_placeholder = Започва с "-----BEGIN PGP SIGNATURE-----"
+key_signature_ssh_placeholder = Започва с "-----BEGIN SSH SIGNATURE-----"
+saved_successfully = Настройките бяха запазени успешно.
+no_activity = Няма скорошна дейност
+theme_desc = Това ще бъде вашата тема по подразбиране в целия сайт.
+keep_activity_private = Скриване на дейността от профилната страница
+lookup_avatar_by_mail = Търсене на профилна снимка по адреса на ел. поща
+password_incorrect = Текущата парола е неправилна.
+change_username_redirect_prompt = Старото потребителско име ще се пренасочва, докато някой не го вземе.
+principal_content = Съдържание
+manage_ssh_principals = Управление на SSH Certificate Principals
+twofa_disabled = Двуфакторното удостоверяване е изключено.
+orgs_none = Не сте участник в никакви организации.
+repos_none = Не притежавате никакви хранилища.
+blocked_users_none = Няма блокирани потребители.
+profile_desc = Контролирайте как вашият профил се показва на другите потребители. Вашият основен адрес на ел. поща ще се използва за известия, възстановяване на паролата и уеб базирани Git операции.
+permission_write = Четене и Писане
+twofa_disable = Изключване на двуфакторното удостоверяване
+twofa_enroll = Включване на двуфакторно удостоверяване
+ssh_key_name_used = Вече съществува SSH ключ със същото име във вашия акаунт.
+
+[packages]
+container.labels.value = Стойност
+alpine.repository.repositories = Хранилища
+dependency.version = Версия
+title = Пакети
+empty = Все още няма пакети.
+empty.documentation = За повече информация относно регистъра на пакетите вижте документацията.
+container.labels.key = Ключ
+requirements = Изисквания
+details = Подробности
+details.license = Лиценз
+container.labels = Етикети
+versions = Версии
+empty.repo = Качихте ли пакет, но той не се показва тук? Отидете в настройките за пакети и го свържете към това хранилище.
+keywords = Ключови думи
+details.author = Автор
+about = Относно този пакет
+settings.delete.success = Пакетът бе изтрит.
+settings.delete = Изтриване на пакета
+container.details.platform = Платформа
+settings.delete.error = Неуспешно изтриване на пакет.
+
+[tool]
+hours = %d часа
+now = сега
+raw_seconds = секунди
+1m = 1 минута
+1s = 1 секунда
+months = %d месеца
+weeks = %d седмици
+1w = 1 седмица
+years = %d години
+seconds = %d секунди
+days = %d дни
+1d = 1 ден
+minutes = %d минути
+1mon = 1 месец
+1h = 1 час
+1y = 1 година
+future = бъдеще
+raw_minutes = минути
+
+[common]
+language = Език
+cancel = Отказ
+captcha = CAPTCHA
+create_new = Създаване…
+preview = Преглеждане
+disabled = Изключено
+licenses = Лицензи
+sign_in = Вход
+copy_content = Копиране на съдържанието
+user_profile_and_more = Профил и Настройки…
+view = Преглед
+your_settings = Настройки
+mirrors = Огледала
+explore = Разглеждане
+write = Писане
+twofa = Двуфакторно удостоверяване
+version = Версия
+copy_success = Копирано!
+help = Помощ
+loading = Зареждане…
+name = Име
+sign_in_or = или
+edit = Редактиране
+concept_code_repository = Хранилище
+page = Страница
+forks = Разклонения
+concept_user_organization = Организация
+link_account = Свързване на акаунт
+your_profile = Профил
+sign_out = Изход
+settings = Настройки
+locked = Заключено
+error = Грешка
+dashboard = Табло
+logo = Лого
+toc = Съдържание
+copy_url = Копиране на URL
+new_mirror = Ново огледало
+re_type = Потвърдете паролата
+copy = Копиране
+enabled = Включено
+new_org = Нова организация
+milestones = Етапи
+rss_feed = RSS Емисия
+never = Никога
+new_project = Нов проект
+your_starred = Отбелязани
+value = Стойност
+sources = Източници
+notifications = Известия
+repository = Хранилище
+add_all = Добавяне на всичко
+new_project_column = Нова колона
+add = Добавяне
+organization = Организация
+new_migrate = Нова миграция
+save = Запазване
+sign_in_with_provider = Влизане с %s
+ok = Добре
+manage_org = Управление на организациите
+new_repo = Ново хранилище
+register = Регистрация
+mirror = Огледало
+username = Потребителско име
+password = Парола
+template = Шаблон
+signed_in_as = Влезли сте като
+sign_up = Регистриране
+enable_javascript = Този сайт изисква JavaScript.
+home = Начало
+email = Адрес на ел. поща
+issues = Задачи
+retry = Повторен опит
+remove = Премахване
+admin_panel = Управление на сайта
+account_settings = Настройки на акаунта
+powered_by = Осъществено от %s
+pull_requests = Заявки за сливане
+collaborative = Съвместни
+all = Всички
+activities = Дейности
+new_fork = Ново разклонение на хранилище
+unpin = Откачване
+pin = Закачване
+
+[repo]
+issues.context.edit = Редактиране
+stargazers = Отбелязали със звезда
+template = Шаблон
+issues.delete = Изтриване
+repo_lang = Език
+unwatch = Прекратяване на наблюдаването
+settings.slack_username = Потребителско име
+settings.discord_username = Потребителско име
+issues.filter_sort.mostforks = Най-много разклонения
+activity = Дейност
+issues = Задачи
+settings.update_settings = Обновяване на настройките
+visibility = Видимост
+settings.site = Уебсайт
+watchers = Наблюдаващи
+projects.new = Нов проект
+issues.dependency.remove = Премахване
+issues.filter_sort.moststars = Най-много звезди
+desc.template = Шаблон
+activity.new_issues_count_n = Нови задачи
+settings.options = Хранилище
+activity.overview = Обзор
+fork = Разклоняване
+wiki.new_page = Страница
+commitstatus.error = Грешка
+projects.description_placeholder = Описание
+issues.filter_sort.feweststars = Най-малко звезди
+code = Код
+repo_desc = Описание
+no_desc = Няма описание
+milestones = Етапи
+issues.label_description = Описание
+wiki.page = Страница
+settings.delete_collaborator = Премахване
+settings.units.overview = Обзор
+watch = Наблюдаване
+issues.filter_sort.fewestforks = Най-малко разклонения
+star = Отбелязване
+issues.edit = Редактиране
+issues.create = Създаване на задача
+activity.new_issues_count_1 = Нова задача
+milestones.desc = Описание
+issues.new.milestone = Етап
+issues.new = Нова задача
+release.source_code = Програмен код
+settings = Настройки
+forks = Разклонения
+editor.or = или
+search = Търсене
+issues.new_label_desc_placeholder = Описание
+watch_guest_user = Влезте, за да наблюдавате това хранилище.
+migrate_items_milestones = Етапи
+unstar = Премахване на звездата
+owner = Притежател
+issues.num_comments_1 = %d коментар
+issues.context.delete = Изтриване
+issues.label_title = Име
+issues.save = Запазване
+issues.label_edit = Редактиране
+issues.label_delete = Изтриване
+issues.previous = Предишна
+create_repo = Създаване на хранилище
+template_helper = Хранилището да е шаблон
+repo_name = Име на хранилището
+issues.label.filter_sort.alphabetically = По азбучен ред
+settings.event_repository = Хранилище
+issues.label.filter_sort.reverse_alphabetically = По низходящ азбучен ред
+issues.filter_sort.oldest = Най-стари
+issues.filter_sort = Подреждане
+issues.filter_sort.latest = Най-нови
+projects.column.new = Нова колона
+issues.closed_title = Затворени
+issues.context.quote_reply = Цитиране в отговор
+issues.context.reference_issue = Препратка в нова задача
+milestones.title = Заглавие
+packages = Пакети
+settings.title = Заглавие
+pulls.status_checks_details = Подробности
+issues.context.copy_link = Копиране на връзката
+projects.column.new_submit = Създаване на колона
+projects = Проекти
+projects.create = Създаване на проект
+projects.title = Заглавие
+issues.filter_sort.recentupdate = Наскоро обновени
+issues.filter_sort.leastupdate = Отдавна обновени
+issues.next = Следваща
+issues.open_title = Отворени
+pulls.made_using_agit = AGit
+issues.num_comments = %d коментара
+issues.filter_sort.leastcomment = Най-малко коментирани
+issues.filter_sort.mostcomment = Най-много коментирани
+issues.keyword_search_unavailable = В момента търсенето по ключова дума не е налично. Моля, свържете се с вашия администратор на сайта.
+repo_desc_helper = Въведете кратко описание (опционално)
+mirror_address = Клониране от URL
+owner_helper = Някои организации може да не се показват в падащото меню поради ограничение за максимален брой хранилища.
+new_repo_helper = Хранилище съдържа всички файлове на проекта, включително хронологията на ревизиите. Вече хоствате хранилище другаде? Мигрирайте хранилище.
+repo_name_helper = Добрите имена на хранилища използват кратки, запомнящи се и уникални ключови думи.
+migrated_from = Мигрирано от %[2]s
+visibility_description = Само притежателят или участниците в организацията, ако имат права, ще могат да го видят.
+projects.description = Описание (опционално)
+template_select = Изберете шаблон.
+visibility_helper = Хранилището да е частно
+license = Лиценз
+license_helper = Изберете лицензионен файл.
+readme = README
+migrate.clone_address = Мигриране / Клониране от URL
+migrated_from_fake = Мигрирано от %[1]s
+migrate.migrate = Мигриране от %s
+settings.search_user_placeholder = Потърсете потребител…
+issues.new_label = Нов етикет
+issues.new_label_placeholder = Име на етикета
+issues.label_count = %d етикета
+issues.new.labels = Етикети
+issues.new.clear_labels = Изчистване на етикетите
+issues.create_label = Създаване на етикет
+issues.filter_label_no_select = Всички етикети
+milestones.filter_sort.least_issues = Най-малко задачи
+milestones.filter_sort.most_issues = Най-много задачи
+settings.add_webhook = Добавяне на уеб-кука
+template.webhooks = Уеб-куки
+issues.label_templates.info = Все още няма етикети. Създайте етикет с "Нов етикет" или използвайте предварително зададен набор от етикети:
+labels = Етикети
+license_helper_desc = Лицензът определя какво могат и какво не могат да правят другите с вашия код. Не сте сигурни кой е подходящ за вашия проект? Вижте Избиране на лиценз.
+issues.choose.blank = По подразбиране
+settings.hooks = Уеб-куки
+issue_labels = Етикети за задачите
+issue_labels_helper = Изберете набор от етикети за задачите.
+readme_helper_desc = Това е мястото, където можете да напишете пълно описание на вашия проект.
+repo_gitignore_helper = Изберете .gitignore шаблони.
+auto_init = Да се инициализира хранилище (Добавя .gitignore, Лиценз и README)
+template.issue_labels = Етикети за задачите
+migrate_items_labels = Етикети
+issues.label_templates.title = Зареждане на предварително зададен набор от етикети
+issues.label_templates.helper = Изберете набор от етикети
+projects.template.desc = Шаблон
+projects.card_type.text_only = Само текст
+projects.card_type.images_and_text = Изображения и текст
+wiki = Уики
+wiki.welcome = Добре дошли в Уикито.
+wiki.create_first_page = Създаване на първата страница
+editor.upload_file = Качване на файл
+projects.column.color = Цвят
+editor.cancel_lower = Отказ
+pulls = Заявки за сливане
+editor.upload_files_to_dir = Качване на файлове в "%s"
+settings.slack_color = Цвят
+issues.label_color = Цвят
+create_new_repo_command = Създаване на ново хранилище в командния ред
+editor.new_file = Нов файл
+wiki.welcome_desc = Уикито ви позволява да пишете и споделяте документация със сътрудници.
+wiki.cancel = Отказ
+projects.template.desc_helper = Изберете шаблон за проекта, за да започнете
+issues.cancel = Отказ
+settings.transfer_owner = Нов притежател
+wiki.new_page_button = Нова страница
+commit_graph.color = Цвят
+projects.create_success = Проектът "%s" е създаден.
+projects.type.none = Няма
+projects.new_subheader = Координирайте, проследявайте и обновявайте работата си на едно място, така че проектите да останат прозрачни и по график.
+projects.open = Отваряне
+projects.close = Затваряне
+milestones.new = Нов етап
+milestones.cancel = Отказ
+settings.http_method = HTTP Метод
+clone_helper = Нуждаете се от помощ за клониране? Посетете Помощ.
+migrate_items_pullrequests = Заявки за сливане
+migrate_items_wiki = Уики
+quick_guide = Бързо ръководство
+clone_this_repo = Клонирайте това хранилище
+push_exist_repo = Изтласкване на съществуващо хранилище от командния ред
+editor.cancel = Отказ
+projects.column.new_title = Име
+projects.column.edit_title = Име
+fork_from = Разклоняване от
+diff.comment.placeholder = Оставете коментар
+projects.edit = Редактиране на проекта
+projects.modify = Редактиране на проекта
+issues.new.no_label = Няма етикет
+issues.new.title_empty = Заглавието не може да бъде празно
+issues.new.projects = Проекти
+issues.new.clear_projects = Изчистване на проектите
+issues.new.no_projects = Няма проект
+issues.new.open_projects = Отворени проекти
+issues.new.closed_projects = Затворени проекти
+milestones.update_ago = Обновен %s
+issues.filter_type.assigned_to_you = Възложени на вас
+issues.filter_type.created_by_you = Създадени от вас
+issues.filter_type.mentioning_you = Споменаващи ви
+issues.filter_sort.farduedate = Най-далечен краен срок
+issues.filter_sort.nearduedate = Най-близък краен срок
+wiki.edit_page_button = Редактиране
+activity.period.monthly = 1 месец
+activity.period.quarterly = 3 месеца
+activity.period.semiyearly = 6 месеца
+activity.title.user_1 = %d потребител
+activity.title.user_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.closed_issues_count_1 = Затворена задача
+activity.closed_issues_count_n = Затворени задачи
+activity.title.issues_1 = %d задача
+activity.title.issues_n = %d задачи
+wiki.pages = Страници
+activity.git_stats_author_1 = %d автор
+activity.git_stats_and_deletions = и
+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.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 активна заявка за сливане
+wiki.page_title = Заглавие на страницата
+wiki.page_content = Съдържание на страницата
+wiki.filter_page = Филтриране на страница
+wiki.back_to_wiki = Обратно към уики страницата
+wiki.wiki_page_revisions = Ревизии на уики страницата
+wiki.file_revision = Ревизия на страницата
+activity.title.issues_created_by = %s създадена от %s
+wiki.delete_page_notice_1 = Изтриването на уики страницата "%s" не може да бъде отменено. Продължаване?
+wiki.page_name_desc = Въведете име за тази уики страница. Някои специални имена са: "Home", "_Sidebar" и "_Footer".
+wiki.page_already_exists = Вече съществува уики страница със същото име.
+wiki.reserved_page = Името на уики страницата "%s" е резервирано.
+wiki.last_updated = Последно обновяване %s
+settings.event_release = Издание
+wiki.desc = Пишете и споделяйте документация със сътрудници.
+wiki.default_commit_message = Напишете бележка относно това обновяване на страницата (опционално).
+release.releases = Издания
+wiki.last_commit_info = %s редактира тази страница %s
+migrate_items_releases = Издания
+release = Издание
+releases = Издания
+settings.desc = Настройките са мястото, където можете да управлявате настройките за хранилището
+settings.external_wiki_url_error = URL на външното уики не е валиден URL.
+settings.external_wiki_url = URL на външно уики
+settings.confirm_wiki_delete = Изтриване на данните на уикито
+settings.delete = Изтриване на това хранилище
+settings.deletion_success = Хранилището е изтрито.
+settings.update_settings_success = Настройките на хранилището са обновени.
+settings.confirm_delete = Изтриване на хранилището
+settings.add_collaborator_success = Сътрудникът е добавен.
+settings.event_wiki = Уики
+settings.delete_desc = Изтриването на хранилище е перманентно и не може да бъде отменено.
+settings.advanced_settings = Разширени настройки
+settings.delete_notices_1 = - Тази операция НЕ МОЖЕ да бъде отменена.
+settings.enter_repo_name = Въведете имената на притежателя и хранилището точно както е показано:
+settings.use_external_wiki = Използване на външно уики
+settings.wiki_delete_desc = Изтриването на данните на уикито на хранилището е перманентно и не може да бъде отменено.
+settings.wiki_delete = Изтриване на данните на уикито
+settings.collaboration.admin = Администратор
+settings.collaboration = Сътрудници
+settings.collaboration.write = Писане
+settings.collaboration.read = Четене
+settings.collaboration.owner = Притежател
+settings.basic_settings = Основни настройки
+settings.wiki_desc = Включване на уики на хранилището
+settings.use_internal_wiki = Използване на вграденото уики
+settings.wiki_globally_editable = Позволяване на всеки да редактира уикито
+settings.add_collaborator = Добавяне на сътрудник
+repo_size = Размер на хранилището
+settings.danger_zone = Опасна зона
+issues.closed_by = от %[3]s бе затворена %[1]s
+issues.delete_comment_confirm = Сигурни ли сте, че искате да изтриете този коментар?
+issues.author_helper = Този потребител е авторът.
+issues.ref_closed_from = `затвори тази задача %[4]s %[2]s`
+milestones.due_date = Краен срок (опционално)
+settings.wiki_delete_notices_1 = - Това ще изтрие перманентно и ще деактивира уикито на хранилището %s.
+settings.wiki_deletion_success = Данните на уикито на хранилището са изтрити.
+settings.collaborator_deletion = Премахване на сътрудника
+settings.remove_collaborator_success = Сътрудникът е премахнат.
+settings.archive.header = Архивиране на това хранилище
+issues.filter_poster = Автор
+issues.commented_at = `коментира %s`
+settings.transfer_desc = Прехвърлете това хранилище на потребител или на организация, за които имате администраторски права.
+settings.archive.button = Архивиране на хранилището
+issues.role.owner_helper = Този потребител е притежателят на това хранилище.
+settings.delete_notices_2 = - Тази операция ще изтрие перманентно хранилището %s, включително кода, задачите, коментарите, данните на уикито и настройките за сътрудници.
+settings.admin_settings = Администраторски настройки
+issues.role.owner = Притежател
+settings.transfer = Прехвърляне на притежанието
+issues.author = Автор
+issues.closed_at = `затвори тази задача %[2]s`
+settings.collaborator_deletion_desc = Премахването на сътрудник ще отнеме достъпа му до това хранилище. Продължаване?
+commits.message = Съобщение
+issues.due_date_not_set = Няма зададен краен срок.
+issues.subscribe = Записване
+issues.no_content = Няма предоставено описание.
+issues.unsubscribe = Отписване
+issues.new.no_milestone = Няма етап
+issues.action_close = Затваряне
+issues.action_label = Етикет
+issues.action_milestone = Етап
+issues.opened_by_fake = отворена %[1]s от %[2]s
+issues.closed_by_fake = от %[2]s бе затворена %[1]s
+issues.due_date = Краен срок
+milestones.no_due_date = Няма краен срок
+commits.author = Автор
+commits.date = Дата
+issues.filter_project_all = Всички проекти
+issues.filter_label_select_no_label = Без етикет
+issues.filter_label = Етикет
+issues.filter_type.all_issues = Всички задачи
+issues.filter_poster_no_select = Всички автори
+issues.opened_by = отворена %[1]s от %[3]s
+issues.action_open = Отваряне
+pulls.closed_at = `затвори тази заявка за сливане %[2]s`
+pulls.reopened_at = `отвори наново тази заявка за сливане %[2]s`
+issues.reopened_at = `отвори наново тази задача %[2]s`
+projects.column.edit = Редактиране на колоната
+issues.close = Затваряне на задачата
+issues.ref_reopened_from = `отвори наново тази задача %[4]s %[2]s`
+projects.deletion = Изтриване на проекта
+projects.edit_success = Проектът "%s" е обновен.
+projects.deletion_success = Проектът е изтрит.
+issues.create_comment = Коментиране
+unescape_control_characters = Отекраниране
+editor.file_delete_success = Файлът "%s" е изтрит.
+projects.type.uncategorized = Некатегоризирано
+projects.column.set_default = Задаване по подразбиране
+projects.column.assigned_to = Възложено на
+issues.reopen_comment_issue = Коментиране и Отваряне
+issues.reopen_issue = Отваряне наново
+issues.close_comment_issue = Коментиране и Затваряне
+milestones.filter_sort.latest_due_date = Най-късен краен срок
+diff.view_file = Преглед на файла
+release.deletion_success = Изданието е изтрито.
+projects.column.delete = Изтриване на колоната
+migrate.migrating = Мигриране от %s ...
+escape_control_characters = Екраниране
+issues.label_deletion_success = Етикетът е изтрит.
+pulls.is_closed = Заявката за сливане е затворена.
+milestones.filter_sort.earliest_due_data = Най-ранен краен срок
+issues.filter_type = Тип
+issues.filter_milestones = Филтриране на етап
+issues.filter_assignees = Филтриране на изпълнител
+issues.filter_projects = Филтриране на проект
+issues.filter_labels = Филтриране на етикет
+issues.new.clear_milestone = Изчистване на етапа
+issues.new.open_milestone = Отворени етапи
+issues.new.closed_milestone = Затворени етапи
+issues.new.assignees = Възложена на
+issues.new.no_items = Няма елементи
+issues.new.no_assignees = Няма изпълнители
+issues.new.clear_assignees = Изчистване на изпълнителите
+issues.filter_project_none = Без проект
+issues.filter_milestone = Етап
+issues.filter_milestone_all = Всички етапи
+issues.filter_milestone_open = Отворени етапи
+issues.filter_milestone_none = Без етапи
+issues.filter_project = Проект
+issues.num_participants = %d участващи
+issues.filter_assignee = Изпълнител
+issues.filter_milestone_closed = Затворени етапи
+issues.filter_assginee_no_select = Всички изпълнители
+issues.filter_assginee_no_assignee = Без изпълнител
+activity.opened_prs_count_1 = Предложена заявка за сливане
+activity.opened_prs_count_n = Предложени заявки за сливане
+activity.title.prs_merged_by = %s слята от %s
+activity.merged_prs_label = Слята
+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 издания
+milestones.completeness = %d%% Завършен
+activity.title.prs_opened_by = %s предложена от %s
+issues.action_milestone_no_select = Без етап
+issues.action_assignee_no_select = Без изпълнител
+milestones.edit = Редактиране на етапа
+milestones.create_success = Етапът "%s" е създаден.
+milestones.create = Създаване на етап
+milestones.clear = Изчистване
+milestones.deletion = Изтриване на етапа
+milestones.edit_success = Етапът "%s" е обновен.
+milestones.modify = Обновяване на етапа
+milestones.deletion_success = Етапът е изтрит.
+milestones.filter_sort.most_complete = Най-много завършен
+milestones.filter_sort.least_complete = Най-малко завършен
+activity.git_stats_file_n = %d файла
+activity.git_stats_file_1 = %d файл
+issues.action_assignee = Изпълнител
+milestones.closed = Затворен %s
+milestones.open = Отваряне
+milestones.close = Затваряне
+issues.label_templates.use = Използване на набор от етикети
+issues.add_milestone_at = `добави това към етапа %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_project_at = `добави това към проекта %s %s`
+issues.remove_project_at = `премахна това от проекта %s %s`
+issues.remove_milestone_at = `премахна това от етапа %s %s`
+issues.change_title_at = `промени заглавието от %s на %s %s`
+template.avatar = Профилна снимка
+desc.sha256 = SHA256
+editor.filename_cannot_be_empty = Името на файла не може да бъде празно.
+release.title_empty = Заглавието не може да бъде празно.
+settings.webhook.payload = Съдържание
+settings.deploy_key_content = Съдържание
+clone_in_vsc = Клониране във VS Code
+use_template = Използване на този шаблон
+download_file = Изтегляне на файла
+editor.add_file = Добавяне на файл
+editor.edit_file = Редактиране на файла
+editor.this_file_locked = Файлът е заключен
+editor.delete_this_file = Изтриване на файла
+clone_in_vscodium = Клониране във VSCodium
+download_zip = Изтегляне на ZIP
+download_tar = Изтегляне на TAR.GZ
+desc.public = Публично
+desc.archived = Архивирано
+desc.internal = Вътрешно
+migrate_items_merge_requests = Заявки за Merge
+migrate_items_issues = Задачи
+fork_guest_user = Влезте, за да разклоните това хранилище.
+actions = Действия
+more_operations = Още операции
+download_archive = Изтегляне на хранилището
+branch = Клон
+tree = Дърво
+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 = Маркери
+star_guest_user = Влезте, за отбелязване на това хранилище със звезда.
+download_bundle = Изтегляне на BUNDLE
+desc.private = Частно
+settings.branches = Клонове
+editor.name_your_file = Име на файла…
+issues.label.filter_sort.by_size = Най-малък размер
+issues.delete.title = Изтриване на тази задача?
+pulls.new = Нова заявка за сливане
+pulls.no_results = Няма намерени резултати.
+release.title = Заглавие на изданието
+issues.unpin_issue = Откачване на задачата
+issues.pin_comment = закачи това %s
+issues.lock = Заключване на обсъждането
+issues.lock_confirm = Заключване
+issues.unlock_confirm = Отключване
+issues.due_date_form_edit = Редактиране
+issues.due_date_form_remove = Премахване
+issues.due_date_modified = промени крайния срок от %[2]s на %[1]s %[3]s
+pulls.compare_changes = Нова заявка за сливане
+activity.title.releases_published_by = %s публикувано от %s
+topic.manage_topics = Управление на темите
+topic.done = Готово
+find_file.go_to_file = Отиване към файл
+reactions_more = и още %d
+issues.unpin_comment = откачи това %s
+lines = реда
+line = ред
+editor.edit_this_file = Редактиране на файла
+editor.preview_changes = Преглеждане на промените
+default_branch = Стандартен клон
+default_branch_label = стандартен
+template.topics = Теми
+editor.branch_does_not_exist = Клонът "%s" не съществува в това хранилище.
+editor.no_changes_to_show = Няма промени за показване.
+issues.choose.get_started = Първи стъпки
+issues.change_milestone_at = `промени етапа от %s на %s %s`
+issues.change_project_at = `промени проекта от %s на %s %s`
+issues.self_assign_at = `си само-възложи това %s`
+issues.remove_assignee_at = `е премахнат като изпълнител от %s %s`
+issues.remove_self_assignment = `се само-премахна като изпълнител %s`
+issues.add_assignee_at = `му бе възложено това от %s %s`
+pulls.merged_by = от %[3]s бе слята %[1]s
+pulls.merged_by_fake = от %[2]s бе слята %[1]s
+issues.label_deletion = Изтриване на етикета
+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 = Няма указан клон/маркер
+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.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 = Потвърждаване
+no = Не
+modify = Обновяване
+cancel = Отказ
+yes = Да
+
+[editor]
+buttons.list.ordered.tooltip = Добавяне на номериран списък
+buttons.bold.tooltip = Добавяне на удебелен текст
+buttons.quote.tooltip = Цитиран текст
+buttons.code.tooltip = Добавяне на код
+buttons.list.unordered.tooltip = Добавяне на неномериран списък
+buttons.heading.tooltip = Добавяне на заглавка
+buttons.switch_to_legacy.tooltip = Използване на стария редактор
+buttons.list.task.tooltip = Добавяне на списък със задачи
+buttons.enable_monospace_font = Включване на равноширок шрифт
+buttons.mention.tooltip = Споменаване на потребител или екип
+buttons.italic.tooltip = Добавяне на курсив текст
+buttons.link.tooltip = Добавяне на връзка
+buttons.disable_monospace_font = Изключване на равноширокия шрифт
+buttons.ref.tooltip = Препратка към задача или заявка за сливане
+
+[org]
+teams.write_access = Писане
+settings.location = Местоположение
+team_desc = Описание
+teams.join = Присъединяване
+org_desc = Описание
+settings.update_settings = Обновяване на настройките
+teams.leave = Напускане
+settings = Настройки
+members.remove.detail = Премахване на %[1]s от %[2]s?
+settings.visibility = Видимост
+settings.options = Организация
+teams.leave.detail = Напускане на %s?
+teams.can_create_org_repo = Създаване на хранилища
+teams.settings = Настройки
+settings.website = Уебсайт
+code = Код
+members.remove = Премахване
+teams.all_repositories = Всички хранилища
+teams.update_settings = Обновяване на настройките
+settings.full_name = Пълно име
+members.leave = Напускане
+members.leave.detail = Напускане на %s?
+teams.read_access = Четене
+org_name_holder = Име на организацията
+create_org = Създаване на организация
+settings.visibility.public = Публична
+settings.visibility.limited_shortname = Ограничена
+settings.visibility.private_shortname = Частна
+settings.permission = Разрешения
+settings.visibility.limited = Ограничена (Видима само за удостоверени потребители)
+settings.visibility.private = Частна (Видима само за участниците в организацията)
+org_name_helper = Имената на организациите е добре да са кратки и запомнящи се.
+org_full_name_holder = Пълно име на организацията
+teams = Екипи
+lower_members = участници
+lower_repositories = хранилища
+settings.repoadminchangeteam = Админ. на хранилището да може да добавя и премахва достъп за екипи
+settings.email = Ел. поща за връзка
+settings.delete_account = Изтриване на тази организация
+settings.delete_org_title = Изтриване на организацията
+settings.confirm_delete_account = Потвърждаване на изтриването
+create_new_team = Нов екип
+create_team = Създаване на екип
+team_name = Име на екипа
+team_name_helper = Имената на екипите е добре да са кратки и запомнящи се.
+members = Участници
+team_desc_helper = Опишете предназначението или ролята на екипа.
+settings.delete = Изтриване на организацията
+follow_blocked_user = Не можете да следвате тази организация, защото тя ви е блокирала.
+settings.delete_prompt = Организацията ще бъде премахната завинаги. Това НЕ МОЖЕ да бъде отменено!
+settings.labels_desc = Добавете етикети, които могат да се използват за задачи за всички хранилища в тази организация.
+teams.none_access = Без достъп
+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 = Парола
+user = Потребителско име
+admin_email = Адрес на ел. поща
+path = Път
+password = Парола
+host = Хост
+ssl_mode = SSL
+install = Инсталация
+install_btn_confirm = Инсталиране на Forgejo
+app_name = Заглавие на сайта
+admin_name = Потреб. име за администратор
+confirm_password = Потвърдете паролата
+title = Първоначална конфигурация
+domain = Домейн на сървъра
+require_db_desc = Forgejo изисква MySQL, PostgreSQL, MSSQL, SQLite3 или TiDB (MySQL протокол).
+general_title = Общи настройки
+email_title = Настройки на ел. поща
+db_schema = Схема
+db_title = Настройки на базата данни
+db_type = Тип база данни
+db_name = Име на база данни
+optional_title = Опционални настройки
+mailer_user = SMTP Потребителско име
+mailer_password = SMTP Парола
+disable_gravatar = Изключване на Gravatar
+smtp_addr = SMTP Хост
+smtp_port = SMTP Порт
+app_name_helper = Можете да въведете името на компанията си тук.
+admin_title = Настройки на администраторския акаунт
+err_empty_admin_password = Администраторската парола не може да бъде празна.
+docker_helper = Ако стартирате Forgejo в Docker, моля, прочетете документацията преди да промените настройки.
+sqlite_helper = Път на файла за SQLite3 базата данни.
Въведете абсолютен път, ако стартирате Forgejo като service.
+err_empty_admin_email = Администраторският адрес на ел. поща не може да бъде празен.
+password_algorithm = Алгоритъм за хеш. на паролите
+default_keep_email_private = Скриване на адресите на ел. поща по подразбиране
+
+[filter]
+string.asc = А - Я
+string.desc = Я - А
+
+[mail]
+release.title = Заглавие: %s
+issue.action.close = @%[1]s затвори #%[2]d.
+register_success = Успешна регистрация
+release.downloads = Изтегляния:
+issue.x_mentioned_you = @%s ви спомена:
+release.download.zip = Програмен код (ZIP)
+release.download.targz = Програмен код (TAR.GZ)
+issue.in_tree_path = В %s:
+release.note = Бележка:
+hi_user_x = Здравейте %s,
+admin.new_user.user_info = Информация за потребителя
+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
+user_bio = Биография
+repositories = Хранилища
+activity = Публична дейност
+projects = Проекти
+code = Код
+overview = Обзор
+watched = Наблюдавани хранилища
+unfollow = Прекратяване на следването
+block = Блокиране
+settings = Потребителски настройки
+starred = Отбелязани хранилища
+following = Следвани
+unblock = Прекратяване на блокирането
+follow = Последване
+followers = Последователи
+block_user = Блокиране на потребителя
+change_avatar = Променете профилната си снимка…
+email_visibility.limited = Вашият адрес на ел. поща е видим за всички удостоверени потребители
+disabled_public_activity = Този потребител е изключил публичната видимост на дейността.
+email_visibility.private = Вашият адрес на ел. поща е видим само за вас и администраторите
+
+[home]
+filter = Други филтри
+show_archived = Архивирани
+search_repos = Намиране на хранилище…
+my_orgs = Моите организации
+uname_holder = Потреб. име или Адрес на ел. поща
+my_repos = Хранилища
+show_both_archived_unarchived = Показване на и архивирани и неархивирани
+feed_of = Емисия на "%s"
+issues.in_your_repos = Във вашите хранилища
+show_both_private_public = Показване на и публични и частни
+show_only_private = Показване само на частни
+show_private = Частни
+password_holder = Парола
+show_more_repos = Показване на повече хранилища…
+show_only_unarchived = Показване само на неархивирани
+my_mirrors = Моите огледала
+show_only_archived = Показване само на архивирани
+view_home = Преглед на %s
+collaborative_repos = Съвместни хранилища
+switch_dashboard_context = Превключване на контекста на таблото
+show_only_public = Показване само на публични
+
+[admin]
+packages.version = Версия
+packages.name = Име
+users.full_name = Пълно име
+dashboard = Табло
+repositories = Хранилища
+users.name = Потребителско име
+organizations = Организации
+repos.forks = Разклонения
+repos.stars = Звезди
+config.mailer_name = Име
+repos.name = Име
+orgs.name = Име
+users.edit = Редактиране
+config.db_user = Потребителско име
+config.db_name = Име
+first_page = Първа
+config.app_name = Заглавие на сайта
+packages.repository = Хранилище
+notices.type_1 = Хранилище
+config.domain = Домейн на сървъра
+users.max_repo_creation = Максимален брой хранилища
+defaulthooks = Уеб-куки по подразбиране
+auths.sspi_default_language = Потребителски език по подразбиране
+hooks = Уеб-куки
+systemhooks = Системни уеб-куки
+orgs.new_orga = Нова организация
+config.https_only = Само HTTPS
+users.update_profile_success = Потребителският акаунт бе обновен.
+users.new_success = Потребителският акаунт "%s" бе създаден.
+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 = Целта не може да бъде намерена.
+report_message = Ако смятате, че това е грешка на Forgejo, моля, потърсете в задачите на Codeberg или отворете нова задача, ако е необходимо.
+network_error = Мрежова грешка
+occurred = Възникна грешка
+
+[form]
+UserName = Потребителско име
+Email = Адрес на ел. поща
+Password = Парола
+RepoName = Име на хранилището
+username_been_taken = Потребителското име вече е заето.
+SSPIDefaultLanguage = Език по подразбиране
+password_not_match = Паролите не съвпадат.
+captcha_incorrect = CAPTCHA кодът е неправилен.
+username_change_not_local_user = Потребители, които не са локални не могат да променят потребителското си име.
+username_password_incorrect = Неправилно потребителско име или парола.
+user_not_exist = Потребителят не съществува.
+lang_select_error = Изберете език от списъка.
+HttpsUrl = HTTPS URL
+require_error = ` не може да бъде празно.`
+Retype = Потвърдете паролата
+url_error = `"%s" не е валиден URL.`
+Content = Съдържание
+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`
+rename_repo = преименува хранилище от %[1]s
на %[3]s
+review_dismissed_reason = Причина:
+comment_issue = `коментира в задача %[3]s#%[2]s`
+starred_repo = отбеляза със звезда %[2]s
+create_repo = създаде хранилище %s
+create_issue = `отвори задача %[3]s#%[2]s`
+reopen_pull_request = `отвори наново заявка за сливане %[3]s#%[2]s`
+create_pull_request = `създаде заявка за сливане %[3]s#%[2]s`
+reopen_issue = `отвори наново задача %[3]s#%[2]s`
+commit_repo = изтласка към %[3]s на %[4]s
+close_pull_request = `затвори заявка за сливане %[3]s#%[2]s`
+comment_pull = `коментира в заявка за сливане %[3]s#%[2]s`
+merge_pull_request = `сля заявка за сливане %[3]s#%[2]s`
+auto_merge_pull_request = `сля автоматично заявка за сливане %[3]s#%[2]s`
+watched_repo = започна да наблюдава %[2]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
+openid_connect_submit = Свързване
+sign_up_successful = Акаунтът е създаден успешно. Добре дошли!
+login_userpass = Влизане
+forgot_password = Забравена парола?
+sign_up_now = Нуждаете се от акаунт? Регистрирайте се сега.
+forgot_password_title = Забравена парола
+openid_register_title = Създаване на нов акаунт
+account_activated = Акаунтът е активиран
+social_register_helper_msg = Вече имате акаунт? Свържете го сега!
+verify = Потвърждаване
+create_new_account = Регистриране на акаунт
+active_your_account = Активирайте акаунта си
+register_helper_msg = Вече имате акаунт? Влезте сега!
+reset_password = Възстановяване на акаунта
+disable_register_prompt = Регистрирането е изключено. Моля, свържете се с вашия администратор на сайта.
+remember_me = Запомни ме
+openid_signin_desc = Въведете своя OpenID URI. Например: alice.openid.example.org или https://openid.example.org/alice.
+disable_register_mail = Потвърждението по ел. поща за регистрация е изключено.
+manual_activation_only = Свържете се с вашия администратор на сайта, за да завършите активирането.
+must_change_password = Обновете паролата си
+password_too_short = Дължината на паролата не може да бъде по-малка от %d знака.
+
+[aria]
+footer.software = Относно софтуера
+footer.links = Връзки
+footer = Долен колонтитул
+
+[startpage]
+install = Лесен за инсталиране
+lightweight = Лек
+license = Отворен код
+install_desc = Просто стартирайте двоичния файл за вашата платформа, използвайте Docker, или го получете пакетирано.
+app_desc = Безпроблемна Git услуга със самостоятелен хостинг
+platform = Междуплатформен
+lightweight_desc = Forgejo има ниски минимални изисквания и може да работи на икономичен Raspberry Pi. Спестете енергията на вашата машина!
+platform_desc = Forgejo работи навсякъде, където Go може да се компилира: Windows, macOS, Linux, ARM, и т.н. Изберете, което харесвате!
+license_desc = Вземете Forgejo! Присъединете се към нас, допринасяйки, за да направите този проект още по-добър. Не се колебайте да сътрудничите!
+
+[notification]
+subscriptions = Абонаменти
+unread = Непрочетени
+no_subscriptions = Няма абонаменти
+mark_as_unread = Отбелязване като непрочетено
+no_read = Няма прочетени известия.
+mark_as_read = Отбелязване като прочетено
+notifications = Известия
+read = Прочетени
+watching = Наблюдавани
+no_unread = Няма непрочетени известия.
+mark_all_as_read = Отбелязване на всички като прочетени
+pin = Закачване на известието
+
+[explore]
+code_search_results = Резултати от търсенето на "%s"
+go_to = Отиване към
+repos = Хранилища
+users = Потребители
+search = Търсене
+code = Код
+organizations = Организации
+code_last_indexed_at = Последно индексиран %s
+repo_no_results = Няма намерени съответстващи хранилища.
+user_no_results = Няма намерени съответстващи потребители.
+org_no_results = Няма намерени съответстващи организации.
+
+[actions]
+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 = По-малко
+number_of_contributions_in_the_last_12_months = %s приноса през последните 12 месеца
+no_contributions = Няма приноси
+more = Повече
+
+[git.filemode]
+directory = Директория
+symbolic_link = Символна връзка
+normal_file = Обикновен файл
+executable_file = Изпълним файл
+changed_filemode = %[1]s → %[2]s
+submodule = Подмодул
+
+
+[dropzone]
+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_bn.ini b/options/locale/locale_bn.ini
new file mode 100644
index 0000000000..8741fee98c
--- /dev/null
+++ b/options/locale/locale_bn.ini
@@ -0,0 +1,6 @@
+
+
+
+[common]
+help = সাহায্য
+dashboard = ড্যাশবোর্ড
\ No newline at end of file
diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini
index 5b0caf67c9..be798000f5 100644
--- a/options/locale/locale_cs-CZ.ini
+++ b/options/locale/locale_cs-CZ.ini
@@ -5,6 +5,7 @@ explore=Procházet
help=Nápověda
logo=Logo
sign_in=Přihlásit se
+sign_in_with_provider=Přihlásit se pomocí %s
sign_in_or=nebo
sign_out=Odhlásit se
sign_up=Registrovat se
@@ -17,6 +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 problémů
create_new=Vytvořit…
user_profile_and_more=Profily a nastavení…
signed_in_as=Přihlášen jako
@@ -29,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
@@ -80,6 +82,7 @@ milestones=Milníky
ok=OK
cancel=Zrušit
+retry=Znovu
rerun=Znovu spustit
rerun_all=Znovu spustit všechny úlohy
save=Uložit
@@ -87,14 +90,17 @@ add=Přidat
add_all=Přidat vše
remove=Odstranit
remove_all=Odstranit vše
-remove_label_str=`Odstranit položku "%s"`
+remove_label_str=Odstranit položku „%s“
edit=Upravit
+view=Zobrazit
enabled=Povolený
disabled=Zakázané
+locked=Uzamčeno
copy=Kopírovat
copy_url=Kopírovat URL
+copy_hash=Kopírovat hash
copy_content=Kopírovat obsah
copy_branch=Kopírovat jméno větve
copy_success=Zkopírováno!
@@ -107,6 +113,7 @@ loading=Načítá se…
error=Chyba
error404=Stránka, kterou se snažíte zobrazit, buď neexistuje, nebo nemáte oprávnění ji zobrazit.
+go_back=Zpět
never=Nikdy
unknown=Neznámý
@@ -128,11 +135,15 @@ concept_user_organization=Organizace
show_timestamps=Zobrazit časové značky
show_log_seconds=Zobrazit sekundy
show_full_screen=Zobrazit celou obrazovku
+download_logs=Stáhnout logy
+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
[aria]
navbar=Navigační lišta
@@ -155,7 +166,7 @@ buttons.code.tooltip=Přidat kód
buttons.link.tooltip=Přidat odkaz
buttons.list.unordered.tooltip=Přidat seznam odrážek
buttons.list.ordered.tooltip=Přidat číslovaný seznam
-buttons.list.task.tooltip=Přidat seznam úkolů
+buttons.list.task.tooltip=Přidat seznam úloh
buttons.mention.tooltip=Uveďte uživatele nebo tým
buttons.ref.tooltip=Odkaz na issue nebo pull request
buttons.switch_to_legacy.tooltip=Místo toho použít starší editor
@@ -168,16 +179,19 @@ string.desc=Z – A
[error]
occurred=Došlo k chybě
+report_message=Pokud jste si jisti, že se jedná o chybu Gitea, prosím vyhledejte problém na GitHub a v případě potřeby založte nový problém.
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
@@ -220,6 +234,7 @@ repo_path_helper=Všechny vzdálené repozitáře Gitu budou uloženy do tohoto
lfs_path=Kořenový adresář Git LFS
lfs_path_helper=V tomto adresáři budou uloženy soubory, které jsou sledovány Git LFS. Pokud ponecháte prázdné, LFS zakážete.
run_user=Spustit jako uživatel
+run_user_helper=Zadejte uživatelské jméno, pod kterým Gitea běží v operačním systému. Pozor: tento uživatel musí mít přístup ke kořenovému adresáři repozitářů.
domain=Doména serveru
domain_helper=Adresa domény, nebo hostitele serveru.
ssh_port=Port SSH serveru
@@ -266,13 +281,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_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
@@ -285,12 +300,17 @@ 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.
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
@@ -336,7 +356,7 @@ repo_no_results=Nebyly nalezeny žádné odpovídající repozitáře.
user_no_results=Nebyly nalezeni žádní odpovídající uživatelé.
org_no_results=Nebyly nalezeny žádné odpovídající organizace.
code_no_results=Nebyl nalezen žádný zdrojový kód odpovídající hledanému výrazu.
-code_search_results=`Výsledky hledání pro "%s"`
+code_search_results=Výsledky hledání pro „%s“
code_last_indexed_at=Naposledy indexováno %s
relevant_repositories_tooltip=Repozitáře, které jsou rozštěpení nebo nemají žádné téma, ikonu a žádný popis jsou skryty.
relevant_repositories=Zobrazují se pouze relevantní repositáře, zobrazit nefiltrované výsledky.
@@ -349,9 +369,11 @@ disable_register_prompt=Registrace jsou vypnuty. Prosíme, kontaktujte správce
disable_register_mail=E-mailové potvrzení o registraci je zakázané.
manual_activation_only=Pro dokončení aktivace kontaktujte správce webu.
remember_me=Pamatovat si toto zařízení
+remember_me.compromised=Přihlašovací token již není platný, což může znamenat napadení účtu. Zkontrolujte prosím svůj účet pro neobvyklé aktivity.
forgot_password_title=Zapomenuté heslo
forgot_password=Zapomenuté heslo?
sign_up_now=Potřebujete účet? Zaregistrujte se.
+sign_up_successful=Účet byl úspěšně vytvořen. Vítejte!
confirmation_mail_sent_prompt=Na adresu %s byl zaslán nový potvrzovací e-mail. Zkontrolujte prosím vaši doručenou poštu během následujících %s, abyste dokončili proces registrace.
must_change_password=Aktualizujte své heslo
allow_password_change=Vyžádat od uživatele změnu hesla (doporučeno)
@@ -359,6 +381,7 @@ reset_password_mail_sent_prompt=Na adresu %s byl zaslán potvrzovací e-m
active_your_account=Aktivujte si váš účet
account_activated=Účet byl aktivován
prohibit_login=Přihlášení zakázáno
+prohibit_login_desc=Vašemu účtu je zakázáno se přihlásit, kontaktujte prosím správce webu.
resent_limit_prompt=Omlouváme se, ale před chvílí jste požádal o zaslání aktivačního e-mailu. Počkejte prosím 3 minuty a pak to zkuste znovu.
has_unconfirmed_mail=Zdravím, %s, máte nepotvrzenou e-mailovou adresu (%s). Pokud jste nedostali e-mail pro potvrzení nebo potřebujete zaslat nový, klikněte prosím na tlačítku níže.
resend_mail=Klikněte zde pro odeslání aktivačního e-mailu
@@ -366,8 +389,10 @@ email_not_associate=Tato e-mailová adresa není spojena s žádným účtem.
send_reset_mail=Zaslat e-mail pro obnovení účtu
reset_password=Obnovení účtu
invalid_code=Tento potvrzující kód je neplatný nebo mu vypršela platnost.
+invalid_code_forgot_password=Váš potvrzovací kód je neplatný nebo mu vypršela platnost. Klikněte zde pro vytvoření nového kódu.
invalid_password=Vaše heslo se neshoduje s heslem, které bylo použito k vytvoření účtu.
reset_password_helper=Obnovit účet
+reset_password_wrong_user=Jste přihlášen/a jako %s, ale odkaz pro obnovení účtu je pro %s
password_too_short=Délka hesla musí být minimálně %d znaků.
non_local_account=Externě ověřovaní uživatelé nemohou aktualizovat své heslo prostřednictvím webového rozhraní Forgejo.
verify=Ověřit
@@ -392,6 +417,7 @@ openid_connect_title=Připojení k existujícímu účtu
openid_connect_desc=Zvolené OpenID URI není známé. Přidružte nový účet zde.
openid_register_title=Vytvořit nový účet
openid_register_desc=Zvolené OpenID URI není známé. Přidružte nový účet zde.
+openid_signin_desc=Zadejte vaši OpenID URI. Například: alice.openid.example.org nebo https://openid.example.org/alice.
disable_forgot_password_mail=Obnovení účtu je zakázáno, protože není nastaven žádný e-mail. Obraťte se na správce webu.
disable_forgot_password_mail_admin=Obnovení účtu je dostupné pouze po nastavení e-mailu. Pro povolení obnovy účtu nastavte prosím e-mail.
email_domain_blacklisted=Nemůžete se registrovat s vaší e-mailovou adresou.
@@ -401,13 +427,19 @@ authorize_application_created_by=Tuto aplikaci vytvořil %s.
authorize_application_description=Pokud povolíte přístup, bude moci přistupovat a zapisovat do všech vašich informací o účtu včetně soukromých repozitářů a organizací.
authorize_title=Autorizovat „%s“ pro přístup k vašemu účtu?
authorization_failed=Autorizace selhala
+authorization_failed_desc=Autorizace selhala, protože jsme detekovali neplatný požadavek. Kontaktujte prosím správce aplikace, kterou jste se pokoušeli autorizovat.
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 = Nemůžete odebrat posledního administrátora. Vždy musí existovat alespoň jeden administrátor.
[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
@@ -416,17 +448,18 @@ activate_account.text_1=Ahoj %[1]s, děkujeme za registraci na %[2]s!
activate_account.text_2=Pro aktivaci vašeho účtu do %s klikněte na následující odkaz:
activate_email=Ověřte vaši e-mailovou adresu
+activate_email.title=%s, prosím ověřte vaši e-mailovou adresu
activate_email.text=Pro aktivaci vašeho účtu do %s klikněte na následující odkaz:
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á
@@ -468,6 +501,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
@@ -500,8 +536,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ů.`
@@ -511,6 +547,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č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ý.
@@ -546,7 +583,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.
@@ -555,13 +592,23 @@ 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=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 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 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.`
+
[user]
change_avatar=Změnit váš avatar…
+joined_on=Přidal/a se %s
repositories=Repozitáře
activity=Veřejná aktivita
followers=Sledující
@@ -577,10 +624,20 @@ user_bio=Životopis
disabled_public_activity=Tento uživatel zakázal veřejnou viditelnost aktivity.
email_visibility.limited=Vaše e-mailová adresa je viditelná pro všechny ověřené uživatele
email_visibility.private=Vaše e-mailová adresa je viditelná pouze pro vás a administrátory
+show_on_map=Zobrazit toto místo na mapě
+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.
+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
@@ -598,9 +655,13 @@ delete=Smazat účet
twofa=Dvoufaktorové ověřování
account_link=Propojené účty
organization=Organizace
+uid=UID
webauthn=Bezpečnostní klíče
public_profile=Veřejný profil
+biography_placeholder=Řekněte nám něco o sobě! (Můžete použít Markdown)
+location_placeholder=Sdílejte svou přibližnou polohu s ostatními
+profile_desc=Nastavte, jak bude váš profil zobrazen ostatním uživatelům. Vaše hlavní e-mailová adresa bude použita pro oznámení, obnovení hesla a operace Git.
password_username_disabled=Externí uživatelé nemohou měnit svoje uživatelské jméno. Kontaktujte prosím svého administrátora pro více detailů.
full_name=Celé jméno
website=Web
@@ -608,15 +669,20 @@ location=Místo
update_theme=Aktualizovat motiv vzhledu
update_profile=Aktualizovat profil
update_language=Aktualizovat jazyk
-update_language_not_found=Jazyk "%s" není k dispozici.
+update_language_not_found=Jazyk „%s“ není k dispozici.
update_language_success=Jazyk byl aktualizován.
update_profile_success=Váš profil byl aktualizován.
change_username=Vaše uživatelské jméno bylo změněno.
+change_username_prompt=Poznámka: Změna uživatelského jména také změní URL vašeho účtu.
+change_username_redirect_prompt=Staré uživatelské jméno bude přesměrováváno, dokud nebude znovu obsazeno.
continue=Pokračovat
cancel=Zrušit
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 %s 中的团队 %s。
teams.invite.by=邀请人 %s
teams.invite.description=请点击下面的按钮加入团队。
+follow_blocked_user = 你无法关注此组织,因为此组织已屏蔽你。
[admin]
dashboard=管理面板
+self_check=自我检查
identity_access=身份及认证
users=帐户管理
organizations=组织管理
@@ -2772,6 +2831,7 @@ dashboard.delete_missing_repos=删除所有丢失 Git 文件的仓库
dashboard.delete_missing_repos.started=删除所有丢失 Git 文件的仓库任务已启动。
dashboard.delete_generated_repository_avatars=删除生成的仓库头像
dashboard.sync_repo_branches=将缺少的分支从 git 数据同步到数据库
+dashboard.sync_repo_tags=从 git 数据同步标签到数据库
dashboard.update_mirrors=更新镜像仓库
dashboard.repo_health_check=健康检查所有仓库
dashboard.check_repo_stats=检查所有仓库统计
@@ -2826,6 +2886,7 @@ dashboard.stop_endless_tasks=停止永不停止的任务
dashboard.cancel_abandoned_jobs=取消丢弃的任务
dashboard.start_schedule_tasks=开始调度任务
dashboard.sync_branch.started=分支同步已开始
+dashboard.sync_tag.started=标签同步已开始
dashboard.rebuild_issue_indexer=重建工单索引
users.user_manage_panel=用户帐户管理
@@ -2868,7 +2929,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=重置两步验证
@@ -3251,6 +3312,20 @@ notices.type_2=任务
notices.desc=提示描述
notices.op=操作
notices.delete_success=系统通知已被删除。
+dashboard.sync_repo_tags = 将 git 数据中的标签同步到数据库
+dashboard.sync_tag.started = 标签同步开始
+self_check = 自检
+self_check.no_problem_found = 未找到问题。
+self_check.database_collation_mismatch = 期望数据库使用排序规则:%s
+self_check.database_collation_case_insensitive = 数据库正在使用 %s 排序规则,这是一种不敏感的排序规则。 尽管 Forgejo 可以使用它,但在极少数情况下可能无法按照预期工作。
+self_check.database_inconsistent_collation_columns = 数据库正在使用 %s 排序规则,但与这些列使用的排序规则不匹配。 这可能会导致一些意想不到的问题。
+self_check.database_fix_mysql = 对于 MySQL/MariaDB 用户,您可以使用 "gitea doctor convert" 命令来修复排序规则问题,也可以通过SQL命令 "ALTER ... COLLATE ..." 来手动修复问题。
+self_check.database_fix_mssql = 对于 MSSQL 用户,目前您只能通过SQL命令 "ALTER ... COLLATE ..." 来手动修复问题。
+
+self_check.no_problem_found=尚未发现问题。
+self_check.database_collation_mismatch=期望数据库使用的校验方式:%s
+self_check.database_collation_case_insensitive=数据库正在使用一个校验 %s, 这是一个不敏感的校验. 虽然Gitea可以与它合作,但可能有一些罕见的情况不如预期的那样起作用。
+self_check.database_fix_mysql=对于MySQL/MariaDB用户,您可以使用“gitea doctor convert”命令来解决校验问题。 或者您也可以通过 "ALTER ... COLLATE ..." 这样的SQL 来手动解决这个问题。
[action]
create_repo=创建了仓库 %s
@@ -3436,6 +3511,9 @@ rpm.registry=从命令行设置此注册中心:
rpm.distros.redhat=在基于 RedHat 的发行版
rpm.distros.suse=在基于 SUSE 的发行版
rpm.install=要安装包,请运行以下命令:
+rpm.repository=仓库信息
+rpm.repository.architectures=架构
+rpm.repository.multiple_groups=此软件包可在多个组中使用。
rubygems.install=要使用 gem 安装软件包,请运行以下命令:
rubygems.install2=或将它添加到 Gemfile:
rubygems.dependencies.runtime=运行时依赖
@@ -3489,6 +3567,9 @@ owner.settings.cleanuprules.success.delete=清理规则已删除。
owner.settings.chef.title=Chef 注册中心
owner.settings.chef.keypair=生成密钥对
owner.settings.chef.keypair.description=需要密钥对才能向 Chef 注册中心进行身份验证。如果您之前已经生成过密钥对,生成新的密钥对将丢弃旧的密钥对。
+rpm.repository = 仓库信息
+rpm.repository.architectures = 架构
+rpm.repository.multiple_groups = 该软件包可在多个组中使用。
[secrets]
secrets=密钥
@@ -3568,8 +3649,8 @@ runs.actors_no_select=所有操作者
runs.status_no_select=所有状态
runs.no_results=没有匹配的结果。
runs.no_workflows=目前还没有工作流。
-runs.no_workflows.quick_start=不知道如何启动Gitea Action?请参阅 快速启动指南
-runs.no_workflows.documentation=更多有关 Gitea Action 的信息,请访问 文档。
+runs.no_workflows.quick_start=不知道如何使用 Gitea Actions吗?请查看 快速启动指南。
+runs.no_workflows.documentation=关于Gitea Actions的更多信息,请参阅 文档。
runs.no_runs=工作流尚未运行过。
runs.empty_commit_message=(空白的提交消息)
@@ -3588,7 +3669,7 @@ variables.none=目前还没有变量。
variables.deletion=删除变量
variables.deletion.description=删除变量是永久性的,无法撤消。继续吗?
variables.description=变量将被传给特定的 Actions,其它情况将不能读取
-variables.id_not_exist=ID %d 变量不存在。
+variables.id_not_exist=ID为 %d 的变量不存在。
variables.edit=编辑变量
variables.deletion.failed=删除变量失败。
variables.deletion.success=变量已被删除。
diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini
index b00c6e6b3d..289a3e7b5d 100644
--- a/options/locale/locale_zh-HK.ini
+++ b/options/locale/locale_zh-HK.ini
@@ -196,6 +196,7 @@ auth_failed=授權驗證失敗:%v
target_branch_not_exist=目標分支不存在
+
[user]
repositories=儲存庫列表
activity=公開活動
@@ -538,6 +539,8 @@ activity.merged_prs_label=已合併
activity.closed_issue_label=已關閉
activity.new_issues_count_1=建立問題
+contributors.contribution_type.commits=提交歷史
+
search=搜尋
settings=儲存庫設定
@@ -640,6 +643,8 @@ release.downloads=下載附件
+[graphs]
+
[org]
org_name_holder=組織名稱
org_full_name_holder=組織全名
@@ -916,6 +921,7 @@ notices.desc=描述
notices.op=操作
notices.delete_success=已刪除系統提示。
+
[action]
create_repo=建立了儲存庫 %s
rename_repo=重新命名儲存庫 %[1]s
為 %[3]s
diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini
index 604f727292..e49be30691 100644
--- a/options/locale/locale_zh-TW.ini
+++ b/options/locale/locale_zh-TW.ini
@@ -556,6 +556,7 @@ org_still_own_packages=此組織仍然擁有一個以上的套件,請先刪除
target_branch_not_exist=目標分支不存在
+
[user]
change_avatar=更改大頭貼...
repositories=儲存庫
@@ -641,7 +642,7 @@ uploaded_avatar_not_a_image=上傳的檔案不是圖片
update_avatar_success=您的大頭貼已更新
update_user_avatar_success=已更新使用者的大頭貼。
-change_password=更新密碼
+update_password=更新密碼
old_password=目前的密碼
new_password=新的密碼
retype_new_password=確認新密碼
@@ -1777,6 +1778,8 @@ activity.git_stats_and_deletions=和
activity.git_stats_deletion_1=刪除 %d 行
activity.git_stats_deletion_n=刪除 %d 行
+contributors.contribution_type.commits=提交歷史
+
search=搜尋
search.search_repo=搜尋儲存庫
search.type.tooltip=搜尋類型
@@ -2323,6 +2326,8 @@ error.csv.too_large=無法渲染此檔案,因為它太大了。
error.csv.unexpected=無法渲染此檔案,因為它包含了未預期的字元,於第 %d 行第 %d 列。
error.csv.invalid_field_count=無法渲染此檔案,因為它第 %d 行的欄位數量有誤。
+[graphs]
+
[org]
org_name_holder=組織名稱
org_full_name_holder=組織全名
@@ -2935,6 +2940,7 @@ notices.desc=描述
notices.op=操作
notices.delete_success=已刪除系統提示。
+
[action]
create_repo=建立了儲存庫 %s
rename_repo=重新命名儲存庫 %[1]s
為 %[3]s
@@ -3111,6 +3117,8 @@ pypi.requires=需要 Python
pypi.install=執行下列命令以使用 pip 安裝此套件:
rpm.registry=透過下列命令設定此註冊中心:
rpm.install=執行下列命令安裝此套件:
+rpm.repository=儲存庫資訊
+rpm.repository.architectures=架構
rubygems.install=執行下列命令以使用 gem 安裝此套件:
rubygems.install2=或將它加到 Gemfile:
rubygems.dependencies.runtime=執行階段相依性
diff --git a/package-lock.json b/package-lock.json
index 3572749b89..c833670f72 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "gitea",
+ "name": "forgejo",
"lockfileVersion": 3,
"requires": true,
"packages": {
@@ -18,59 +18,69 @@
"@webcomponents/custom-elements": "1.6.0",
"add-asset-webpack-plugin": "2.0.1",
"ansi_up": "6.0.2",
- "asciinema-player": "3.6.3",
+ "asciinema-player": "3.6.4",
+ "chart.js": "4.4.1",
+ "chartjs-adapter-dayjs-4": "1.0.4",
+ "chartjs-plugin-zoom": "2.0.1",
"clippie": "4.0.6",
"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",
"esbuild-loader": "4.0.3",
"escape-goat": "4.0.0",
"fast-glob": "3.3.2",
"htmx.org": "1.9.10",
+ "idiomorph": "0.3.0",
"jquery": "3.7.1",
"katex": "0.16.9",
"license-checker-webpack-plugin": "0.2.1",
- "lightningcss-loader": "2.1.0",
- "mermaid": "10.7.0",
+ "mermaid": "10.8.0",
"mini-css-extract-plugin": "2.8.0",
"minimatch": "9.0.3",
- "monaco-editor": "0.45.0",
+ "monaco-editor": "0.46.0",
"monaco-editor-webpack-plugin": "7.1.0",
- "pdfobject": "2.2.12",
+ "pdfobject": "2.3.0",
+ "postcss": "8.4.35",
+ "postcss-loader": "8.1.0",
"pretty-ms": "9.0.0",
"sortablejs": "1.15.2",
- "swagger-ui-dist": "5.11.2",
+ "swagger-ui-dist": "5.11.6",
+ "tailwindcss": "3.4.1",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0",
"tippy.js": "6.3.7",
"toastify-js": "1.12.0",
"tributejs": "5.1.3",
"uint8-to-base64": "0.2.0",
- "vue": "3.4.15",
+ "vue": "3.4.19",
"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.1",
+ "webpack": "5.90.2",
"webpack-cli": "5.1.4",
"wrap-ansi": "9.0.0"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.1.0",
- "@playwright/test": "1.41.1",
+ "@playwright/test": "1.41.2",
"@stoplight/spectral-cli": "6.11.0",
- "@stylistic/eslint-plugin-js": "1.5.4",
+ "@stylistic/eslint-plugin-js": "1.6.2",
"@stylistic/stylelint-plugin": "2.0.0",
- "@vitejs/plugin-vue": "5.0.3",
+ "@vitejs/plugin-vue": "5.0.4",
"eslint": "8.56.0",
"eslint-plugin-array-func": "4.0.0",
+ "eslint-plugin-github": "4.10.1",
"eslint-plugin-i": "2.29.1",
"eslint-plugin-jquery": "1.5.1",
"eslint-plugin-no-jquery": "2.7.0",
"eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-regexp": "2.2.0",
- "eslint-plugin-sonarjs": "0.23.0",
- "eslint-plugin-unicorn": "50.0.1",
- "eslint-plugin-vitest": "0.3.21",
+ "eslint-plugin-sonarjs": "0.24.0",
+ "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-scoped-css": "2.7.2",
@@ -82,8 +92,8 @@
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
"stylelint-declaration-strict-value": "1.10.4",
"svgo": "3.2.0",
- "updates": "15.1.1",
- "vite-string-plugin": "1.1.3",
+ "updates": "15.1.2",
+ "vite-string-plugin": "1.1.5",
"vitest": "1.2.2"
},
"engines": {
@@ -99,6 +109,17 @@
"node": ">=0.10.0"
}
},
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@asyncapi/specs": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-4.3.1.tgz",
@@ -112,7 +133,6 @@
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
- "dev": true,
"dependencies": {
"@babel/highlight": "^7.23.4",
"chalk": "^2.4.2"
@@ -125,7 +145,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
@@ -137,7 +156,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@@ -151,7 +169,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
"dependencies": {
"color-name": "1.1.3"
}
@@ -159,14 +176,12 @@
"node_modules/@babel/code-frame/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
- "dev": true
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/@babel/code-frame/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "dev": true,
"engines": {
"node": ">=0.8.0"
}
@@ -175,7 +190,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "dev": true,
"engines": {
"node": ">=4"
}
@@ -184,7 +198,6 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
@@ -196,7 +209,6 @@
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
- "dev": true,
"engines": {
"node": ">=6.9.0"
}
@@ -205,7 +217,6 @@
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
"integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
- "dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
@@ -219,7 +230,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
@@ -231,7 +241,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@@ -245,7 +254,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
"dependencies": {
"color-name": "1.1.3"
}
@@ -253,14 +261,12 @@
"node_modules/@babel/highlight/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
- "dev": true
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/@babel/highlight/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "dev": true,
"engines": {
"node": ">=0.8.0"
}
@@ -269,7 +275,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "dev": true,
"engines": {
"node": ">=4"
}
@@ -277,14 +282,12 @@
"node_modules/@babel/highlight/node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/@babel/highlight/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
@@ -1017,6 +1020,12 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@github/browserslist-config": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@github/browserslist-config/-/browserslist-config-1.0.0.tgz",
+ "integrity": "sha512-gIhjdJp/c2beaIWWIlsXdqXVRUz3r2BxBCpfz/F3JXHvSAQ1paMYjLH+maEATtENg+k5eLV7gA+9yPp762ieuw==",
+ "dev": true
+ },
"node_modules/@github/combobox-nav": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.3.1.tgz",
@@ -1099,7 +1108,6 @@
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
@@ -1116,7 +1124,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
- "dev": true,
"engines": {
"node": ">=12"
},
@@ -1128,7 +1135,6 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true,
"engines": {
"node": ">=12"
},
@@ -1139,14 +1145,12 @@
"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==",
- "dev": true
+ "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",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
@@ -1163,7 +1167,6 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
"dependencies": {
"ansi-regex": "^6.0.1"
},
@@ -1178,7 +1181,6 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
@@ -1217,9 +1219,9 @@
}
},
"node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
- "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"engines": {
"node": ">=6.0.0"
}
@@ -1279,6 +1281,11 @@
"jsep": "^0.4.0||^1.0.0"
}
},
+ "node_modules/@kurkle/color": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
+ "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
+ },
"node_modules/@mcaptcha/core-glue": {
"version": "0.1.0-alpha-5",
"resolved": "https://registry.npmjs.org/@mcaptcha/core-glue/-/core-glue-0.1.0-alpha-5.tgz",
@@ -1364,19 +1371,30 @@
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "dev": true,
"optional": true,
"engines": {
"node": ">=14"
}
},
+ "node_modules/@pkgr/core": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
+ "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unts"
+ }
+ },
"node_modules/@playwright/test": {
- "version": "1.41.1",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.1.tgz",
- "integrity": "sha512-9g8EWTjiQ9yFBXc6HjCWe41msLpxEX0KhmfmPl9RPLJdfzL4F0lg2BdJ91O9azFdl11y1pmpwdjBiSxvqc+btw==",
+ "version": "1.41.2",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz",
+ "integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==",
"dev": true,
"dependencies": {
- "playwright": "1.41.1"
+ "playwright": "1.41.2"
},
"bin": {
"playwright": "cli.js"
@@ -1447,9 +1465,9 @@
"dev": true
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz",
- "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==",
+ "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==",
"cpu": [
"arm"
],
@@ -1460,9 +1478,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz",
- "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==",
+ "version": "4.11.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.11.0.tgz",
+ "integrity": "sha512-0ij3iw7sT5jbcdXofWO2NqDNjSVVsf6itcAkV2I6Xsq4+6wjW1A8rViVB67TfBEan7PV2kbLzT8rhOVWLI2YXw==",
"cpu": [
"arm64"
],
@@ -1473,9 +1491,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz",
- "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==",
+ "version": "4.11.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.11.0.tgz",
+ "integrity": "sha512-yPLs6RbbBMupArf6qv1UDk6dzZvlH66z6NLYEwqTU0VHtss1wkI4UYeeMS7TVj5QRVvaNAWYKP0TD/MOeZ76Zg==",
"cpu": [
"arm64"
],
@@ -1486,9 +1504,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz",
- "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==",
+ "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==",
"cpu": [
"x64"
],
@@ -1499,9 +1517,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz",
- "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==",
+ "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==",
"cpu": [
"arm"
],
@@ -1512,9 +1530,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz",
- "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==",
+ "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==",
"cpu": [
"arm64"
],
@@ -1525,9 +1543,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz",
- "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==",
+ "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==",
"cpu": [
"arm64"
],
@@ -1538,9 +1556,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz",
- "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==",
+ "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==",
"cpu": [
"riscv64"
],
@@ -1551,9 +1569,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz",
- "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==",
+ "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==",
"cpu": [
"x64"
],
@@ -1564,9 +1582,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz",
- "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==",
+ "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==",
"cpu": [
"x64"
],
@@ -1577,9 +1595,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz",
- "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==",
+ "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==",
"cpu": [
"arm64"
],
@@ -1590,9 +1608,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz",
- "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==",
+ "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==",
"cpu": [
"ia32"
],
@@ -1603,9 +1621,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz",
- "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==",
+ "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==",
"cpu": [
"x64"
],
@@ -2073,11 +2091,12 @@
"dev": true
},
"node_modules/@stylistic/eslint-plugin-js": {
- "version": "1.5.4",
- "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.5.4.tgz",
- "integrity": "sha512-3ctWb3NvJNV1MsrZN91cYp2EGInLPSoZKphXIbIRx/zjZxKwLDr9z4LMOWtqjq14li/OgqUUcMq5pj8fgbLoTw==",
+ "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==",
"dev": true,
"dependencies": {
+ "@types/eslint": "^8.56.2",
"acorn": "^8.11.3",
"escape-string-regexp": "^4.0.0",
"eslint-visitor-keys": "^3.4.3",
@@ -2197,6 +2216,12 @@
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
+ "node_modules/@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+ "dev": true
+ },
"node_modules/@types/marked": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.3.2.tgz",
@@ -2216,9 +2241,9 @@
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
},
"node_modules/@types/node": {
- "version": "20.11.14",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.14.tgz",
- "integrity": "sha512-w3yWCcwULefjP9DmDDsgUskrMoOy5Z8MiwKHr1FvqGPtx7CvJzQvxD7eKpxNtklQxLruxSXWddyeRtyud0RcXQ==",
+ "version": "20.11.19",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz",
+ "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==",
"dependencies": {
"undici-types": "~5.26.4"
}
@@ -2236,9 +2261,9 @@
"dev": true
},
"node_modules/@types/semver": {
- "version": "7.5.6",
- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
- "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
+ "version": "7.5.7",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz",
+ "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==",
"dev": true
},
"node_modules/@types/tern": {
@@ -2260,14 +2285,77 @@
"integrity": "sha512-XOfUup9r3Y06nFAZh3WvO0rBU4OtlfPB/vgxpjg+NRdGU6CN6djdc6OEiH+PcqHCY6eFLo9Ista73uarf4gnBg==",
"dev": true
},
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.20.0.tgz",
- "integrity": "sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==",
+ "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==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.20.0",
- "@typescript-eslint/visitor-keys": "6.20.0"
+ "@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",
+ "debug": "^4.3.4",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.4",
+ "natural-compare": "^1.4.0",
+ "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"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
+ "eslint": "^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "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==",
+ "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",
+ "debug": "^4.3.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"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "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"
@@ -2277,10 +2365,37 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "6.21.0",
+ "@typescript-eslint/utils": "6.21.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "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"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@typescript-eslint/types": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz",
- "integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==",
+ "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"
@@ -2291,13 +2406,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.20.0.tgz",
- "integrity": "sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==",
+ "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.20.0",
- "@typescript-eslint/visitor-keys": "6.20.0",
+ "@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",
@@ -2319,17 +2434,17 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.20.0.tgz",
- "integrity": "sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==",
+ "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.20.0",
- "@typescript-eslint/types": "6.20.0",
- "@typescript-eslint/typescript-estree": "6.20.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": {
@@ -2344,12 +2459,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz",
- "integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==",
+ "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.20.0",
+ "@typescript-eslint/types": "6.21.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -2367,9 +2482,9 @@
"dev": true
},
"node_modules/@vitejs/plugin-vue": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.3.tgz",
- "integrity": "sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==",
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz",
+ "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==",
"dev": true,
"engines": {
"node": "^18.0.0 || >=20.0.0"
@@ -2449,9 +2564,9 @@
}
},
"node_modules/@vitest/snapshot/node_modules/magic-string": {
- "version": "0.30.6",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz",
- "integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==",
+ "version": "0.30.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
+ "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
@@ -2503,46 +2618,46 @@
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.4.15",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz",
- "integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==",
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz",
+ "integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==",
"dependencies": {
- "@babel/parser": "^7.23.6",
- "@vue/shared": "3.4.15",
+ "@babel/parser": "^7.23.9",
+ "@vue/shared": "3.4.19",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-dom": {
- "version": "3.4.15",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz",
- "integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==",
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz",
+ "integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==",
"dependencies": {
- "@vue/compiler-core": "3.4.15",
- "@vue/shared": "3.4.15"
+ "@vue/compiler-core": "3.4.19",
+ "@vue/shared": "3.4.19"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.4.15",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz",
- "integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==",
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.19.tgz",
+ "integrity": "sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==",
"dependencies": {
- "@babel/parser": "^7.23.6",
- "@vue/compiler-core": "3.4.15",
- "@vue/compiler-dom": "3.4.15",
- "@vue/compiler-ssr": "3.4.15",
- "@vue/shared": "3.4.15",
+ "@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",
"estree-walker": "^2.0.2",
- "magic-string": "^0.30.5",
+ "magic-string": "^0.30.6",
"postcss": "^8.4.33",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-sfc/node_modules/magic-string": {
- "version": "0.30.6",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz",
- "integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==",
+ "version": "0.30.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
+ "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
@@ -2551,57 +2666,57 @@
}
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.4.15",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz",
- "integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==",
+ "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==",
"dependencies": {
- "@vue/compiler-dom": "3.4.15",
- "@vue/shared": "3.4.15"
+ "@vue/compiler-dom": "3.4.19",
+ "@vue/shared": "3.4.19"
}
},
"node_modules/@vue/reactivity": {
- "version": "3.4.15",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz",
- "integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==",
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.19.tgz",
+ "integrity": "sha512-+VcwrQvLZgEclGZRHx4O2XhyEEcKaBi50WbxdVItEezUf4fqRh838Ix6amWTdX0CNb/b6t3Gkz3eOebfcSt+UA==",
"dependencies": {
- "@vue/shared": "3.4.15"
+ "@vue/shared": "3.4.19"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.4.15",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz",
- "integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==",
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.19.tgz",
+ "integrity": "sha512-/Z3tFwOrerJB/oyutmJGoYbuoadphDcJAd5jOuJE86THNZji9pYjZroQ2NFsZkTxOq0GJbb+s2kxTYToDiyZzw==",
"dependencies": {
- "@vue/reactivity": "3.4.15",
- "@vue/shared": "3.4.15"
+ "@vue/reactivity": "3.4.19",
+ "@vue/shared": "3.4.19"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.4.15",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz",
- "integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==",
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.19.tgz",
+ "integrity": "sha512-IyZzIDqfNCF0OyZOauL+F4yzjMPN2rPd8nhqPP2N1lBn3kYqJpPHHru+83Rkvo2lHz5mW+rEeIMEF9qY3PB94g==",
"dependencies": {
- "@vue/runtime-core": "3.4.15",
- "@vue/shared": "3.4.15",
+ "@vue/runtime-core": "3.4.19",
+ "@vue/shared": "3.4.19",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.4.15",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz",
- "integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==",
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.19.tgz",
+ "integrity": "sha512-eAj2p0c429RZyyhtMRnttjcSToch+kTWxFPHlzGMkR28ZbF1PDlTcmGmlDxccBuqNd9iOQ7xPRPAGgPVj+YpQw==",
"dependencies": {
- "@vue/compiler-ssr": "3.4.15",
- "@vue/shared": "3.4.15"
+ "@vue/compiler-ssr": "3.4.19",
+ "@vue/shared": "3.4.19"
},
"peerDependencies": {
- "vue": "3.4.15"
+ "vue": "3.4.19"
}
},
"node_modules/@vue/shared": {
- "version": "3.4.15",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
- "integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g=="
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
+ "integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw=="
},
"node_modules/@webassemblyjs/ast": {
"version": "1.11.6",
@@ -2960,19 +3075,53 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
- "node_modules/array-buffer-byte-length": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
- "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.2",
- "is-array-buffer": "^3.0.1"
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz",
+ "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "is-array-buffer": "^3.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -2986,6 +3135,25 @@
"node": ">=0.10.0"
}
},
+ "node_modules/array-includes": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz",
+ "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "get-intrinsic": "^1.2.1",
+ "is-string": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -2995,18 +3163,93 @@
"node": ">=8"
}
},
- "node_modules/arraybuffer.prototype.slice": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz",
- "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==",
+ "node_modules/array.prototype.filter": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz",
+ "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==",
"dev": true,
"dependencies": {
- "array-buffer-byte-length": "^1.0.0",
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1",
- "get-intrinsic": "^1.2.1",
- "is-array-buffer": "^3.0.2",
+ "es-array-method-boxes-properly": "^1.0.0",
+ "is-string": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.findlastindex": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz",
+ "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.3.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flat": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz",
+ "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz",
+ "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz",
+ "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.2.1",
+ "get-intrinsic": "^1.2.3",
+ "is-array-buffer": "^3.0.4",
"is-shared-array-buffer": "^1.0.2"
},
"engines": {
@@ -3026,9 +3269,9 @@
}
},
"node_modules/asciinema-player": {
- "version": "3.6.3",
- "resolved": "https://registry.npmjs.org/asciinema-player/-/asciinema-player-3.6.3.tgz",
- "integrity": "sha512-62aDgLpbuduhmpFfNgPOzf6fOluACLsftVnjpWJjUXX6dqhqTckFqWoJ+ayA0XjSlZ9l9wXTcJqRqvAAIpMblg==",
+ "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==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"solid-js": "^1.3.0"
@@ -3055,6 +3298,12 @@
"node": ">=4"
}
},
+ "node_modules/ast-types-flow": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
+ "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
+ "dev": true
+ },
"node_modules/astral-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
@@ -3073,6 +3322,15 @@
"astring": "bin/astring"
}
},
+ "node_modules/asynciterator.prototype": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz",
+ "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==",
+ "dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ }
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -3092,9 +3350,9 @@
}
},
"node_modules/available-typed-arrays": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
- "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
+ "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==",
"dev": true,
"engines": {
"node": ">= 0.4"
@@ -3103,6 +3361,24 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/axe-core": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz",
+ "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/axobject-query": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
+ "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==",
+ "dev": true,
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -3135,6 +3411,14 @@
"node": "*"
}
},
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -3161,9 +3445,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.22.3",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz",
- "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==",
+ "version": "4.23.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
+ "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
"funding": [
{
"type": "opencollective",
@@ -3179,8 +3463,8 @@
}
],
"dependencies": {
- "caniuse-lite": "^1.0.30001580",
- "electron-to-chromium": "^1.4.648",
+ "caniuse-lite": "^1.0.30001587",
+ "electron-to-chromium": "^1.4.668",
"node-releases": "^2.0.14",
"update-browserslist-db": "^1.0.13"
},
@@ -3247,14 +3531,19 @@
}
},
"node_modules/call-bind": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
- "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+ "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
"dev": true,
"dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.1",
- "set-function-length": "^1.1.1"
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -3264,15 +3553,22 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
"engines": {
"node": ">=6"
}
},
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/caniuse-lite": {
- "version": "1.0.30001581",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001581.tgz",
- "integrity": "sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==",
+ "version": "1.0.30001587",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz",
+ "integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==",
"funding": [
{
"type": "opencollective",
@@ -3330,6 +3626,40 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "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==",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=7"
+ }
+ },
+ "node_modules/chartjs-adapter-dayjs-4": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/chartjs-adapter-dayjs-4/-/chartjs-adapter-dayjs-4-1.0.4.tgz",
+ "integrity": "sha512-yy9BAYW4aNzPVrCWZetbILegTRb7HokhgospPoC3b5iZ5qdlqNmXts2KdSp6AqnjkPAp/YWyHDxLvIvwt5x81w==",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "chart.js": ">=4.0.1",
+ "dayjs": "^1.9.7"
+ }
+ },
+ "node_modules/chartjs-plugin-zoom": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-2.0.1.tgz",
+ "integrity": "sha512-ogOmLu6e+Q7E1XWOCOz9YwybMslz9qNfGV2a+qjfmqJYpsw5ZMoRHZBUyW+NGhkpQ5PwwPA/+rikHpBZb7PZuA==",
+ "dependencies": {
+ "hammerjs": "^2.0.8"
+ },
+ "peerDependencies": {
+ "chart.js": ">=3.2.0"
+ }
+ },
"node_modules/check-error": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
@@ -3342,6 +3672,40 @@
"node": "*"
}
},
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/chrome-trace-event": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
@@ -3529,12 +3893,12 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/core-js-compat": {
- "version": "3.35.1",
- "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz",
- "integrity": "sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==",
+ "version": "3.36.0",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.0.tgz",
+ "integrity": "sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==",
"dev": true,
"dependencies": {
- "browserslist": "^4.22.2"
+ "browserslist": "^4.22.3"
},
"funding": {
"type": "opencollective",
@@ -3553,7 +3917,6 @@
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
"integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
- "dev": true,
"dependencies": {
"env-paths": "^2.2.1",
"import-fresh": "^3.3.0",
@@ -3671,6 +4034,35 @@
"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",
@@ -3767,30 +4159,6 @@
"cytoscape": "^3.2.0"
}
},
- "node_modules/cytoscape-fcose": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
- "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
- "dependencies": {
- "cose-base": "^2.2.0"
- },
- "peerDependencies": {
- "cytoscape": "^3.2.0"
- }
- },
- "node_modules/cytoscape-fcose/node_modules/cose-base": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz",
- "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
- "dependencies": {
- "layout-base": "^2.0.0"
- }
- },
- "node_modules/cytoscape-fcose/node_modules/layout-base": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz",
- "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="
- },
"node_modules/d3": {
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz",
@@ -4213,6 +4581,12 @@
"lodash-es": "^4.17.21"
}
},
+ "node_modules/damerau-levenshtein": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
+ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
+ "dev": true
+ },
"node_modules/data-uri-to-buffer": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz",
@@ -4308,17 +4682,20 @@
"dev": true
},
"node_modules/define-data-property": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
- "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dev": true,
"dependencies": {
- "get-intrinsic": "^1.2.1",
- "gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.0"
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-properties": {
@@ -4372,21 +4749,15 @@
"node": ">=6"
}
},
- "node_modules/detect-libc": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
- "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
- "bin": {
- "detect-libc": "bin/detect-libc.js"
- },
- "engines": {
- "node": ">=0.10"
- }
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
},
"node_modules/diff": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
- "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+ "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
"engines": {
"node": ">=0.3.1"
}
@@ -4412,6 +4783,11 @@
"node": ">=8"
}
},
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
+ },
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -4496,8 +4872,7 @@
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"node_modules/easymde": {
"version": "2.18.0",
@@ -4512,9 +4887,9 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.4.653",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.653.tgz",
- "integrity": "sha512-wA2A2LQCqnEwQAvwADQq3KpMpNwgAUBnRmrFgRzHnPhbQUFArTR32Ab46f4p0MovDLcg4uqd4nCsN2hTltslpA=="
+ "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=="
},
"node_modules/elkjs": {
"version": "0.9.1",
@@ -4561,15 +4936,14 @@
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
- "dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/envinfo": {
- "version": "7.11.0",
- "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz",
- "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz",
+ "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==",
"bin": {
"envinfo": "dist/cli.js"
},
@@ -4581,56 +4955,57 @@
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
- "dev": true,
"dependencies": {
"is-arrayish": "^0.2.1"
}
},
"node_modules/es-abstract": {
- "version": "1.22.3",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz",
- "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==",
+ "version": "1.22.4",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.4.tgz",
+ "integrity": "sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==",
"dev": true,
"dependencies": {
- "array-buffer-byte-length": "^1.0.0",
- "arraybuffer.prototype.slice": "^1.0.2",
- "available-typed-arrays": "^1.0.5",
- "call-bind": "^1.0.5",
- "es-set-tostringtag": "^2.0.1",
+ "array-buffer-byte-length": "^1.0.1",
+ "arraybuffer.prototype.slice": "^1.0.3",
+ "available-typed-arrays": "^1.0.6",
+ "call-bind": "^1.0.7",
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "es-set-tostringtag": "^2.0.2",
"es-to-primitive": "^1.2.1",
"function.prototype.name": "^1.1.6",
- "get-intrinsic": "^1.2.2",
- "get-symbol-description": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "get-symbol-description": "^1.0.2",
"globalthis": "^1.0.3",
"gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.0",
+ "has-property-descriptors": "^1.0.2",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
- "hasown": "^2.0.0",
- "internal-slot": "^1.0.5",
- "is-array-buffer": "^3.0.2",
+ "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-regex": "^1.1.4",
"is-shared-array-buffer": "^1.0.2",
"is-string": "^1.0.7",
- "is-typed-array": "^1.1.12",
+ "is-typed-array": "^1.1.13",
"is-weakref": "^1.0.2",
"object-inspect": "^1.13.1",
"object-keys": "^1.1.1",
- "object.assign": "^4.1.4",
- "regexp.prototype.flags": "^1.5.1",
- "safe-array-concat": "^1.0.1",
- "safe-regex-test": "^1.0.0",
+ "object.assign": "^4.1.5",
+ "regexp.prototype.flags": "^1.5.2",
+ "safe-array-concat": "^1.1.0",
+ "safe-regex-test": "^1.0.3",
"string.prototype.trim": "^1.2.8",
"string.prototype.trimend": "^1.0.7",
"string.prototype.trimstart": "^1.0.7",
- "typed-array-buffer": "^1.0.0",
+ "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",
"unbox-primitive": "^1.0.2",
- "which-typed-array": "^1.1.13"
+ "which-typed-array": "^1.1.14"
},
"engines": {
"node": ">= 0.4"
@@ -4640,18 +5015,18 @@
}
},
"node_modules/es-aggregate-error": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.11.tgz",
- "integrity": "sha512-DCiZiNlMlbvofET/cE55My387NiLvuGToBEZDdK9U2G3svDCjL8WOgO5Il6lO83nQ8qmag/R9nArdpaFQ/m3lA==",
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.12.tgz",
+ "integrity": "sha512-j0PupcmELoVbYS2NNrsn5zcLLEsryQwP02x8fRawh7c2eEaPHwJFAxltZsqV7HJjsF57+SMpYyVRWgbVLfOagg==",
"dev": true,
"dependencies": {
- "define-data-property": "^1.1.0",
+ "define-data-property": "^1.1.1",
"define-properties": "^1.2.1",
- "es-abstract": "^1.22.1",
- "function-bind": "^1.1.1",
- "get-intrinsic": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.1.0",
+ "function-bind": "^1.1.2",
"globalthis": "^1.0.3",
- "has-property-descriptors": "^1.0.0",
+ "has-property-descriptors": "^1.0.1",
"set-function-name": "^2.0.1"
},
"engines": {
@@ -4661,6 +5036,59 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/es-array-method-boxes-properly": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
+ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==",
+ "dev": true
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+ "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-iterator-helpers": {
+ "version": "1.0.17",
+ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz",
+ "integrity": "sha512-lh7BsUqelv4KUbR5a/ZTaGGIMLCjPGPqJ6q+Oq24YP0RdyptX1uzm4vvaqzk7Zx3bpl/76YLTTDj9L7uYQ92oQ==",
+ "dev": true,
+ "dependencies": {
+ "asynciterator.prototype": "^1.0.0",
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.4",
+ "es-errors": "^1.3.0",
+ "es-set-tostringtag": "^2.0.2",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "globalthis": "^1.0.3",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.7",
+ "iterator.prototype": "^1.1.2",
+ "safe-array-concat": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/es-module-lexer": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
@@ -4680,6 +5108,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-shim-unscopables": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz",
+ "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.0"
+ }
+ },
"node_modules/es-to-primitive": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
@@ -4752,9 +5189,9 @@
}
},
"node_modules/escalade": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
- "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
+ "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"engines": {
"node": ">=6"
}
@@ -4852,6 +5289,18 @@
"eslint": ">=6.0.0"
}
},
+ "node_modules/eslint-config-prettier": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
+ "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
+ "dev": true,
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
"node_modules/eslint-import-resolver-node": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
@@ -4910,6 +5359,92 @@
"eslint": ">=8.40.0"
}
},
+ "node_modules/eslint-plugin-escompat": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-escompat/-/eslint-plugin-escompat-3.4.0.tgz",
+ "integrity": "sha512-ufTPv8cwCxTNoLnTZBFTQ5SxU2w7E7wiMIS7PSxsgP1eAxFjtSaoZ80LRn64hI8iYziE6kJG6gX/ZCJVxh48Bg==",
+ "dev": true,
+ "dependencies": {
+ "browserslist": "^4.21.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=5.14.1"
+ }
+ },
+ "node_modules/eslint-plugin-eslint-comments": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz",
+ "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==",
+ "dev": true,
+ "dependencies": {
+ "escape-string-regexp": "^1.0.5",
+ "ignore": "^5.0.5"
+ },
+ "engines": {
+ "node": ">=6.5.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=4.19.1"
+ }
+ },
+ "node_modules/eslint-plugin-eslint-comments/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/eslint-plugin-filenames": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-filenames/-/eslint-plugin-filenames-1.3.2.tgz",
+ "integrity": "sha512-tqxJTiEM5a0JmRCUYQmxw23vtTxrb2+a3Q2mMOPhFxvt7ZQQJmdiuMby9B/vUAuVMghyP7oET+nIf6EO6CBd/w==",
+ "dev": true,
+ "dependencies": {
+ "lodash.camelcase": "4.3.0",
+ "lodash.kebabcase": "4.1.1",
+ "lodash.snakecase": "4.1.1",
+ "lodash.upperfirst": "4.3.1"
+ },
+ "peerDependencies": {
+ "eslint": "*"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "@github/browserslist-config": "^1.0.0",
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
+ "@typescript-eslint/parser": "^6.0.0",
+ "aria-query": "^5.3.0",
+ "eslint-config-prettier": ">=8.0.0",
+ "eslint-plugin-escompat": "^3.3.3",
+ "eslint-plugin-eslint-comments": "^3.2.0",
+ "eslint-plugin-filenames": "^1.3.2",
+ "eslint-plugin-i18n-text": "^1.0.1",
+ "eslint-plugin-import": "^2.25.2",
+ "eslint-plugin-jsx-a11y": "^6.7.1",
+ "eslint-plugin-no-only-tests": "^3.0.0",
+ "eslint-plugin-prettier": "^5.0.0",
+ "eslint-rule-documentation": ">=1.0.0",
+ "jsx-ast-utils": "^3.3.2",
+ "prettier": "^3.0.0",
+ "svg-element-attributes": "^1.3.1"
+ },
+ "bin": {
+ "eslint-ignore-errors": "bin/eslint-ignore-errors.js"
+ },
+ "peerDependencies": {
+ "eslint": "^8.0.1"
+ }
+ },
"node_modules/eslint-plugin-i": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-i/-/eslint-plugin-i-2.29.1.tgz",
@@ -4957,6 +5492,98 @@
"node": "*"
}
},
+ "node_modules/eslint-plugin-i18n-text": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-i18n-text/-/eslint-plugin-i18n-text-1.0.1.tgz",
+ "integrity": "sha512-3G3UetST6rdqhqW9SfcfzNYMpQXS7wNkJvp6dsXnjzGiku6Iu5hl3B0kmk6lIcFPwYjhQIY+tXVRtK9TlGT7RA==",
+ "dev": true,
+ "peerDependencies": {
+ "eslint": ">=5.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-import": {
+ "version": "2.29.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz",
+ "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.7",
+ "array.prototype.findlastindex": "^1.2.3",
+ "array.prototype.flat": "^1.3.2",
+ "array.prototype.flatmap": "^1.3.2",
+ "debug": "^3.2.7",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.9",
+ "eslint-module-utils": "^2.8.0",
+ "hasown": "^2.0.0",
+ "is-core-module": "^2.13.1",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.7",
+ "object.groupby": "^1.0.1",
+ "object.values": "^1.1.7",
+ "semver": "^6.3.1",
+ "tsconfig-paths": "^3.15.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
"node_modules/eslint-plugin-jquery": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-jquery/-/eslint-plugin-jquery-1.5.1.tgz",
@@ -4966,6 +5593,64 @@
"eslint": ">=5.4.0"
}
},
+ "node_modules/eslint-plugin-jsx-a11y": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz",
+ "integrity": "sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.23.2",
+ "aria-query": "^5.3.0",
+ "array-includes": "^3.1.7",
+ "array.prototype.flatmap": "^1.3.2",
+ "ast-types-flow": "^0.0.8",
+ "axe-core": "=4.7.0",
+ "axobject-query": "^3.2.1",
+ "damerau-levenshtein": "^1.0.8",
+ "emoji-regex": "^9.2.2",
+ "es-iterator-helpers": "^1.0.15",
+ "hasown": "^2.0.0",
+ "jsx-ast-utils": "^3.3.5",
+ "language-tags": "^1.0.9",
+ "minimatch": "^3.1.2",
+ "object.entries": "^1.1.7",
+ "object.fromentries": "^2.0.7"
+ },
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
+ }
+ },
+ "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "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",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/eslint-plugin-no-jquery": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.7.0.tgz",
@@ -4975,6 +5660,15 @@
"eslint": ">=2.3.0"
}
},
+ "node_modules/eslint-plugin-no-only-tests": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-3.1.0.tgz",
+ "integrity": "sha512-Lf4YW/bL6Un1R6A76pRZyE1dl1vr31G/ev8UzIc/geCgFWyrKil8hVjYqWVKGB/UIGmb6Slzs9T0wNezdSVegw==",
+ "dev": true,
+ "engines": {
+ "node": ">=5.0.0"
+ }
+ },
"node_modules/eslint-plugin-no-use-extend-native": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-no-use-extend-native/-/eslint-plugin-no-use-extend-native-0.5.0.tgz",
@@ -4990,6 +5684,36 @@
"node": ">=6.0.0"
}
},
+ "node_modules/eslint-plugin-prettier": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz",
+ "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==",
+ "dev": true,
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0",
+ "synckit": "^0.8.6"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-plugin-prettier"
+ },
+ "peerDependencies": {
+ "@types/eslint": ">=8.0.0",
+ "eslint": ">=8.0.0",
+ "eslint-config-prettier": "*",
+ "prettier": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/eslint": {
+ "optional": true
+ },
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
"node_modules/eslint-plugin-regexp": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.2.0.tgz",
@@ -5012,21 +5736,21 @@
}
},
"node_modules/eslint-plugin-sonarjs": {
- "version": "0.23.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.23.0.tgz",
- "integrity": "sha512-z44T3PBf9W7qQ/aR+NmofOTyg6HLhSEZOPD4zhStqBpLoMp8GYhFksuUBnCxbnf1nfISpKBVkQhiBLFI/F4Wlg==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.24.0.tgz",
+ "integrity": "sha512-87zp50mbbNrSTuoEOebdRQBPa0mdejA5UEjyuScyIw8hEpEjfWP89Qhkq5xVZfVyVSRQKZc9alVm7yRKQvvUmg==",
"dev": true,
"engines": {
- "node": ">=14"
+ "node": ">=16"
},
"peerDependencies": {
"eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/eslint-plugin-unicorn": {
- "version": "50.0.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-50.0.1.tgz",
- "integrity": "sha512-KxenCZxqSYW0GWHH18okDlOQcpezcitm5aOSz6EnobyJ6BIByiPDviQRjJIUAjG/tMN11958MxaQ+qCoU6lfDA==",
+ "version": "51.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-51.0.1.tgz",
+ "integrity": "sha512-MuR/+9VuB0fydoI0nIn2RDA5WISRn4AsJyNSaNKLVwie9/ONvQhxOBbkfSICBPnzKrB77Fh6CZZXjgTt/4Latw==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
@@ -5057,12 +5781,12 @@
}
},
"node_modules/eslint-plugin-vitest": {
- "version": "0.3.21",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.3.21.tgz",
- "integrity": "sha512-oYwR1MrwaBw/OG6CKU+SJYleAc442w6CWL1RTQl5WLwy8X3sh0bgHIQk5iEtmTak3Q+XAvZglr0bIoDOjFdkcw==",
+ "version": "0.3.22",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.3.22.tgz",
+ "integrity": "sha512-atkFGQ7aVgcuSeSMDqnyevIyUpfBPMnosksgEPrKE7Y8xQlqG/5z2IQ6UDau05zXaaFv7Iz8uzqvIuKshjZ0Zw==",
"dev": true,
"dependencies": {
- "@typescript-eslint/utils": "^6.20.0"
+ "@typescript-eslint/utils": "^6.21.0"
},
"engines": {
"node": "^18.0.0 || >= 20.0.0"
@@ -5146,6 +5870,15 @@
"eslint": ">=5"
}
},
+ "node_modules/eslint-rule-documentation": {
+ "version": "1.0.23",
+ "resolved": "https://registry.npmjs.org/eslint-rule-documentation/-/eslint-rule-documentation-1.0.23.tgz",
+ "integrity": "sha512-pWReu3fkohwyvztx/oQWWgld2iad25TfUdi6wvhhaDPIQjHU/pyvlKgXFw1kX31SQK2Nq9MH+vRDWB0ZLy8fYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
"node_modules/eslint-scope": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -5383,9 +6116,9 @@
}
},
"node_modules/fastq": {
- "version": "1.17.0",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz",
- "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==",
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+ "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
"dependencies": {
"reusify": "^1.0.4"
}
@@ -5516,7 +6249,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
- "dev": true,
"dependencies": {
"cross-spawn": "^7.0.0",
"signal-exit": "^4.0.1"
@@ -5565,7 +6297,6 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
@@ -5640,16 +6371,20 @@
}
},
"node_modules/get-intrinsic": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
- "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"dev": true,
"dependencies": {
+ "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
"hasown": "^2.0.0"
},
+ "engines": {
+ "node": ">= 0.4"
+ },
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -5698,13 +6433,14 @@
}
},
"node_modules/get-symbol-description": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
- "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz",
+ "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.2",
- "get-intrinsic": "^1.1.1"
+ "call-bind": "^1.0.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4"
},
"engines": {
"node": ">= 0.4"
@@ -5747,7 +6483,6 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
"dependencies": {
"is-glob": "^4.0.3"
},
@@ -5908,6 +6643,14 @@
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.12.5.tgz",
"integrity": "sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ=="
},
+ "node_modules/hammerjs": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
+ "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/has-bigints": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
@@ -5926,12 +6669,12 @@
}
},
"node_modules/has-property-descriptors": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
- "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
"dependencies": {
- "get-intrinsic": "^1.2.2"
+ "es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -5962,12 +6705,12 @@
}
},
"node_modules/has-tostringtag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
- "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"dependencies": {
- "has-symbols": "^1.0.2"
+ "has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
@@ -5982,9 +6725,9 @@
"integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg=="
},
"node_modules/hasown": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
- "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
+ "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
"dependencies": {
"function-bind": "^1.1.2"
},
@@ -6061,9 +6804,9 @@
"integrity": "sha512-UgchasltTCrTuU2DQLom3ohHrBvwr7OqpwyAVJ9VxtNBng4XKkVsqrv0Qr3srqvM9ZNI3f1MmvVQQqK7KW/bTA=="
},
"node_modules/http-proxy-agent": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz",
- "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dev": true,
"dependencies": {
"agent-base": "^7.1.0",
@@ -6074,9 +6817,9 @@
}
},
"node_modules/https-proxy-agent": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
- "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz",
+ "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==",
"dev": true,
"dependencies": {
"agent-base": "^7.0.2",
@@ -6117,6 +6860,11 @@
"postcss": "^8.1.0"
}
},
+ "node_modules/idiomorph": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/idiomorph/-/idiomorph-0.3.0.tgz",
+ "integrity": "sha512-UhV1Ey5xCxIwR9B+OgIjQa+1Jx99XQ1vQHUsKBU1RpQzCx1u+b+N6SOXgf5mEJDqemUI/ffccu6+71l2mJUsRA=="
+ },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -6137,9 +6885,9 @@
]
},
"node_modules/ignore": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
- "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
+ "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
"dev": true,
"engines": {
"node": ">= 4"
@@ -6159,7 +6907,6 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
- "dev": true,
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
@@ -6231,12 +6978,12 @@
}
},
"node_modules/internal-slot": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz",
- "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
+ "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==",
"dev": true,
"dependencies": {
- "get-intrinsic": "^1.2.2",
+ "es-errors": "^1.3.0",
"hasown": "^2.0.0",
"side-channel": "^1.0.4"
},
@@ -6261,14 +7008,16 @@
}
},
"node_modules/is-array-buffer": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
- "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
+ "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
- "get-intrinsic": "^1.2.0",
- "is-typed-array": "^1.1.10"
+ "get-intrinsic": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -6277,8 +7026,22 @@
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
- "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
- "dev": true
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
+ },
+ "node_modules/is-async-function": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz",
+ "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
},
"node_modules/is-bigint": {
"version": "1.0.4",
@@ -6292,6 +7055,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-boolean-object": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
@@ -6369,6 +7143,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-finalizationregistry": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz",
+ "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -6377,6 +7163,21 @@
"node": ">=8"
}
},
+ "node_modules/is-generator-function": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
+ "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-get-set-prop": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-get-set-prop/-/is-get-set-prop-1.0.0.tgz",
@@ -6407,6 +7208,15 @@
"js-types": "^1.0.0"
}
},
+ "node_modules/is-map": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
+ "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-negative-zero": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
@@ -6511,6 +7321,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-set": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz",
+ "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"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",
@@ -6566,12 +7385,12 @@
}
},
"node_modules/is-typed-array": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
- "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz",
+ "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==",
"dev": true,
"dependencies": {
- "which-typed-array": "^1.1.11"
+ "which-typed-array": "^1.1.14"
},
"engines": {
"node": ">= 0.4"
@@ -6589,6 +7408,15 @@
"is-potential-custom-element-name": "^1.0.0"
}
},
+ "node_modules/is-weakmap": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz",
+ "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-weakref": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@@ -6601,6 +7429,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-weakset": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz",
+ "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
@@ -6620,11 +7461,23 @@
"node": ">=0.10.0"
}
},
+ "node_modules/iterator.prototype": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz",
+ "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "get-intrinsic": "^1.2.1",
+ "has-symbols": "^1.0.3",
+ "reflect.getprototypeof": "^1.0.4",
+ "set-function-name": "^2.0.1"
+ }
+ },
"node_modules/jackspeak": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
- "dev": true,
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
@@ -6665,6 +7518,14 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/jiti": {
+ "version": "1.21.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
+ "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
"node_modules/jquery": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
@@ -6677,9 +7538,9 @@
"dev": true
},
"node_modules/js-tokens": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.2.tgz",
- "integrity": "sha512-Olnt+V7xYdvGze9YTbGFZIfQXuGV4R3nQwwl8BrtgaPE/wq8UFpUHWuTNc05saowhSr1ZO6tx+V6RjE9D5YQog==",
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz",
+ "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==",
"dev": true
},
"node_modules/js-types": {
@@ -6850,6 +7711,21 @@
"node": ">=0.10.0"
}
},
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
+ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "object.assign": "^4.1.4",
+ "object.values": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/just-extend": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-5.1.1.tgz",
@@ -6906,6 +7782,24 @@
"integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==",
"dev": true
},
+ "node_modules/language-subtag-registry": {
+ "version": "0.3.22",
+ "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz",
+ "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==",
+ "dev": true
+ },
+ "node_modules/language-tags": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz",
+ "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==",
+ "dev": true,
+ "dependencies": {
+ "language-subtag-registry": "^0.3.20"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/layout-base": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
@@ -6993,229 +7887,18 @@
"node": ">=8"
}
},
- "node_modules/lightningcss": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.23.0.tgz",
- "integrity": "sha512-SEArWKMHhqn/0QzOtclIwH5pXIYQOUEkF8DgICd/105O+GCgd7jxjNod/QPnBCSWvpRHQBGVz5fQ9uScby03zA==",
- "dependencies": {
- "detect-libc": "^1.0.3"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- },
- "optionalDependencies": {
- "lightningcss-darwin-arm64": "1.23.0",
- "lightningcss-darwin-x64": "1.23.0",
- "lightningcss-freebsd-x64": "1.23.0",
- "lightningcss-linux-arm-gnueabihf": "1.23.0",
- "lightningcss-linux-arm64-gnu": "1.23.0",
- "lightningcss-linux-arm64-musl": "1.23.0",
- "lightningcss-linux-x64-gnu": "1.23.0",
- "lightningcss-linux-x64-musl": "1.23.0",
- "lightningcss-win32-x64-msvc": "1.23.0"
- }
- },
- "node_modules/lightningcss-darwin-arm64": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.23.0.tgz",
- "integrity": "sha512-kl4Pk3Q2lnE6AJ7Qaij47KNEfY2/UXRZBT/zqGA24B8qwkgllr/j7rclKOf1axcslNXvvUdztjo4Xqh39Yq1aA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-darwin-x64": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.23.0.tgz",
- "integrity": "sha512-KeRFCNoYfDdcolcFXvokVw+PXCapd2yHS1Diko1z1BhRz/nQuD5XyZmxjWdhmhN/zj5sH8YvWsp0/lPLVzqKpg==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-freebsd-x64": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.23.0.tgz",
- "integrity": "sha512-xhnhf0bWPuZxcqknvMDRFFo2TInrmQRWZGB0f6YoAsZX8Y+epfjHeeOIGCfAmgF0DgZxHwYc8mIR5tQU9/+ROA==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.23.0.tgz",
- "integrity": "sha512-fBamf/bULvmWft9uuX+bZske236pUZEoUlaHNBjnueaCTJ/xd8eXgb0cEc7S5o0Nn6kxlauMBnqJpF70Bgq3zg==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.23.0.tgz",
- "integrity": "sha512-RS7sY77yVLOmZD6xW2uEHByYHhQi5JYWmgVumYY85BfNoVI3DupXSlzbw+b45A9NnVKq45+oXkiN6ouMMtTwfg==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.23.0.tgz",
- "integrity": "sha512-cU00LGb6GUXCwof6ACgSMKo3q7XYbsyTj0WsKHLi1nw7pV0NCq8nFTn6ZRBYLoKiV8t+jWl0Hv8KkgymmK5L5g==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.23.0.tgz",
- "integrity": "sha512-q4jdx5+5NfB0/qMbXbOmuC6oo7caPnFghJbIAV90cXZqgV8Am3miZhC4p+sQVdacqxfd+3nrle4C8icR3p1AYw==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-musl": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.23.0.tgz",
- "integrity": "sha512-G9Ri3qpmF4qef2CV/80dADHKXRAQeQXpQTLx7AiQrBYQHqBjB75oxqj06FCIe5g4hNCqLPnM9fsO4CyiT1sFSQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-loader": {
+ "node_modules/lilconfig": {
"version": "2.1.0",
- "resolved": "https://registry.npmjs.org/lightningcss-loader/-/lightningcss-loader-2.1.0.tgz",
- "integrity": "sha512-mB+M/lvs/GdXT4yc8ZiNgLUAbYpPI9grDyC3ybz/Zo6s4GZv53iZnLTnkJT/Qm3Sh89dbFUm+omoHFXCfZtcXw==",
- "dependencies": {
- "browserslist": "^4.21.4",
- "lightningcss": "^1.16.0",
- "webpack-sources": "^3.2.3"
- },
- "peerDependencies": {
- "webpack": ">=5"
- }
- },
- "node_modules/lightningcss-loader/node_modules/webpack-sources": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
- "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
"engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.23.0.tgz",
- "integrity": "sha512-1rcBDJLU+obPPJM6qR5fgBUiCdZwZLafZM5f9kwjFLkb/UBNIzmae39uCSmh71nzPCTXZqHbvwu23OWnWEz+eg==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "node": ">=10"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
- "dev": true
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
"node_modules/linkify-it": {
"version": "5.0.0",
@@ -7293,12 +7976,30 @@
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
"integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA=="
},
+ "node_modules/lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
+ "dev": true
+ },
+ "node_modules/lodash.kebabcase": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
+ "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==",
+ "dev": true
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
+ "node_modules/lodash.snakecase": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
+ "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
+ "dev": true
+ },
"node_modules/lodash.sortedlastindex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/lodash.sortedlastindex/-/lodash.sortedlastindex-4.1.0.tgz",
@@ -7334,6 +8035,12 @@
"integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==",
"dev": true
},
+ "node_modules/lodash.upperfirst": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz",
+ "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==",
+ "dev": true
+ },
"node_modules/loupe": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
@@ -7546,9 +8253,9 @@
"dev": true
},
"node_modules/meow": {
- "version": "13.1.0",
- "resolved": "https://registry.npmjs.org/meow/-/meow-13.1.0.tgz",
- "integrity": "sha512-o5R/R3Tzxq0PJ3v3qcQJtSvSE9nKOLSAaDuuoMzDVuGTwHdccMWcYomh9Xolng2tjT6O/Y83d+0coVGof6tqmA==",
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
+ "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
"dev": true,
"engines": {
"node": ">=18"
@@ -7571,16 +8278,15 @@
}
},
"node_modules/mermaid": {
- "version": "10.7.0",
- "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.7.0.tgz",
- "integrity": "sha512-PsvGupPCkN1vemAAjScyw4pw34p4/0dZkSrqvAB26hUvJulOWGIwt35FZWmT9wPIi4r0QLa5X0PB4YLIGn0/YQ==",
+ "version": "10.8.0",
+ "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.8.0.tgz",
+ "integrity": "sha512-9CzfSreRjdDJxX796+jW4zjEq0DVw5xVF0nWsqff8OTbrt+ml0TZ5PyYUjjUZJa2NYxYJZZXewEquxGiM8qZEA==",
"dependencies": {
"@braintree/sanitize-url": "^6.0.1",
"@types/d3-scale": "^4.0.3",
"@types/d3-scale-chromatic": "^3.0.0",
- "cytoscape": "^3.23.0",
+ "cytoscape": "^3.28.1",
"cytoscape-cose-bilkent": "^4.1.0",
- "cytoscape-fcose": "^2.1.0",
"d3": "^7.4.0",
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.10",
@@ -8116,7 +8822,6 @@
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
"integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
- "dev": true,
"engines": {
"node": ">=16 || 14 >=14.17"
}
@@ -8134,9 +8839,9 @@
}
},
"node_modules/monaco-editor": {
- "version": "0.45.0",
- "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.45.0.tgz",
- "integrity": "sha512-mjv1G1ZzfEE3k9HZN0dQ2olMdwIfaeAAjFiwNprLfYNRSz7ctv9XuCT7gPtBGrMUeV1/iZzYKj17Khu1hxoHOA=="
+ "version": "0.46.0",
+ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.46.0.tgz",
+ "integrity": "sha512-ADwtLIIww+9FKybWscd7OCfm9odsFYHImBRI1v9AviGce55QY8raT+9ihH8jX/E/e6QVSGM+pKj4jSUSRmALNQ=="
},
"node_modules/monaco-editor-webpack-plugin": {
"version": "7.1.0",
@@ -8168,6 +8873,16 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
@@ -8311,7 +9026,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -8378,6 +9092,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@@ -8414,6 +9136,67 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/object.entries": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz",
+ "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz",
+ "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.groupby": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz",
+ "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==",
+ "dev": true,
+ "dependencies": {
+ "array.prototype.filter": "^1.0.3",
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.0.0"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz",
+ "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -8496,7 +9279,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
"dependencies": {
"callsites": "^3.0.0"
},
@@ -8508,7 +9290,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
- "dev": true,
"dependencies": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
@@ -8578,7 +9359,6 @@
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
- "dev": true,
"dependencies": {
"lru-cache": "^9.1.1 || ^10.0.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
@@ -8594,7 +9374,6 @@
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
"integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
- "dev": true,
"engines": {
"node": "14 || >=16.14"
}
@@ -8624,9 +9403,9 @@
}
},
"node_modules/pdfobject": {
- "version": "2.2.12",
- "resolved": "https://registry.npmjs.org/pdfobject/-/pdfobject-2.2.12.tgz",
- "integrity": "sha512-D0oyD/sj8j82AMaJhoyMaY1aD5TkbpU3FbJC6w9/cpJlZRpYHqAkutXw1Ca/FKjYPZmTAu58uGIfgOEaDlbY8A=="
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pdfobject/-/pdfobject-2.3.0.tgz",
+ "integrity": "sha512-w/9pXDXTDs3IDmOri/w8lM/w6LHR0/F4fcBLLzH+4csSoyshQ5su0TE7k0FLHZO7aOjVLDGecqd1M89+PVpVAA=="
},
"node_modules/picocolors": {
"version": "1.0.0",
@@ -8644,6 +9423,22 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
+ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
@@ -8721,12 +9516,12 @@
"dev": true
},
"node_modules/playwright": {
- "version": "1.41.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.1.tgz",
- "integrity": "sha512-gdZAWG97oUnbBdRL3GuBvX3nDDmUOuqzV/D24dytqlKt+eI5KbwusluZRGljx1YoJKZ2NRPaeWiFTeGZO7SosQ==",
+ "version": "1.41.2",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz",
+ "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==",
"dev": true,
"dependencies": {
- "playwright-core": "1.41.1"
+ "playwright-core": "1.41.2"
},
"bin": {
"playwright": "cli.js"
@@ -8739,9 +9534,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.41.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.1.tgz",
- "integrity": "sha512-/KPO5DzXSMlxSX77wy+HihKGOunh3hqndhqeo/nMxfigiKzogn8kfL0ZBDu0L1RKgan5XHCPmn6zXd2NUJgjhg==",
+ "version": "1.41.2",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz",
+ "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
@@ -8769,9 +9564,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.33",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
- "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
+ "version": "8.4.35",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
+ "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
"funding": [
{
"type": "opencollective",
@@ -8810,6 +9605,70 @@
"node": "^12 || >=14"
}
},
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "cosmiconfig": "^9.0.0",
+ "jiti": "^1.20.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "@rspack/core": "0.x || 1.x",
+ "postcss": "^7.0.0 || ^8.0.1",
+ "webpack": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rspack/core": {
+ "optional": true
+ },
+ "webpack": {
+ "optional": true
+ }
+ }
+ },
"node_modules/postcss-modules-extract-imports": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
@@ -8865,6 +9724,24 @@
"postcss": "^8.1.0"
}
},
+ "node_modules/postcss-nested": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
+ "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
+ "dependencies": {
+ "postcss-selector-parser": "^6.0.11"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
"node_modules/postcss-resolve-nested-selector": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz",
@@ -8958,6 +9835,33 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
+ "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/pretty-format": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
@@ -9075,6 +9979,14 @@
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
"dev": true
},
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
"node_modules/read-pkg": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
@@ -9177,6 +10089,17 @@
"node": ">=8"
}
},
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
"node_modules/rechoir": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
@@ -9200,6 +10123,27 @@
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
+ "node_modules/reflect.getprototypeof": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz",
+ "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.0.0",
+ "get-intrinsic": "^1.2.3",
+ "globalthis": "^1.0.3",
+ "which-builtin-type": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
@@ -9228,14 +10172,15 @@
}
},
"node_modules/regexp.prototype.flags": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
- "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==",
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
+ "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.2.0",
- "set-function-name": "^2.0.0"
+ "call-bind": "^1.0.6",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "set-function-name": "^2.0.1"
},
"engines": {
"node": ">= 0.4"
@@ -9336,7 +10281,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
"engines": {
"node": ">=4"
}
@@ -9490,13 +10434,13 @@
]
},
"node_modules/safe-regex-test": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz",
- "integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz",
+ "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.5",
- "get-intrinsic": "^1.2.2",
+ "call-bind": "^1.0.6",
+ "es-errors": "^1.3.0",
"is-regex": "^1.1.4"
},
"engines": {
@@ -9568,9 +10512,9 @@
}
},
"node_modules/semver": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
- "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+ "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -9609,14 +10553,15 @@
}
},
"node_modules/set-function-length": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz",
- "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
+ "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
"dev": true,
"dependencies": {
- "define-data-property": "^1.1.1",
+ "define-data-property": "^1.1.2",
+ "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.2",
+ "get-intrinsic": "^1.2.3",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.1"
},
@@ -9669,14 +10614,18 @@
}
},
"node_modules/side-channel": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
- "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
+ "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.0",
- "get-intrinsic": "^1.0.2",
- "object-inspect": "^1.9.0"
+ "call-bind": "^1.0.6",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4",
+ "object-inspect": "^1.13.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -9692,7 +10641,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
"engines": {
"node": ">=14"
},
@@ -9739,9 +10687,9 @@
}
},
"node_modules/solid-js": {
- "version": "1.8.12",
- "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.8.12.tgz",
- "integrity": "sha512-sLE/i6M9FSWlov3a2pTC5ISzanH2aKwqXTZj+bbFt4SUrVb4iGEa7fpILBMOxsQjkv3eXqEk6JVLlogOdTe0UQ==",
+ "version": "1.8.15",
+ "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.8.15.tgz",
+ "integrity": "sha512-d0QP/efr3UVcwGgWVPveQQ0IHOH6iU7yUhc2piy8arNG8wxKmvUy1kFxyF8owpmfCWGB87usDKMaVnsNYZm+Vw==",
"dependencies": {
"csstype": "^3.1.0",
"seroval": "^1.0.3",
@@ -9822,9 +10770,9 @@
}
},
"node_modules/spdx-exceptions": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.4.0.tgz",
- "integrity": "sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw=="
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
+ "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="
},
"node_modules/spdx-expression-parse": {
"version": "3.0.1",
@@ -9844,9 +10792,9 @@
}
},
"node_modules/spdx-license-ids": {
- "version": "3.0.16",
- "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz",
- "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw=="
+ "version": "3.0.17",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz",
+ "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg=="
},
"node_modules/spdx-ranges": {
"version": "2.1.1",
@@ -9903,7 +10851,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -9974,7 +10921,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -9982,6 +10928,15 @@
"node": ">=8"
}
},
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/strip-final-newline": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
@@ -10284,6 +11239,56 @@
"node": ">= 8"
}
},
+ "node_modules/sucrase": {
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "glob": "^10.3.10",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/sucrase/node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/sucrase/node_modules/glob": {
+ "version": "10.3.10",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+ "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+ "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/superstruct": {
"version": "0.10.13",
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.10.13.tgz",
@@ -10324,6 +11329,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/svg-element-attributes": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/svg-element-attributes/-/svg-element-attributes-1.3.1.tgz",
+ "integrity": "sha512-Bh05dSOnJBf3miNMqpsormfNtfidA/GxQVakhtn0T4DECWKeXQRQUceYjJ+OxYiiLdGe4Jo9iFV8wICFapFeIA==",
+ "dev": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/svg-tags": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz",
@@ -10365,9 +11380,9 @@
}
},
"node_modules/swagger-ui-dist": {
- "version": "5.11.2",
- "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.2.tgz",
- "integrity": "sha512-jQG0cRgJNMZ7aCoiFofnoojeSaa/+KgWaDlfgs8QN+BXoGMpxeMVY5OEnjq4OlNvF3yjftO8c9GRAgcHlO+u7A=="
+ "version": "5.11.6",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.6.tgz",
+ "integrity": "sha512-K5BpYuMoPpJY7NwCHIWohH6tU9o0fs1+plNT5KJ+3BBlVEh4H1CpeKJV8o91lpscVY9oqb2jmaAassnW3wVoTg=="
},
"node_modules/symbol-tree": {
"version": "3.2.4",
@@ -10387,6 +11402,22 @@
"node": ">=14"
}
},
+ "node_modules/synckit": {
+ "version": "0.8.8",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz",
+ "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==",
+ "dev": true,
+ "dependencies": {
+ "@pkgr/core": "^0.1.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unts"
+ }
+ },
"node_modules/table": {
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz",
@@ -10403,6 +11434,87 @@
"node": ">=10.0.0"
}
},
+ "node_modules/tailwindcss": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
+ "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.5.3",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.0",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.19.1",
+ "lilconfig": "^2.1.0",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.23",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.1",
+ "postcss-nested": "^6.0.1",
+ "postcss-selector-parser": "^6.0.11",
+ "resolve": "^1.22.2",
+ "sucrase": "^3.32.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/postcss-load-config": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
+ "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "lilconfig": "^3.0.0",
+ "yaml": "^2.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
+ "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
"node_modules/tapable": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
@@ -10412,9 +11524,9 @@
}
},
"node_modules/terser": {
- "version": "5.27.0",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz",
- "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==",
+ "version": "5.27.1",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.1.tgz",
+ "integrity": "sha512-29wAr6UU/oQpnTw5HoadwjUZnFQXGdOfj0LjZ4sVxzqwHh/QVkvr7m8y9WoR4iN3FRitVduTc6KdjcW38Npsug==",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
@@ -10517,6 +11629,25 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/throttle-debounce": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.0.tgz",
@@ -10546,9 +11677,9 @@
}
},
"node_modules/tinyspy": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz",
- "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==",
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
+ "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
"dev": true,
"engines": {
"node": ">=14.0.0"
@@ -10620,12 +11751,12 @@
"integrity": "sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ=="
},
"node_modules/ts-api-utils": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
- "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz",
+ "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==",
"dev": true,
"engines": {
- "node": ">=16.13.0"
+ "node": ">=16"
},
"peerDependencies": {
"typescript": ">=4.2.0"
@@ -10639,6 +11770,35 @@
"node": ">=6.10"
}
},
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
+ "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
+ "dev": true,
+ "dependencies": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "node_modules/tsconfig-paths/node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
@@ -10679,14 +11839,14 @@
}
},
"node_modules/typed-array-buffer": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
- "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.1.tgz",
+ "integrity": "sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.2",
- "get-intrinsic": "^1.2.1",
- "is-typed-array": "^1.1.10"
+ "call-bind": "^1.0.6",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.13"
},
"engines": {
"node": ">= 0.4"
@@ -10758,9 +11918,9 @@
}
},
"node_modules/typo-js": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.3.tgz",
- "integrity": "sha512-67Hyl94beZX8gmTap7IDPrG5hy2cHftgsCAcGvE1tzuxGT+kRB+zSBin0wIMwysYw8RUCBCvv9UfQl8TNM75dA=="
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.4.tgz",
+ "integrity": "sha512-Oy/k+tFle5NAA3J/yrrYGfvEnPVrDZ8s8/WCwjUE75k331QyKIsFss7byQ/PzBmXLY6h1moRnZbnaxWBe3I3CA=="
},
"node_modules/uc.micro": {
"version": "2.0.0",
@@ -10769,9 +11929,9 @@
"dev": true
},
"node_modules/ufo": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz",
- "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz",
+ "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==",
"dev": true
},
"node_modules/uint8-to-base64": {
@@ -10850,9 +12010,9 @@
}
},
"node_modules/updates": {
- "version": "15.1.1",
- "resolved": "https://registry.npmjs.org/updates/-/updates-15.1.1.tgz",
- "integrity": "sha512-dMz/4251b0lV7yR58tuydCKaiWxOa18YM8fnRgtiDVzQ5ALopTZhMckv00w0nSMj6OFMFKLshTZGkX4dAebaaw==",
+ "version": "15.1.2",
+ "resolved": "https://registry.npmjs.org/updates/-/updates-15.1.2.tgz",
+ "integrity": "sha512-+/JT4NChl82iexV9G80TY5HF3ubQ5O9UTOk3LlCo4Y4aRCYvo1h4bJE8YkP0PE7KiFRWIQq/rPmUYrY2QF8wVA==",
"dev": true,
"bin": {
"updates": "bin/updates.js"
@@ -10948,13 +12108,13 @@
}
},
"node_modules/vite": {
- "version": "5.0.12",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz",
- "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==",
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz",
+ "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==",
"dev": true,
"dependencies": {
"esbuild": "^0.19.3",
- "postcss": "^8.4.32",
+ "postcss": "^8.4.35",
"rollup": "^4.2.0"
},
"bin": {
@@ -11025,9 +12185,9 @@
}
},
"node_modules/vite-string-plugin": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/vite-string-plugin/-/vite-string-plugin-1.1.3.tgz",
- "integrity": "sha512-uHL8BV2tBf32T2slYpS0vRzGVrAS3iuivtGknjzyecvpSq2AiBSkyLAjEvvIZuZGDDGFHyGX+5+yc3OBPjWDlA==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/vite-string-plugin/-/vite-string-plugin-1.1.5.tgz",
+ "integrity": "sha512-KRCIFX3PWVUuEjpi9O7EKLT9E27OqOA3RimIvVx6cziLAUxvnk2VvHQfMrP+mKkqyqqSmnnYyTig3OyDnK/zlA==",
"dev": true
},
"node_modules/vite/node_modules/@types/estree": {
@@ -11051,9 +12211,9 @@
}
},
"node_modules/vite/node_modules/rollup": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz",
- "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==",
+ "version": "4.11.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.11.0.tgz",
+ "integrity": "sha512-2xIbaXDXjf3u2tajvA5xROpib7eegJ9Y/uPlSFhXLNpK9ampCczXAhLEb5yLzJyG3LAdI1NWtNjDXiLyniNdjQ==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
@@ -11066,19 +12226,19 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.9.6",
- "@rollup/rollup-android-arm64": "4.9.6",
- "@rollup/rollup-darwin-arm64": "4.9.6",
- "@rollup/rollup-darwin-x64": "4.9.6",
- "@rollup/rollup-linux-arm-gnueabihf": "4.9.6",
- "@rollup/rollup-linux-arm64-gnu": "4.9.6",
- "@rollup/rollup-linux-arm64-musl": "4.9.6",
- "@rollup/rollup-linux-riscv64-gnu": "4.9.6",
- "@rollup/rollup-linux-x64-gnu": "4.9.6",
- "@rollup/rollup-linux-x64-musl": "4.9.6",
- "@rollup/rollup-win32-arm64-msvc": "4.9.6",
- "@rollup/rollup-win32-ia32-msvc": "4.9.6",
- "@rollup/rollup-win32-x64-msvc": "4.9.6",
+ "@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",
"fsevents": "~2.3.2"
}
},
@@ -11149,9 +12309,9 @@
}
},
"node_modules/vitest/node_modules/magic-string": {
- "version": "0.30.6",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz",
- "integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==",
+ "version": "0.30.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
+ "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
@@ -11161,15 +12321,15 @@
}
},
"node_modules/vue": {
- "version": "3.4.15",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz",
- "integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==",
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.19.tgz",
+ "integrity": "sha512-W/7Fc9KUkajFU8dBeDluM4sRGc/aa4YJnOYck8dkjgZoXtVsn3OeTGni66FV1l3+nvPA7VBFYtPioaGKUmEADw==",
"dependencies": {
- "@vue/compiler-dom": "3.4.15",
- "@vue/compiler-sfc": "3.4.15",
- "@vue/runtime-dom": "3.4.15",
- "@vue/server-renderer": "3.4.15",
- "@vue/shared": "3.4.15"
+ "@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"
},
"peerDependencies": {
"typescript": "*"
@@ -11189,6 +12349,15 @@
"vue": "^3.2.37"
}
},
+ "node_modules/vue-chartjs": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.0.tgz",
+ "integrity": "sha512-8XqX0JU8vFZ+WA2/knz4z3ThClduni2Nm0BMe2u0mXgTfd9pXrmJ07QBI+WAij5P/aPmPMX54HCE1seWL37ZdQ==",
+ "peerDependencies": {
+ "chart.js": "^4.1.1",
+ "vue": "^3.0.0-0 || ^2.7.0"
+ }
+ },
"node_modules/vue-eslint-parser": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz",
@@ -11294,9 +12463,9 @@
}
},
"node_modules/webpack": {
- "version": "5.90.1",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz",
- "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==",
+ "version": "5.90.2",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.2.tgz",
+ "integrity": "sha512-ziXu8ABGr0InCMEYFnHrYweinHK2PWrMqnwdHk2oK3rRhv/1B+2FnfwYv5oD+RrknK/Pp/Hmyvu+eAsaMYhzCw==",
"dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.5",
@@ -11555,17 +12724,58 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/which-typed-array": {
- "version": "1.1.13",
- "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz",
- "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==",
+ "node_modules/which-builtin-type": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz",
+ "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==",
"dev": true,
"dependencies": {
- "available-typed-arrays": "^1.0.5",
- "call-bind": "^1.0.4",
+ "function.prototype.name": "^1.1.5",
+ "has-tostringtag": "^1.0.0",
+ "is-async-function": "^2.0.0",
+ "is-date-object": "^1.0.5",
+ "is-finalizationregistry": "^1.0.2",
+ "is-generator-function": "^1.0.10",
+ "is-regex": "^1.1.4",
+ "is-weakref": "^1.0.2",
+ "isarray": "^2.0.5",
+ "which-boxed-primitive": "^1.0.2",
+ "which-collection": "^1.0.1",
+ "which-typed-array": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz",
+ "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==",
+ "dev": true,
+ "dependencies": {
+ "is-map": "^2.0.1",
+ "is-set": "^2.0.1",
+ "is-weakmap": "^2.0.1",
+ "is-weakset": "^2.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz",
+ "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.6",
+ "call-bind": "^1.0.5",
"for-each": "^0.3.3",
"gopd": "^1.0.1",
- "has-tostringtag": "^1.0.0"
+ "has-tostringtag": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
@@ -11616,7 +12826,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
@@ -11754,6 +12963,14 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"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==",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/yargs": {
"version": "17.3.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz",
diff --git a/package.json b/package.json
index 3defca1a3d..3f0f9103cf 100644
--- a/package.json
+++ b/package.json
@@ -17,59 +17,69 @@
"@webcomponents/custom-elements": "1.6.0",
"add-asset-webpack-plugin": "2.0.1",
"ansi_up": "6.0.2",
- "asciinema-player": "3.6.3",
+ "asciinema-player": "3.6.4",
+ "chart.js": "4.4.1",
+ "chartjs-adapter-dayjs-4": "1.0.4",
+ "chartjs-plugin-zoom": "2.0.1",
"clippie": "4.0.6",
"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",
"esbuild-loader": "4.0.3",
"escape-goat": "4.0.0",
"fast-glob": "3.3.2",
"htmx.org": "1.9.10",
+ "idiomorph": "0.3.0",
"jquery": "3.7.1",
"katex": "0.16.9",
"license-checker-webpack-plugin": "0.2.1",
- "lightningcss-loader": "2.1.0",
- "mermaid": "10.7.0",
+ "mermaid": "10.8.0",
"mini-css-extract-plugin": "2.8.0",
"minimatch": "9.0.3",
- "monaco-editor": "0.45.0",
+ "monaco-editor": "0.46.0",
"monaco-editor-webpack-plugin": "7.1.0",
- "pdfobject": "2.2.12",
+ "pdfobject": "2.3.0",
+ "postcss": "8.4.35",
+ "postcss-loader": "8.1.0",
"pretty-ms": "9.0.0",
"sortablejs": "1.15.2",
- "swagger-ui-dist": "5.11.2",
+ "swagger-ui-dist": "5.11.6",
+ "tailwindcss": "3.4.1",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0",
"tippy.js": "6.3.7",
"toastify-js": "1.12.0",
"tributejs": "5.1.3",
"uint8-to-base64": "0.2.0",
- "vue": "3.4.15",
+ "vue": "3.4.19",
"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.1",
+ "webpack": "5.90.2",
"webpack-cli": "5.1.4",
"wrap-ansi": "9.0.0"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.1.0",
- "@playwright/test": "1.41.1",
+ "@playwright/test": "1.41.2",
"@stoplight/spectral-cli": "6.11.0",
- "@stylistic/eslint-plugin-js": "1.5.4",
+ "@stylistic/eslint-plugin-js": "1.6.2",
"@stylistic/stylelint-plugin": "2.0.0",
- "@vitejs/plugin-vue": "5.0.3",
+ "@vitejs/plugin-vue": "5.0.4",
"eslint": "8.56.0",
"eslint-plugin-array-func": "4.0.0",
+ "eslint-plugin-github": "4.10.1",
"eslint-plugin-i": "2.29.1",
"eslint-plugin-jquery": "1.5.1",
"eslint-plugin-no-jquery": "2.7.0",
"eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-regexp": "2.2.0",
- "eslint-plugin-sonarjs": "0.23.0",
- "eslint-plugin-unicorn": "50.0.1",
- "eslint-plugin-vitest": "0.3.21",
+ "eslint-plugin-sonarjs": "0.24.0",
+ "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-scoped-css": "2.7.2",
@@ -81,13 +91,11 @@
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
"stylelint-declaration-strict-value": "1.10.4",
"svgo": "3.2.0",
- "updates": "15.1.1",
- "vite-string-plugin": "1.1.3",
+ "updates": "15.1.2",
+ "vite-string-plugin": "1.1.5",
"vitest": "1.2.2"
},
"browserslist": [
- "defaults",
- "not ie > 0",
- "not ie_mob > 0"
+ "defaults"
]
}
diff --git a/playwright.config.js b/playwright.config.js
index b7badf1cc0..6595bc4312 100644
--- a/playwright.config.js
+++ b/playwright.config.js
@@ -11,6 +11,13 @@ export default {
testDir: './tests/e2e/',
testMatch: /.*\.test\.e2e\.js/, // Match any .test.e2e.js files
+ /**
+ * Only run one test at a time, running multiple could lead to a inconsistent
+ * database state.
+ */
+ fullyParallel: false,
+ workers: 1,
+
/* Maximum time one test can run for. */
timeout: 30 * 1000,
@@ -64,13 +71,12 @@ export default {
},
},
- // disabled because of https://github.com/go-gitea/gitea/issues/21355
- // {
- // name: 'firefox',
- // use: {
- // ...devices['Desktop Firefox'],
- // },
- // },
+ {
+ name: 'firefox',
+ use: {
+ ...devices['Desktop Firefox'],
+ },
+ },
{
name: 'webkit',
diff --git a/poetry.lock b/poetry.lock
index 74d202c919..4cb58c6ef2 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -342,13 +342,13 @@ telegram = ["requests"]
[[package]]
name = "yamllint"
-version = "1.33.0"
+version = "1.35.0"
description = "A linter for YAML files."
optional = false
python-versions = ">=3.8"
files = [
- {file = "yamllint-1.33.0-py3-none-any.whl", hash = "sha256:28a19f5d68d28d8fec538a1db21bb2d84c7dc2e2ea36266da8d4d1c5a683814d"},
- {file = "yamllint-1.33.0.tar.gz", hash = "sha256:2dceab9ef2d99518a2fcf4ffc964d44250ac4459be1ba3ca315118e4a1a81f7d"},
+ {file = "yamllint-1.35.0-py3-none-any.whl", hash = "sha256:601b0adaaac6d9bacb16a2e612e7ee8d23caf941ceebf9bfe2cff0f196266004"},
+ {file = "yamllint-1.35.0.tar.gz", hash = "sha256:9bc99c3e9fe89b4c6ee26e17aa817cf2d14390de6577cb6e2e6ed5f72120c835"},
]
[package.dependencies]
@@ -361,4 +361,4 @@ dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
-content-hash = "175c87d138a47ba190a2c3f16b801f694915cc6f2367a358585df9cd1b17ff96"
+content-hash = "ba1c2c4235872f67354b5f52aa5bf0cd616354961530d9dc907f9fba28cc1ece"
diff --git a/public/assets/img/404.png b/public/assets/img/404.png
deleted file mode 100644
index 8b66c971f4..0000000000
Binary files a/public/assets/img/404.png and /dev/null differ
diff --git a/public/assets/img/500.png b/public/assets/img/500.png
deleted file mode 100644
index dab69206ad..0000000000
Binary files a/public/assets/img/500.png and /dev/null differ
diff --git a/public/assets/img/svg/gitea-discord.svg b/public/assets/img/svg/gitea-discord.svg
index 6ebbdcdcc3..2edcb4fed7 100644
--- a/public/assets/img/svg/gitea-discord.svg
+++ b/public/assets/img/svg/gitea-discord.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-forgejo.svg b/public/assets/img/svg/gitea-forgejo.svg
index ef617c00f3..22ae790357 100644
--- a/public/assets/img/svg/gitea-forgejo.svg
+++ b/public/assets/img/svg/gitea-forgejo.svg
@@ -1,9 +1 @@
-
+
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-gitea.svg b/public/assets/img/svg/gitea-gitea.svg
index 5d89fa1da9..61ef37074e 100644
--- a/public/assets/img/svg/gitea-gitea.svg
+++ b/public/assets/img/svg/gitea-gitea.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index cf65f0ddfa..ef763da24d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -9,7 +9,7 @@ python = "^3.8"
[tool.poetry.group.dev.dependencies]
djlint = "1.34.1"
-yamllint = "1.33.0"
+yamllint = "1.35.0"
[tool.djlint]
profile="golang"
diff --git a/routers/api/actions/artifacts.go b/routers/api/actions/artifacts.go
index 3363c4c0e8..9fbd3f045d 100644
--- a/routers/api/actions/artifacts.go
+++ b/routers/api/actions/artifacts.go
@@ -63,6 +63,7 @@ package actions
import (
"crypto/md5"
+ "errors"
"fmt"
"net/http"
"strconv"
@@ -426,7 +427,19 @@ func (ar artifactRoutes) getDownloadArtifactURL(ctx *ArtifactContext) {
var items []downloadArtifactResponseItem
for _, artifact := range artifacts {
- downloadURL := ar.buildArtifactURL(runID, strconv.FormatInt(artifact.ID, 10), "download")
+ var downloadURL string
+ if setting.Actions.ArtifactStorage.MinioConfig.ServeDirect {
+ u, err := ar.fs.URL(artifact.StoragePath, artifact.ArtifactName)
+ if err != nil && !errors.Is(err, storage.ErrURLNotSupported) {
+ log.Error("Error getting serve direct url: %v", err)
+ }
+ if u != nil {
+ downloadURL = u.String()
+ }
+ }
+ if downloadURL == "" {
+ downloadURL = ar.buildArtifactURL(runID, strconv.FormatInt(artifact.ID, 10), "download")
+ }
item := downloadArtifactResponseItem{
Path: util.PathJoinRel(itemPath, artifact.ArtifactPath),
ItemType: "file",
diff --git a/routers/api/actions/ping/ping.go b/routers/api/actions/ping/ping.go
index 55219fe12b..13985c93a3 100644
--- a/routers/api/actions/ping/ping.go
+++ b/routers/api/actions/ping/ping.go
@@ -12,7 +12,7 @@ import (
pingv1 "code.gitea.io/actions-proto-go/ping/v1"
"code.gitea.io/actions-proto-go/ping/v1/pingv1connect"
- "github.com/bufbuild/connect-go"
+ "connectrpc.com/connect"
)
func NewPingServiceHandler() (string, http.Handler) {
diff --git a/routers/api/actions/ping/ping_test.go b/routers/api/actions/ping/ping_test.go
index f39e94a1f3..098b003ea2 100644
--- a/routers/api/actions/ping/ping_test.go
+++ b/routers/api/actions/ping/ping_test.go
@@ -11,7 +11,7 @@ import (
pingv1 "code.gitea.io/actions-proto-go/ping/v1"
"code.gitea.io/actions-proto-go/ping/v1/pingv1connect"
- "github.com/bufbuild/connect-go"
+ "connectrpc.com/connect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
diff --git a/routers/api/actions/runner/interceptor.go b/routers/api/actions/runner/interceptor.go
index ddc754dbc7..c2f4ade174 100644
--- a/routers/api/actions/runner/interceptor.go
+++ b/routers/api/actions/runner/interceptor.go
@@ -15,7 +15,7 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
- "github.com/bufbuild/connect-go"
+ "connectrpc.com/connect"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
diff --git a/routers/api/actions/runner/runner.go b/routers/api/actions/runner/runner.go
index 8df6f297ce..caaad2b83b 100644
--- a/routers/api/actions/runner/runner.go
+++ b/routers/api/actions/runner/runner.go
@@ -16,7 +16,7 @@ import (
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
"code.gitea.io/actions-proto-go/runner/v1/runnerv1connect"
- "github.com/bufbuild/connect-go"
+ "connectrpc.com/connect"
gouuid "github.com/google/uuid"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
diff --git a/routers/api/packages/alpine/alpine.go b/routers/api/packages/alpine/alpine.go
index bb14c5163a..3701fc97c9 100644
--- a/routers/api/packages/alpine/alpine.go
+++ b/routers/api/packages/alpine/alpine.go
@@ -14,6 +14,7 @@ import (
"strings"
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"
@@ -30,6 +31,53 @@ func apiError(ctx *context.Context, status int, obj any) {
})
}
+func createOrAddToExisting(ctx *context.Context, pck *alpine_module.Package, branch, repository, architecture string, buf packages_module.HashedSizeReader, fileMetadataRaw []byte) {
+ _, _, err := packages_service.CreatePackageOrAddFileToExisting(
+ ctx,
+ &packages_service.PackageCreationInfo{
+ PackageInfo: packages_service.PackageInfo{
+ Owner: ctx.Package.Owner,
+ PackageType: packages_model.TypeAlpine,
+ Name: pck.Name,
+ Version: pck.Version,
+ },
+ Creator: ctx.Doer,
+ Metadata: pck.VersionMetadata,
+ },
+ &packages_service.PackageFileCreationInfo{
+ PackageFileInfo: packages_service.PackageFileInfo{
+ Filename: fmt.Sprintf("%s-%s.apk", pck.Name, pck.Version),
+ CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, architecture),
+ },
+ Creator: ctx.Doer,
+ Data: buf,
+ IsLead: true,
+ Properties: map[string]string{
+ alpine_module.PropertyBranch: branch,
+ alpine_module.PropertyRepository: repository,
+ alpine_module.PropertyArchitecture: architecture,
+ alpine_module.PropertyMetadata: string(fileMetadataRaw),
+ },
+ },
+ )
+ if err != nil {
+ switch err {
+ case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile:
+ apiError(ctx, http.StatusConflict, err)
+ case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
+ apiError(ctx, http.StatusForbidden, err)
+ default:
+ apiError(ctx, http.StatusInternalServerError, err)
+ }
+ return
+ }
+
+ if err := alpine_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, branch, repository, pck.FileMetadata.Architecture); err != nil {
+ apiError(ctx, http.StatusInternalServerError, err)
+ return
+ }
+}
+
func GetRepositoryKey(ctx *context.Context) {
_, pub, err := alpine_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID)
if err != nil {
@@ -133,49 +181,36 @@ func UploadPackageFile(ctx *context.Context) {
return
}
- _, _, err = packages_service.CreatePackageOrAddFileToExisting(
- ctx,
- &packages_service.PackageCreationInfo{
- PackageInfo: packages_service.PackageInfo{
- Owner: ctx.Package.Owner,
- PackageType: packages_model.TypeAlpine,
- Name: pck.Name,
- Version: pck.Version,
- },
- Creator: ctx.Doer,
- Metadata: pck.VersionMetadata,
- },
- &packages_service.PackageFileCreationInfo{
- PackageFileInfo: packages_service.PackageFileInfo{
- Filename: fmt.Sprintf("%s-%s.apk", pck.Name, pck.Version),
- CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, pck.FileMetadata.Architecture),
- },
- Creator: ctx.Doer,
- Data: buf,
- IsLead: true,
- Properties: map[string]string{
- alpine_module.PropertyBranch: branch,
- alpine_module.PropertyRepository: repository,
- alpine_module.PropertyArchitecture: pck.FileMetadata.Architecture,
- alpine_module.PropertyMetadata: string(fileMetadataRaw),
- },
- },
- )
- if err != nil {
- switch err {
- case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile:
- apiError(ctx, http.StatusConflict, err)
- case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
- apiError(ctx, http.StatusForbidden, err)
- default:
+ // Check whether the package being uploaded has no architecture defined.
+ // If true, loop through the available architectures in the repo and create
+ // the package file for the each architecture. If there are no architectures
+ // available on the repository, fallback to x86_64
+ if pck.FileMetadata.Architecture == "noarch" {
+ architectures, err := alpine_model.GetArchitectures(ctx, ctx.Package.Owner.ID, repository)
+ if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
+ return
}
- return
- }
- if err := alpine_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, branch, repository, pck.FileMetadata.Architecture); err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
+ if len(architectures) == 0 {
+ architectures = []string{
+ "x86_64",
+ }
+ }
+
+ for _, arch := range architectures {
+ pck.FileMetadata.Architecture = arch
+
+ fileMetadataRaw, err := json.Marshal(pck.FileMetadata)
+ if err != nil {
+ apiError(ctx, http.StatusInternalServerError, err)
+ return
+ }
+
+ createOrAddToExisting(ctx, pck, branch, repository, pck.FileMetadata.Architecture, buf, fileMetadataRaw)
+ }
+ } else {
+ createOrAddToExisting(ctx, pck, branch, repository, pck.FileMetadata.Architecture, buf, fileMetadataRaw)
}
ctx.Status(http.StatusCreated)
diff --git a/routers/api/packages/swift/swift.go b/routers/api/packages/swift/swift.go
index 427e262d06..6ad289e51e 100644
--- a/routers/api/packages/swift/swift.go
+++ b/routers/api/packages/swift/swift.go
@@ -157,7 +157,7 @@ func EnumeratePackageVersions(ctx *context.Context) {
}
type Resource struct {
- Name string `json:"id"`
+ Name string `json:"name"`
Type string `json:"type"`
Checksum string `json:"checksum"`
}
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index 272996f43d..2ce7651a09 100644
--- a/routers/api/v1/admin/user.go
+++ b/routers/api/v1/admin/user.go
@@ -21,7 +21,6 @@ import (
"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/user"
"code.gitea.io/gitea/routers/api/v1/utils"
@@ -117,11 +116,8 @@ func CreateUser(ctx *context.APIContext) {
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
- IsActive: util.OptionalBoolTrue,
- }
-
- if form.Restricted != nil {
- overwriteDefault.IsRestricted = util.OptionalBoolOf(*form.Restricted)
+ IsActive: optional.Some(true),
+ IsRestricted: optional.FromPtr(form.Restricted),
}
if form.Visibility != "" {
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 6d56bb36ec..76c7992308 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -9,7 +9,7 @@
//
// Schemes: https, http
// BasePath: /api/v1
-// Version: {{AppVer | JSEscape | Safe}}
+// Version: {{AppVer | JSEscape}}
// License: MIT http://opensource.org/licenses/MIT
//
// Consumes:
@@ -974,7 +974,9 @@ func Routes() *web.Route {
m.Get("/{target}", user.CheckFollowing)
})
- m.Get("/starred", user.GetStarredRepos)
+ if !setting.Repository.DisableStars {
+ m.Get("/starred", user.GetStarredRepos)
+ }
m.Get("/subscriptions", user.GetWatchedRepos)
}, context_service.UserAssignmentAPI())
@@ -1049,14 +1051,16 @@ func Routes() *web.Route {
Post(bind(api.CreateRepoOption{}), repo.Create)
// (repo scope)
- m.Group("/starred", func() {
- m.Get("", user.GetMyStarredRepos)
- m.Group("/{username}/{reponame}", func() {
- m.Get("", user.IsStarring)
- m.Put("", user.Star)
- m.Delete("", user.Unstar)
- }, repoAssignment())
- }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
+ if !setting.Repository.DisableStars {
+ m.Group("/starred", func() {
+ m.Get("", user.GetMyStarredRepos)
+ m.Group("/{username}/{reponame}", func() {
+ m.Get("", user.IsStarring)
+ m.Put("", user.Star)
+ m.Delete("", user.Unstar)
+ }, repoAssignment())
+ }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
+ }
m.Get("/times", repo.ListMyTrackedTimes)
m.Get("/stopwatches", repo.GetStopwatches)
m.Get("/subscriptions", user.GetMyWatchedRepos)
@@ -1174,8 +1178,10 @@ func Routes() *web.Route {
m.Get("/raw/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFile)
m.Get("/media/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFileOrLFS)
m.Get("/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
- m.Combo("/forks").Get(repo.ListForks).
- Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
+ if !setting.Repository.DisableForks {
+ m.Combo("/forks").Get(repo.ListForks).
+ Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
+ }
m.Group("/branches", func() {
m.Get("", repo.ListBranches)
m.Get("/*", repo.GetBranch)
@@ -1219,7 +1225,9 @@ func Routes() *web.Route {
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw)
- m.Get("/stargazers", repo.ListStargazers)
+ if !setting.Repository.DisableStars {
+ m.Get("/stargazers", repo.ListStargazers)
+ }
m.Get("/subscribers", repo.ListSubscribers)
m.Group("/subscription", func() {
m.Get("", user.IsWatching)
@@ -1300,6 +1308,7 @@ func Routes() *web.Route {
Delete(bind(api.PullReviewRequestOptions{}), repo.DeleteReviewRequests).
Post(bind(api.PullReviewRequestOptions{}), repo.CreateReviewRequests)
})
+ m.Get("/{base}/*", repo.GetPullRequestByBaseHead)
}, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
m.Group("/statuses", func() {
m.Combo("/{sha}").Get(repo.GetCommitStatuses).
@@ -1310,6 +1319,7 @@ func Routes() *web.Route {
m.Group("/{ref}", func() {
m.Get("/status", repo.GetCombinedCommitStatusByRef)
m.Get("/statuses", repo.GetCommitStatusesByRef)
+ m.Get("/pull", repo.GetCommitPullRequest)
}, context.ReferencesGitRepo())
}, reqRepoReader(unit.TypeCode))
m.Group("/git", func() {
diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index bd02a8afc4..2cdbcd25a2 100644
--- a/routers/api/v1/repo/branch.go
+++ b/routers/api/v1/repo/branch.go
@@ -17,9 +17,9 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/optional"
repo_module "code.gitea.io/gitea/modules/repository"
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/convert"
@@ -141,7 +141,7 @@ func DeleteBranch(ctx *context.APIContext) {
// check whether branches of this repository has been synced
totalNumOfBranches, err := db.Count[git_model.Branch](ctx, git_model.FindBranchOptions{
RepoID: ctx.Repo.Repository.ID,
- IsDeletedBranch: util.OptionalBoolFalse,
+ IsDeletedBranch: optional.Some(false),
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "CountBranches", err)
@@ -340,7 +340,7 @@ func ListBranches(ctx *context.APIContext) {
branchOpts := git_model.FindBranchOptions{
ListOptions: listOptions,
RepoID: ctx.Repo.Repository.ID,
- IsDeletedBranch: util.OptionalBoolFalse,
+ IsDeletedBranch: optional.Some(false),
}
var err error
totalNumOfBranches, err = db.Count[git_model.Branch](ctx, branchOpts)
diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go
index 43b6400009..d01cf6b8bc 100644
--- a/routers/api/v1/repo/commits.go
+++ b/routers/api/v1/repo/commits.go
@@ -10,6 +10,7 @@ import (
"net/http"
"strconv"
+ 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"
@@ -323,3 +324,53 @@ func DownloadCommitDiffOrPatch(ctx *context.APIContext) {
return
}
}
+
+// GetCommitPullRequest returns the pull request of the commit
+func GetCommitPullRequest(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/commits/{sha}/pull repository repoGetCommitPullRequest
+ // ---
+ // summary: Get the pull request of the commit
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // - name: sha
+ // in: path
+ // description: SHA of the commit to get
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/PullRequest"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ pr, err := issues_model.GetPullRequestByMergedCommit(ctx, ctx.Repo.Repository.ID, ctx.Params(":sha"))
+ if err != nil {
+ if issues_model.IsErrPullRequestNotExist(err) {
+ ctx.Error(http.StatusNotFound, "GetPullRequestByMergedCommit", err)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ }
+ return
+ }
+
+ if err = pr.LoadBaseRepo(ctx); err != nil {
+ ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
+ return
+ }
+ if err = pr.LoadHeadRepo(ctx); err != nil {
+ ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
+ return
+ }
+ ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
+}
diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go
index 7988dec8d3..94a6381e25 100644
--- a/routers/api/v1/repo/file.go
+++ b/routers/api/v1/repo/file.go
@@ -425,7 +425,7 @@ func canReadFiles(r *context.Repository) bool {
return r.Permission.CanRead(unit.TypeCode)
}
-func base64Reader(s string) (io.Reader, error) {
+func base64Reader(s string) (io.ReadSeeker, error) {
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return nil, err
@@ -779,13 +779,13 @@ func changeFilesCommitMessage(ctx *context.APIContext, files []*files_service.Ch
}
message := ""
if len(createFiles) != 0 {
- message += ctx.Tr("repo.editor.add", strings.Join(createFiles, ", ")+"\n")
+ message += ctx.Locale.TrString("repo.editor.add", strings.Join(createFiles, ", ")+"\n")
}
if len(updateFiles) != 0 {
- message += ctx.Tr("repo.editor.update", strings.Join(updateFiles, ", ")+"\n")
+ message += ctx.Locale.TrString("repo.editor.update", strings.Join(updateFiles, ", ")+"\n")
}
if len(deleteFiles) != 0 {
- message += ctx.Tr("repo.editor.delete", strings.Join(deleteFiles, ", "))
+ message += ctx.Locale.TrString("repo.editor.delete", strings.Join(deleteFiles, ", "))
}
return strings.Trim(message, "\n")
}
diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go
index 6f70e6bcec..35d7658b6c 100644
--- a/routers/api/v1/repo/issue_comment.go
+++ b/routers/api/v1/repo/issue_comment.go
@@ -395,7 +395,7 @@ func CreateIssueComment(ctx *context.APIContext) {
}
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
- ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked")))
+ ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Locale.TrString("repo.issues.comment_on_locked")))
return
}
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index b1b486e017..ef9b4893fb 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -187,6 +187,91 @@ func GetPullRequest(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
}
+// GetPullRequest returns a single PR based on index
+func GetPullRequestByBaseHead(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/pulls/{base}/{head} repository repoGetPullRequestByBaseHead
+ // ---
+ // summary: Get a pull request by base and head
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // - name: base
+ // in: path
+ // description: base of the pull request to get
+ // type: string
+ // required: true
+ // - name: head
+ // in: path
+ // description: head of the pull request to get
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/PullRequest"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ var headRepoID int64
+ var headBranch string
+ head := ctx.Params("*")
+ if strings.Contains(head, ":") {
+ split := strings.SplitN(head, ":", 2)
+ headBranch = split[1]
+ var owner, name string
+ if strings.Contains(split[0], "/") {
+ split = strings.Split(split[0], "/")
+ owner = split[0]
+ name = split[1]
+ } else {
+ owner = split[0]
+ name = ctx.Repo.Repository.Name
+ }
+ repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner, name)
+ if err != nil {
+ if repo_model.IsErrRepoNotExist(err) {
+ ctx.NotFound()
+ } else {
+ ctx.Error(http.StatusInternalServerError, "GetRepositoryByOwnerName", err)
+ }
+ return
+ }
+ headRepoID = repo.ID
+ } else {
+ headRepoID = ctx.Repo.Repository.ID
+ headBranch = head
+ }
+
+ pr, err := issues_model.GetPullRequestByBaseHeadInfo(ctx, ctx.Repo.Repository.ID, headRepoID, ctx.Params(":base"), headBranch)
+ if err != nil {
+ if issues_model.IsErrPullRequestNotExist(err) {
+ ctx.NotFound()
+ } else {
+ ctx.Error(http.StatusInternalServerError, "GetPullRequestByBaseHeadInfo", err)
+ }
+ return
+ }
+
+ if err = pr.LoadBaseRepo(ctx); err != nil {
+ ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
+ return
+ }
+ if err = pr.LoadHeadRepo(ctx); err != nil {
+ ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
+ return
+ }
+ ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
+}
+
// DownloadPullDiffOrPatch render a pull's raw diff or patch
func DownloadPullDiffOrPatch(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}.{diffType} repository repoDownloadPullDiffOrPatch
diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go
index ab8deab362..e39a096add 100644
--- a/routers/api/v1/repo/pull_review.go
+++ b/routers/api/v1/repo/pull_review.go
@@ -339,6 +339,7 @@ func CreatePullReviewComment(ctx *context.APIContext) {
opts.Path,
line,
review.ID,
+ nil,
)
if err != nil {
ctx.InternalServerError(err)
@@ -508,6 +509,7 @@ func CreatePullReview(ctx *context.APIContext) {
true, // pending review
0, // no reply
opts.CommitID,
+ nil,
); err != nil {
ctx.Error(http.StatusInternalServerError, "CreateCodeComment", err)
return
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index 08e79e544a..de105f474f 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -897,6 +897,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
AllowRebase: true,
AllowRebaseMerge: true,
AllowSquash: true,
+ AllowFastForwardOnly: true,
AllowManualMerge: true,
AutodetectManualMerge: false,
AllowRebaseUpdate: true,
@@ -923,6 +924,9 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
if opts.AllowSquash != nil {
config.AllowSquash = *opts.AllowSquash
}
+ if opts.AllowFastForwardOnly != nil {
+ config.AllowFastForwardOnly = *opts.AllowFastForwardOnly
+ }
if opts.AllowManualMerge != nil {
config.AllowManualMerge = *opts.AllowManualMerge
}
diff --git a/routers/api/v1/repo/repo_test.go b/routers/api/v1/repo/repo_test.go
index 29e2d1f21d..08ba7fabac 100644
--- a/routers/api/v1/repo/repo_test.go
+++ b/routers/api/v1/repo/repo_test.go
@@ -35,6 +35,7 @@ func TestRepoEdit(t *testing.T) {
allowRebase := false
allowRebaseMerge := false
allowSquashMerge := false
+ allowFastForwardOnlyMerge := false
archived := true
opts := api.EditRepoOption{
Name: &ctx.Repo.Repository.Name,
@@ -50,6 +51,7 @@ func TestRepoEdit(t *testing.T) {
AllowRebase: &allowRebase,
AllowRebaseMerge: &allowRebaseMerge,
AllowSquash: &allowSquashMerge,
+ AllowFastForwardOnly: &allowFastForwardOnlyMerge,
Archived: &archived,
}
diff --git a/routers/api/v1/settings/settings.go b/routers/api/v1/settings/settings.go
index 02bda1309d..957b839e66 100644
--- a/routers/api/v1/settings/settings.go
+++ b/routers/api/v1/settings/settings.go
@@ -61,6 +61,7 @@ func GetGeneralRepoSettings(ctx *context.APIContext) {
HTTPGitDisabled: setting.Repository.DisableHTTPGit,
MigrationsDisabled: setting.Repository.DisableMigrations,
StarsDisabled: setting.Repository.DisableStars,
+ ForksDisabled: setting.Repository.DisableForks,
TimeTrackingDisabled: !setting.Service.EnableTimetracking,
LFSDisabled: !setting.LFS.StartServer,
})
diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go
index 263e335873..c7fa98a697 100644
--- a/routers/api/v1/swagger/repo.go
+++ b/routers/api/v1/swagger/repo.go
@@ -263,7 +263,7 @@ type swaggerChangedFileList struct {
PerPage int `json:"X-PerPage"`
// Total commit count
- Total int `json:"X-Total"`
+ Total int `json:"X-Total-Count"`
// Total number of pages
PageCount int `json:"X-PageCount"`
diff --git a/routers/api/v1/utils/git.go b/routers/api/v1/utils/git.go
index 2299cdc247..5e80190017 100644
--- a/routers/api/v1/utils/git.go
+++ b/routers/api/v1/utils/git.go
@@ -72,7 +72,7 @@ func searchRefCommitByType(ctx *context.APIContext, refType, filter string) (str
// ConvertToObjectID returns a full-length SHA1 from a potential ID string
func ConvertToObjectID(ctx gocontext.Context, repo *context.Repository, commitID string) (git.ObjectID, error) {
- objectFormat, _ := repo.GitRepo.GetObjectFormat()
+ objectFormat := repo.GetObjectFormat()
if len(commitID) == objectFormat.FullLength() && objectFormat.IsValid(commitID) {
sha, err := git.NewIDFromString(commitID)
if err == nil {
diff --git a/routers/install/install.go b/routers/install/install.go
index 5c43bd486a..13504953ce 100644
--- a/routers/install/install.go
+++ b/routers/install/install.go
@@ -7,6 +7,7 @@ package install
import (
"fmt"
"net/http"
+ "net/mail"
"os"
"os/exec"
"path/filepath"
@@ -25,11 +26,11 @@ import (
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/user"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers/common"
@@ -423,6 +424,11 @@ func SubmitInstall(ctx *context.Context) {
}
if len(strings.TrimSpace(form.SMTPAddr)) > 0 {
+ if _, err := mail.ParseAddress(form.SMTPFrom); err != nil {
+ ctx.RenderWithErr(ctx.Tr("install.smtp_from_invalid"), tplInstall, &form)
+ return
+ }
+
cfg.Section("mailer").Key("ENABLED").SetValue("true")
cfg.Section("mailer").Key("SMTP_ADDR").SetValue(form.SMTPAddr)
cfg.Section("mailer").Key("SMTP_PORT").SetValue(form.SMTPPort)
@@ -537,8 +543,8 @@ func SubmitInstall(ctx *context.Context) {
IsAdmin: true,
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
- IsRestricted: util.OptionalBoolFalse,
- IsActive: util.OptionalBoolTrue,
+ IsRestricted: optional.Some(false),
+ IsActive: optional.Some(true),
}
if err = user_model.CreateUser(ctx, u, overwriteDefault); err != nil {
diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go
index 90d8287f06..f28ae4c0eb 100644
--- a/routers/private/hook_pre_receive.go
+++ b/routers/private/hook_pre_receive.go
@@ -145,7 +145,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
repo := ctx.Repo.Repository
gitRepo := ctx.Repo.GitRepo
- objectFormat, _ := gitRepo.GetObjectFormat()
+ objectFormat := ctx.Repo.GetObjectFormat()
if branchName == repo.DefaultBranch && newCommitID == objectFormat.EmptyObjectID().String() {
log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo)
diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go
index d31cb1cd25..58bb281731 100644
--- a/routers/web/admin/admin.go
+++ b/routers/web/admin/admin.go
@@ -7,8 +7,8 @@ package admin
import (
"fmt"
"net/http"
+ "reflect"
"runtime"
- "sort"
"time"
activities_model "code.gitea.io/gitea/models/activities"
@@ -16,7 +16,6 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
- "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/updatechecker"
@@ -28,13 +27,14 @@ import (
)
const (
- tplDashboard base.TplName = "admin/dashboard"
- tplSelfCheck base.TplName = "admin/self_check"
- tplCron base.TplName = "admin/cron"
- tplQueue base.TplName = "admin/queue"
- tplStacktrace base.TplName = "admin/stacktrace"
- tplQueueManage base.TplName = "admin/queue_manage"
- tplStats base.TplName = "admin/stats"
+ tplDashboard base.TplName = "admin/dashboard"
+ tplSystemStatus base.TplName = "admin/system_status"
+ tplSelfCheck base.TplName = "admin/self_check"
+ tplCron base.TplName = "admin/cron"
+ tplQueue base.TplName = "admin/queue"
+ tplStacktrace base.TplName = "admin/stacktrace"
+ tplQueueManage base.TplName = "admin/queue_manage"
+ tplStats base.TplName = "admin/stats"
)
var sysStatus struct {
@@ -72,7 +72,7 @@ var sysStatus struct {
// Garbage collector statistics.
NextGC string // next run in HeapAlloc time (bytes)
- LastGC string // last run in absolute time (ns)
+ LastGCTime string // last run time
PauseTotalNs string
PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256]
NumGC uint32
@@ -110,7 +110,7 @@ func updateSystemStatus() {
sysStatus.OtherSys = base.FileSize(int64(m.OtherSys))
sysStatus.NextGC = base.FileSize(int64(m.NextGC))
- sysStatus.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000)
+ sysStatus.LastGCTime = time.Unix(0, int64(m.LastGC)).Format(time.RFC3339)
sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000)
sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000)
sysStatus.NumGC = m.NumGC
@@ -132,7 +132,6 @@ func Dashboard(ctx *context.Context) {
ctx.Data["PageIsAdminDashboard"] = true
ctx.Data["NeedUpdate"] = updatechecker.GetNeedUpdate(ctx)
ctx.Data["RemoteVersion"] = updatechecker.GetRemoteVersion(ctx)
- // FIXME: update periodically
updateSystemStatus()
ctx.Data["SysStatus"] = sysStatus
ctx.Data["SSH"] = setting.SSH
@@ -140,6 +139,12 @@ func Dashboard(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplDashboard)
}
+func SystemStatus(ctx *context.Context) {
+ updateSystemStatus()
+ ctx.Data["SysStatus"] = sysStatus
+ ctx.HTML(http.StatusOK, tplSystemStatus)
+}
+
// DashboardPost run an admin operation
func DashboardPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.AdminDashboardForm)
@@ -219,26 +224,22 @@ func CronTasks(ctx *context.Context) {
func MonitorStats(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.monitor.stats")
ctx.Data["PageIsAdminMonitorStats"] = true
- bs, err := json.Marshal(activities_model.GetStatistic(ctx).Counter)
- if err != nil {
- ctx.ServerError("MonitorStats", err)
- return
- }
- statsCounter := map[string]any{}
- err = json.Unmarshal(bs, &statsCounter)
- if err != nil {
- ctx.ServerError("MonitorStats", err)
- return
- }
- statsKeys := make([]string, 0, len(statsCounter))
- for k := range statsCounter {
- if statsCounter[k] == nil {
+ modelStats := activities_model.GetStatistic(ctx).Counter
+ stats := map[string]any{}
+
+ // To avoid manually converting the values of the stats struct to an map,
+ // and to avoid using JSON to do this for us (JSON encoder converts numbers to
+ // scientific notation). Use reflect to convert the struct to an map.
+ rv := reflect.ValueOf(modelStats)
+ for i := 0; i < rv.NumField(); i++ {
+ field := rv.Field(i)
+ // Preserve old behavior, do not show arrays that are empty.
+ if field.Kind() == reflect.Slice && field.Len() == 0 {
continue
}
- statsKeys = append(statsKeys, k)
+ stats[rv.Type().Field(i).Name] = field.Interface()
}
- sort.Strings(statsKeys)
- ctx.Data["StatsKeys"] = statsKeys
- ctx.Data["StatsCounter"] = statsCounter
+
+ ctx.Data["Stats"] = stats
ctx.HTML(http.StatusOK, tplStats)
}
diff --git a/routers/web/admin/admin_test.go b/routers/web/admin/admin_test.go
index 2b65ab3ea3..452291e179 100644
--- a/routers/web/admin/admin_test.go
+++ b/routers/web/admin/admin_test.go
@@ -6,6 +6,11 @@ package admin
import (
"testing"
+ 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"
"github.com/stretchr/testify/assert"
)
@@ -66,3 +71,46 @@ func TestShadowPassword(t *testing.T) {
assert.EqualValues(t, k.Result, shadowPassword(k.Provider, k.CfgItem))
}
}
+
+func TestMonitorStats(t *testing.T) {
+ unittest.PrepareTestEnv(t)
+
+ t.Run("Normal", func(t *testing.T) {
+ defer test.MockVariableValue(&setting.Metrics.EnabledIssueByLabel, false)()
+ defer test.MockVariableValue(&setting.Metrics.EnabledIssueByRepository, false)()
+
+ ctx, _ := contexttest.MockContext(t, "admin/stats")
+ MonitorStats(ctx)
+
+ // Test some of the stats manually.
+ mappedStats := ctx.Data["Stats"].(map[string]any)
+ stats := activities_model.GetStatistic(ctx).Counter
+
+ assert.EqualValues(t, stats.Comment, mappedStats["Comment"])
+ assert.EqualValues(t, stats.Issue, mappedStats["Issue"])
+ assert.EqualValues(t, stats.User, mappedStats["User"])
+ assert.EqualValues(t, stats.Milestone, mappedStats["Milestone"])
+
+ // Ensure that these aren't set.
+ assert.Empty(t, stats.IssueByLabel)
+ assert.Empty(t, stats.IssueByRepository)
+ assert.Nil(t, mappedStats["IssueByLabel"])
+ assert.Nil(t, mappedStats["IssueByRepository"])
+ })
+
+ t.Run("IssueByX", func(t *testing.T) {
+ defer test.MockVariableValue(&setting.Metrics.EnabledIssueByLabel, true)()
+ defer test.MockVariableValue(&setting.Metrics.EnabledIssueByRepository, true)()
+
+ ctx, _ := contexttest.MockContext(t, "admin/stats")
+ MonitorStats(ctx)
+
+ mappedStats := ctx.Data["Stats"].(map[string]any)
+ stats := activities_model.GetStatistic(ctx).Counter
+
+ assert.NotEmpty(t, stats.IssueByLabel)
+ assert.NotEmpty(t, stats.IssueByRepository)
+ assert.EqualValues(t, stats.IssueByLabel, mappedStats["IssueByLabel"])
+ assert.EqualValues(t, stats.IssueByRepository, mappedStats["IssueByRepository"])
+ })
+}
diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go
index 2cf63c646d..7fdd18dfae 100644
--- a/routers/web/admin/auths.go
+++ b/routers/web/admin/auths.go
@@ -210,16 +210,16 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi.Source, error) {
if util.IsEmptyString(form.SSPISeparatorReplacement) {
ctx.Data["Err_SSPISeparatorReplacement"] = true
- return nil, errors.New(ctx.Tr("form.SSPISeparatorReplacement") + ctx.Tr("form.require_error"))
+ return nil, errors.New(ctx.Locale.TrString("form.SSPISeparatorReplacement") + ctx.Locale.TrString("form.require_error"))
}
if separatorAntiPattern.MatchString(form.SSPISeparatorReplacement) {
ctx.Data["Err_SSPISeparatorReplacement"] = true
- return nil, errors.New(ctx.Tr("form.SSPISeparatorReplacement") + ctx.Tr("form.alpha_dash_dot_error"))
+ return nil, errors.New(ctx.Locale.TrString("form.SSPISeparatorReplacement") + ctx.Locale.TrString("form.alpha_dash_dot_error"))
}
if form.SSPIDefaultLanguage != "" && !langCodePattern.MatchString(form.SSPIDefaultLanguage) {
ctx.Data["Err_SSPIDefaultLanguage"] = true
- return nil, errors.New(ctx.Tr("form.lang_select_error"))
+ return nil, errors.New(ctx.Locale.TrString("form.lang_select_error"))
}
return &sspi.Source{
diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go
index af184fa9eb..adb9799c01 100644
--- a/routers/web/admin/users.go
+++ b/routers/web/admin/users.go
@@ -140,7 +140,7 @@ func NewUserPost(ctx *context.Context) {
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
Visibility: &form.Visibility,
}
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index 2adcb3aea0..4e4079d8ff 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -579,16 +579,8 @@ func GrantApplicationOAuth(ctx *context.Context) {
// OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities
func OIDCWellKnown(ctx *context.Context) {
- t, err := ctx.Render.TemplateLookup("user/auth/oidc_wellknown", nil)
- if err != nil {
- ctx.ServerError("unable to find template", err)
- return
- }
- ctx.Resp.Header().Set("Content-Type", "application/json")
ctx.Data["SigningKey"] = oauth2.DefaultSigningKey
- if err = t.Execute(ctx.Resp, ctx.Data); err != nil {
- ctx.ServerError("unable to execute template", err)
- }
+ ctx.JSONTemplate("user/auth/oidc_wellknown")
}
// OIDCKeys generates the JSON Web Key Set
@@ -990,7 +982,7 @@ func SignInOAuthCallback(ctx *context.Context) {
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
- IsActive: util.OptionalBoolOf(!setting.OAuth2Client.RegisterEmailConfirm && !setting.Service.RegisterManualConfirm),
+ IsActive: optional.Some(!setting.OAuth2Client.RegisterEmailConfirm && !setting.Service.RegisterManualConfirm),
}
source := authSource.Cfg.(*oauth2.Source)
diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go
index 5af1696a64..1f2d133282 100644
--- a/routers/web/auth/password.go
+++ b/routers/web/auth/password.go
@@ -37,7 +37,7 @@ func ForgotPasswd(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title")
if setting.MailService == nil {
- log.Warn(ctx.Tr("auth.disable_forgot_password_mail_admin"))
+ log.Warn("no mail service configured")
ctx.Data["IsResetDisable"] = true
ctx.HTML(http.StatusOK, tplForgotPassword)
return
@@ -204,7 +204,7 @@ func ResetPasswdPost(ctx *context.Context) {
Password: optional.Some(ctx.FormString("password")),
MustChangePassword: optional.Some(false),
}
- if err := user_service.UpdateAuth(ctx, ctx.Doer, opts); err != nil {
+ if err := user_service.UpdateAuth(ctx, u, opts); err != nil {
ctx.Data["IsResetForm"] = true
ctx.Data["Err_Password"] = true
switch {
diff --git a/routers/web/feed/convert.go b/routers/web/feed/convert.go
index 3cb3e62245..5186d1a524 100644
--- a/routers/web/feed/convert.go
+++ b/routers/web/feed/convert.go
@@ -6,6 +6,7 @@ package feed
import (
"fmt"
"html"
+ "html/template"
"net/http"
"net/url"
"strconv"
@@ -80,119 +81,120 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
// title
title = act.ActUser.DisplayName() + " "
+ var titleExtra template.HTML
switch act.OpType {
case activities_model.ActionCreateRepo:
- title += ctx.TrHTMLEscapeArgs("action.create_repo", act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.create_repo", act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx))
link.Href = act.GetRepoAbsoluteLink(ctx)
case activities_model.ActionRenameRepo:
- title += ctx.TrHTMLEscapeArgs("action.rename_repo", act.GetContent(), act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.rename_repo", act.GetContent(), act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx))
link.Href = act.GetRepoAbsoluteLink(ctx)
case activities_model.ActionCommitRepo:
link.Href = toBranchLink(ctx, act)
if len(act.Content) != 0 {
- title += ctx.TrHTMLEscapeArgs("action.commit_repo", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetBranch(), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.commit_repo", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetBranch(), act.ShortRepoPath(ctx))
} else {
- title += ctx.TrHTMLEscapeArgs("action.create_branch", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetBranch(), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.create_branch", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetBranch(), act.ShortRepoPath(ctx))
}
case activities_model.ActionCreateIssue:
link.Href = toIssueLink(ctx, act)
- title += ctx.TrHTMLEscapeArgs("action.create_issue", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.create_issue", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionCreatePullRequest:
link.Href = toPullLink(ctx, act)
- title += ctx.TrHTMLEscapeArgs("action.create_pull_request", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.create_pull_request", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionTransferRepo:
link.Href = act.GetRepoAbsoluteLink(ctx)
- title += ctx.TrHTMLEscapeArgs("action.transfer_repo", act.GetContent(), act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.transfer_repo", act.GetContent(), act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx))
case activities_model.ActionPushTag:
link.Href = toTagLink(ctx, act)
- title += ctx.TrHTMLEscapeArgs("action.push_tag", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetTag(), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.push_tag", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetTag(), act.ShortRepoPath(ctx))
case activities_model.ActionCommentIssue:
issueLink := toIssueLink(ctx, act)
if link.Href == "#" {
link.Href = issueLink
}
- title += ctx.TrHTMLEscapeArgs("action.comment_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.comment_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionMergePullRequest:
pullLink := toPullLink(ctx, act)
if link.Href == "#" {
link.Href = pullLink
}
- title += ctx.TrHTMLEscapeArgs("action.merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionAutoMergePullRequest:
pullLink := toPullLink(ctx, act)
if link.Href == "#" {
link.Href = pullLink
}
- title += ctx.TrHTMLEscapeArgs("action.auto_merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.auto_merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionCloseIssue:
issueLink := toIssueLink(ctx, act)
if link.Href == "#" {
link.Href = issueLink
}
- title += ctx.TrHTMLEscapeArgs("action.close_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.close_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionReopenIssue:
issueLink := toIssueLink(ctx, act)
if link.Href == "#" {
link.Href = issueLink
}
- title += ctx.TrHTMLEscapeArgs("action.reopen_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.reopen_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionClosePullRequest:
pullLink := toPullLink(ctx, act)
if link.Href == "#" {
link.Href = pullLink
}
- title += ctx.TrHTMLEscapeArgs("action.close_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.close_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionReopenPullRequest:
pullLink := toPullLink(ctx, act)
if link.Href == "#" {
link.Href = pullLink
}
- title += ctx.TrHTMLEscapeArgs("action.reopen_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.reopen_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionDeleteTag:
link.Href = act.GetRepoAbsoluteLink(ctx)
- title += ctx.TrHTMLEscapeArgs("action.delete_tag", act.GetRepoAbsoluteLink(ctx), act.GetTag(), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.delete_tag", act.GetRepoAbsoluteLink(ctx), act.GetTag(), act.ShortRepoPath(ctx))
case activities_model.ActionDeleteBranch:
link.Href = act.GetRepoAbsoluteLink(ctx)
- title += ctx.TrHTMLEscapeArgs("action.delete_branch", act.GetRepoAbsoluteLink(ctx), html.EscapeString(act.GetBranch()), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.delete_branch", act.GetRepoAbsoluteLink(ctx), html.EscapeString(act.GetBranch()), act.ShortRepoPath(ctx))
case activities_model.ActionMirrorSyncPush:
srcLink := toSrcLink(ctx, act)
if link.Href == "#" {
link.Href = srcLink
}
- title += ctx.TrHTMLEscapeArgs("action.mirror_sync_push", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.mirror_sync_push", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx))
case activities_model.ActionMirrorSyncCreate:
srcLink := toSrcLink(ctx, act)
if link.Href == "#" {
link.Href = srcLink
}
- title += ctx.TrHTMLEscapeArgs("action.mirror_sync_create", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.mirror_sync_create", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx))
case activities_model.ActionMirrorSyncDelete:
link.Href = act.GetRepoAbsoluteLink(ctx)
- title += ctx.TrHTMLEscapeArgs("action.mirror_sync_delete", act.GetRepoAbsoluteLink(ctx), act.GetBranch(), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.mirror_sync_delete", act.GetRepoAbsoluteLink(ctx), act.GetBranch(), act.ShortRepoPath(ctx))
case activities_model.ActionApprovePullRequest:
pullLink := toPullLink(ctx, act)
- title += ctx.TrHTMLEscapeArgs("action.approve_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.approve_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionRejectPullRequest:
pullLink := toPullLink(ctx, act)
- title += ctx.TrHTMLEscapeArgs("action.reject_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.reject_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionCommentPull:
pullLink := toPullLink(ctx, act)
- title += ctx.TrHTMLEscapeArgs("action.comment_pull", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.comment_pull", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionPublishRelease:
releaseLink := toReleaseLink(ctx, act)
if link.Href == "#" {
link.Href = releaseLink
}
- title += ctx.TrHTMLEscapeArgs("action.publish_release", act.GetRepoAbsoluteLink(ctx), releaseLink, act.ShortRepoPath(ctx), act.Content)
+ titleExtra = ctx.Locale.Tr("action.publish_release", act.GetRepoAbsoluteLink(ctx), releaseLink, act.ShortRepoPath(ctx), act.Content)
case activities_model.ActionPullReviewDismissed:
pullLink := toPullLink(ctx, act)
- title += ctx.TrHTMLEscapeArgs("action.review_dismissed", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx), act.GetIssueInfos()[1])
+ titleExtra = ctx.Locale.Tr("action.review_dismissed", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx), act.GetIssueInfos()[1])
case activities_model.ActionStarRepo:
link.Href = act.GetRepoAbsoluteLink(ctx)
- title += ctx.TrHTMLEscapeArgs("action.starred_repo", act.GetRepoAbsoluteLink(ctx), act.GetRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.starred_repo", act.GetRepoAbsoluteLink(ctx), act.GetRepoPath(ctx))
case activities_model.ActionWatchRepo:
link.Href = act.GetRepoAbsoluteLink(ctx)
- title += ctx.TrHTMLEscapeArgs("action.watched_repo", act.GetRepoAbsoluteLink(ctx), act.GetRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.watched_repo", act.GetRepoAbsoluteLink(ctx), act.GetRepoPath(ctx))
default:
return nil, fmt.Errorf("unknown action type: %v", act.OpType)
}
@@ -234,7 +236,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
case activities_model.ActionCloseIssue, activities_model.ActionReopenIssue, activities_model.ActionClosePullRequest, activities_model.ActionReopenPullRequest:
desc = act.GetIssueTitle(ctx)
case activities_model.ActionPullReviewDismissed:
- desc = ctx.Tr("action.review_dismissed_reason") + "\n\n" + act.GetIssueInfos()[2]
+ desc = ctx.Locale.TrString("action.review_dismissed_reason") + "\n\n" + act.GetIssueInfos()[2]
}
}
if len(content) == 0 {
@@ -243,7 +245,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
// It's a common practice for feed generators to use plain text titles.
// See https://codeberg.org/forgejo/forgejo/pulls/1595
- plainTitle, err := html2text.FromString(title, html2text.Options{OmitLinks: true})
+ plainTitle, err := html2text.FromString(title+" "+string(titleExtra), html2text.Options{OmitLinks: true})
if err != nil {
return nil, err
}
diff --git a/routers/web/feed/profile.go b/routers/web/feed/profile.go
index 04f84c0c8d..3feca68d61 100644
--- a/routers/web/feed/profile.go
+++ b/routers/web/feed/profile.go
@@ -56,7 +56,7 @@ func showUserFeed(ctx *context.Context, formatType string) {
}
feed := &feeds.Feed{
- Title: ctx.Tr("home.feed_of", ctx.ContextUser.DisplayName()),
+ Title: ctx.Locale.TrString("home.feed_of", ctx.ContextUser.DisplayName()),
Link: &feeds.Link{Href: ctx.ContextUser.HTMLURL()},
Description: ctxUserDescription,
Created: time.Now(),
diff --git a/routers/web/feed/release.go b/routers/web/feed/release.go
index 57b0c92766..558c03dba7 100644
--- a/routers/web/feed/release.go
+++ b/routers/web/feed/release.go
@@ -28,10 +28,10 @@ func ShowReleaseFeed(ctx *context.Context, repo *repo_model.Repository, isReleas
var link *feeds.Link
if isReleasesOnly {
- title = ctx.Tr("repo.release.releases_for", repo.FullName())
+ title = ctx.Locale.TrString("repo.release.releases_for", repo.FullName())
link = &feeds.Link{Href: repo.HTMLURL() + "/release"}
} else {
- title = ctx.Tr("repo.release.tags_for", repo.FullName())
+ title = ctx.Locale.TrString("repo.release.tags_for", repo.FullName())
link = &feeds.Link{Href: repo.HTMLURL() + "/tags"}
}
diff --git a/routers/web/feed/repo.go b/routers/web/feed/repo.go
index 5fcad26779..51c24510c7 100644
--- a/routers/web/feed/repo.go
+++ b/routers/web/feed/repo.go
@@ -27,7 +27,7 @@ func ShowRepoFeed(ctx *context.Context, repo *repo_model.Repository, formatType
}
feed := &feeds.Feed{
- Title: ctx.Tr("home.feed_of", repo.FullName()),
+ Title: ctx.Locale.TrString("home.feed_of", repo.FullName()),
Link: &feeds.Link{Href: repo.HTMLURL()},
Description: repo.Description,
Created: time.Now(),
diff --git a/routers/web/org/home.go b/routers/web/org/home.go
index 8bf02b2c42..36f543dc45 100644
--- a/routers/web/org/home.go
+++ b/routers/web/org/home.go
@@ -11,7 +11,6 @@ import (
"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/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
@@ -46,17 +45,6 @@ func Home(ctx *context.Context) {
ctx.Data["PageIsUserProfile"] = true
ctx.Data["Title"] = org.DisplayName()
- if len(org.Description) != 0 {
- desc, err := markdown.RenderString(&markup.RenderContext{
- Ctx: ctx,
- Metas: map[string]string{"mode": "document"},
- }, org.Description)
- if err != nil {
- ctx.ServerError("RenderString", err)
- return
- }
- ctx.Data["RenderedDescription"] = desc
- }
var orderBy db.SearchOrderBy
ctx.Data["SortType"] = ctx.FormString("sort")
@@ -131,18 +119,12 @@ func Home(ctx *context.Context) {
return
}
- var isFollowing bool
- if ctx.Doer != nil {
- isFollowing = user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
- }
-
ctx.Data["Repos"] = repos
ctx.Data["Total"] = count
ctx.Data["Members"] = members
ctx.Data["Teams"] = ctx.Org.Teams
ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
ctx.Data["PageIsViewRepositories"] = true
- ctx.Data["IsFollowing"] = isFollowing
err = shared_user.LoadHeaderCount(ctx)
if err != nil {
diff --git a/routers/web/org/org.go b/routers/web/org/org.go
index 52f8df8a1c..1e4544730e 100644
--- a/routers/web/org/org.go
+++ b/routers/web/org/org.go
@@ -29,7 +29,7 @@ func Create(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("new_org")
ctx.Data["DefaultOrgVisibilityMode"] = setting.Service.DefaultOrgVisibilityMode
if !ctx.Doer.CanCreateOrganization() {
- ctx.ServerError("Not allowed", errors.New(ctx.Tr("org.form.create_org_not_allowed")))
+ ctx.ServerError("Not allowed", errors.New(ctx.Locale.TrString("org.form.create_org_not_allowed")))
return
}
ctx.HTML(http.StatusOK, tplCreateOrg)
@@ -41,7 +41,7 @@ func CreatePost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("new_org")
if !ctx.Doer.CanCreateOrganization() {
- ctx.ServerError("Not allowed", errors.New(ctx.Tr("org.form.create_org_not_allowed")))
+ ctx.ServerError("Not allowed", errors.New(ctx.Locale.TrString("org.form.create_org_not_allowed")))
return
}
diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go
index 03798a712c..f062127d24 100644
--- a/routers/web/org/projects.go
+++ b/routers/web/org/projects.go
@@ -353,7 +353,7 @@ func ViewProject(ctx *context.Context) {
}
if boards[0].ID == 0 {
- boards[0].Title = ctx.Tr("repo.projects.type.uncategorized")
+ boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized")
}
issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
@@ -377,16 +377,16 @@ func ViewProject(ctx *context.Context) {
linkedPrsMap := make(map[int64][]*issues_model.Issue)
for _, issuesList := range issuesMap {
for _, issue := range issuesList {
- var referencedIds []int64
+ var referencedIDs []int64
for _, comment := range issue.Comments {
if comment.RefIssueID != 0 && comment.RefIsPull {
- referencedIds = append(referencedIds, comment.RefIssueID)
+ referencedIDs = append(referencedIDs, comment.RefIssueID)
}
}
- if len(referencedIds) > 0 {
+ if len(referencedIDs) > 0 {
if linkedPrs, err := issues_model.Issues(ctx, &issues_model.IssuesOptions{
- IssueIDs: referencedIds,
+ IssueIDs: referencedIDs,
IsPull: util.OptionalBoolTrue,
}); err == nil {
linkedPrsMap[issue.ID] = linkedPrs
@@ -679,7 +679,7 @@ func MoveIssues(ctx *context.Context) {
board = &project_model.Board{
ID: 0,
ProjectID: project.ID,
- Title: ctx.Tr("repo.projects.type.uncategorized"),
+ Title: ctx.Locale.TrString("repo.projects.type.uncategorized"),
}
} else {
board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go
index fe528a483b..19aca26711 100644
--- a/routers/web/repo/actions/actions.go
+++ b/routers/web/repo/actions/actions.go
@@ -61,17 +61,17 @@ func List(ctx *context.Context) {
var workflows []Workflow
if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("IsEmpty", err)
return
} else if !empty {
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
if err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("GetBranchCommit", err)
return
}
entries, err := actions.ListWorkflows(commit)
if err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("ListWorkflows", err)
return
}
@@ -95,12 +95,12 @@ func List(ctx *context.Context) {
workflow := Workflow{Entry: *entry}
content, err := actions.GetContentFromEntry(entry)
if err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("GetContentFromEntry", err)
return
}
wf, err := model.ReadWorkflow(bytes.NewReader(content))
if err != nil {
- workflow.ErrMsg = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", err.Error())
+ workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error())
workflows = append(workflows, workflow)
continue
}
@@ -115,7 +115,7 @@ func List(ctx *context.Context) {
continue
}
if !allRunnerLabels.Contains(ro) {
- workflow.ErrMsg = ctx.Locale.Tr("actions.runs.no_matching_online_runner_helper", ro)
+ workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", ro)
break
}
}
@@ -172,7 +172,7 @@ func List(ctx *context.Context) {
runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("FindAndCount", err)
return
}
@@ -181,7 +181,7 @@ func List(ctx *context.Context) {
}
if err := actions_model.RunList(runs).LoadTriggerUser(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("LoadTriggerUser", err)
return
}
@@ -189,7 +189,7 @@ func List(ctx *context.Context) {
actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("GetActors", err)
return
}
ctx.Data["Actors"] = repo.MakeSelfOnTop(ctx.Doer, actors)
diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
index 63e3a352a5..05675d73c0 100644
--- a/routers/web/repo/actions/view.go
+++ b/routers/web/repo/actions/view.go
@@ -1,4 +1,5 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
+// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
@@ -35,13 +36,19 @@ func View(ctx *context_module.Context) {
ctx.Data["PageIsActions"] = true
runIndex := ctx.ParamsInt64("run")
jobIndex := ctx.ParamsInt64("job")
+
+ job, _ := getRunJobs(ctx, runIndex, jobIndex)
+ if ctx.Written() {
+ return
+ }
+
+ workflowName := job.Run.WorkflowID
+
ctx.Data["RunIndex"] = runIndex
ctx.Data["JobIndex"] = jobIndex
ctx.Data["ActionsURL"] = ctx.Repo.RepoLink + "/actions"
-
- if getRunJobs(ctx, runIndex, jobIndex); ctx.Written() {
- return
- }
+ ctx.Data["WorkflowName"] = workflowName
+ ctx.Data["WorkflowURL"] = ctx.Repo.RepoLink + "/actions?workflow=" + workflowName
ctx.HTML(http.StatusOK, tplViewActions)
}
@@ -60,6 +67,33 @@ func ViewLatest(ctx *context_module.Context) {
ctx.Redirect(run.HTMLURL(), http.StatusTemporaryRedirect)
}
+func ViewLatestWorkflowRun(ctx *context_module.Context) {
+ branch := ctx.FormString("branch")
+ if branch == "" {
+ branch = ctx.Repo.Repository.DefaultBranch
+ }
+ branch = fmt.Sprintf("refs/heads/%s", branch)
+ event := ctx.FormString("event")
+
+ workflowFile := ctx.Params("workflow_name")
+ run, err := actions_model.GetLatestRunForBranchAndWorkflow(ctx, ctx.Repo.Repository.ID, branch, workflowFile, event)
+ if err != nil {
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.NotFound("GetLatestRunForBranchAndWorkflow", err)
+ } else {
+ ctx.ServerError("GetLatestRunForBranchAndWorkflow", err)
+ }
+ return
+ }
+
+ err = run.LoadAttributes(ctx)
+ if err != nil {
+ ctx.ServerError("LoadAttributes", err)
+ return
+ }
+ ctx.Redirect(run.HTMLURL(), http.StatusTemporaryRedirect)
+}
+
type ViewRequest struct {
LogCursors []struct {
Step int `json:"step"`
@@ -71,15 +105,16 @@ type ViewRequest struct {
type ViewResponse struct {
State struct {
Run struct {
- Link string `json:"link"`
- Title string `json:"title"`
- Status string `json:"status"`
- CanCancel bool `json:"canCancel"`
- CanApprove bool `json:"canApprove"` // the run needs an approval and the doer has permission to approve
- CanRerun bool `json:"canRerun"`
- Done bool `json:"done"`
- Jobs []*ViewJob `json:"jobs"`
- Commit ViewCommit `json:"commit"`
+ Link string `json:"link"`
+ Title string `json:"title"`
+ Status string `json:"status"`
+ CanCancel bool `json:"canCancel"`
+ CanApprove bool `json:"canApprove"` // the run needs an approval and the doer has permission to approve
+ CanRerun bool `json:"canRerun"`
+ CanDeleteArtifact bool `json:"canDeleteArtifact"`
+ Done bool `json:"done"`
+ Jobs []*ViewJob `json:"jobs"`
+ Commit ViewCommit `json:"commit"`
} `json:"run"`
CurrentJob struct {
Title string `json:"title"`
@@ -103,6 +138,7 @@ type ViewJob struct {
type ViewCommit struct {
LocaleCommit string `json:"localeCommit"`
LocalePushedBy string `json:"localePushedBy"`
+ LocaleWorkflow string `json:"localeWorkflow"`
ShortSha string `json:"shortSHA"`
Link string `json:"link"`
Pusher ViewUser `json:"pusher"`
@@ -160,6 +196,7 @@ func ViewPost(ctx *context_module.Context) {
resp.State.Run.CanCancel = !run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
resp.State.Run.CanApprove = run.NeedApproval && ctx.Repo.CanWrite(unit.TypeActions)
resp.State.Run.CanRerun = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
+ resp.State.Run.CanDeleteArtifact = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
resp.State.Run.Done = run.Status.IsDone()
resp.State.Run.Jobs = make([]*ViewJob, 0, len(jobs)) // marshal to '[]' instead fo 'null' in json
resp.State.Run.Status = run.Status.String()
@@ -182,8 +219,9 @@ func ViewPost(ctx *context_module.Context) {
Link: run.RefLink(),
}
resp.State.Run.Commit = ViewCommit{
- LocaleCommit: ctx.Tr("actions.runs.commit"),
- LocalePushedBy: ctx.Tr("actions.runs.pushed_by"),
+ LocaleCommit: ctx.Locale.TrString("actions.runs.commit"),
+ LocalePushedBy: ctx.Locale.TrString("actions.runs.pushed_by"),
+ LocaleWorkflow: ctx.Locale.TrString("actions.runs.workflow"),
ShortSha: base.ShortSha(run.CommitSHA),
Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA),
Pusher: pusher,
@@ -208,7 +246,7 @@ func ViewPost(ctx *context_module.Context) {
resp.State.CurrentJob.Title = current.Name
resp.State.CurrentJob.Detail = current.Status.LocaleString(ctx.Locale)
if run.NeedApproval {
- resp.State.CurrentJob.Detail = ctx.Locale.Tr("actions.need_approval_desc")
+ resp.State.CurrentJob.Detail = ctx.Locale.TrString("actions.need_approval_desc")
}
resp.State.CurrentJob.Steps = make([]*ViewJobStep, 0) // marshal to '[]' instead fo 'null' in json
resp.Logs.StepsLog = make([]*ViewStepLog, 0) // marshal to '[]' instead fo 'null' in json
@@ -549,6 +587,24 @@ func ArtifactsView(ctx *context_module.Context) {
ctx.JSON(http.StatusOK, artifactsResponse)
}
+func ArtifactsDeleteView(ctx *context_module.Context) {
+ runIndex := ctx.ParamsInt64("run")
+ artifactName := ctx.Params("artifact_name")
+
+ run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
+ if err != nil {
+ ctx.NotFoundOrServerError("GetRunByIndex", func(err error) bool {
+ return errors.Is(err, util.ErrNotExist)
+ }, err)
+ return
+ }
+ if err = actions_model.SetArtifactNeedDelete(ctx, run.ID, artifactName); err != nil {
+ ctx.Error(http.StatusInternalServerError, err.Error())
+ return
+ }
+ ctx.JSON(http.StatusOK, struct{}{})
+}
+
func ArtifactsDownloadView(ctx *context_module.Context) {
runIndex := ctx.ParamsInt64("run")
artifactName := ctx.Params("artifact_name")
@@ -576,6 +632,14 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
return
}
+ // if artifacts status is not uploaded-confirmed, treat it as not found
+ for _, art := range artifacts {
+ if art.Status != int64(actions_model.ArtifactStatusUploadConfirmed) {
+ ctx.Error(http.StatusNotFound, "artifact not found")
+ return
+ }
+ }
+
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip; filename*=UTF-8''%s.zip", url.PathEscape(artifactName), artifactName))
writer := zip.NewWriter(ctx.Resp)
diff --git a/routers/web/repo/activity.go b/routers/web/repo/activity.go
index 3d030edaca..af99c4ed98 100644
--- a/routers/web/repo/activity.go
+++ b/routers/web/repo/activity.go
@@ -22,6 +22,8 @@ func Activity(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.activity")
ctx.Data["PageIsActivity"] = true
+ ctx.Data["PageIsPulse"] = true
+
ctx.Data["Period"] = ctx.Params("period")
timeUntil := time.Now()
diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go
index fa9dd8a08c..2c938dc966 100644
--- a/routers/web/repo/blame.go
+++ b/routers/web/repo/blame.go
@@ -16,9 +16,11 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/highlight"
"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/modules/util"
+ files_service "code.gitea.io/gitea/services/repository/files"
)
type blameRow struct {
@@ -68,6 +70,14 @@ func RefBlame(ctx *context.Context) {
ctx.Data["FileSize"] = blob.Size()
ctx.Data["FileName"] = blob.Name()
+ // Do not display a blame view if the size of the file is
+ // larger than what is configured as the maximum.
+ if blob.Size() >= setting.UI.MaxDisplayFileSize {
+ ctx.Data["IsFileTooLarge"] = true
+ ctx.HTML(http.StatusOK, tplRepoHome)
+ return
+ }
+
ctx.Data["NumLinesSet"] = true
ctx.Data["NumLines"], err = blob.GetBlobLineCount()
if err != nil {
@@ -102,10 +112,7 @@ type blameResult struct {
func performBlame(ctx *context.Context, commit *git.Commit, file string, bypassBlameIgnore bool) (*blameResult, error) {
repoPath := ctx.Repo.Repository.RepoPath()
- objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat()
- if err != nil {
- return nil, err
- }
+ objectFormat := ctx.Repo.GetObjectFormat()
blameReader, err := git.CreateBlameReader(ctx, objectFormat, repoPath, commit, file, bypassBlameIgnore)
if err != nil {
@@ -218,31 +225,11 @@ func processBlameParts(ctx *context.Context, blameParts []*git.BlamePart) map[st
func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames map[string]*user_model.UserCommit) {
repoLink := ctx.Repo.RepoLink
- language := ""
-
- indexFilename, worktree, deleteTemporaryFile, err := ctx.Repo.GitRepo.ReadTreeToTemporaryIndex(ctx.Repo.CommitID)
- if err == nil {
- defer deleteTemporaryFile()
-
- filename2attribute2info, err := ctx.Repo.GitRepo.CheckAttribute(git.CheckAttributeOpts{
- CachedOnly: true,
- Attributes: []string{"linguist-language", "gitlab-language"},
- Filenames: []string{ctx.Repo.TreePath},
- IndexFile: indexFilename,
- WorkTree: worktree,
- })
- if err != nil {
- log.Error("Unable to load attributes for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err)
- }
-
- language = filename2attribute2info[ctx.Repo.TreePath]["linguist-language"]
- if language == "" || language == "unspecified" {
- language = filename2attribute2info[ctx.Repo.TreePath]["gitlab-language"]
- }
- if language == "unspecified" {
- language = ""
- }
+ language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath)
+ if err != nil {
+ log.Error("Unable to get file language for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err)
}
+
lines := make([]string, 0)
rows := make([]*blameRow, 0)
escapeStatus := &charset.EscapeStatus{}
@@ -298,7 +285,7 @@ func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames
lexerName = lexerNameForLine
}
- br.EscapeStatus, br.Code = charset.EscapeControlHTML(line, ctx.Locale)
+ br.EscapeStatus, br.Code = charset.EscapeControlHTML(line, ctx.Locale, charset.FileviewContext)
rows = append(rows, br)
escapeStatus = escapeStatus.Or(br.EscapeStatus)
}
diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go
index 25dd881219..63516bb4d9 100644
--- a/routers/web/repo/cherry_pick.go
+++ b/routers/web/repo/cherry_pick.go
@@ -104,9 +104,9 @@ func CherryPickPost(ctx *context.Context) {
message := strings.TrimSpace(form.CommitSummary)
if message == "" {
if form.Revert {
- message = ctx.Tr("repo.commit.revert-header", sha)
+ message = ctx.Locale.TrString("repo.commit.revert-header", sha)
} else {
- message = ctx.Tr("repo.commit.cherry-pick-header", sha)
+ message = ctx.Locale.TrString("repo.commit.cherry-pick-header", sha)
}
}
@@ -115,11 +115,18 @@ func CherryPickPost(ctx *context.Context) {
message += "\n\n" + form.CommitMessage
}
+ gitIdentity := getGitIdentity(ctx, form.CommitMailID, tplCherryPick, &form)
+ if ctx.Written() {
+ return
+ }
+
opts := &files.ApplyDiffPatchOptions{
LastCommitID: form.LastCommit,
OldBranch: ctx.Repo.BranchName,
NewBranch: branchName,
Message: message,
+ Author: gitIdentity,
+ Committer: gitIdentity,
}
// First lets try the simple plain read-tree -m approach
diff --git a/routers/web/repo/code_frequency.go b/routers/web/repo/code_frequency.go
new file mode 100644
index 0000000000..48ade655b7
--- /dev/null
+++ b/routers/web/repo/code_frequency.go
@@ -0,0 +1,41 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "errors"
+ "net/http"
+
+ "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/context"
+ contributors_service "code.gitea.io/gitea/services/repository"
+)
+
+const (
+ tplCodeFrequency base.TplName = "repo/activity"
+)
+
+// CodeFrequency renders the page to show repository code frequency
+func CodeFrequency(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("repo.activity.navbar.code_frequency")
+
+ ctx.Data["PageIsActivity"] = true
+ ctx.Data["PageIsCodeFrequency"] = true
+ ctx.PageData["repoLink"] = ctx.Repo.RepoLink
+
+ ctx.HTML(http.StatusOK, tplCodeFrequency)
+}
+
+// CodeFrequencyData returns JSON of code frequency data
+func CodeFrequencyData(ctx *context.Context) {
+ if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.CommitID); err != nil {
+ if errors.Is(err, contributors_service.ErrAwaitGeneration) {
+ ctx.Status(http.StatusAccepted)
+ return
+ }
+ ctx.ServerError("GetCodeFrequencyData", err)
+ } else {
+ ctx.JSON(http.StatusOK, contributorStats["total"].Weeks)
+ }
+}
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index a3593815b8..535487d5fd 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -31,6 +31,7 @@ import (
"code.gitea.io/gitea/modules/gitrepo"
"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/typesniffer"
@@ -126,7 +127,7 @@ func setCsvCompareContext(ctx *context.Context) {
return CsvDiffResult{nil, ""}
}
- errTooLarge := errors.New(ctx.Locale.Tr("repo.error.csv.too_large"))
+ errTooLarge := errors.New(ctx.Locale.TrString("repo.error.csv.too_large"))
csvReaderFromCommit := func(ctx *markup.RenderContext, blob *git.Blob) (*csv.Reader, io.Closer, error) {
if blob == nil {
@@ -311,14 +312,14 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo {
baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(ci.BaseBranch)
baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(ci.BaseBranch)
baseIsTag := ctx.Repo.GitRepo.IsTagExist(ci.BaseBranch)
- objectFormat, _ := ctx.Repo.GitRepo.GetObjectFormat()
+
if !baseIsCommit && !baseIsBranch && !baseIsTag {
// Check if baseBranch is short sha commit hash
if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(ci.BaseBranch); baseCommit != nil {
ci.BaseBranch = baseCommit.ID.String()
ctx.Data["BaseBranch"] = ci.BaseBranch
baseIsCommit = true
- } else if ci.BaseBranch == objectFormat.EmptyObjectID().String() {
+ } else if ci.BaseBranch == ctx.Repo.GetObjectFormat().EmptyObjectID().String() {
if isSameRepo {
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ci.HeadBranch))
} else {
@@ -700,7 +701,7 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor
ListOptions: db.ListOptions{
ListAll: true,
},
- IsDeletedBranch: util.OptionalBoolFalse,
+ IsDeletedBranch: optional.Some(false),
})
if err != nil {
return nil, nil, err
@@ -757,7 +758,7 @@ func CompareDiff(ctx *context.Context) {
ListOptions: db.ListOptions{
ListAll: true,
},
- IsDeletedBranch: util.OptionalBoolFalse,
+ IsDeletedBranch: optional.Some(false),
})
if err != nil {
ctx.ServerError("GetBranches", err)
diff --git a/routers/web/repo/contributors.go b/routers/web/repo/contributors.go
new file mode 100644
index 0000000000..bcfef7580a
--- /dev/null
+++ b/routers/web/repo/contributors.go
@@ -0,0 +1,44 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "errors"
+ "net/http"
+
+ "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/context"
+ contributors_service "code.gitea.io/gitea/services/repository"
+)
+
+const (
+ tplContributors base.TplName = "repo/activity"
+)
+
+// Contributors render the page to show repository contributors graph
+func Contributors(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("repo.activity.navbar.contributors")
+
+ ctx.Data["PageIsActivity"] = true
+ ctx.Data["PageIsContributors"] = true
+
+ ctx.PageData["contributionType"] = "commits"
+
+ ctx.PageData["repoLink"] = ctx.Repo.RepoLink
+
+ ctx.HTML(http.StatusOK, tplContributors)
+}
+
+// ContributorsData renders JSON of contributors along with their weekly commit statistics
+func ContributorsData(ctx *context.Context) {
+ if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.CommitID); err != nil {
+ if errors.Is(err, contributors_service.ErrAwaitGeneration) {
+ ctx.Status(http.StatusAccepted)
+ return
+ }
+ ctx.ServerError("GetContributorStats", err)
+ } else {
+ ctx.JSON(http.StatusOK, contributorStats)
+ }
+}
diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go
index 39d9967d02..075477e5f0 100644
--- a/routers/web/repo/editor.go
+++ b/routers/web/repo/editor.go
@@ -121,6 +121,18 @@ func getSelectableEmailAddresses(ctx *context.Context) ([]*user_model.ActivatedE
return commitEmails, nil
}
+// CommonEditorData sets common context data that is used by the editor.
+func CommonEditorData(ctx *context.Context) {
+ // Set context for selectable email addresses.
+ commitEmails, err := getSelectableEmailAddresses(ctx)
+ if err != nil {
+ ctx.ServerError("getSelectableEmailAddresses", err)
+ return
+ }
+ ctx.Data["CommitMails"] = commitEmails
+ ctx.Data["DefaultCommitMail"] = ctx.Doer.GetEmail()
+}
+
func editFile(ctx *context.Context, isNewFile bool) {
ctx.Data["PageIsEdit"] = true
ctx.Data["IsNewFile"] = isNewFile
@@ -183,9 +195,6 @@ func editFile(ctx *context.Context, isNewFile bool) {
}
d, _ := io.ReadAll(dataRc)
- if err := dataRc.Close(); err != nil {
- log.Error("Error whilst closing blob data: %v", err)
- }
buf = append(buf, d...)
if content, err := charset.ToUTF8(buf, charset.ConvertOpts{KeepBOM: true}); err != nil {
@@ -199,12 +208,6 @@ func editFile(ctx *context.Context, isNewFile bool) {
treeNames = append(treeNames, fileName)
}
- commitEmails, err := getSelectableEmailAddresses(ctx)
- if err != nil {
- ctx.ServerError("getSelectableEmailAddresses", err)
- return
- }
-
ctx.Data["TreeNames"] = treeNames
ctx.Data["TreePaths"] = treePaths
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
@@ -220,8 +223,6 @@ func editFile(ctx *context.Context, isNewFile bool) {
ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",")
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
ctx.Data["EditorconfigJson"] = GetEditorConfig(ctx, treePath)
- ctx.Data["CommitMails"] = commitEmails
- ctx.Data["DefaultCommitMail"] = ctx.Doer.GetEmail()
ctx.HTML(http.StatusOK, tplEditFile)
}
@@ -257,12 +258,6 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
branchName = form.NewBranchName
}
- commitEmails, err := getSelectableEmailAddresses(ctx)
- if err != nil {
- ctx.ServerError("getSelectableEmailAddresses", err)
- return
- }
-
ctx.Data["PageIsEdit"] = true
ctx.Data["PageHasPosted"] = true
ctx.Data["IsNewFile"] = isNewFile
@@ -279,8 +274,6 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",")
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
ctx.Data["EditorconfigJson"] = GetEditorConfig(ctx, form.TreePath)
- ctx.Data["CommitMails"] = commitEmails
- ctx.Data["DefaultCommitMail"] = ctx.Doer.GetEmail()
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplEditFile)
@@ -300,9 +293,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
message := strings.TrimSpace(form.CommitSummary)
if len(message) == 0 {
if isNewFile {
- message = ctx.Tr("repo.editor.add", form.TreePath)
+ message = ctx.Locale.TrString("repo.editor.add", form.TreePath)
} else {
- message = ctx.Tr("repo.editor.update", form.TreePath)
+ message = ctx.Locale.TrString("repo.editor.update", form.TreePath)
}
}
form.CommitMessage = strings.TrimSpace(form.CommitMessage)
@@ -315,28 +308,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
operation = "create"
}
- gitIdentity := &files_service.IdentityOptions{
- Name: ctx.Doer.Name,
- }
-
- // -1 is defined as placeholder email.
- if form.CommitMailID == -1 {
- gitIdentity.Email = ctx.Doer.GetPlaceholderEmail()
- } else {
- // Check if the given email is activated.
- email, err := user_model.GetEmailAddressByID(ctx, ctx.Doer.ID, form.CommitMailID)
- if err != nil {
- ctx.ServerError("GetEmailAddressByID", err)
- return
- }
-
- if email == nil || !email.IsActivated {
- ctx.Data["Err_CommitMailID"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_mail"), tplEditFile, &form)
- return
- }
-
- gitIdentity.Email = email.Email
+ gitIdentity := getGitIdentity(ctx, form.CommitMailID, tplEditFile, form)
+ if ctx.Written() {
+ return
}
if _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
@@ -479,7 +453,7 @@ func DiffPreviewPost(ctx *context.Context) {
}
if diff.NumFiles == 0 {
- ctx.PlainText(http.StatusOK, ctx.Tr("repo.editor.no_changes_to_show"))
+ ctx.PlainText(http.StatusOK, ctx.Locale.TrString("repo.editor.no_changes_to_show"))
return
}
ctx.Data["File"] = diff.Files[0]
@@ -546,13 +520,18 @@ func DeleteFilePost(ctx *context.Context) {
message := strings.TrimSpace(form.CommitSummary)
if len(message) == 0 {
- message = ctx.Tr("repo.editor.delete", ctx.Repo.TreePath)
+ message = ctx.Locale.TrString("repo.editor.delete", ctx.Repo.TreePath)
}
form.CommitMessage = strings.TrimSpace(form.CommitMessage)
if len(form.CommitMessage) > 0 {
message += "\n\n" + form.CommitMessage
}
+ gitIdentity := getGitIdentity(ctx, form.CommitMailID, tplDeleteFile, &form)
+ if ctx.Written() {
+ return
+ }
+
if _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
LastCommitID: form.LastCommit,
OldBranch: ctx.Repo.BranchName,
@@ -563,8 +542,10 @@ func DeleteFilePost(ctx *context.Context) {
TreePath: ctx.Repo.TreePath,
},
},
- Message: message,
- Signoff: form.Signoff,
+ Message: message,
+ Signoff: form.Signoff,
+ Author: gitIdentity,
+ Committer: gitIdentity,
}); err != nil {
// This is where we handle all the errors thrown by repofiles.DeleteRepoFile
if git.IsErrNotExist(err) || models.IsErrRepoFileDoesNotExist(err) {
@@ -755,7 +736,7 @@ func UploadFilePost(ctx *context.Context) {
if dir == "" {
dir = "/"
}
- message = ctx.Tr("repo.editor.upload_files_to_dir", dir)
+ message = ctx.Locale.TrString("repo.editor.upload_files_to_dir", dir)
}
form.CommitMessage = strings.TrimSpace(form.CommitMessage)
@@ -763,6 +744,11 @@ func UploadFilePost(ctx *context.Context) {
message += "\n\n" + form.CommitMessage
}
+ gitIdentity := getGitIdentity(ctx, form.CommitMailID, tplUploadFile, &form)
+ if ctx.Written() {
+ return
+ }
+
if err := files_service.UploadRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.UploadRepoFileOptions{
LastCommitID: ctx.Repo.CommitID,
OldBranch: oldBranchName,
@@ -771,6 +757,8 @@ func UploadFilePost(ctx *context.Context) {
Message: message,
Files: form.Files,
Signoff: form.Signoff,
+ Author: gitIdentity,
+ Committer: gitIdentity,
}); err != nil {
if git_model.IsErrLFSFileLocked(err) {
ctx.Data["Err_TreePath"] = true
@@ -941,3 +929,33 @@ func GetClosestParentWithFiles(treePath string, commit *git.Commit) string {
}
return treePath
}
+
+// getGitIdentity returns the Git identity that should be used for an Git
+// operation, that takes into account an user's specified email.
+func getGitIdentity(ctx *context.Context, commitMailID int64, tpl base.TplName, form any) *files_service.IdentityOptions {
+ gitIdentity := &files_service.IdentityOptions{
+ Name: ctx.Doer.Name,
+ }
+
+ // -1 is defined as placeholder email.
+ if commitMailID == -1 {
+ gitIdentity.Email = ctx.Doer.GetPlaceholderEmail()
+ } else {
+ // Check if the given email is activated.
+ email, err := user_model.GetEmailAddressByID(ctx, ctx.Doer.ID, commitMailID)
+ if err != nil {
+ ctx.ServerError("GetEmailAddressByID", err)
+ return nil
+ }
+
+ if email == nil || !email.IsActivated {
+ ctx.Data["Err_CommitMailID"] = true
+ ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_mail"), tplEditFile, form)
+ return nil
+ }
+
+ gitIdentity.Email = email.Email
+ }
+
+ return gitIdentity
+}
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 3e7b099bba..963c6289b2 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -32,6 +32,7 @@ import (
"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"
issue_template "code.gitea.io/gitea/modules/issue/template"
@@ -717,16 +718,12 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is
tmp.ItemID = -review.ReviewerTeamID
}
- if ctx.Repo.IsAdmin() {
- // Admin can dismiss or re-request any review requests
+ if canChooseReviewer {
+ // Users who can choose reviewers can also remove review requests
tmp.CanChange = true
} else if ctx.Doer != nil && ctx.Doer.ID == review.ReviewerID && review.Type == issues_model.ReviewTypeRequest {
// A user can refuse review requests
tmp.CanChange = true
- } else if (canChooseReviewer || (ctx.Doer != nil && ctx.Doer.ID == issue.PosterID)) && review.Type != issues_model.ReviewTypeRequest &&
- ctx.Doer.ID != review.ReviewerID {
- // The poster of the PR, a manager, or official reviewers can re-request review from other reviewers
- tmp.CanChange = true
}
pullReviews = append(pullReviews, tmp)
@@ -1042,7 +1039,7 @@ func renderErrorOfTemplates(ctx *context.Context, errs map[string]error) string
})
if err != nil {
log.Debug("render flash error: %v", err)
- flashError = ctx.Tr("repo.issues.choose.ignore_invalid_templates")
+ flashError = ctx.Locale.TrString("repo.issues.choose.ignore_invalid_templates")
}
return flashError
}
@@ -1448,7 +1445,7 @@ func ViewIssue(ctx *context.Context) {
return
}
- ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
+ ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, emoji.ReplaceAliases(issue.Title))
iw := new(issues_model.IssueWatch)
if ctx.Doer != nil {
@@ -1534,18 +1531,9 @@ func ViewIssue(ctx *context.Context) {
}
if issue.IsPull {
- canChooseReviewer := ctx.Repo.CanWrite(unit.TypePullRequests)
+ canChooseReviewer := false
if ctx.Doer != nil && ctx.IsSigned {
- if !canChooseReviewer {
- canChooseReviewer = ctx.Doer.ID == issue.PosterID
- }
- if !canChooseReviewer {
- canChooseReviewer, err = issues_model.IsOfficialReviewer(ctx, issue, ctx.Doer)
- if err != nil {
- ctx.ServerError("IsOfficialReviewer", err)
- return
- }
- }
+ canChooseReviewer = issue_service.CanDoerChangeReviewRequests(ctx, ctx.Doer, repo, issue)
}
RetrieveRepoReviewers(ctx, repo, issue, canChooseReviewer)
@@ -1664,7 +1652,7 @@ func ViewIssue(ctx *context.Context) {
}
ghostMilestone := &issues_model.Milestone{
ID: -1,
- Name: ctx.Tr("repo.issues.deleted_milestone"),
+ Name: ctx.Locale.TrString("repo.issues.deleted_milestone"),
}
if comment.OldMilestoneID > 0 && comment.OldMilestone == nil {
comment.OldMilestone = ghostMilestone
@@ -1681,7 +1669,7 @@ func ViewIssue(ctx *context.Context) {
ghostProject := &project_model.Project{
ID: -1,
- Title: ctx.Tr("repo.issues.deleted_project"),
+ Title: ctx.Locale.TrString("repo.issues.deleted_project"),
}
if comment.OldProjectID > 0 && comment.OldProject == nil {
@@ -1739,6 +1727,10 @@ func ViewIssue(ctx *context.Context) {
for _, codeComments := range comment.Review.CodeComments {
for _, lineComments := range codeComments {
for _, c := range lineComments {
+ if err := c.LoadAttachments(ctx); err != nil {
+ ctx.ServerError("LoadAttachments", err)
+ return
+ }
// Check tag.
role, ok = marked[c.PosterID]
if ok {
@@ -1871,6 +1863,8 @@ func ViewIssue(ctx *context.Context) {
mergeStyle = repo_model.MergeStyleRebaseMerge
} else if prConfig.AllowSquash {
mergeStyle = repo_model.MergeStyleSquash
+ } else if prConfig.AllowFastForwardOnly {
+ mergeStyle = repo_model.MergeStyleFastForwardOnly
} else if prConfig.AllowManualMerge {
mergeStyle = repo_model.MergeStyleManuallyMerged
}
@@ -3026,27 +3020,34 @@ func NewComment(ctx *context.Context) {
// check whether the ref of PR in base repo is consistent with the head commit of head branch in the head repo
// get head commit of PR
if pull.Flow == issues_model.PullRequestFlowGithub {
- prHeadRef := pull.GetGitRefName()
if err := pull.LoadBaseRepo(ctx); err != nil {
ctx.ServerError("Unable to load base repo", err)
return
}
+ if err := pull.LoadHeadRepo(ctx); err != nil {
+ ctx.ServerError("Unable to load head repo", err)
+ return
+ }
+
+ // Check if the base branch of the pull request still exists.
+ if ok := git.IsBranchExist(ctx, pull.BaseRepo.RepoPath(), pull.BaseBranch); !ok {
+ ctx.JSONError(ctx.Tr("repo.pulls.reopen_failed.base_branch"))
+ return
+ }
+
+ // Check if the head branch of the pull request still exists.
+ if ok := git.IsBranchExist(ctx, pull.HeadRepo.RepoPath(), pull.HeadBranch); !ok {
+ ctx.JSONError(ctx.Tr("repo.pulls.reopen_failed.head_branch"))
+ return
+ }
+
+ prHeadRef := pull.GetGitRefName()
prHeadCommitID, err := git.GetFullCommitID(ctx, pull.BaseRepo.RepoPath(), prHeadRef)
if err != nil {
ctx.ServerError("Get head commit Id of pr fail", err)
return
}
- // get head commit of branch in the head repo
- if err := pull.LoadHeadRepo(ctx); err != nil {
- ctx.ServerError("Unable to load head repo", err)
- return
- }
- if ok := git.IsBranchExist(ctx, pull.HeadRepo.RepoPath(), pull.BaseBranch); !ok {
- // todo localize
- ctx.JSONError("The origin branch is delete, cannot reopen.")
- return
- }
headBranchRef := pull.GetGitHeadBranchRefName()
headBranchCommitID, err := git.GetFullCommitID(ctx, pull.HeadRepo.RepoPath(), headBranchRef)
if err != nil {
diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go
index 31e6ac608c..4dc537a06e 100644
--- a/routers/web/repo/issue_content_history.go
+++ b/routers/web/repo/issue_content_history.go
@@ -56,12 +56,12 @@ func GetContentHistoryList(ctx *context.Context) {
for _, item := range items {
var actionText string
if item.IsDeleted {
- actionTextDeleted := ctx.Locale.Tr("repo.issues.content_history.deleted")
+ actionTextDeleted := ctx.Locale.TrString("repo.issues.content_history.deleted")
actionText = "" + actionTextDeleted + ""
} else if item.IsFirstCreated {
- actionText = ctx.Locale.Tr("repo.issues.content_history.created")
+ actionText = ctx.Locale.TrString("repo.issues.content_history.created")
} else {
- actionText = ctx.Locale.Tr("repo.issues.content_history.edited")
+ actionText = ctx.Locale.TrString("repo.issues.content_history.edited")
}
username := item.UserName
diff --git a/routers/web/repo/issue_label_test.go b/routers/web/repo/issue_label_test.go
index e0d49e44e1..742f12114d 100644
--- a/routers/web/repo/issue_label_test.go
+++ b/routers/web/repo/issue_label_test.go
@@ -123,7 +123,7 @@ func TestDeleteLabel(t *testing.T) {
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
unittest.AssertNotExistsBean(t, &issues_model.Label{ID: 2})
unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{LabelID: 2})
- assert.Equal(t, ctx.Tr("repo.issues.label_deletion_success"), ctx.Flash.SuccessMsg)
+ assert.EqualValues(t, ctx.Tr("repo.issues.label_deletion_success"), ctx.Flash.SuccessMsg)
}
func TestUpdateIssueLabel_Clear(t *testing.T) {
diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go
index c04435cf1b..03ea03467a 100644
--- a/routers/web/repo/patch.go
+++ b/routers/web/repo/patch.go
@@ -79,7 +79,7 @@ func NewDiffPatchPost(ctx *context.Context) {
// `message` will be both the summary and message combined
message := strings.TrimSpace(form.CommitSummary)
if len(message) == 0 {
- message = ctx.Tr("repo.editor.patch")
+ message = ctx.Locale.TrString("repo.editor.patch")
}
form.CommitMessage = strings.TrimSpace(form.CommitMessage)
@@ -87,12 +87,19 @@ func NewDiffPatchPost(ctx *context.Context) {
message += "\n\n" + form.CommitMessage
}
+ gitIdenitity := getGitIdentity(ctx, form.CommitMailID, tplPatchFile, &form)
+ if ctx.Written() {
+ return
+ }
+
fileResponse, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, &files.ApplyDiffPatchOptions{
LastCommitID: form.LastCommit,
OldBranch: ctx.Repo.BranchName,
NewBranch: branchName,
Message: message,
Content: strings.ReplaceAll(form.Content, "\r", ""),
+ Author: gitIdenitity,
+ Committer: gitIdenitity,
})
if err != nil {
if git_model.IsErrBranchAlreadyExists(err) {
diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go
index 4908bb796d..cc0127e7e1 100644
--- a/routers/web/repo/projects.go
+++ b/routers/web/repo/projects.go
@@ -315,7 +315,7 @@ func ViewProject(ctx *context.Context) {
}
if boards[0].ID == 0 {
- boards[0].Title = ctx.Tr("repo.projects.type.uncategorized")
+ boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized")
}
issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
@@ -339,16 +339,16 @@ func ViewProject(ctx *context.Context) {
linkedPrsMap := make(map[int64][]*issues_model.Issue)
for _, issuesList := range issuesMap {
for _, issue := range issuesList {
- var referencedIds []int64
+ var referencedIDs []int64
for _, comment := range issue.Comments {
if comment.RefIssueID != 0 && comment.RefIsPull {
- referencedIds = append(referencedIds, comment.RefIssueID)
+ referencedIDs = append(referencedIDs, comment.RefIssueID)
}
}
- if len(referencedIds) > 0 {
+ if len(referencedIDs) > 0 {
if linkedPrs, err := issues_model.Issues(ctx, &issues_model.IssuesOptions{
- IssueIDs: referencedIds,
+ IssueIDs: referencedIDs,
IsPull: util.OptionalBoolTrue,
}); err == nil {
linkedPrsMap[issue.ID] = linkedPrs
@@ -633,7 +633,7 @@ func MoveIssues(ctx *context.Context) {
board = &project_model.Board{
ID: 0,
ProjectID: project.ID,
- Title: ctx.Tr("repo.projects.type.uncategorized"),
+ Title: ctx.Locale.TrString("repo.projects.type.uncategorized"),
}
} else {
board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index 7830c17ced..561039c413 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -1,5 +1,6 @@
-// Copyright 2018 The Gitea Authors.
-// Copyright 2014 The Gogs Authors.
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// Copyright 2024 The Forgejo Authors. All rights reserved.
// All rights reserved.
// SPDX-License-Identifier: MIT
@@ -28,10 +29,12 @@ 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/emoji"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
issue_template "code.gitea.io/gitea/modules/issue/template"
"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/upload"
@@ -115,21 +118,21 @@ func getRepository(ctx *context.Context, repoID int64) *repo_model.Repository {
return repo
}
-func getForkRepository(ctx *context.Context) *repo_model.Repository {
- forkRepo := getRepository(ctx, ctx.ParamsInt64(":repoid"))
- if ctx.Written() {
- return nil
+func updateForkRepositoryInContext(ctx *context.Context, forkRepo *repo_model.Repository) bool {
+ if forkRepo == nil {
+ ctx.NotFound("No repository in context", nil)
+ return false
}
if forkRepo.IsEmpty {
log.Trace("Empty repository %-v", forkRepo)
- ctx.NotFound("getForkRepository", nil)
- return nil
+ ctx.NotFound("updateForkRepositoryInContext", nil)
+ return false
}
if err := forkRepo.LoadOwner(ctx); err != nil {
ctx.ServerError("LoadOwner", err)
- return nil
+ return false
}
ctx.Data["repo_name"] = forkRepo.Name
@@ -142,7 +145,7 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository {
ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx, ctx.Doer.ID)
if err != nil {
ctx.ServerError("GetOrgsCanCreateRepoByUserID", err)
- return nil
+ return false
}
var orgs []*organization.Organization
for _, org := range ownedOrgs {
@@ -170,7 +173,7 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository {
traverseParentRepo, err = repo_model.GetRepositoryByID(ctx, traverseParentRepo.ForkID)
if err != nil {
ctx.ServerError("GetRepositoryByID", err)
- return nil
+ return false
}
}
@@ -184,7 +187,7 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository {
} else {
ctx.Data["CanForkRepo"] = false
ctx.Flash.Error(ctx.Tr("repo.fork_no_valid_owners"), true)
- return nil
+ return false
}
branches, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
@@ -192,20 +195,25 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository {
ListOptions: db.ListOptions{
ListAll: true,
},
- IsDeletedBranch: util.OptionalBoolFalse,
+ IsDeletedBranch: optional.Some(false),
// Add it as the first option
ExcludeBranchNames: []string{ctx.Repo.Repository.DefaultBranch},
})
if err != nil {
ctx.ServerError("FindBranchNames", err)
- return nil
+ return false
}
ctx.Data["Branches"] = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...)
- return forkRepo
+ return true
}
-// Fork render repository fork page
+// ForkByID redirects (with 301 Moved Permanently) to the repository's `/fork` page
+func ForkByID(ctx *context.Context) {
+ ctx.Redirect(ctx.Repo.Repository.Link()+"/fork", http.StatusMovedPermanently)
+}
+
+// Fork renders the repository fork page
func Fork(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("new_fork")
@@ -217,8 +225,7 @@ func Fork(ctx *context.Context) {
ctx.Flash.Error(msg, true)
}
- getForkRepository(ctx)
- if ctx.Written() {
+ if !updateForkRepositoryInContext(ctx, ctx.Repo.Repository) {
return
}
@@ -236,8 +243,8 @@ func ForkPost(ctx *context.Context) {
return
}
- forkRepo := getForkRepository(ctx)
- if ctx.Written() {
+ forkRepo := ctx.Repo.Repository
+ if !updateForkRepositoryInContext(ctx, forkRepo) {
return
}
@@ -340,7 +347,7 @@ func getPullInfo(ctx *context.Context) (issue *issues_model.Issue, ok bool) {
ctx.ServerError("LoadRepo", err)
return nil, false
}
- ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
+ ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, emoji.ReplaceAliases(issue.Title))
ctx.Data["Issue"] = issue
if !issue.IsPull {
@@ -377,6 +384,11 @@ func setMergeTarget(ctx *context.Context, pull *issues_model.PullRequest) {
} else {
ctx.Data["HeadTarget"] = pull.MustHeadUserName(ctx) + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch
}
+
+ if pull.Flow == issues_model.PullRequestFlowAGit {
+ ctx.Data["MadeUsingAGit"] = true
+ }
+
ctx.Data["BaseTarget"] = pull.BaseBranch
ctx.Data["HeadBranchLink"] = pull.GetHeadBranchLink(ctx)
ctx.Data["BaseBranchLink"] = pull.GetBaseBranchLink(ctx)
@@ -658,6 +670,24 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
}
if pb != nil && pb.EnableStatusCheck {
+
+ var missingRequiredChecks []string
+ for _, requiredContext := range pb.StatusCheckContexts {
+ contextFound := false
+ matchesRequiredContext := createRequiredContextMatcher(requiredContext)
+ for _, presentStatus := range commitStatuses {
+ if matchesRequiredContext(presentStatus.Context) {
+ contextFound = true
+ break
+ }
+ }
+
+ if !contextFound {
+ missingRequiredChecks = append(missingRequiredChecks, requiredContext)
+ }
+ }
+ ctx.Data["MissingRequiredChecks"] = missingRequiredChecks
+
ctx.Data["is_context_required"] = func(context string) bool {
for _, c := range pb.StatusCheckContexts {
if c == context {
@@ -726,10 +756,22 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
return compareInfo
}
+func createRequiredContextMatcher(requiredContext string) func(string) bool {
+ if gp, err := glob.Compile(requiredContext); err == nil {
+ return func(contextToCheck string) bool {
+ return gp.Match(contextToCheck)
+ }
+ }
+
+ return func(contextToCheck string) bool {
+ return requiredContext == contextToCheck
+ }
+}
+
type pullCommitList struct {
Commits []pull_service.CommitInfo `json:"commits"`
LastReviewCommitSha string `json:"last_review_commit_sha"`
- Locale map[string]string `json:"locale"`
+ Locale map[string]any `json:"locale"`
}
// GetPullCommits get all commits for given pull request
@@ -747,7 +789,7 @@ func GetPullCommits(ctx *context.Context) {
}
// Get the needed locale
- resp.Locale = map[string]string{
+ resp.Locale = map[string]any{
"lang": ctx.Locale.Language(),
"show_all_commits": ctx.Tr("repo.pulls.show_all_commits"),
"stats_num_commits": ctx.TrN(len(commits), "repo.activity.git_stats_commit_1", "repo.activity.git_stats_commit_n", len(commits)),
@@ -944,6 +986,21 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
return
}
+ for _, file := range diff.Files {
+ for _, section := range file.Sections {
+ for _, line := range section.Lines {
+ for _, comments := range line.Conversations {
+ for _, comment := range comments {
+ if err := comment.LoadAttachments(ctx); err != nil {
+ ctx.ServerError("LoadAttachments", err)
+ return
+ }
+ }
+ }
+ }
+ }
+ }
+
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch)
if err != nil {
ctx.ServerError("LoadProtectedBranch", err)
@@ -1271,19 +1328,19 @@ func MergePullRequest(ctx *context.Context) {
return
}
ctx.Flash.Error(flashError)
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
} else if models.IsErrMergeUnrelatedHistories(err) {
log.Debug("MergeUnrelatedHistories error: %v", err)
ctx.Flash.Error(ctx.Tr("repo.pulls.unrelated_histories"))
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
} else if git.IsErrPushOutOfDate(err) {
log.Debug("MergePushOutOfDate error: %v", err)
ctx.Flash.Error(ctx.Tr("repo.pulls.merge_out_of_date"))
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
} else if models.IsErrSHADoesNotMatch(err) {
log.Debug("MergeHeadOutOfDate error: %v", err)
ctx.Flash.Error(ctx.Tr("repo.pulls.head_out_of_date"))
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
} else if git.IsErrPushRejected(err) {
log.Debug("MergePushRejected error: %v", err)
pushrejErr := err.(*git.ErrPushRejected)
diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go
index e399176a4a..798d3d5454 100644
--- a/routers/web/repo/pull_review.go
+++ b/routers/web/repo/pull_review.go
@@ -15,6 +15,7 @@ import (
"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/forms"
pull_service "code.gitea.io/gitea/services/pull"
@@ -49,6 +50,8 @@ func RenderNewCodeCommentForm(ctx *context.Context) {
return
}
ctx.Data["AfterCommitID"] = pullHeadCommitID
+ ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
+ upload.AddUploadContext(ctx, "comment")
ctx.HTML(http.StatusOK, tplNewComment)
}
@@ -74,6 +77,11 @@ func CreateCodeComment(ctx *context.Context) {
signedLine *= -1
}
+ var attachments []string
+ if setting.Attachment.Enabled {
+ attachments = form.Files
+ }
+
comment, err := pull_service.CreateCodeComment(ctx,
ctx.Doer,
ctx.Repo.GitRepo,
@@ -84,6 +92,7 @@ func CreateCodeComment(ctx *context.Context) {
!form.SingleReview,
form.Reply,
form.LatestCommitID,
+ attachments,
)
if err != nil {
ctx.ServerError("CreateCodeComment", err)
@@ -153,12 +162,23 @@ func UpdateResolveConversation(ctx *context.Context) {
}
func renderConversation(ctx *context.Context, comment *issues_model.Comment, origin string) {
- comments, err := issues_model.FetchCodeCommentsByLine(ctx, comment.Issue, ctx.Doer, comment.TreePath, comment.Line, true)
+ comments, err := issues_model.FetchCodeConversation(ctx, comment, ctx.Doer)
if err != nil {
ctx.ServerError("FetchCodeCommentsByLine", err)
return
}
ctx.Data["PageIsPullFiles"] = (origin == "diff")
+
+ for _, c := range comments {
+ if err := c.LoadAttachments(ctx); err != nil {
+ ctx.ServerError("LoadAttachments", err)
+ return
+ }
+ }
+
+ ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
+ upload.AddUploadContext(ctx, "comment")
+
ctx.Data["comments"] = comments
if ctx.Data["CanMarkConversation"], err = issues_model.CanMarkConversation(ctx, comment.Issue, ctx.Doer); err != nil {
ctx.ServerError("CanMarkConversation", err)
@@ -209,9 +229,9 @@ func SubmitReview(ctx *context.Context) {
if issue.IsPoster(ctx.Doer.ID) {
var translated string
if reviewType == issues_model.ReviewTypeApprove {
- translated = ctx.Tr("repo.issues.review.self.approval")
+ translated = ctx.Locale.TrString("repo.issues.review.self.approval")
} else {
- translated = ctx.Tr("repo.issues.review.self.rejection")
+ translated = ctx.Locale.TrString("repo.issues.review.self.rejection")
}
ctx.Flash.Error(translated)
diff --git a/routers/web/repo/pull_review_test.go b/routers/web/repo/pull_review_test.go
new file mode 100644
index 0000000000..68f68d7bb0
--- /dev/null
+++ b/routers/web/repo/pull_review_test.go
@@ -0,0 +1,78 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "net/http/httptest"
+ "testing"
+
+ "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/pull"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRenderConversation(t *testing.T) {
+ unittest.PrepareTestEnv(t)
+
+ pr, _ := issues_model.GetPullRequestByID(db.DefaultContext, 2)
+ _ = pr.LoadIssue(db.DefaultContext)
+ _ = pr.Issue.LoadPoster(db.DefaultContext)
+ _ = pr.Issue.LoadRepo(db.DefaultContext)
+
+ run := func(name string, cb func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder)) {
+ t.Run(name, func(t *testing.T) {
+ ctx, resp := contexttest.MockContext(t, "/")
+ ctx.Render = templates.HTMLRenderer()
+ contexttest.LoadUser(t, ctx, pr.Issue.PosterID)
+ contexttest.LoadRepo(t, ctx, pr.BaseRepoID)
+ contexttest.LoadGitRepo(t, ctx)
+ defer ctx.Repo.GitRepo.Close()
+ cb(t, ctx, resp)
+ })
+ }
+
+ var preparedComment *issues_model.Comment
+ run("prepare", func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder) {
+ comment, err := pull.CreateCodeComment(ctx, pr.Issue.Poster, ctx.Repo.GitRepo, pr.Issue, 1, "content", "", false, 0, pr.HeadCommitID, nil)
+ if !assert.NoError(t, err) {
+ return
+ }
+ comment.Invalidated = true
+ err = issues_model.UpdateCommentInvalidate(ctx, comment)
+ if !assert.NoError(t, err) {
+ return
+ }
+ preparedComment = comment
+ })
+ if !assert.NotNil(t, preparedComment) {
+ return
+ }
+ run("diff with outdated", func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder) {
+ ctx.Data["ShowOutdatedComments"] = true
+ renderConversation(ctx, preparedComment, "diff")
+ assert.Contains(t, resp.Body.String(), `
- {{ctx.Locale.Tr "packages.settings.delete.notice" `` `` | Safe}}
+ {{ctx.Locale.Tr "packages.settings.delete.notice" (``|SafeHTML) (``|SafeHTML)}}
{{template "base/modal_actions_confirm" .}}
diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl
index fdba0734a2..e11247aed4 100644
--- a/templates/admin/repo/list.tmpl
+++ b/templates/admin/repo/list.tmpl
@@ -101,7 +101,7 @@
{{ctx.Locale.Tr "repo.settings.delete_desc"}}
- {{ctx.Locale.Tr "repo.settings.delete_notices_2" `
` | Safe}}
+ {{ctx.Locale.Tr "repo.settings.delete_notices_2" (`
`|SafeHTML)}}
{{ctx.Locale.Tr "repo.settings.delete_notices_fork_1"}}
{{template "base/modal_actions_confirm" .}}
diff --git a/templates/admin/stacktrace.tmpl b/templates/admin/stacktrace.tmpl
index 894e41f8d7..42944615c3 100644
--- a/templates/admin/stacktrace.tmpl
+++ b/templates/admin/stacktrace.tmpl
@@ -39,7 +39,7 @@
{{ctx.Locale.Tr "admin.monitor.process.cancel"}}
-
{{ctx.Locale.Tr "admin.monitor.process.cancel_notices" `` | Safe}}
+
{{ctx.Locale.Tr "admin.monitor.process.cancel_notices" (``|SafeHTML)}}
{{ctx.Locale.Tr "admin.monitor.process.cancel_desc"}}
{{template "base/modal_actions_confirm" .}}
diff --git a/templates/admin/stats.tmpl b/templates/admin/stats.tmpl
index 04fa862a85..70f2aa7fb4 100644
--- a/templates/admin/stats.tmpl
+++ b/templates/admin/stats.tmpl
@@ -5,10 +5,10 @@
- {{range $statsKey := .StatsKeys}}
+ {{range $statsKey, $statsValue := .Stats}}
{{$statsKey}} |
- {{index $.StatsCounter $statsKey}} |
+ {{$statsValue}} |
{{end}}
diff --git a/templates/admin/system_status.tmpl b/templates/admin/system_status.tmpl
new file mode 100644
index 0000000000..7b5c9be6cc
--- /dev/null
+++ b/templates/admin/system_status.tmpl
@@ -0,0 +1,62 @@
+
+ - {{ctx.Locale.Tr "admin.dashboard.server_uptime"}}
+ - {{.SysStatus.StartTime}}
+ - {{ctx.Locale.Tr "admin.dashboard.current_goroutine"}}
+ - {{.SysStatus.NumGoroutine}}
+
+ - {{ctx.Locale.Tr "admin.dashboard.current_memory_usage"}}
+ - {{.SysStatus.MemAllocated}}
+ - {{ctx.Locale.Tr "admin.dashboard.total_memory_allocated"}}
+ - {{.SysStatus.MemTotal}}
+ - {{ctx.Locale.Tr "admin.dashboard.memory_obtained"}}
+ - {{.SysStatus.MemSys}}
+ - {{ctx.Locale.Tr "admin.dashboard.pointer_lookup_times"}}
+ - {{.SysStatus.Lookups}}
+ - {{ctx.Locale.Tr "admin.dashboard.memory_allocate_times"}}
+ - {{.SysStatus.MemMallocs}}
+ - {{ctx.Locale.Tr "admin.dashboard.memory_free_times"}}
+ - {{.SysStatus.MemFrees}}
+
+ - {{ctx.Locale.Tr "admin.dashboard.current_heap_usage"}}
+ - {{.SysStatus.HeapAlloc}}
+ - {{ctx.Locale.Tr "admin.dashboard.heap_memory_obtained"}}
+ - {{.SysStatus.HeapSys}}
+ - {{ctx.Locale.Tr "admin.dashboard.heap_memory_idle"}}
+ - {{.SysStatus.HeapIdle}}
+ - {{ctx.Locale.Tr "admin.dashboard.heap_memory_in_use"}}
+ - {{.SysStatus.HeapInuse}}
+ - {{ctx.Locale.Tr "admin.dashboard.heap_memory_released"}}
+ - {{.SysStatus.HeapReleased}}
+ - {{ctx.Locale.Tr "admin.dashboard.heap_objects"}}
+ - {{.SysStatus.HeapObjects}}
+
+ - {{ctx.Locale.Tr "admin.dashboard.bootstrap_stack_usage"}}
+ - {{.SysStatus.StackInuse}}
+ - {{ctx.Locale.Tr "admin.dashboard.stack_memory_obtained"}}
+ - {{.SysStatus.StackSys}}
+ - {{ctx.Locale.Tr "admin.dashboard.mspan_structures_usage"}}
+ - {{.SysStatus.MSpanInuse}}
+ - {{ctx.Locale.Tr "admin.dashboard.mspan_structures_obtained"}}
+ - {{.SysStatus.MSpanSys}}
+ - {{ctx.Locale.Tr "admin.dashboard.mcache_structures_usage"}}
+ - {{.SysStatus.MCacheInuse}}
+ - {{ctx.Locale.Tr "admin.dashboard.mcache_structures_obtained"}}
+ - {{.SysStatus.MCacheSys}}
+ - {{ctx.Locale.Tr "admin.dashboard.profiling_bucket_hash_table_obtained"}}
+ - {{.SysStatus.BuckHashSys}}
+ - {{ctx.Locale.Tr "admin.dashboard.gc_metadata_obtained"}}
+ - {{.SysStatus.GCSys}}
+ - {{ctx.Locale.Tr "admin.dashboard.other_system_allocation_obtained"}}
+ - {{.SysStatus.OtherSys}}
+
+ - {{ctx.Locale.Tr "admin.dashboard.next_gc_recycle"}}
+ - {{.SysStatus.NextGC}}
+ - {{ctx.Locale.Tr "admin.dashboard.last_gc_time"}}
+ - {{.SysStatus.LastGCTime}}
+ - {{ctx.Locale.Tr "admin.dashboard.total_gc_pause"}}
+ - {{.SysStatus.PauseTotalNs}}
+ - {{ctx.Locale.Tr "admin.dashboard.last_gc_pause"}}
+ - {{.SysStatus.PauseNs}}
+ - {{ctx.Locale.Tr "admin.dashboard.gc_times"}}
+ - {{.SysStatus.NumGC}}
+
diff --git a/templates/admin/user/new.tmpl b/templates/admin/user/new.tmpl
index 81f70511d0..bcb53d8131 100644
--- a/templates/admin/user/new.tmpl
+++ b/templates/admin/user/new.tmpl
@@ -26,7 +26,7 @@
{{ctx.Locale.Tr "settings.visibility"}}
-
+
{{if .DefaultUserVisibilityMode.IsPublic}}{{ctx.Locale.Tr "settings.visibility.public"}}{{end}}
{{if .DefaultUserVisibilityMode.IsLimited}}{{ctx.Locale.Tr "settings.visibility.limited"}}{{end}}
diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl
index d65a3626a4..fed426a469 100644
--- a/templates/base/footer.tmpl
+++ b/templates/base/footer.tmpl
@@ -16,6 +16,5 @@
{{template "custom/footer" .}}
- {{ctx.DataRaceCheck $.Context}}
diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl
index 21addc7176..ad3419793e 100644
--- a/templates/base/head.tmpl
+++ b/templates/base/head.tmpl
@@ -3,7 +3,7 @@
{{/* Display `- .Repsository.FullName` only if `.Title` does not already start with that. */}}
-
{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if and (.Repository.Name) (not (StringUtils.HasPrefix .Title .Repository.FullName))}}{{.Repository.FullName}} - {{end}}{{AppName}}
+
{{if .Title}}{{.Title}} - {{end}}{{if and (.Repository.Name) (not (StringUtils.HasPrefix .Title .Repository.FullName))}}{{.Repository.FullName}} - {{end}}{{AppName}}
{{if .ManifestData}}
{{end}}
@@ -30,8 +30,7 @@
{{template "base/head_style" .}}
{{template "custom/header" .}}
-
- {{ctx.DataRaceCheck $.Context}}
+
{{template "custom/body_outer_pre" .}}
diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl
index b4a93688e3..fb0301aeb1 100644
--- a/templates/base/head_navbar.tmpl
+++ b/templates/base/head_navbar.tmpl
@@ -20,7 +20,7 @@
{{end}}
-
+
diff --git a/templates/code/searchcombo.tmpl b/templates/code/searchcombo.tmpl
index 48dc13b47b..d451bc0ad8 100644
--- a/templates/code/searchcombo.tmpl
+++ b/templates/code/searchcombo.tmpl
@@ -7,7 +7,7 @@
{{else if .SearchResults}}
- {{ctx.Locale.Tr "explore.code_search_results" (.Keyword|Escape) | Str2html}}
+ {{ctx.Locale.Tr "explore.code_search_results" .Keyword}}
{{template "code/searchresults" .}}
{{else if .Keyword}}
diff --git a/templates/devtest/gitea-ui.tmpl b/templates/devtest/gitea-ui.tmpl
index 73293ddf48..ccf188609c 100644
--- a/templates/devtest/gitea-ui.tmpl
+++ b/templates/devtest/gitea-ui.tmpl
@@ -275,6 +275,12 @@
ps: no JS code attached, so just a layout
{{template "shared/combomarkdowneditor" .}}
+
+
Tailwind CSS Demo
+
+
+
+
{{template "base/footer" .}}
diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl
index c51dcaa3ff..848afd305c 100644
--- a/templates/explore/repo_list.tmpl
+++ b/templates/explore/repo_list.tmpl
@@ -39,7 +39,9 @@
{{if not $.DisableStars}}
{{svg "octicon-star" 16}}{{.NumStars}}
{{end}}
- {{svg "octicon-git-branch" 16}}{{.NumForks}}
+ {{if not $.DisableForks}}
+ {{svg "octicon-git-branch" 16}}{{.NumForks}}
+ {{end}}
{{$description := .DescriptionHTML $.Context}}
diff --git a/templates/explore/repo_search.tmpl b/templates/explore/repo_search.tmpl
index eaf2e7a090..96f34d508a 100644
--- a/templates/explore/repo_search.tmpl
+++ b/templates/explore/repo_search.tmpl
@@ -2,6 +2,7 @@
{{ctx.Locale.Tr "admin.dashboard.system_status"}}
--- {{ctx.Locale.Tr "admin.dashboard.server_uptime"}}
- {{.SysStatus.StartTime}}
- - {{ctx.Locale.Tr "admin.dashboard.current_goroutine"}}
- - {{.SysStatus.NumGoroutine}}
-
- - {{ctx.Locale.Tr "admin.dashboard.current_memory_usage"}}
- - {{.SysStatus.MemAllocated}}
- - {{ctx.Locale.Tr "admin.dashboard.total_memory_allocated"}}
- - {{.SysStatus.MemTotal}}
- - {{ctx.Locale.Tr "admin.dashboard.memory_obtained"}}
- - {{.SysStatus.MemSys}}
- - {{ctx.Locale.Tr "admin.dashboard.pointer_lookup_times"}}
- - {{.SysStatus.Lookups}}
- - {{ctx.Locale.Tr "admin.dashboard.memory_allocate_times"}}
- - {{.SysStatus.MemMallocs}}
- - {{ctx.Locale.Tr "admin.dashboard.memory_free_times"}}
- - {{.SysStatus.MemFrees}}
-
- - {{ctx.Locale.Tr "admin.dashboard.current_heap_usage"}}
- - {{.SysStatus.HeapAlloc}}
- - {{ctx.Locale.Tr "admin.dashboard.heap_memory_obtained"}}
- - {{.SysStatus.HeapSys}}
- - {{ctx.Locale.Tr "admin.dashboard.heap_memory_idle"}}
- - {{.SysStatus.HeapIdle}}
- - {{ctx.Locale.Tr "admin.dashboard.heap_memory_in_use"}}
- - {{.SysStatus.HeapInuse}}
- - {{ctx.Locale.Tr "admin.dashboard.heap_memory_released"}}
- - {{.SysStatus.HeapReleased}}
- - {{ctx.Locale.Tr "admin.dashboard.heap_objects"}}
- - {{.SysStatus.HeapObjects}}
-
- - {{ctx.Locale.Tr "admin.dashboard.bootstrap_stack_usage"}}
- - {{.SysStatus.StackInuse}}
- - {{ctx.Locale.Tr "admin.dashboard.stack_memory_obtained"}}
- - {{.SysStatus.StackSys}}
- - {{ctx.Locale.Tr "admin.dashboard.mspan_structures_usage"}}
- - {{.SysStatus.MSpanInuse}}
- - {{ctx.Locale.Tr "admin.dashboard.mspan_structures_obtained"}}
- - {{.SysStatus.MSpanSys}}
- - {{ctx.Locale.Tr "admin.dashboard.mcache_structures_usage"}}
- - {{.SysStatus.MCacheInuse}}
- - {{ctx.Locale.Tr "admin.dashboard.mcache_structures_obtained"}}
- - {{.SysStatus.MCacheSys}}
- - {{ctx.Locale.Tr "admin.dashboard.profiling_bucket_hash_table_obtained"}}
- - {{.SysStatus.BuckHashSys}}
- - {{ctx.Locale.Tr "admin.dashboard.gc_metadata_obtained"}}
- - {{.SysStatus.GCSys}}
- - {{ctx.Locale.Tr "admin.dashboard.other_system_allocation_obtained"}}
- - {{.SysStatus.OtherSys}}
-
- - {{ctx.Locale.Tr "admin.dashboard.next_gc_recycle"}}
- - {{.SysStatus.NextGC}}
- - {{ctx.Locale.Tr "admin.dashboard.last_gc_time"}}
- - {{.SysStatus.LastGC}}
- - {{ctx.Locale.Tr "admin.dashboard.total_gc_pause"}}
- - {{.SysStatus.PauseTotalNs}}
- - {{ctx.Locale.Tr "admin.dashboard.last_gc_pause"}}
- - {{.SysStatus.PauseNs}}
- - {{ctx.Locale.Tr "admin.dashboard.gc_times"}}
- - {{.SysStatus.NumGC}}
-
+ {{/* TODO: make these stats work in multi-server deployments, likely needs per-server stats in DB */}} +