mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-10-02 21:33:16 +00:00
Compare commits
70 commits
2a0d014b8a
...
f45bc5ad62
Author | SHA1 | Date | |
---|---|---|---|
|
f45bc5ad62 | ||
|
c4d2635839 | ||
|
08999e8189 | ||
|
0a5fd7fdb8 | ||
|
e3eaa284bb | ||
|
92305933b1 | ||
|
b2aa644859 | ||
|
0c993085e0 | ||
|
1b06287fe3 | ||
|
c37732d1ae | ||
|
a56c000f60 | ||
|
3a38dbac17 | ||
|
cf56beb3f9 | ||
|
ffed8ac7b4 | ||
|
1a90067754 | ||
|
1d5f8e5488 | ||
|
76a0dcd372 | ||
|
1fa09d1621 | ||
|
e40554f89b | ||
|
735bcf978f | ||
|
c0b17a06ef | ||
|
5b84756137 | ||
|
89742c4913 | ||
|
89d9307d56 | ||
|
40a68aa8f8 | ||
|
84b96a7343 | ||
|
9ec57761f9 | ||
|
e3deb88a8d | ||
|
2ffb08bb88 | ||
|
9d34731198 | ||
|
f709de2403 | ||
|
2675a24649 | ||
|
526054332a | ||
|
6275d1bc50 | ||
|
1ae3b127fc | ||
|
1bdf334844 | ||
|
70014a7bc9 | ||
|
757080addd | ||
|
d000993905 | ||
|
5e342c6fa7 | ||
|
5e06b0c0ec | ||
|
76c6b34270 | ||
|
7fbe6b4dc3 | ||
|
a9f9b94478 | ||
|
cd7f92797b | ||
|
f0dab9cc05 | ||
|
68cade5b1d | ||
|
e1566d2a1f | ||
|
e1a9ad600b | ||
|
db016e0652 | ||
|
6490fc2b99 | ||
|
1e5f7d2116 | ||
|
69b01eb911 | ||
|
c2797ecfc1 | ||
|
b79e465092 | ||
|
a8f141f6d1 | ||
|
c54ebb8bc7 | ||
|
8b62d3d5da | ||
|
1b528a7874 | ||
|
dc9a268d3c | ||
|
4a74113dee | ||
|
60bcdc8bc3 | ||
|
15ec27e658 | ||
|
ed6fef9844 | ||
|
a6508f5b03 | ||
|
9d5f409a5a | ||
|
0cafec4c7a | ||
|
961766744b | ||
|
565084925a | ||
|
6745603edd |
88 changed files with 2802 additions and 2061 deletions
100
.eslintrc.yaml
100
.eslintrc.yaml
|
@ -13,6 +13,7 @@ parserOptions:
|
|||
plugins:
|
||||
- "@eslint-community/eslint-plugin-eslint-comments"
|
||||
- "@stylistic/eslint-plugin-js"
|
||||
- "@vitest"
|
||||
- eslint-plugin-array-func
|
||||
- eslint-plugin-github
|
||||
- eslint-plugin-i
|
||||
|
@ -21,7 +22,6 @@ plugins:
|
|||
- eslint-plugin-regexp
|
||||
- eslint-plugin-sonarjs
|
||||
- eslint-plugin-unicorn
|
||||
- eslint-plugin-vitest
|
||||
- eslint-plugin-vitest-globals
|
||||
- eslint-plugin-wc
|
||||
|
||||
|
@ -50,55 +50,55 @@ overrides:
|
|||
env:
|
||||
vitest-globals/env: true
|
||||
rules:
|
||||
vitest/consistent-test-filename: [0]
|
||||
vitest/consistent-test-it: [0]
|
||||
vitest/expect-expect: [0]
|
||||
vitest/max-expects: [0]
|
||||
vitest/max-nested-describe: [0]
|
||||
vitest/no-alias-methods: [0]
|
||||
vitest/no-commented-out-tests: [0]
|
||||
vitest/no-conditional-expect: [0]
|
||||
vitest/no-conditional-in-test: [0]
|
||||
vitest/no-conditional-tests: [0]
|
||||
vitest/no-disabled-tests: [0]
|
||||
vitest/no-done-callback: [0]
|
||||
vitest/no-duplicate-hooks: [0]
|
||||
vitest/no-focused-tests: [0]
|
||||
vitest/no-hooks: [0]
|
||||
vitest/no-identical-title: [2]
|
||||
vitest/no-interpolation-in-snapshots: [0]
|
||||
vitest/no-large-snapshots: [0]
|
||||
vitest/no-mocks-import: [0]
|
||||
vitest/no-restricted-matchers: [0]
|
||||
vitest/no-restricted-vi-methods: [0]
|
||||
vitest/no-standalone-expect: [0]
|
||||
vitest/no-test-prefixes: [0]
|
||||
vitest/no-test-return-statement: [0]
|
||||
vitest/prefer-called-with: [0]
|
||||
vitest/prefer-comparison-matcher: [0]
|
||||
vitest/prefer-each: [0]
|
||||
vitest/prefer-equality-matcher: [0]
|
||||
vitest/prefer-expect-resolves: [0]
|
||||
vitest/prefer-hooks-in-order: [0]
|
||||
vitest/prefer-hooks-on-top: [2]
|
||||
vitest/prefer-lowercase-title: [0]
|
||||
vitest/prefer-mock-promise-shorthand: [0]
|
||||
vitest/prefer-snapshot-hint: [0]
|
||||
vitest/prefer-spy-on: [0]
|
||||
vitest/prefer-strict-equal: [0]
|
||||
vitest/prefer-to-be: [0]
|
||||
vitest/prefer-to-be-falsy: [0]
|
||||
vitest/prefer-to-be-object: [0]
|
||||
vitest/prefer-to-be-truthy: [0]
|
||||
vitest/prefer-to-contain: [0]
|
||||
vitest/prefer-to-have-length: [0]
|
||||
vitest/prefer-todo: [0]
|
||||
vitest/require-hook: [0]
|
||||
vitest/require-to-throw-message: [0]
|
||||
vitest/require-top-level-describe: [0]
|
||||
vitest/valid-describe-callback: [2]
|
||||
vitest/valid-expect: [2]
|
||||
vitest/valid-title: [2]
|
||||
"@vitest/consistent-test-filename": [0]
|
||||
"@vitest/consistent-test-it": [0]
|
||||
"@vitest/expect-expect": [0]
|
||||
"@vitest/max-expects": [0]
|
||||
"@vitest/max-nested-describe": [0]
|
||||
"@vitest/no-alias-methods": [0]
|
||||
"@vitest/no-commented-out-tests": [0]
|
||||
"@vitest/no-conditional-expect": [0]
|
||||
"@vitest/no-conditional-in-test": [0]
|
||||
"@vitest/no-conditional-tests": [0]
|
||||
"@vitest/no-disabled-tests": [0]
|
||||
"@vitest/no-done-callback": [0]
|
||||
"@vitest/no-duplicate-hooks": [0]
|
||||
"@vitest/no-focused-tests": [0]
|
||||
"@vitest/no-hooks": [0]
|
||||
"@vitest/no-identical-title": [2]
|
||||
"@vitest/no-interpolation-in-snapshots": [0]
|
||||
"@vitest/no-large-snapshots": [0]
|
||||
"@vitest/no-mocks-import": [0]
|
||||
"@vitest/no-restricted-matchers": [0]
|
||||
"@vitest/no-restricted-vi-methods": [0]
|
||||
"@vitest/no-standalone-expect": [0]
|
||||
"@vitest/no-test-prefixes": [0]
|
||||
"@vitest/no-test-return-statement": [0]
|
||||
"@vitest/prefer-called-with": [0]
|
||||
"@vitest/prefer-comparison-matcher": [0]
|
||||
"@vitest/prefer-each": [0]
|
||||
"@vitest/prefer-equality-matcher": [0]
|
||||
"@vitest/prefer-expect-resolves": [0]
|
||||
"@vitest/prefer-hooks-in-order": [0]
|
||||
"@vitest/prefer-hooks-on-top": [2]
|
||||
"@vitest/prefer-lowercase-title": [0]
|
||||
"@vitest/prefer-mock-promise-shorthand": [0]
|
||||
"@vitest/prefer-snapshot-hint": [0]
|
||||
"@vitest/prefer-spy-on": [0]
|
||||
"@vitest/prefer-strict-equal": [0]
|
||||
"@vitest/prefer-to-be": [0]
|
||||
"@vitest/prefer-to-be-falsy": [0]
|
||||
"@vitest/prefer-to-be-object": [0]
|
||||
"@vitest/prefer-to-be-truthy": [0]
|
||||
"@vitest/prefer-to-contain": [0]
|
||||
"@vitest/prefer-to-have-length": [0]
|
||||
"@vitest/prefer-todo": [0]
|
||||
"@vitest/require-hook": [0]
|
||||
"@vitest/require-to-throw-message": [0]
|
||||
"@vitest/require-top-level-describe": [0]
|
||||
"@vitest/valid-describe-callback": [2]
|
||||
"@vitest/valid-expect": [2]
|
||||
"@vitest/valid-title": [2]
|
||||
- files: ["web_src/js/modules/fetch.js", "web_src/js/standalone/**/*"]
|
||||
rules:
|
||||
no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression]
|
||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- id: forgejo
|
||||
uses: https://code.forgejo.org/actions/setup-forgejo@v1
|
||||
|
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
# root is used for testing, allow it
|
||||
if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
@ -37,7 +37,7 @@ jobs:
|
|||
repository="${{ github.repository }}"
|
||||
echo "value=${repository##*/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: https://code.forgejo.org/actions/setup-node@v3
|
||||
- uses: https://code.forgejo.org/actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
|
|
29
.forgejo/workflows/composite/apt-install-from/action.yaml
Normal file
29
.forgejo/workflows/composite/apt-install-from/action.yaml
Normal file
|
@ -0,0 +1,29 @@
|
|||
inputs:
|
||||
packages:
|
||||
description: 'Packages to install'
|
||||
required: true
|
||||
release:
|
||||
description: 'Release to install from'
|
||||
default: testing
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: setup apt package source
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
echo "deb http://deb.debian.org/debian/ ${RELEASE} main" > "/etc/apt/sources.list.d/${RELEASE}.list"
|
||||
env:
|
||||
RELEASE: ${{inputs.release}}
|
||||
- name: install packages
|
||||
run: |
|
||||
apt-get update -qq
|
||||
apt-get -q install -qq -y ${PACKAGES}
|
||||
env:
|
||||
PACKAGES: ${{inputs.packages}}
|
||||
- name: remove temporary package list to prevent using it in other steps
|
||||
run: |
|
||||
rm "/etc/apt/sources.list.d/${RELEASE}.list"
|
||||
apt-get update -qq
|
||||
env:
|
||||
RELEASE: ${{inputs.release}}
|
15
.forgejo/workflows/composite/build-backend/action.yaml
Normal file
15
.forgejo/workflows/composite/build-backend/action.yaml
Normal file
|
@ -0,0 +1,15 @@
|
|||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- run: |
|
||||
su forgejo -c 'make deps-backend'
|
||||
- uses: actions/cache@v4
|
||||
id: cache-backend
|
||||
with:
|
||||
path: '/workspace/forgejo/forgejo/gitea'
|
||||
key: backend-build-${{ github.sha }}
|
||||
- if: steps.cache-backend.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
su forgejo -c 'make backend'
|
||||
env:
|
||||
TAGS: bindata
|
11
.forgejo/workflows/composite/setup-env/action.yaml
Normal file
11
.forgejo/workflows/composite/setup-env/action.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: setup user and permissions
|
||||
run: |
|
||||
git config --add safe.directory '*'
|
||||
adduser --quiet --comment forgejo --disabled-password forgejo
|
||||
chown -R forgejo:forgejo .
|
||||
- uses: https://codeberg.org/fnetx/setup-cache-go@b2214eaf6fb44c7e8512c0f462a2c3ec31f86a73
|
||||
with:
|
||||
username: forgejo
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
runs-on: self-hosted
|
||||
if: vars.DOER != '' && vars.FORGEJO != '' && vars.TO_OWNER != '' && vars.FROM_OWNER != '' && secrets.TOKEN != ''
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: copy & sign
|
||||
uses: https://code.forgejo.org/forgejo/forgejo-build-publish/publish@v5
|
||||
|
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
container:
|
||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
|
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
container:
|
||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
|
||||
- name: event
|
||||
run: |
|
||||
|
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
|
||||
runs-on: docker
|
||||
container:
|
||||
image: code.forgejo.org/forgejo-contrib/renovate:38.80.0
|
||||
image: code.forgejo.org/forgejo-contrib/renovate:38.101.1
|
||||
|
||||
steps:
|
||||
- name: Load renovate repo cache
|
||||
|
|
|
@ -19,27 +19,18 @@ jobs:
|
|||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- run: make deps-backend deps-tools
|
||||
- run: make --always-make -j$(nproc) lint-backend tidy-check swagger-check fmt-check swagger-validate # ensure the "go-licenses" make target runs
|
||||
- run: |
|
||||
make backend
|
||||
env:
|
||||
TAGS: bindata
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: '/workspace/forgejo/forgejo/gitea'
|
||||
key: backend-build-${{ github.sha }}
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows/composite/setup-env
|
||||
- run: su forgejo -c 'make deps-backend deps-tools'
|
||||
- run: su forgejo -c 'make --always-make -j$(nproc) lint-backend tidy-check swagger-check fmt-check swagger-validate' # ensure the "go-licenses" make target runs
|
||||
- uses: ./.forgejo/workflows/composite/build-backend
|
||||
frontend-checks:
|
||||
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
- run: make deps-frontend
|
||||
- run: make lint-frontend
|
||||
- run: make checks-frontend
|
||||
|
@ -66,38 +57,17 @@ jobs:
|
|||
MINIO_ROOT_USER: 123456
|
||||
MINIO_ROOT_PASSWORD: 12345678
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- run: |
|
||||
git config --add safe.directory '*'
|
||||
adduser --quiet --comment forgejo --disabled-password forgejo
|
||||
chown -R forgejo:forgejo .
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows/composite/setup-env
|
||||
- name: install git >= 2.42
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list
|
||||
apt-get update -qq
|
||||
apt-get -q install -qq -y git
|
||||
rm /etc/apt/sources.list.d/testing.list
|
||||
apt-get update -qq
|
||||
uses: ./.forgejo/workflows/composite/apt-install-from
|
||||
with:
|
||||
packages: git
|
||||
- name: test release-notes-assistant.sh
|
||||
run: |
|
||||
apt-get -q install -qq -y jq
|
||||
./release-notes-assistant.sh test_main
|
||||
- run: |
|
||||
su forgejo -c 'make deps-backend'
|
||||
- uses: actions/cache/restore@v4
|
||||
id: cache-backend
|
||||
with:
|
||||
path: '/workspace/forgejo/forgejo/gitea'
|
||||
key: backend-build-${{ github.sha }}
|
||||
- if: steps.cache-backend.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
su forgejo -c 'make backend'
|
||||
env:
|
||||
TAGS: bindata
|
||||
- uses: ./.forgejo/workflows/composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-backend test-check'
|
||||
timeout-minutes: 50
|
||||
|
@ -131,34 +101,13 @@ jobs:
|
|||
image: ${{ matrix.cacher.image }}
|
||||
options: ${{ matrix.cacher.options }}
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- run: |
|
||||
git config --add safe.directory '*'
|
||||
adduser --quiet --comment forgejo --disabled-password forgejo
|
||||
chown -R forgejo:forgejo .
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows/composite/setup-env
|
||||
- name: install git >= 2.42
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list
|
||||
apt-get update -qq
|
||||
apt-get -q install -qq -y git
|
||||
rm /etc/apt/sources.list.d/testing.list
|
||||
apt-get update -qq
|
||||
- run: |
|
||||
su forgejo -c 'make deps-backend'
|
||||
- uses: actions/cache/restore@v4
|
||||
id: cache-backend
|
||||
uses: ./.forgejo/workflows/composite/apt-install-from
|
||||
with:
|
||||
path: '/workspace/forgejo/forgejo/gitea'
|
||||
key: backend-build-${{ github.sha }}
|
||||
- if: steps.cache-backend.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
su forgejo -c 'make backend'
|
||||
env:
|
||||
TAGS: bindata
|
||||
packages: git
|
||||
- uses: ./.forgejo/workflows/composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-remote-cacher test-check'
|
||||
timeout-minutes: 50
|
||||
|
@ -183,35 +132,13 @@ jobs:
|
|||
#
|
||||
MYSQL_EXTRA_FLAGS: --innodb-adaptive-flushing=OFF --innodb-buffer-pool-size=4G --innodb-log-buffer-size=128M --innodb-flush-log-at-trx-commit=0 --innodb-flush-log-at-timeout=30 --innodb-flush-method=nosync --innodb-fsync-threshold=1000000000
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows/composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list
|
||||
apt-get update -qq
|
||||
apt-get install --no-install-recommends -qq -y git git-lfs
|
||||
rm /etc/apt/sources.list.d/testing.list
|
||||
apt-get update -qq
|
||||
- name: setup user and permissions
|
||||
run: |
|
||||
git config --add safe.directory '*'
|
||||
adduser --quiet --comment forgejo --disabled-password forgejo
|
||||
chown -R forgejo:forgejo .
|
||||
- run: |
|
||||
su forgejo -c 'make deps-backend'
|
||||
- uses: actions/cache/restore@v4
|
||||
id: cache-backend
|
||||
uses: ./.forgejo/workflows/composite/apt-install-from
|
||||
with:
|
||||
path: '/workspace/forgejo/forgejo/gitea'
|
||||
key: backend-build-${{ github.sha }}
|
||||
- if: steps.cache-backend.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
su forgejo -c 'make backend'
|
||||
env:
|
||||
TAGS: bindata
|
||||
packages: git git-lfs
|
||||
- uses: ./.forgejo/workflows/composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-mysql-migration test-mysql'
|
||||
timeout-minutes: 50
|
||||
|
@ -237,35 +164,13 @@ jobs:
|
|||
POSTGRES_DB: test
|
||||
POSTGRES_PASSWORD: postgres
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows/composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list
|
||||
apt-get update -qq
|
||||
apt-get install --no-install-recommends -qq -y git git-lfs
|
||||
rm /etc/apt/sources.list.d/testing.list
|
||||
apt-get update -qq
|
||||
- name: setup user and permissions
|
||||
run: |
|
||||
git config --add safe.directory '*'
|
||||
adduser --quiet --comment forgejo --disabled-password forgejo
|
||||
chown -R forgejo:forgejo .
|
||||
- run: |
|
||||
su forgejo -c 'make deps-backend'
|
||||
- uses: actions/cache/restore@v4
|
||||
id: cache-backend
|
||||
uses: ./.forgejo/workflows/composite/apt-install-from
|
||||
with:
|
||||
path: '/workspace/forgejo/forgejo/gitea'
|
||||
key: backend-build-${{ github.sha }}
|
||||
- if: steps.cache-backend.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
su forgejo -c 'make backend'
|
||||
env:
|
||||
TAGS: bindata
|
||||
packages: git git-lfs
|
||||
- uses: ./.forgejo/workflows/composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-pgsql-migration test-pgsql'
|
||||
timeout-minutes: 50
|
||||
|
@ -280,35 +185,13 @@ jobs:
|
|||
container:
|
||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows/composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list
|
||||
apt-get update -qq
|
||||
apt-get install --no-install-recommends -qq -y git git-lfs
|
||||
rm /etc/apt/sources.list.d/testing.list
|
||||
apt-get update -qq
|
||||
- name: setup user and permissions
|
||||
run: |
|
||||
git config --add safe.directory '*'
|
||||
adduser --quiet --comment forgejo --disabled-password forgejo
|
||||
chown -R forgejo:forgejo .
|
||||
- run: |
|
||||
su forgejo -c 'make deps-backend'
|
||||
- uses: actions/cache/restore@v4
|
||||
id: cache-backend
|
||||
uses: ./.forgejo/workflows/composite/apt-install-from
|
||||
with:
|
||||
path: '/workspace/forgejo/forgejo/gitea'
|
||||
key: backend-build-${{ github.sha }}
|
||||
- if: steps.cache-backend.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
su forgejo -c 'make backend'
|
||||
env:
|
||||
TAGS: bindata sqlite sqlite_unlock_notify
|
||||
packages: git git-lfs
|
||||
- uses: ./.forgejo/workflows/composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-sqlite-migration test-sqlite'
|
||||
timeout-minutes: 50
|
||||
|
@ -329,9 +212,7 @@ jobs:
|
|||
container:
|
||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- run: make deps-backend deps-tools
|
||||
- run: make security-check
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows/composite/setup-env
|
||||
- run: su forgejo -c 'make deps-backend deps-tools'
|
||||
- run: su forgejo -c 'make security-check'
|
||||
|
|
2
Makefile
2
Makefile
|
@ -39,7 +39,7 @@ GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasour
|
|||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.25.0 # renovate: datasource=go
|
||||
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go
|
||||
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.16.2 # renovate: datasource=go
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@38.80.0 # renovate: datasource=docker packageName=code.forgejo.org/forgejo-contrib/renovate
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@38.101.1 # renovate: datasource=docker packageName=code.forgejo.org/forgejo-contrib/renovate
|
||||
|
||||
ifeq ($(HAS_GO), yes)
|
||||
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
<div align="center">
|
||||
<img src="./assets/logo.svg" alt="" width="192" align="center" />
|
||||
<h1 align="center">Welcome to Forgejo</h1>
|
||||
|
|
|
@ -529,7 +529,8 @@ INTERNAL_TOKEN =
|
|||
;; HMAC to encode urls with, it **is required** if camo is enabled.
|
||||
;HMAC_KEY =
|
||||
;; Set to true to use camo for https too lese only non https urls are proxyed
|
||||
;ALLWAYS = false
|
||||
;; ALLWAYS is deprecated and will be removed in the future
|
||||
;ALWAYS = false
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
14
go.mod
14
go.mod
|
@ -66,7 +66,7 @@ require (
|
|||
github.com/jhillyerd/enmime v1.3.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.17.9
|
||||
github.com/klauspost/compress v1.17.10
|
||||
github.com/klauspost/cpuid/v2 v2.2.8
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/markbates/goth v1.80.0
|
||||
|
@ -75,7 +75,7 @@ require (
|
|||
github.com/meilisearch/meilisearch-go v0.28.0
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/minio/minio-go/v7 v7.0.74
|
||||
github.com/minio/minio-go/v7 v7.0.77
|
||||
github.com/msteinert/pam v1.2.0
|
||||
github.com/nektos/act v0.2.52
|
||||
github.com/niklasfasching/go-org v1.7.0
|
||||
|
@ -108,7 +108,7 @@ require (
|
|||
golang.org/x/sys v0.25.0
|
||||
golang.org/x/text v0.18.0
|
||||
golang.org/x/tools v0.25.0
|
||||
google.golang.org/grpc v1.66.2
|
||||
google.golang.org/grpc v1.67.1
|
||||
google.golang.org/protobuf v1.34.2
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
|
@ -119,7 +119,7 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.5.0 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||
|
@ -250,7 +250,7 @@ require (
|
|||
github.com/rhysd/actionlint v1.6.27 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
|
@ -283,7 +283,7 @@ require (
|
|||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
@ -293,6 +293,6 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
|
|||
|
||||
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
|
||||
|
||||
replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.21.2
|
||||
replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.21.3
|
||||
|
||||
replace github.com/mholt/archiver/v3 => code.forgejo.org/forgejo/archiver/v3 v3.5.1
|
||||
|
|
28
go.sum
28
go.sum
|
@ -1,11 +1,11 @@
|
|||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
||||
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||
code.forgejo.org/f3/gof3/v3 v3.7.0 h1:ZfuCP8CGm8ZJbWmL+V0pUu3E0X4FCAA7GfRDy/y5/K4=
|
||||
code.forgejo.org/f3/gof3/v3 v3.7.0/go.mod h1:oNhOeqD4DZYjVcNjQXIOdDX9b/1tqxi9ITLS8H9/Csw=
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU=
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM=
|
||||
code.forgejo.org/forgejo/act v1.21.2 h1:LERMtDNZDFXOYYYSU7Yduyyz7sN0t/Xnc1wFlupweiE=
|
||||
code.forgejo.org/forgejo/act v1.21.2/go.mod h1:+PcvJ9iv+NTFeJSh79ra9Jbk9l0vvyA9D9me5/dbxYM=
|
||||
code.forgejo.org/forgejo/act v1.21.3 h1:EeJbrz0aar2QhIcBlOW5gjK1rjrQxcAvQSPpG/R1h5w=
|
||||
code.forgejo.org/forgejo/act v1.21.3/go.mod h1:+PcvJ9iv+NTFeJSh79ra9Jbk9l0vvyA9D9me5/dbxYM=
|
||||
code.forgejo.org/forgejo/archiver/v3 v3.5.1 h1:UmmbA7D5550uf71SQjarmrn6yKwOGxtEjb3jaYYtmSE=
|
||||
code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||
code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=
|
||||
|
@ -448,8 +448,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0=
|
||||
github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
|
@ -502,8 +502,8 @@ github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
|||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.74 h1:fTo/XlPBTSpo3BAMshlwKL5RspXRv9us5UeHEGYCFe0=
|
||||
github.com/minio/minio-go/v7 v7.0.74/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8=
|
||||
github.com/minio/minio-go/v7 v7.0.77 h1:GaGghJRg9nwDVlNbwYjSDJT1rqltQkBFDsypWX1v3Bw=
|
||||
github.com/minio/minio-go/v7 v7.0.77/go.mod h1:AVM3IUN6WwKzmwBxVdjzhH8xq+f57JSbbvzqvUzR6eg=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
|
@ -599,8 +599,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4
|
|||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
|
@ -845,10 +845,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
|
||||
google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
|
|
@ -34,6 +34,7 @@ type ActivityStats struct {
|
|||
OpenedPRAuthorCount int64
|
||||
MergedPRs issues_model.PullRequestList
|
||||
MergedPRAuthorCount int64
|
||||
ActiveIssues issues_model.IssueList
|
||||
OpenedIssues issues_model.IssueList
|
||||
OpenedIssueAuthorCount int64
|
||||
ClosedIssues issues_model.IssueList
|
||||
|
@ -172,7 +173,7 @@ func (stats *ActivityStats) MergedPRPerc() int {
|
|||
|
||||
// ActiveIssueCount returns total active issue count
|
||||
func (stats *ActivityStats) ActiveIssueCount() int {
|
||||
return stats.OpenedIssueCount() + stats.ClosedIssueCount()
|
||||
return len(stats.ActiveIssues)
|
||||
}
|
||||
|
||||
// OpenedIssueCount returns open issue count
|
||||
|
@ -285,13 +286,21 @@ func (stats *ActivityStats) FillIssues(ctx context.Context, repoID int64, fromTi
|
|||
stats.ClosedIssueAuthorCount = count
|
||||
|
||||
// New issues
|
||||
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false)
|
||||
sess = newlyCreatedIssues(ctx, repoID, fromTime)
|
||||
sess.OrderBy("issue.created_unix ASC")
|
||||
stats.OpenedIssues = make(issues_model.IssueList, 0)
|
||||
if err = sess.Find(&stats.OpenedIssues); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Active issues
|
||||
sess = activeIssues(ctx, repoID, fromTime)
|
||||
sess.OrderBy("issue.created_unix ASC")
|
||||
stats.ActiveIssues = make(issues_model.IssueList, 0)
|
||||
if err = sess.Find(&stats.ActiveIssues); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Opened issue authors
|
||||
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false)
|
||||
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil {
|
||||
|
@ -317,6 +326,23 @@ func (stats *ActivityStats) FillUnresolvedIssues(ctx context.Context, repoID int
|
|||
return sess.Find(&stats.UnresolvedIssues)
|
||||
}
|
||||
|
||||
func newlyCreatedIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
|
||||
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
|
||||
And("issue.is_pull = ?", false). // Retain the is_pull check to exclude pull requests
|
||||
And("issue.created_unix >= ?", fromTime.Unix()) // Include all issues created after fromTime
|
||||
|
||||
return sess
|
||||
}
|
||||
|
||||
func activeIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
|
||||
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
|
||||
And("issue.is_pull = ?", false).
|
||||
And("issue.created_unix >= ?", fromTime.Unix()).
|
||||
Or("issue.closed_unix >= ?", fromTime.Unix())
|
||||
|
||||
return sess
|
||||
}
|
||||
|
||||
func issuesForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time, closed, unresolved bool) *xorm.Session {
|
||||
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
|
||||
And("issue.is_closed = ?", closed)
|
||||
|
|
|
@ -245,6 +245,7 @@ func (u *Type) CanBeDefault() bool {
|
|||
// Unit is a section of one repository
|
||||
type Unit struct {
|
||||
Type Type
|
||||
Name string
|
||||
NameKey string
|
||||
URI string
|
||||
DescKey string
|
||||
|
@ -272,6 +273,7 @@ func (u Unit) MaxPerm() perm.AccessMode {
|
|||
var (
|
||||
UnitCode = Unit{
|
||||
TypeCode,
|
||||
"code",
|
||||
"repo.code",
|
||||
"/",
|
||||
"repo.code.desc",
|
||||
|
@ -281,6 +283,7 @@ var (
|
|||
|
||||
UnitIssues = Unit{
|
||||
TypeIssues,
|
||||
"issues",
|
||||
"repo.issues",
|
||||
"/issues",
|
||||
"repo.issues.desc",
|
||||
|
@ -290,6 +293,7 @@ var (
|
|||
|
||||
UnitExternalTracker = Unit{
|
||||
TypeExternalTracker,
|
||||
"ext_issues",
|
||||
"repo.ext_issues",
|
||||
"/issues",
|
||||
"repo.ext_issues.desc",
|
||||
|
@ -299,6 +303,7 @@ var (
|
|||
|
||||
UnitPullRequests = Unit{
|
||||
TypePullRequests,
|
||||
"pulls",
|
||||
"repo.pulls",
|
||||
"/pulls",
|
||||
"repo.pulls.desc",
|
||||
|
@ -308,6 +313,7 @@ var (
|
|||
|
||||
UnitReleases = Unit{
|
||||
TypeReleases,
|
||||
"releases",
|
||||
"repo.releases",
|
||||
"/releases",
|
||||
"repo.releases.desc",
|
||||
|
@ -317,6 +323,7 @@ var (
|
|||
|
||||
UnitWiki = Unit{
|
||||
TypeWiki,
|
||||
"wiki",
|
||||
"repo.wiki",
|
||||
"/wiki",
|
||||
"repo.wiki.desc",
|
||||
|
@ -326,6 +333,7 @@ var (
|
|||
|
||||
UnitExternalWiki = Unit{
|
||||
TypeExternalWiki,
|
||||
"ext_wiki",
|
||||
"repo.ext_wiki",
|
||||
"/wiki",
|
||||
"repo.ext_wiki.desc",
|
||||
|
@ -335,6 +343,7 @@ var (
|
|||
|
||||
UnitProjects = Unit{
|
||||
TypeProjects,
|
||||
"projects",
|
||||
"repo.projects",
|
||||
"/projects",
|
||||
"repo.projects.desc",
|
||||
|
@ -344,6 +353,7 @@ var (
|
|||
|
||||
UnitPackages = Unit{
|
||||
TypePackages,
|
||||
"packages",
|
||||
"repo.packages",
|
||||
"/packages",
|
||||
"packages.desc",
|
||||
|
@ -353,6 +363,7 @@ var (
|
|||
|
||||
UnitActions = Unit{
|
||||
TypeActions,
|
||||
"actions",
|
||||
"repo.actions",
|
||||
"/actions",
|
||||
"actions.unit.desc",
|
||||
|
|
|
@ -76,7 +76,8 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s
|
|||
w.Header().Set("Etag", etag)
|
||||
}
|
||||
if lastModified != nil && !lastModified.IsZero() {
|
||||
w.Header().Set("Last-Modified", lastModified.Format(http.TimeFormat))
|
||||
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
|
||||
w.Header().Set("Last-Modified", lastModified.UTC().Format(http.TimeFormat))
|
||||
}
|
||||
|
||||
if len(etag) > 0 {
|
||||
|
|
|
@ -79,6 +79,7 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
|
|||
httpcache.SetCacheControlInHeader(header, duration)
|
||||
|
||||
if !opts.LastModified.IsZero() {
|
||||
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
|
||||
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ func camoHandleLink(link string) string {
|
|||
if setting.Camo.Enabled {
|
||||
lnkURL, err := url.Parse(link)
|
||||
if err == nil && lnkURL.IsAbs() && !strings.HasPrefix(link, setting.AppURL) &&
|
||||
(setting.Camo.Allways || lnkURL.Scheme != "https") {
|
||||
(setting.Camo.Always || lnkURL.Scheme != "https") {
|
||||
return CamoEncode(link)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ func TestCamoHandleLink(t *testing.T) {
|
|||
"https://image.proxy/eivin43gJwGVIjR9MiYYtFIk0mw/aHR0cDovL3Rlc3RpbWFnZXMub3JnL2ltZy5qcGc",
|
||||
camoHandleLink("http://testimages.org/img.jpg"))
|
||||
|
||||
setting.Camo.Allways = true
|
||||
setting.Camo.Always = true
|
||||
assert.Equal(t,
|
||||
"https://gitea.com/img.jpg",
|
||||
camoHandleLink("https://gitea.com/img.jpg"))
|
||||
|
|
|
@ -73,6 +73,8 @@ var (
|
|||
|
||||
// EmojiShortCodeRegex find emoji by alias like :smile:
|
||||
EmojiShortCodeRegex = regexp.MustCompile(`:[-+\w]+:`)
|
||||
|
||||
InlineCodeBlockRegex = regexp.MustCompile("`[^`]+`")
|
||||
)
|
||||
|
||||
// CSS class for action keywords (e.g. "closes: #1")
|
||||
|
@ -243,6 +245,7 @@ func RenderIssueTitle(
|
|||
title string,
|
||||
) (string, error) {
|
||||
return renderProcessString(ctx, []processor{
|
||||
inlineCodeBlockProcessor,
|
||||
issueIndexPatternProcessor,
|
||||
commitCrossReferencePatternProcessor,
|
||||
hashCurrentPatternProcessor,
|
||||
|
@ -251,6 +254,19 @@ func RenderIssueTitle(
|
|||
}, title)
|
||||
}
|
||||
|
||||
// RenderRefIssueTitle to process title on places where an issue is referenced
|
||||
func RenderRefIssueTitle(
|
||||
ctx *RenderContext,
|
||||
title string,
|
||||
) (string, error) {
|
||||
return renderProcessString(ctx, []processor{
|
||||
inlineCodeBlockProcessor,
|
||||
issueIndexPatternProcessor,
|
||||
emojiShortCodeProcessor,
|
||||
emojiProcessor,
|
||||
}, title)
|
||||
}
|
||||
|
||||
func renderProcessString(ctx *RenderContext, procs []processor, content string) (string, error) {
|
||||
var buf strings.Builder
|
||||
if err := postProcess(ctx, procs, strings.NewReader(content), &buf); err != nil {
|
||||
|
@ -438,6 +454,24 @@ func createKeyword(content string) *html.Node {
|
|||
return span
|
||||
}
|
||||
|
||||
func createInlineCode(content string) *html.Node {
|
||||
code := &html.Node{
|
||||
Type: html.ElementNode,
|
||||
Data: atom.Code.String(),
|
||||
Attr: []html.Attribute{},
|
||||
}
|
||||
|
||||
code.Attr = append(code.Attr, html.Attribute{Key: "class", Val: "inline-code-block"})
|
||||
|
||||
text := &html.Node{
|
||||
Type: html.TextNode,
|
||||
Data: content,
|
||||
}
|
||||
|
||||
code.AppendChild(text)
|
||||
return code
|
||||
}
|
||||
|
||||
func createEmoji(content, class, name string) *html.Node {
|
||||
span := &html.Node{
|
||||
Type: html.ElementNode,
|
||||
|
@ -1070,6 +1104,21 @@ func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
}
|
||||
}
|
||||
|
||||
func inlineCodeBlockProcessor(ctx *RenderContext, node *html.Node) {
|
||||
start := 0
|
||||
next := node.NextSibling
|
||||
for node != nil && node != next && start < len(node.Data) {
|
||||
m := InlineCodeBlockRegex.FindStringSubmatchIndex(node.Data[start:])
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
code := node.Data[m[0]+1 : m[1]-1]
|
||||
replaceContent(node, m[0], m[1], createInlineCode(code))
|
||||
node = node.NextSibling.NextSibling
|
||||
}
|
||||
}
|
||||
|
||||
// emojiShortCodeProcessor for rendering text like :smile: into emoji
|
||||
func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) {
|
||||
start := 0
|
||||
|
|
|
@ -39,8 +39,8 @@ const (
|
|||
var (
|
||||
reName = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+$`)
|
||||
reVer = regexp.MustCompile(`^[a-zA-Z0-9:_.+]+-+[0-9]+$`)
|
||||
reOptDep = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?[a-zA-Z0-9@._+-]+)?(:.*)?$`)
|
||||
rePkgVer = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?[a-zA-Z0-9@._+-]+)?$`)
|
||||
reOptDep = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?([0-9]+:)?[a-zA-Z0-9@._+-]+)?(:.*)?$`)
|
||||
rePkgVer = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?([0-9]+:)?[a-zA-Z0-9@._+-]+)?$`)
|
||||
|
||||
magicZSTD = []byte{0x28, 0xB5, 0x2F, 0xFD}
|
||||
magicXZ = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A}
|
||||
|
@ -71,7 +71,7 @@ type VersionMetadata struct {
|
|||
Conflicts []string `json:"conflicts,omitempty"`
|
||||
Replaces []string `json:"replaces,omitempty"`
|
||||
Backup []string `json:"backup,omitempty"`
|
||||
Xdata []string `json:"xdata,omitempty"`
|
||||
XData []string `json:"xdata,omitempty"`
|
||||
}
|
||||
|
||||
// FileMetadata Metadata related to specific package file.
|
||||
|
@ -125,7 +125,7 @@ func ParsePackage(r *packages.HashedBuffer) (*Package, error) {
|
|||
defer tarball.Close()
|
||||
|
||||
var pkg *Package
|
||||
var mtree bool
|
||||
var mTree bool
|
||||
|
||||
for {
|
||||
f, err := tarball.Read()
|
||||
|
@ -135,24 +135,24 @@ func ParsePackage(r *packages.HashedBuffer) (*Package, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
switch f.Name() {
|
||||
case ".PKGINFO":
|
||||
pkg, err = ParsePackageInfo(tarballType, f)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
return nil, err
|
||||
}
|
||||
case ".MTREE":
|
||||
mtree = true
|
||||
mTree = true
|
||||
}
|
||||
_ = f.Close()
|
||||
}
|
||||
|
||||
if pkg == nil {
|
||||
return nil, util.NewInvalidArgumentErrorf(".PKGINFO file not found")
|
||||
}
|
||||
|
||||
if !mtree {
|
||||
if !mTree {
|
||||
return nil, util.NewInvalidArgumentErrorf(".MTREE file not found")
|
||||
}
|
||||
|
||||
|
@ -220,7 +220,7 @@ func ParsePackageInfo(compressType string, r io.Reader) (*Package, error) {
|
|||
case "replaces":
|
||||
p.VersionMetadata.Replaces = append(p.VersionMetadata.Replaces, value)
|
||||
case "xdata":
|
||||
p.VersionMetadata.Xdata = append(p.VersionMetadata.Xdata, value)
|
||||
p.VersionMetadata.XData = append(p.VersionMetadata.XData, value)
|
||||
case "builddate":
|
||||
bd, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
|
@ -260,48 +260,43 @@ func ValidatePackageSpec(p *Package) error {
|
|||
return util.NewInvalidArgumentErrorf("invalid project URL")
|
||||
}
|
||||
}
|
||||
for _, cd := range p.VersionMetadata.CheckDepends {
|
||||
if !rePkgVer.MatchString(cd) {
|
||||
return util.NewInvalidArgumentErrorf("invalid check dependency: %s", cd)
|
||||
for _, checkDepend := range p.VersionMetadata.CheckDepends {
|
||||
if !rePkgVer.MatchString(checkDepend) {
|
||||
return util.NewInvalidArgumentErrorf("invalid check dependency: %s", checkDepend)
|
||||
}
|
||||
}
|
||||
for _, d := range p.VersionMetadata.Depends {
|
||||
if !rePkgVer.MatchString(d) {
|
||||
return util.NewInvalidArgumentErrorf("invalid dependency: %s", d)
|
||||
for _, depend := range p.VersionMetadata.Depends {
|
||||
if !rePkgVer.MatchString(depend) {
|
||||
return util.NewInvalidArgumentErrorf("invalid dependency: %s", depend)
|
||||
}
|
||||
}
|
||||
for _, md := range p.VersionMetadata.MakeDepends {
|
||||
if !rePkgVer.MatchString(md) {
|
||||
return util.NewInvalidArgumentErrorf("invalid make dependency: %s", md)
|
||||
for _, makeDepend := range p.VersionMetadata.MakeDepends {
|
||||
if !rePkgVer.MatchString(makeDepend) {
|
||||
return util.NewInvalidArgumentErrorf("invalid make dependency: %s", makeDepend)
|
||||
}
|
||||
}
|
||||
for _, p := range p.VersionMetadata.Provides {
|
||||
if !rePkgVer.MatchString(p) {
|
||||
return util.NewInvalidArgumentErrorf("invalid provides: %s", p)
|
||||
for _, provide := range p.VersionMetadata.Provides {
|
||||
if !rePkgVer.MatchString(provide) {
|
||||
return util.NewInvalidArgumentErrorf("invalid provides: %s", provide)
|
||||
}
|
||||
}
|
||||
for _, p := range p.VersionMetadata.Conflicts {
|
||||
if !rePkgVer.MatchString(p) {
|
||||
return util.NewInvalidArgumentErrorf("invalid conflicts: %s", p)
|
||||
for _, conflict := range p.VersionMetadata.Conflicts {
|
||||
if !rePkgVer.MatchString(conflict) {
|
||||
return util.NewInvalidArgumentErrorf("invalid conflicts: %s", conflict)
|
||||
}
|
||||
}
|
||||
for _, p := range p.VersionMetadata.Replaces {
|
||||
if !rePkgVer.MatchString(p) {
|
||||
return util.NewInvalidArgumentErrorf("invalid replaces: %s", p)
|
||||
for _, replace := range p.VersionMetadata.Replaces {
|
||||
if !rePkgVer.MatchString(replace) {
|
||||
return util.NewInvalidArgumentErrorf("invalid replaces: %s", replace)
|
||||
}
|
||||
}
|
||||
for _, p := range p.VersionMetadata.Replaces {
|
||||
if !rePkgVer.MatchString(p) {
|
||||
return util.NewInvalidArgumentErrorf("invalid xdata: %s", p)
|
||||
for _, optDepend := range p.VersionMetadata.OptDepends {
|
||||
if !reOptDep.MatchString(optDepend) {
|
||||
return util.NewInvalidArgumentErrorf("invalid optional dependency: %s", optDepend)
|
||||
}
|
||||
}
|
||||
for _, od := range p.VersionMetadata.OptDepends {
|
||||
if !reOptDep.MatchString(od) {
|
||||
return util.NewInvalidArgumentErrorf("invalid optional dependency: %s", od)
|
||||
}
|
||||
}
|
||||
for _, bf := range p.VersionMetadata.Backup {
|
||||
if strings.HasPrefix(bf, "/") {
|
||||
for _, b := range p.VersionMetadata.Backup {
|
||||
if strings.HasPrefix(b, "/") {
|
||||
return util.NewInvalidArgumentErrorf("backup file contains leading forward slash")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ type Metadata struct {
|
|||
Homepage string `json:"homepage,omitempty"`
|
||||
License Licenses `json:"license,omitempty"`
|
||||
Authors []Author `json:"authors,omitempty"`
|
||||
Bin []string `json:"bin,omitempty"`
|
||||
Autoload map[string]any `json:"autoload,omitempty"`
|
||||
AutoloadDev map[string]any `json:"autoload-dev,omitempty"`
|
||||
Extra map[string]any `json:"extra,omitempty"`
|
||||
|
|
|
@ -3,18 +3,28 @@
|
|||
|
||||
package setting
|
||||
|
||||
import "code.gitea.io/gitea/modules/log"
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
var Camo = struct {
|
||||
Enabled bool
|
||||
ServerURL string `ini:"SERVER_URL"`
|
||||
HMACKey string `ini:"HMAC_KEY"`
|
||||
Allways bool
|
||||
Always bool
|
||||
}{}
|
||||
|
||||
func loadCamoFrom(rootCfg ConfigProvider) {
|
||||
mustMapSetting(rootCfg, "camo", &Camo)
|
||||
if Camo.Enabled {
|
||||
oldValue := rootCfg.Section("camo").Key("ALLWAYS").MustString("")
|
||||
if oldValue != "" {
|
||||
log.Warn("camo.ALLWAYS is deprecated, use camo.ALWAYS instead")
|
||||
Camo.Always, _ = strconv.ParseBool(oldValue)
|
||||
}
|
||||
|
||||
if Camo.ServerURL == "" || Camo.HMACKey == "" {
|
||||
log.Fatal(`Camo settings require "SERVER_URL" and HMAC_KEY`)
|
||||
}
|
||||
|
|
|
@ -172,11 +172,12 @@ func NewFuncMap() template.FuncMap {
|
|||
"RenderCommitMessage": RenderCommitMessage,
|
||||
"RenderCommitMessageLinkSubject": RenderCommitMessageLinkSubject,
|
||||
|
||||
"RenderCommitBody": RenderCommitBody,
|
||||
"RenderCodeBlock": RenderCodeBlock,
|
||||
"RenderIssueTitle": RenderIssueTitle,
|
||||
"RenderEmoji": RenderEmoji,
|
||||
"ReactionToEmoji": ReactionToEmoji,
|
||||
"RenderCommitBody": RenderCommitBody,
|
||||
"RenderCodeBlock": RenderCodeBlock,
|
||||
"RenderIssueTitle": RenderIssueTitle,
|
||||
"RenderRefIssueTitle": RenderRefIssueTitle,
|
||||
"RenderEmoji": RenderEmoji,
|
||||
"ReactionToEmoji": ReactionToEmoji,
|
||||
|
||||
"RenderMarkdownToHtml": RenderMarkdownToHtml,
|
||||
"RenderLabel": RenderLabel,
|
||||
|
|
|
@ -34,7 +34,7 @@ func AvatarHTML(src string, size int, class, name string) template.HTML {
|
|||
name = "avatar"
|
||||
}
|
||||
|
||||
return template.HTML(`<img class="` + class + `" src="` + src + `" title="` + html.EscapeString(name) + `" width="` + sizeStr + `" height="` + sizeStr + `"/>`)
|
||||
return template.HTML(`<img loading="lazy" class="` + class + `" src="` + src + `" title="` + html.EscapeString(name) + `" width="` + sizeStr + `" height="` + sizeStr + `"/>`)
|
||||
}
|
||||
|
||||
// Avatar renders user avatars. args: user, size (int), class (string)
|
||||
|
|
|
@ -130,6 +130,17 @@ func RenderIssueTitle(ctx context.Context, text string, metas map[string]string)
|
|||
return template.HTML(renderedText)
|
||||
}
|
||||
|
||||
// RenderRefIssueTitle renders referenced issue/pull title with defined post processors
|
||||
func RenderRefIssueTitle(ctx context.Context, text string) template.HTML {
|
||||
renderedText, err := markup.RenderRefIssueTitle(&markup.RenderContext{Ctx: ctx}, template.HTMLEscapeString(text))
|
||||
if err != nil {
|
||||
log.Error("RenderRefIssueTitle: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return template.HTML(renderedText)
|
||||
}
|
||||
|
||||
// RenderLabel renders a label
|
||||
// locale is needed due to an import cycle with our context providing the `Tr` function
|
||||
func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML {
|
||||
|
|
|
@ -36,7 +36,7 @@ mail@domain.com
|
|||
@mention-user test
|
||||
#123
|
||||
space
|
||||
`
|
||||
` + "`code :+1: #123 code`\n"
|
||||
|
||||
var testMetas = map[string]string{
|
||||
"user": "user13",
|
||||
|
@ -115,8 +115,8 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
|||
<a href="mailto:mail@domain.com" class="mailto">mail@domain.com</a>
|
||||
<a href="/mention-user" class="mention">@mention-user</a> test
|
||||
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||
space`
|
||||
|
||||
space
|
||||
` + "`code <span class=\"emoji\" aria-label=\"thumbs up\">👍</span> <a href=\"/user13/repo11/issues/123\" class=\"ref-issue\">#123</a> code`"
|
||||
assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas))
|
||||
}
|
||||
|
||||
|
@ -153,10 +153,37 @@ mail@domain.com
|
|||
@mention-user test
|
||||
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||
space
|
||||
<code class="inline-code-block">code :+1: #123 code</code>
|
||||
`
|
||||
assert.EqualValues(t, expected, RenderIssueTitle(context.Background(), testInput, testMetas))
|
||||
}
|
||||
|
||||
func TestRenderRefIssueTitle(t *testing.T) {
|
||||
expected := ` space @mention-user
|
||||
/just/a/path.bin
|
||||
https://example.com/file.bin
|
||||
[local link](file.bin)
|
||||
[remote link](https://example.com)
|
||||
[[local link|file.bin]]
|
||||
[[remote link|https://example.com]]
|
||||
![local image](image.jpg)
|
||||
![remote image](https://example.com/image.jpg)
|
||||
[[local image|image.jpg]]
|
||||
[[remote link|https://example.com/image.jpg]]
|
||||
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
||||
mail@domain.com
|
||||
@mention-user test
|
||||
#123
|
||||
space
|
||||
<code class="inline-code-block">code :+1: #123 code</code>
|
||||
`
|
||||
assert.EqualValues(t, expected, RenderRefIssueTitle(context.Background(), testInput))
|
||||
}
|
||||
|
||||
func TestRenderMarkdownToHtml(t *testing.T) {
|
||||
expected := `<p>space <a href="/mention-user" rel="nofollow">@mention-user</a><br/>
|
||||
/just/a/path.bin
|
||||
|
@ -177,7 +204,8 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
|||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a>
|
||||
<a href="/mention-user" rel="nofollow">@mention-user</a> test
|
||||
#123
|
||||
space</p>
|
||||
space
|
||||
<code>code :+1: #123 code</code></p>
|
||||
`
|
||||
assert.EqualValues(t, expected, RenderMarkdownToHtml(context.Background(), testInput))
|
||||
}
|
||||
|
|
|
@ -225,6 +225,15 @@ func Iif[T any](condition bool, trueVal, falseVal T) T {
|
|||
return falseVal
|
||||
}
|
||||
|
||||
// IfZero returns "def" if "v" is a zero value, otherwise "v"
|
||||
func IfZero[T comparable](v, def T) T {
|
||||
var zero T
|
||||
if v == zero {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func ReserveLineBreakForTextarea(input string) string {
|
||||
// Since the content is from a form which is a textarea, the line endings are \r\n.
|
||||
// It's a standard behavior of HTML.
|
||||
|
|
|
@ -168,6 +168,7 @@ versions.view_all = Вижте всички
|
|||
dependencies = Зависимости
|
||||
published_by_in = Публикуван %[1]s от <a href="%[2]s">%[3]s</a> в <a href="%[4]s"><strong>%[5]s</strong></a>
|
||||
published_by = Публикуван %[1]s от <a href="%[2]s">%[3]s</a>
|
||||
generic.download = Изтеглете пакета от командния ред:
|
||||
|
||||
[tool]
|
||||
hours = %d часа
|
||||
|
@ -1020,7 +1021,7 @@ pulls.title_desc_one = иска да слее %[1]d подаване от <code>
|
|||
pulls.showing_specified_commit_range = Показани са само промените между %[1]s..%[2]s
|
||||
pulls.merged_title_desc_one = сля %[1]d подаване от <code>%[2]s</code> в <code>%[3]s</code> %[4]s
|
||||
pulls.no_merge_access = Не сте упълномощени за сливане на тази заявка за сливане.
|
||||
activity.navbar.code_frequency = Честота на кода
|
||||
activity.navbar.code_frequency = Честота на промените
|
||||
activity.git_stats_pushed_1 = е изтласкал
|
||||
activity.git_stats_push_to_branch = към %s и
|
||||
contributors.contribution_type.commits = Подавания
|
||||
|
@ -1183,6 +1184,15 @@ diff.hide_file_tree = Скриване на файловото дърво
|
|||
tag.ahead.target = в %s след този маркер
|
||||
diff.file_image_width = Широчина
|
||||
activity.unresolved_conv_label = Отворено
|
||||
invisible_runes_line = `Този ред съдържа невидими Уникод знаци`
|
||||
code.desc = Достъп до програмния код, файловете, подаванията и клоновете.
|
||||
settings.branches.update_default_branch = Обновяване на стандартния клон
|
||||
settings.default_branch_desc = Изберете стандартен клон за хранилището, за заявки за сливане и подавания на код:
|
||||
settings.transfer.button = Прехвърляне на притежанието
|
||||
settings.transfer.modal.title = Прехвърляне на притежанието
|
||||
ambiguous_runes_line = `Този ред съдържа двусмислени Уникод знаци`
|
||||
ambiguous_character = `%[1]c [U+%04[1]X] може да бъде объркан с %[2]c [U+%04[2]X]`
|
||||
invisible_runes_header = `Този файл съдържа невидими Уникод знаци`
|
||||
|
||||
[modal]
|
||||
confirm = Потвърждаване
|
||||
|
@ -1279,6 +1289,7 @@ members.member = Участник
|
|||
members.private_helper = Да е видим
|
||||
teams.no_desc = Този екип няма описание
|
||||
settings.delete_org_desc = Тази организация ще бъде изтрита перманентно. Продължаване?
|
||||
open_dashboard = Отваряне на таблото
|
||||
|
||||
[install]
|
||||
admin_password = Парола
|
||||
|
@ -1378,6 +1389,7 @@ followers.title.few = Последователи
|
|||
followers.title.one = Последовател
|
||||
following.title.one = Следван
|
||||
following.title.few = Следвани
|
||||
public_activity.visibility_hint.self_public = Вашата дейност е видима за всички, с изключение на взаимодействията в частни пространства. <a href="%s">Конфигуриране</a>.
|
||||
|
||||
[home]
|
||||
filter = Други филтри
|
||||
|
@ -1544,6 +1556,8 @@ push_tag = изтласка маркер <a href="%[2]s">%[3]s</a> към <a hre
|
|||
approve_pull_request = `одобри <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
reject_pull_request = `предложи промени за <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
compare_branch = Сравняване
|
||||
compare_commits_general = Сравняване на подавания
|
||||
compare_commits = Сравнете %d подавания
|
||||
|
||||
[auth]
|
||||
tab_openid = OpenID
|
||||
|
@ -1572,6 +1586,12 @@ tab_signin = Влизане
|
|||
tab_signup = Регистриране
|
||||
password_pwned = Паролата, която сте избрали, е в <a target="_blank" rel="noopener noreferrer" href="%s">списък с откраднати пароли</a>, разкрити преди това при публични пробиви на данни. Моля, опитайте отново с различна парола.
|
||||
confirmation_mail_sent_prompt = Ново ел. писмо за потвърждение е изпратено до <b>%s</b>. За да завършите процеса на регистрация, моля, проверете входящата си кутия и последвайте предоставената връзка в рамките на следващите %s. Ако адресът за ел. поща е неправилен, можете да влезете и да поискате друго ел. писмо за потвърждение да бъде изпратено на различен адрес.
|
||||
hint_login = Вече имате акаунт? <a href="%s">Влезте!</a>
|
||||
hint_register = Нуждаете се от акаунт? <a href="%s">Регистрирайте се.</a>
|
||||
sign_up_button = Регистрирайте се.
|
||||
back_to_sign_in = Назад към Вход
|
||||
sign_in_openid = Продължаване с OpenID
|
||||
send_reset_mail = Изпращане на ел. писмо за възстановяване
|
||||
|
||||
[aria]
|
||||
footer.software = Относно този софтуер
|
||||
|
@ -1582,7 +1602,7 @@ footer = Долен колонтитул
|
|||
install = Лесен за инсталиране
|
||||
lightweight = Лек
|
||||
license = Отворен код
|
||||
install_desc = Просто <a target="_blank" rel="noopener noreferrer" href="%[1]s">стартирайте двоичния файл</a> за вашата платформа, използвайте <a target="_blank" rel="noopener noreferrer" href="%[2]s">Docker</a>, или го получете <a target="_blank" rel="noopener noreferrer" href="%[3]s">пакетирано</a>.
|
||||
install_desc = Просто <a target="_blank" rel="noopener noreferrer" href="%[1]s">стартирайте двоичния файл</a> за вашата платформа, използвайте <a target="_blank" rel="noopener noreferrer" href="%[2]s">Docker</a>, или го получете <a target="_blank" rel="noopener noreferrer" href="%[3]s">пакетиран</a>.
|
||||
app_desc = Безпроблемна Git услуга със самостоятелен хостинг
|
||||
platform = Междуплатформен
|
||||
lightweight_desc = Forgejo има ниски минимални изисквания и може да работи на икономичен Raspberry Pi. Спестете енергията на вашата машина!
|
||||
|
@ -1670,6 +1690,7 @@ contributors.what = приноси
|
|||
recent_commits.what = скорошни подавания
|
||||
component_loading = Зареждане на %s...
|
||||
component_loading_info = Това може да отнеме известно време…
|
||||
code_frequency.what = честота на промените
|
||||
|
||||
[projects]
|
||||
type-1.display_name = Индивидуален проект
|
||||
|
|
|
@ -842,8 +842,8 @@ add_key=Přidat klíč
|
|||
ssh_desc=Tyto veřejné klíče SSH jsou propojeny s vaším účtem. Odpovídající soukromé klíče umožní plný přístup k vašim repozitářům. Klíče SSH, které byly ověřeny, mohou být použity pro ověření Git commitů podepsaných přes SSH.
|
||||
principal_desc=Tyto SSH Principal certifikáty jsou přidruženy k vašemu účtu a umožňují plný přístup do vašich repozitářů.
|
||||
gpg_desc=Tyto veřejné klíče GPG jsou propojeny s vaším účtem a používají se k ověření vašich commitů. Uložte je na bezpečné místo, jelikož umožňují podepsat commity vaší identitou.
|
||||
ssh_helper=<strong>Potřebujete pomoct?</strong> Podívejte se do příručky GitHubu na to <a href="%s">vytvoření vlastních klíčů SSH</a> nebo vyřešte <a href="%s">běžné problémy</a>, se kterými se můžete potkat při použití SSH.
|
||||
gpg_helper=<strong>Potřebujete pomoct?</strong> Podívejte se do příručky GitHubu <a href="%s">o GPG</a>.
|
||||
ssh_helper=<strong>Potřebujete pomoct?</strong> Podívejte se do příručky, jak <a href="%s">vytvořit vlastní klíče SSH</a> nebo vyřešte <a href="%s">běžné problémy</a>, se kterými se můžete potkat při použití SSH.
|
||||
gpg_helper=<strong>Potřebujete pomoct?</strong> Podívejte se do příručky <a href="%s">o GPG</a>.
|
||||
add_new_key=Přidat klíč SSH
|
||||
add_new_gpg_key=Přidat klíč GPG
|
||||
key_content_ssh_placeholder=Začíná s „ssh-ed25519“, „ssh-rsa“, „ecdsa-sha2-nistp256“, „ecdsa-sha2-nistp384“, „ecdsa-sha2-nistp521“, „sk-ecdsa-sha2-nistp256@openssh.com“ nebo „sk-ssh-ed25519@openssh.com“
|
||||
|
@ -1427,7 +1427,7 @@ commitstatus.failure=Chyba
|
|||
commitstatus.pending=Čekající
|
||||
commitstatus.success=Úspěch
|
||||
|
||||
ext_issues=Přístup k externím problémům
|
||||
ext_issues=Externí problémy
|
||||
ext_issues.desc=Odkaz na externí systém problémů.
|
||||
|
||||
projects=Projekty
|
||||
|
@ -1608,9 +1608,9 @@ issues.no_content=K dispozici není žádný popis.
|
|||
issues.close=Zavřít problém
|
||||
issues.comment_pull_merged_at=sloučený commit %[1]s do %[2]s %[3]s
|
||||
issues.comment_manually_pull_merged_at=ručně sloučený commit %[1]s do %[2]s %[3]s
|
||||
issues.close_comment_issue=Okomentovat a zavřít
|
||||
issues.close_comment_issue=Zavřít s komentářem
|
||||
issues.reopen_issue=Znovu otevřít
|
||||
issues.reopen_comment_issue=Okomentovat a znovu otevřít
|
||||
issues.reopen_comment_issue=Znovu otevřít s komentářem
|
||||
issues.create_comment=Okomentovat
|
||||
issues.closed_at=`uzavřel/a tento problém <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at=`znovu otevřel/a tento problém <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
|
@ -1995,7 +1995,7 @@ signing.wont_sign.commitssigned=Sloučení nebude podepsáno, protože všechny
|
|||
signing.wont_sign.approved=Sloučení nebude podepsáno, protože požadavek na natažení není schválen.
|
||||
signing.wont_sign.not_signed_in=Nejste přihlášeni.
|
||||
|
||||
ext_wiki=Přístup k externí Wiki
|
||||
ext_wiki=Externí wiki
|
||||
ext_wiki.desc=Odkaz do externí Wiki.
|
||||
|
||||
wiki=Wiki
|
||||
|
@ -2436,7 +2436,7 @@ settings.protect_branch_name_pattern=Vzor jména chráněné větve
|
|||
settings.protect_branch_name_pattern_desc=Vzory názvů chráněných větví. Pro vzorovou syntaxi viz <a href="%s">dokumentace</a>. Příklady: main, release/**
|
||||
settings.protect_patterns=Vzory
|
||||
settings.protect_protected_file_patterns=Vzory chráněných souborů (oddělené středníkem „;“)
|
||||
settings.protect_protected_file_patterns_desc=Chráněné soubory, které nemají povoleno být měněny přímo, i když uživatel má právo přidávat, upravovat nebo mazat soubory v této větvi. Více vzorů lze oddělit pomocí středníku („;“). Podívejte se na <a href="%s">github.com/gobwas/glob</a> dokumentaci pro syntaxi vzoru. Příklady: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||
settings.protect_protected_file_patterns_desc=Chráněné soubory, které nemají povoleno být měněny přímo, i když uživatel má právo přidávat, upravovat nebo mazat soubory v této větvi. Více vzorů lze oddělit pomocí středníku („;“). Podívejte se na dokumentaci <a href="%[1]s">%[2]s</a> pro syntaxi vzoru. Příklady: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||
settings.protect_unprotected_file_patterns=Vzory nechráněných souborů (oddělené středníkem „;“)
|
||||
settings.protect_unprotected_file_patterns_desc=Nechráněné soubory, které je možné měnit přímo, pokud má uživatel právo zápisu, čímž se obejde omezení push. Více vzorů lze oddělit pomocí středníku („;“). Podívejte se na <a href="%[1]s">%[2]s</a> dokumentaci pro syntaxi vzoru. Příklady: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||
settings.add_protected_branch=Zapnout ochranu
|
||||
|
@ -3822,7 +3822,7 @@ management=Správa tajných klíčů
|
|||
[actions]
|
||||
actions=Akce
|
||||
|
||||
unit.desc=Spravovat integrované pipeliny CI/CD pomocí funkce Forgejo Actions
|
||||
unit.desc=Spravovat integrované pipeliny CI/CD pomocí funkce Forgejo Actions.
|
||||
|
||||
status.unknown=Neznámý
|
||||
status.waiting=Čekání
|
||||
|
@ -3988,3 +3988,23 @@ eib = EiB
|
|||
|
||||
[translation_meta]
|
||||
test = diky vsem za pomoc :)
|
||||
|
||||
[repo.permissions]
|
||||
pulls.write = <b>Zapisovat:</b> Zavírat žádosti o sloučení a spravovat metadata jako štítky, milníky, zpracovatele, data dokončení a závislosti.
|
||||
packages.write = <b>Zapisovat:</b> Zveřejňovat a mazat balíčky připojené k repozitáři.
|
||||
projects.read = <b>Číst:</b> Přístup k projektovým nástěnkám repozitáře.
|
||||
code.write = <b>Zapisovat:</b> Odesílat změny do repozitáře, vytvářet větve a značky.
|
||||
issues.write = <b>Zapisovat:</b> Zavírat problémy a spravovat metadata jako štítky, milníky, zpracovatele, data dokončení a závislosti.
|
||||
pulls.read = <b>Číst:</b> Číst a vytvářet žádosti o sloučení.
|
||||
releases.read = <b>Číst:</b> Zobrazovat a stahovat vydání.
|
||||
releases.write = <b>Zapisovat:</b> Zveřejňovat, upravovat a mazat vydání a jejich soubory.
|
||||
wiki.read = <b>Číst:</b> Číst integrovanou wiki a její historii.
|
||||
wiki.write = <b>Zapisovat:</b> Vytvářet, aktualizovat a mazat stránky v integrované wiki.
|
||||
projects.write = <b>Zapisovat:</b> Vytvářet projekty a sloupce a upravovat je.
|
||||
packages.read = <b>Číst:</b> Zobrazovat a stahovat balíčky připojené k repozitáři.
|
||||
actions.read = <b>Číst:</b> Zobrazovat integrované pipeliny CI/CD a jejich protokoly.
|
||||
actions.write = <b>Zapisovat:</b> Ručně spouštět, restartovat, rušit nebo schvalovat čekající pipeliny CI/CD.
|
||||
ext_wiki = Přístup k odkazu v externí wiki. Oprávnění jsou spravována externě.
|
||||
code.read = <b>Číst:</b> Přístup a klonování kódu v repozitáři.
|
||||
issues.read = <b>Číst:</b> Číst a vytvářet problémy a komentáře.
|
||||
ext_issues = Přístup k odkazu v externím sledovacím systému problémů. Oprávnění jsou spravována externě.
|
|
@ -1420,7 +1420,7 @@ commitstatus.failure=Fehler
|
|||
commitstatus.pending=Ausstehend
|
||||
commitstatus.success=Erfolg
|
||||
|
||||
ext_issues=Zugriff auf Externe Issues
|
||||
ext_issues=Externe Issues
|
||||
ext_issues.desc=Link zu externem Issuetracker.
|
||||
|
||||
projects=Projekte
|
||||
|
@ -1601,9 +1601,9 @@ issues.no_content=Keine Beschreibung angegeben.
|
|||
issues.close=Issue schließen
|
||||
issues.comment_pull_merged_at=hat Commit %[1]s in %[2]s %[3]s zusammengeführt
|
||||
issues.comment_manually_pull_merged_at=hat Commit %[1]s in %[2]s %[3]s manuell zusammengeführt
|
||||
issues.close_comment_issue=Kommentieren und schließen
|
||||
issues.close_comment_issue=Mit Kommentar schließen
|
||||
issues.reopen_issue=Wieder öffnen
|
||||
issues.reopen_comment_issue=Kommentieren und wieder öffnen
|
||||
issues.reopen_comment_issue=Mit Kommentar wieder öffnen
|
||||
issues.create_comment=Kommentieren
|
||||
issues.closed_at=`hat diesen Issue <a id="%[1]s" href="#%[1]s">%[2]s</a> geschlossen`
|
||||
issues.reopened_at=`hat dieses Issue <a id="%[1]s" href="#%[1]s">%[2]s</a> wieder geöffnet`
|
||||
|
@ -1984,7 +1984,7 @@ signing.wont_sign.commitssigned=Der Merge-Commit wird nicht signiert werden, da
|
|||
signing.wont_sign.approved=Der Merge-Commit wird nicht signiert werden, da der Pull-Request nicht genehmigt wurde.
|
||||
signing.wont_sign.not_signed_in=Du bist nicht eingeloggt.
|
||||
|
||||
ext_wiki=Zugriff auf externes Wiki
|
||||
ext_wiki=Externes Wiki
|
||||
ext_wiki.desc=Verweis auf externes Wiki.
|
||||
|
||||
wiki=Wiki
|
||||
|
@ -3792,7 +3792,7 @@ management=Secrets verwalten
|
|||
[actions]
|
||||
actions=Actions
|
||||
|
||||
unit.desc=Integrierte CI/CD-Pipelines mit Forgejo-Actions verwalten
|
||||
unit.desc=Integrierte CI/CD-Pipelines mit Forgejo-Actions verwalten.
|
||||
|
||||
status.unknown=Unbekannt
|
||||
status.waiting=Wartend
|
||||
|
@ -3965,3 +3965,23 @@ eib = EiB
|
|||
|
||||
[translation_meta]
|
||||
test = ok
|
||||
|
||||
[repo.permissions]
|
||||
code.write = <b>Schreiben:</b> In das Repository pushen, Branches und Tags erstellen.
|
||||
code.read = <b>Lesen:</b> Zugriff auf das Repository und Klonen.
|
||||
issues.read = <b>Lesen:</b> Issues und Kommentare lesen und erstellen.
|
||||
issues.write = <b>Schreiben:</b> Issues schließen und Metadaten wie Labels, Meilensteine, Zuweisungen, Fälligkeitsdaten und Abhängigkeiten verwalten.
|
||||
pulls.read = <b>Lesen:</b> Pull-Requests lesen und erstellen.
|
||||
releases.read = <b>Lesen:</b> Releases ansehen und herunterladen.
|
||||
releases.write = <b>Schreiben:</b> Releases und ihre Assets veröffentlichen, bearbeiten und löschen.
|
||||
wiki.read = <b>Read:</b> Das integrierte Wiki und seine Historie lesen.
|
||||
wiki.write = <b>Schreiben:</b> Seiten im integrierten Wiki erstellen, aktualisieren und löschen.
|
||||
projects.read = <b>Lesen:</b> Zugriff auf Projektboards des Repositories.
|
||||
projects.write = <b>Schreiben:</b> Projekte und Spalten erstellen und bearbeiten.
|
||||
packages.read = <b>Lesen:</b> Pakete dieses Repositories betrachten und herunterladen.
|
||||
packages.write = <b>Schreiben:</b> Pakete dieses Repositories veröffentlichen und löschen.
|
||||
actions.read = <b>Lesen:</b> Integrierte CI/CD-Pipelines und ihre Logs betrachten.
|
||||
actions.write = <b>Schreiben:</b> Ausstehende CI/CD-Pipelines manuell auslösen, neustarten, abbrechen oder genehmigen.
|
||||
ext_issues = Zugriff auf den Link zu einem externen Issue-Tracker. Die Berechtigungen werden extern verwaltet.
|
||||
ext_wiki = Zugriff auf den Link zu einem externen Wiki. Die Berechtigungen werden extern verwaltet.
|
||||
pulls.write = <b>Schreiben:</b> Pull-Requests schließen und Metadaten wie Labels, Meilensteine, Zuweisungen, Fälligkeitsdaten und Abhängigkeiten verwalten.
|
|
@ -51,7 +51,7 @@ webauthn_error_empty=Πρέπει να ορίσετε ένα όνομα για
|
|||
webauthn_error_timeout=Το χρονικό όριο έφτασε πριν το κλειδί να διαβαστεί. Παρακαλώ ανανεώστε τη σελίδα και προσπαθήστε ξανά.
|
||||
webauthn_reload=Ανανέωση
|
||||
|
||||
repository=Αποθετήριο
|
||||
repository=Repository
|
||||
organization=Οργανισμός
|
||||
mirror=Αντίγραφο
|
||||
new_repo=Νέο αποθετήριο
|
||||
|
@ -129,7 +129,7 @@ archived=Αρχειοθετήθηκε
|
|||
|
||||
concept_system_global=Γενικό
|
||||
concept_user_individual=Ατομικό
|
||||
concept_code_repository=Αποθετήριο
|
||||
concept_code_repository=Repository
|
||||
concept_user_organization=Οργανισμός
|
||||
|
||||
show_timestamps=Εμφάνιση χρονοσημάνσεων
|
||||
|
@ -160,11 +160,11 @@ invalid_data = Τα δεδομένα δεν είναι έγκυρα: %v
|
|||
test = Τεστ
|
||||
copy_generic = Αντιγραφή στο πρόχειρο
|
||||
error413 = Έχετε εξαντλήσει τους διαθέσιμους πόρους σας.
|
||||
new_repo.link = Νέο αποθετήριο
|
||||
new_repo.link = Νέο repository
|
||||
new_migrate.link = Νέα μεταφορά
|
||||
new_org.link = Νέος οργανισμός
|
||||
new_migrate.title = Νέα μεταφορά
|
||||
new_repo.title = Νέο αποθετήριο
|
||||
new_repo.title = Νέο repository
|
||||
new_org.title = Νέος οργανισμός
|
||||
|
||||
[aria]
|
||||
|
@ -504,8 +504,8 @@ reset_password.text=Εφόσον το αίτημα δημιουργήθηκε α
|
|||
|
||||
register_success=Η εγγραφή ολοκληρώθηκε επιτυχώς
|
||||
|
||||
issue_assigned.pull=Ο/Η @%[1]s σας έχει αναθέσει στο pull request %[2]s στο αποθετήριο %[3]s.
|
||||
issue_assigned.issue=Ο/Η @%[1]s σας ανέθεσε το ζήτημα %[2]s στο αποθετήριο %[3]s.
|
||||
issue_assigned.pull=Ο/Η @%[1]s σας έχει αναθέσει στο pull request %[2]s στο repository %[3]s.
|
||||
issue_assigned.issue=Ο/Η @%[1]s σας ανέθεσε το ζήτημα %[2]s στο repository %[3]s.
|
||||
|
||||
issue.x_mentioned_you=Ο/Η <b>@%s</b> σας ανέφερε:
|
||||
issue.action.force_push=Ο/Η <b>%[1]s</b> έκανε force-push το <b>%[2]s</b> από %[3]s σε %[4]s.
|
||||
|
@ -530,13 +530,13 @@ release.downloads=Λήψεις:
|
|||
release.download.zip=Πηγαίος Κώδικας (ZIP)
|
||||
release.download.targz=Πηγαίος Κώδικας (TAR.GZ)
|
||||
|
||||
repo.transfer.subject_to=Ο/Η %s θα ήθελε να μεταφέρει το αποθετήριο «%s» σε %s
|
||||
repo.transfer.subject_to_you=Ο/Η %s θα ήθελε να σας μεταφέρει το αποθετήριο «%s»
|
||||
repo.transfer.subject_to=Ο/Η %s θα ήθελε να μεταφέρει το repository «%s» στο %s
|
||||
repo.transfer.subject_to_you=Ο/Η %s θα ήθελε να σας μεταφέρει το repository «%s»
|
||||
repo.transfer.to_you=εσάς
|
||||
repo.transfer.body=Για να αποδεχτείτε ή να απορρίψετε το αίτημα αυτό, επισκεφθείτε το %s ή απλά αγνοήστε το.
|
||||
|
||||
repo.collaborator.added.subject=Ο/Η %s σας πρόσθεσε στο %s ως συνεργάτη
|
||||
repo.collaborator.added.text=Είστε πλέον συνεργάτης στο αποθετήριο:
|
||||
repo.collaborator.added.text=Είστε πλέον συνεργάτης στο repository:
|
||||
|
||||
team_invite.subject=Ο/Η %[1]s σας προσκάλεσε να συμμετέχετε στον οργανισμό %[2]s
|
||||
team_invite.text_1=Ο/Η %[1]s σας προσκάλεσε να συμμετέχετε στην ομάδα %[2]s του οργανισμού %[3]s.
|
||||
|
@ -615,10 +615,10 @@ username_change_not_local_user=Δεν επιτρέπεται στους μη τ
|
|||
username_has_not_been_changed=Το όνομα χρήστη δεν άλλαξε
|
||||
repo_name_been_taken=Το όνομα του αποθετηρίου χρησιμοποιείται ήδη.
|
||||
repository_force_private=Η επιλογή Μόνο Ιδιωτικά είναι ενεργοποιημένη: τα ιδιωτικά αποθετήρια δεν μπορούν να δημοσιευθούν.
|
||||
repository_files_already_exist=Αρχεία υπάρχουν ήδη για αυτό το αποθετήριο. Επικοινωνήστε με το διαχειριστή του συστήματος.
|
||||
repository_files_already_exist.adopt=Αρχεία υπάρχουν ήδη για αυτό το αποθετήριο και μπορούν να Υιοθετηθούν μόνο.
|
||||
repository_files_already_exist.delete=Τα αρχεία υπάρχουν ήδη για αυτόν το αποθετήριο. Πρέπει να τα διαγράψετε.
|
||||
repository_files_already_exist.adopt_or_delete=Τα αρχεία υπάρχουν ήδη για αυτόν το αποθετήριο. Είτε υιοθετήστε τα είτε διαγράψτε τα.
|
||||
repository_files_already_exist=Αρχεία υπάρχουν ήδη για αυτό το repository. Επικοινωνήστε με το διαχειριστή του συστήματος.
|
||||
repository_files_already_exist.adopt=Αρχεία υπάρχουν ήδη για αυτό το repository και μπορούν μόνο να υιοθετηθούν.
|
||||
repository_files_already_exist.delete=Τα αρχεία υπάρχουν ήδη για αυτόν το repository. Πρέπει να τα διαγράψετε.
|
||||
repository_files_already_exist.adopt_or_delete=Υπάρχουν ήδη τα αρχεία για αυτό το repository. Πρέπει να τα υιοθετήσετε ή να τα διαγράψετε.
|
||||
visit_rate_limit=Συναντήθηκε το όριο ρυθμού κατά την απομακρυσμένη πρόσβαση.
|
||||
2fa_auth_required=Απαιτήθηκε ταυτοποίηση δύο παραγόντων κατά την απομακρυσμένη πρόσβαση.
|
||||
org_name_been_taken=Το όνομα του οργανισμού χρησιμοποιείται ήδη.
|
||||
|
@ -925,7 +925,7 @@ access_token_deletion_cancel_action=Άκυρο
|
|||
access_token_deletion_confirm_action=Διαγραφή
|
||||
access_token_deletion_desc=Η διαγραφή ενός διακριτικού θα ανακαλέσει οριστικά την πρόσβαση στο λογαριασμό σας για εφαρμογές που το χρησιμοποιούν. Συνέχεια;
|
||||
delete_token_success=Το διακριτικό έχει διαγραφεί. Οι εφαρμογές που το χρησιμοποιούν δεν έχουν πλέον πρόσβαση στο λογαριασμό σας.
|
||||
repo_and_org_access=Πρόσβαση στο Αποθετήριο και Οργανισμό
|
||||
repo_and_org_access=Πρόσβαση στο repository και οργανισμό
|
||||
permissions_public_only=Δημόσια μόνο
|
||||
permissions_access_all=Όλα (δημόσια, ιδιωτικά, και περιορισμένα)
|
||||
select_permissions=Επιλογή δικαιωμάτων
|
||||
|
@ -1005,7 +1005,7 @@ remove_account_link_success=Ο συνδεδεμένος λογαριασμός
|
|||
hooks.desc=Προσθήκη webhooks που θα ενεργοποιούνται για <strong>όλα τα αποθετήρια</strong> που σας ανήκουν.
|
||||
|
||||
orgs_none=Δεν είστε μέλος σε κάποιο οργανισμό.
|
||||
repos_none=Δεν σας ανήκει κανένα κάποιο αποθετήριο.
|
||||
repos_none=Δεν υπάρχει κάποιο repository που σας ανήκει.
|
||||
|
||||
delete_account=Διαγραφή του λογαριασμού σας
|
||||
delete_prompt=Αυτή η ενέργεια θα διαγράψει μόνιμα το λογαριασμό σας. <strong>ΔΕΝ ΘΑ ΜΠΟΡΕΙ</strong> να επανέλθει.
|
||||
|
@ -1047,7 +1047,7 @@ language.localization_project = Βοηθήστε μας να μεταφράσο
|
|||
language.description = Από εδώ και στο εξής, αυτή η γλώσσα θα χρησιμοποιείται από προεπιλογή για τον λογαριασμό σας.
|
||||
|
||||
[repo]
|
||||
new_repo_helper=Ένα αποθετήριο περιέχει όλα τα αρχεία έργου, συμπεριλαμβανομένου του ιστορικού εκδόσεων. Ήδη φιλοξενείται αλλού; <a href="%s">Μετεγκατάσταση αποθετηρίου.</a>
|
||||
new_repo_helper=Ένα repository περιέχει όλα τα αρχεία έργου, συμπεριλαμβανομένου του ιστορικού εκδόσεων. Έχετε ήδη ένα που φιλοξενείται κάπου αλλού; <a href="%s">Μεταφορά αποθετηρίου.</a>
|
||||
owner=Ιδιοκτήτης
|
||||
owner_helper=Ορισμένοι οργανισμοί ενδέχεται να μην εμφανίζονται στο αναπτυσσόμενο μενού λόγω του μέγιστου αριθμού αποθετηρίων.
|
||||
repo_name=Όνομα αποθετηρίου
|
||||
|
@ -1055,7 +1055,7 @@ repo_name_helper=Τα καλά ονόματα αποθετηρίων χρησι
|
|||
repo_size=Μέγεθος Αποθετηρίου
|
||||
template=Πρότυπο
|
||||
template_select=Επιλέξτε ένα πρότυπο
|
||||
template_helper=Μετατροπή σε πρότυπο αποθετήριο
|
||||
template_helper=Μετατροπή σε πρότυπο repository
|
||||
template_description=Τα πρότυπα αποθετήρια επιτρέπουν στους χρήστες να δημιουργήσουν νέα αποθετήρια με την ίδια δομή, αρχεία και προαιρετικές ρυθμίσεις.
|
||||
visibility=Ορατότητα
|
||||
visibility_description=Μόνο ο ιδιοκτήτης ή τα μέλη του οργανισμού εάν έχουν δικαιώματα, θα είναι σε θέση να το δουν.
|
||||
|
@ -1070,7 +1070,7 @@ fork_to_different_account=Fork σε διαφορετικό λογαριασμό
|
|||
fork_visibility_helper=Η ορατότητα ενός fork αποθετηρίου δεν μπορεί να αλλάξει.
|
||||
fork_branch=Κλάδος που θα κλωνοποιηθεί στο fork
|
||||
all_branches=Όλοι οι κλάδοι
|
||||
fork_no_valid_owners=Αυτό το αποθετήριο δεν μπορεί να γίνει fork επειδή δεν υπάρχουν έγκυροι ιδιοκτήτες.
|
||||
fork_no_valid_owners=Αυτό το repository δεν μπορεί να γίνει fork, επειδή δεν υπάρχουν έγκυροι ιδιοκτήτες.
|
||||
use_template=Χρήση αυτού του πρότυπου
|
||||
clone_in_vsc=Κλωνοποίηση στο VS Code
|
||||
download_zip=Λήψη ZIP
|
||||
|
@ -1120,7 +1120,7 @@ mirror_password_blank_placeholder=(Μη ορισμένο)
|
|||
mirror_password_help=Αλλάξτε το όνομα χρήστη για να διαγράψετε έναν αποθηκευμένο κωδικό πρόσβασης.
|
||||
watchers=Παρατηρητές
|
||||
stargazers=Θαυμαστές
|
||||
stars_remove_warning=Αυτό θα αφαιρέσει όλα τα αστέρια από αυτό το αποθετήριο.
|
||||
stars_remove_warning=Αυτό θα αφαιρέσει όλα τα αστέρια από αυτό το repository.
|
||||
forks=Forks
|
||||
reactions_more=και %d περισσότερα
|
||||
unit_disabled=Ο διαχειριστής του ιστότοπου έχει απενεργοποιήσει αυτήν την ενότητα αποθετηρίου.
|
||||
|
@ -1129,7 +1129,7 @@ adopt_search=Εισάγετε όνομα χρήστη για αναζήτηση
|
|||
adopt_preexisting_label=Υιοθέτηση αρχείων
|
||||
adopt_preexisting=Υιοθετήστε τα προϋπάρχοντα αρχεία
|
||||
adopt_preexisting_content=Δημιουργία αποθετηρίου από %s
|
||||
adopt_preexisting_success=Υιοθετήθηκαν αρχεία και δημιουργήθηκε το αποθετήριο από %s
|
||||
adopt_preexisting_success=Υιοθετήθηκαν αρχεία και δημιουργήθηκε το repository από %s
|
||||
delete_preexisting_label=Διαγραφή
|
||||
delete_preexisting=Διαγραφή αρχείων που προϋπήρχαν
|
||||
delete_preexisting_content=Διαγραφή αρχείων στο %s
|
||||
|
@ -1159,17 +1159,17 @@ desc.archived=Αρχειοθετημένο
|
|||
template.items=Αντικείμενα προτύπου
|
||||
template.git_content=Περιεχόμενο Git (Προεπιλεγμένος κλάδος)
|
||||
template.git_hooks=Git hooks
|
||||
template.git_hooks_tooltip=Δεν θα μπορέσετε να αφαιρέσετε ή να τροποποιήσετε τα Git hook αφού τα έχετε προσθέσει. Επιλέξτε την ρύθμιση αυτή μόνο αν εμπιστεύεστε το πρότυπο αποθετήριο.
|
||||
template.git_hooks_tooltip=Δεν θα μπορέσετε να αφαιρέσετε ή να τροποποιήσετε τα Git hook αφού τα έχετε προσθέσει. Επιλέξτε την ρύθμιση αυτή μόνο αν εμπιστεύεστε το πρότυπο repository.
|
||||
template.webhooks=Webhooks
|
||||
template.topics=Θέματα
|
||||
template.avatar=Εικόνα
|
||||
template.issue_labels=Ταμπέλες ζητημάτων
|
||||
template.one_item=Πρέπει να επιλέξετε τουλάχιστον ένα αντικείμενο στο πρότυπο
|
||||
template.invalid=Πρέπει να επιλέξετε ένα πρότυπο αποθετήριο
|
||||
template.invalid=Πρέπει να επιλέξετε ένα πρότυπο repository
|
||||
|
||||
archive.title=Αυτό το αποθετήρειο αρχειοθετήθηκε. Μπορείτε να προβάλετε αρχεία και να τα κλωνοποιήσετε, αλλά δεν μπορείτε να ωθήσετε ή να ανοίξετε ζητήματα ή pull requests.
|
||||
archive.title_date=Αυτό το αποθετήριο έχει αρχειοθετηθεί στο %s. Μπορείτε να προβάλετε αρχεία και να κλωνοποιήσετε, αλλά δεν μπορείτε να ωθήσετε ή να ανοίξετε ζητήματα ή pull requests.
|
||||
archive.issue.nocomment=Αυτό το αποθετήριο αρχειοθετήθηκε. Δεν μπορείτε να σχολιάσετε σε ζητήματα.
|
||||
archive.title_date=Αυτό το repository αρχειοθετήθηκε στις %s. Μπορείτε να δείτε τα αρχεία του και να το κλωνοποιήσετε, αλλά δεν μπορείτε να κάνετε push, να ανοίξετε ζητήματα ή pull requests.
|
||||
archive.issue.nocomment=Αυτό το repository έχει αρχειοθετηθεί. Δεν μπορείτε να σχολιάσετε σε ζητήματα.
|
||||
archive.pull.nocomment=Αυτό το repo αρχειοθετήθηκε. Δεν μπορείτε να σχολιάσετε στα pull requests.
|
||||
|
||||
form.reach_limit_of_creation_1=Έχετε ήδη συμπληρώσει το όριο του %d αποθετηρίου.
|
||||
|
@ -1180,7 +1180,7 @@ form.name_pattern_not_allowed=Το μοτίβο «%s» δεν επιτρέπετ
|
|||
need_auth=Εξουσιοδότηση
|
||||
migrate_options=Ρυθμίσεις μεταφοράς
|
||||
migrate_service=Υπηρεσία Μεταφοράς
|
||||
migrate_options_mirror_helper=Αυτό το αποθετήριο θα είναι είδωλο
|
||||
migrate_options_mirror_helper=Αυτό το repository θα είναι είδωλο
|
||||
migrate_options_lfs=Μεταφορά αρχείων LFS
|
||||
migrate_options_lfs_endpoint.label=Άκρο LFS
|
||||
migrate_options_lfs_endpoint.description=Η μεταφορά θα προσπαθήσει να χρησιμοποιήσει το Git remote για να <a target="_blank" rel="noopener noreferrer" href="%s">καθορίσει τον διακομιστή LFS</a>. Μπορείτε επίσης να καθορίσετε ένα δικό σας endpoint αν τα δεδομένα LFS του αποθετηρίου αποθηκεύονται κάπου αλλού.
|
||||
|
@ -1233,10 +1233,10 @@ migrate.cancel_migrating_confirm=Θέλετε να ακυρώσετε αυτή
|
|||
mirror_from=είδωλο του
|
||||
forked_from=forked από
|
||||
generated_from=παραγμένο από
|
||||
fork_from_self=Δεν μπορείτε να κάνετε fork σε ένα αποθετήριο που κατέχετε.
|
||||
fork_guest_user=Συνδεθείτε για να κάνετε fork αυτό το αποθετήριο.
|
||||
watch_guest_user=Συνδεθείτε για να παρακολουθήσετε αυτό το αποθετήριο.
|
||||
star_guest_user=Συνδεθείτε για να δώσετε ένα αστέρι σε αυτό το αποθετήριο.
|
||||
fork_from_self=Δεν μπορείτε να κάνετε fork ένα repository που σας ανήκει.
|
||||
fork_guest_user=Συνδεθείτε για να κάνετε fork αυτό το repository.
|
||||
watch_guest_user=Συνδεθείτε για να παρακολουθήσετε αυτό το repository.
|
||||
star_guest_user=Συνδεθείτε για να δώσετε ένα αστέρι σε αυτό το repository.
|
||||
unwatch=Παύση ακολούθησης
|
||||
watch=Παρακολούθηση
|
||||
unstar=Αφαίρεση αστεριού
|
||||
|
@ -1248,11 +1248,11 @@ more_operations=Περισσότερες λειτουργίες
|
|||
no_desc=Χωρίς περιγραφή
|
||||
quick_guide=Γρήγορος οδηγός
|
||||
clone_this_repo=Κλωνοποίηση αυτού του αποθετηρίου
|
||||
cite_this_repo=Αναφορά σε αυτό το αποθετήριο
|
||||
cite_this_repo=Αναφορά σε αυτό το repository
|
||||
create_new_repo_command=Δημιουργία νέου αποθετηρίου στη γραμμή εντολών
|
||||
push_exist_repo=Προώθηση ενός υπάρχοντος αποθετηρίου από τη γραμμή εντολών
|
||||
empty_message=Αυτό το αποθετήριο δεν έχει περιεχόμενο.
|
||||
broken_message=Τα δεδομένα Git που διέπουν αυτό το αποθετήριο δεν μπορούν να διαβαστούν. Επικοινωνήστε με το διαχειριστή ή διαγράψτε αυτό το αποθετήριο.
|
||||
empty_message=Αυτό το repository δεν έχει περιεχόμενο.
|
||||
broken_message=Τα δεδομένα Git που διέπουν αυτό το αποθετήριο δεν μπορούν να διαβαστούν. Επικοινωνήστε με το διαχειριστή ή διαγράψτε αυτό το repository.
|
||||
|
||||
code=Κώδικας
|
||||
code.desc=Πρόσβαση στον πηγαίο κώδικα, αρχεία, υποβολές και κλάδους.
|
||||
|
@ -1330,7 +1330,7 @@ editor.cannot_edit_non_text_files=Τα δυαδικά αρχεία δεν μπο
|
|||
editor.edit_this_file=Επεξεργασία αρχείου
|
||||
editor.this_file_locked=Το αρχείο είναι κλειδωμένο
|
||||
editor.must_be_on_a_branch=Πρέπει να βρίσκεστε σε έναν κλάδο για να κάνετε ή να προτείνετε αλλαγές σε αυτό το αρχείο.
|
||||
editor.fork_before_edit=Πρέπει να κάνετε fork αυτό το αποθετήριο για να κάνετε ή να προτείνετε αλλαγές σε αυτό το αρχείο.
|
||||
editor.fork_before_edit=Πρέπει να κάνετε fork αυτό το repository για να κάνετε ή να προτείνετε αλλαγές σε αυτό το αρχείο.
|
||||
editor.delete_this_file=Διαγραφή αρχείου
|
||||
editor.must_have_write_access=Πρέπει να έχετε πρόσβαση εγγραφής για να κάνετε ή να προτείνετε αλλαγές σε αυτό το αρχείο.
|
||||
editor.file_delete_success=Το αρχείο «%s» έχει διαγραφεί.
|
||||
|
@ -1359,15 +1359,15 @@ editor.new_branch_name_desc=Όνομα νέου κλάδου…
|
|||
editor.cancel=Ακύρωση
|
||||
editor.filename_cannot_be_empty=Το όνομα αρχείου δεν μπορεί να είναι κενό.
|
||||
editor.filename_is_invalid=Το όνομα αρχείου δεν είναι έγκυρο: "%s".
|
||||
editor.branch_does_not_exist=Ο κλάδος "%s" δεν υπάρχει σε αυτό το αποθετήριο.
|
||||
editor.branch_already_exists=Ο κλάδος «%s» υπάρχει ήδη σε αυτό το αποθετήριο.
|
||||
editor.directory_is_a_file=Το όνομα φακέλου «%s» χρησιμοποιείται ήδη ως όνομα αρχείου σε αυτό το αποθετήριο.
|
||||
editor.branch_does_not_exist=Ο κλάδος "%s" δεν υπάρχει σε αυτό το repository.
|
||||
editor.branch_already_exists=Ο κλάδος «%s» υπάρχει ήδη σε αυτό το repository.
|
||||
editor.directory_is_a_file=Το όνομα φακέλου «%s» χρησιμοποιείται ήδη ως όνομα αρχείου σε αυτό το repository.
|
||||
editor.file_is_a_symlink=`Το «%s» είναι συμβολικός σύνδεσμος. Οι συμβολικοί σύνδεσμοι δεν μπορούν να επεξεργαστούν στην ενσωματωμένη εφαρμογή`
|
||||
editor.filename_is_a_directory=Το όνομα αρχείου «%s» χρησιμοποιείται ήδη ως όνομα φακέλου σε αυτό το αποθετήριο.
|
||||
editor.file_editing_no_longer_exists=Το αρχείο «%s», το οποίο επεξεργάζεστε, δεν υπάρχει πλέον σε αυτό το αποθετήριο.
|
||||
editor.file_deleting_no_longer_exists=Το αρχείο «%s», το οποίο διαγράφεται, δεν υπάρχει πλέον σε αυτό το αποθετήριο.
|
||||
editor.filename_is_a_directory=Το όνομα αρχείου «%s» χρησιμοποιείται ήδη ως όνομα φακέλου σε αυτό το repository.
|
||||
editor.file_editing_no_longer_exists=Το αρχείο «%s», το οποίο επεξεργάζεστε, δεν υπάρχει πλέον σε αυτό το repository.
|
||||
editor.file_deleting_no_longer_exists=Το αρχείο «%s», το οποίο διαγράφεται, δεν υπάρχει πλέον σε αυτό το repository.
|
||||
editor.file_changed_while_editing=Προέκυψαν κάποιες αλλαγές στα περιεχόμενα του αρχείου από τότε που ξεκινήσατε να τα επεξεργάζεστε. <a target="_blank" rel="noopener noreferrer" href="%s">Κάντε κλικ εδώ</a> για να τα δείτε ή <strong>ξανακάντε μία υποβολή των αλλαγών σας</strong> για να τις αντικαταστήσετε.
|
||||
editor.file_already_exists=Υπάρχει ήδη ένα αρχείο με το όνομα «%s» σε αυτό το αποθετήριο.
|
||||
editor.file_already_exists=Υπάρχει ήδη ένα αρχείο με το όνομα «%s» σε αυτό το repository.
|
||||
editor.commit_empty_file_header=Υποβολή ενός κενού αρχείου
|
||||
editor.commit_empty_file_text=Το αρχείο που πρόκειται να υποβληθεί είναι κενό. Συνέχεια;
|
||||
editor.no_changes_to_show=Δεν υπάρχουν αλλαγές για εμφάνιση.
|
||||
|
@ -1620,13 +1620,13 @@ issues.author_helper=Αυτός ο χρήστης είναι ο συγγραφέ
|
|||
issues.role.owner=Ιδιοκτήτης
|
||||
issues.role.owner_helper=Αυτός ο χρήστης είναι ο ιδιοκτήτης αυτού του αποθετηρίου.
|
||||
issues.role.member=Μέλος
|
||||
issues.role.member_helper=Αυτός ο χρήστης είναι μέλος του οργανισμού, του οποίου ανήκει το αποθετήριο.
|
||||
issues.role.member_helper=Αυτός ο χρήστης είναι μέλος του οργανισμού, του οποίου ανήκει το repository.
|
||||
issues.role.collaborator=Συνεργάτης
|
||||
issues.role.collaborator_helper=Ο χρήστης έλαβε πρόσκληση συνεργασίας στο αποθετήριο.
|
||||
issues.role.collaborator_helper=Ο χρήστης έλαβε πρόσκληση συνεργασίας στο repository.
|
||||
issues.role.first_time_contributor=Συντελεστής για πρώτη φορά
|
||||
issues.role.first_time_contributor_helper=Αυτή είναι η πρώτη συνεισφορά αυτού του χρήστη στο αποθετήριο.
|
||||
issues.role.first_time_contributor_helper=Αυτή είναι η πρώτη συνεισφορά αυτού του χρήστη στο repository.
|
||||
issues.role.contributor=Συντελεστής
|
||||
issues.role.contributor_helper=Αυτός ο χρήστης έχει προηγούμενές υποβολές (commits) στο αποθετήριο.
|
||||
issues.role.contributor_helper=Αυτός ο χρήστης έχει προηγούμενές υποβολές (commits) στο repository.
|
||||
issues.re_request_review=Επαναίτηση ανασκόπησης
|
||||
issues.is_stale=Έχουν υπάρξει αλλαγές σε αυτό το PR από αυτή την αναθεώρηση
|
||||
issues.remove_request_review=Αφαίρεση αιτήματος αναθεώρησης
|
||||
|
@ -1678,7 +1678,7 @@ issues.unlock_comment=: ξεκλείδωσε αυτή τη συνομιλία %s
|
|||
issues.lock_confirm=Κλείδωμα
|
||||
issues.unlock_confirm=Ξεκλείδωμα
|
||||
issues.lock.notice_1=- Άλλοι χρήστες δεν μπορούν να αφήσουν νέα σχόλια σε αυτό το ζήτημα.
|
||||
issues.lock.notice_2=- Εσείς και άλλοι συνεργάτες που έχουν πρόσβαση στο αποθετήριο θα μπορείτε ακόμα να αφήσετε σχόλια που θα είναι ορατά σε άλλους.
|
||||
issues.lock.notice_2=- Εσείς, μαζί με τους συνεργάτες σας που έχουν πρόσβαση στο repository, θα μπορείτε ακόμα να αφήσετε σχόλια που θα μπορούν να βλέπουν και άλλοι.
|
||||
issues.lock.notice_3=- Θα μπορείτε να ξεκλειδώσετε αυτό το ζήτημα πιο μετά.
|
||||
issues.unlock.notice_1=- Όλοι θα βρίσκονται πάλι σε θέση να αφήσουν σχόλιο σε αυτό το ζήτημα.
|
||||
issues.unlock.notice_2=- Θα μπορείτε πάντα να ξανακλειδώσετε αυτό το ζήτημα πιο μετά.
|
||||
|
@ -1759,7 +1759,7 @@ issues.dependency.add_error_dep_issue_not_exist=Εξαρτώμενο ζήτημ
|
|||
issues.dependency.add_error_dep_not_exist=Δεν υπάρχει η Εξάρτηση.
|
||||
issues.dependency.add_error_dep_exists=Η Εξάρτηση υπάρχει ήδη.
|
||||
issues.dependency.add_error_cannot_create_circular=Δεν μπορείτε να δημιουργήσετε μια εξάρτηση με δύο ζητήματα που μπλοκάρουν το ένα το άλλο.
|
||||
issues.dependency.add_error_dep_not_same_repo=Και τα δύο ζητήματα πρέπει να βρίσκονται στο ίδιο αποθετήριο.
|
||||
issues.dependency.add_error_dep_not_same_repo=Και τα δύο ζητήματα πρέπει να βρίσκονται στο ίδιο repository.
|
||||
issues.review.self.approval=Δεν μπορείτε να εγκρίνετε το δικό σας pull request.
|
||||
issues.review.self.rejection=Δεν μπορείτε να ζητήσετε αλλαγές στο δικό σας pull request.
|
||||
issues.review.approve=ενέκρινε τις αλλαγές %s
|
||||
|
@ -1922,7 +1922,7 @@ pulls.closed_at=`έκλεισε αυτό το pull request <a id="%[1]s" href="#
|
|||
pulls.reopened_at=`άνοιξε ξανά αυτό το pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
pulls.cmd_instruction_hint=Προβολή οδηγιών γραμμής εντολών
|
||||
pulls.cmd_instruction_checkout_title=Έλεγχος
|
||||
pulls.cmd_instruction_checkout_desc=Από το αποθετήριο του έργου σας, ελέγξτε έναν νέο κλάδο και δοκιμάστε τις αλλαγές.
|
||||
pulls.cmd_instruction_checkout_desc=Από το repository του έργου σας, ελέγξτε έναν νέο κλάδο και δοκιμάστε τις αλλαγές.
|
||||
pulls.cmd_instruction_merge_title=Συγχώνευση
|
||||
pulls.cmd_instruction_merge_desc=Συγχώνευση των αλλαγών και ενημέρωση στο Gitea.
|
||||
pulls.clear_merge_message=Εκκαθάριση μηνύματος συγχώνευσης
|
||||
|
@ -2101,8 +2101,8 @@ search.code_no_results=Δεν βρέθηκε πηγαίος κώδικας πο
|
|||
search.code_search_unavailable=Η αναζήτηση κώδικα δεν είναι διαθέσιμη αυτή τη στιγμή. Παρακαλώ επικοινωνήστε με το διαχειριστή.
|
||||
|
||||
settings=Ρυθμίσεις
|
||||
settings.desc=Στις Ρυθμίσεις μπορείτε να διαχειριστείτε τις ρυθμίσεις για το αποθετήριο
|
||||
settings.options=Αποθετήριο
|
||||
settings.desc=Στις Ρυθμίσεις μπορείτε να διαχειριστείτε τις ρυθμίσεις για το repository
|
||||
settings.options=Repository
|
||||
settings.collaboration=Συνεργάτες
|
||||
settings.collaboration.admin=Διαχειριστής
|
||||
settings.collaboration.write=Εγγραφή
|
||||
|
@ -2113,18 +2113,18 @@ settings.hooks=Webhooks
|
|||
settings.githooks=Git hooks
|
||||
settings.basic_settings=Βασικές ρυθμίσεις
|
||||
settings.mirror_settings=Ρυθμίσεις ειδώλου
|
||||
settings.mirror_settings.docs=Ρυθμίστε τον αυτόματο συγχρονισμό των υποβολών, ετικετών και κλάδων του αποθετηρίου σας σε ένα άλλο αποθετήριο.
|
||||
settings.mirror_settings.docs.disabled_pull_mirror.instructions=Ρυθμίστε τον αυτόματο συγχρονισμό των υποβολών, ετικετών και κλάδων του έργου σας με ένα άλλο αποθετήριο. Τα είδωλα τύπου λήψης έχουν απενεργοποιηθεί από τον διαχειριστή σας.
|
||||
settings.mirror_settings.docs.disabled_push_mirror.instructions=Ρυθμίστε την αυτόματη λήψη υποβολών, ετικετών και κλάδων από ένα άλλο αποθετήριο.
|
||||
settings.mirror_settings.docs=Ρυθμίστε τον αυτόματο συγχρονισμό των commit, ετικετών και κλάδων του αποθετηρίου σας σε ένα άλλο repository.
|
||||
settings.mirror_settings.docs.disabled_pull_mirror.instructions=Ρυθμίστε τον αυτόματο συγχρονισμό των commit, ετικετών και κλάδων του έργου σας με ένα άλλο repository. Τα είδωλα τύπου λήψης έχουν απενεργοποιηθεί από τον διαχειριστή σας.
|
||||
settings.mirror_settings.docs.disabled_push_mirror.instructions=Ρυθμίστε την αυτόματη λήψη υποβολών, ετικετών και κλάδων από ένα άλλο repository.
|
||||
settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=Αυτή τη στιγμή, αυτό μπορεί να γίνει μόνο στο μενού "Νέα Μεταφορά". Για περισσότερες πληροφορίες, συμβουλευτείτε το:
|
||||
settings.mirror_settings.docs.disabled_push_mirror.info=Τα είδωλα ώθησης έχουν απενεργοποιηθεί από το διαχειριστή σας.
|
||||
settings.mirror_settings.docs.no_new_mirrors=Το αποθετήριο σας αντιγράφει τις αλλαγές προς ή από ένα άλλο αποθετήριο. Λάβετε υπόψη ότι δεν μπορείτε να δημιουργήσετε νέα είδωλα αυτή τη στιγμή.
|
||||
settings.mirror_settings.docs.no_new_mirrors=Το repository σας αντιγράφει τις αλλαγές προς ή από ένα άλλο repository. Λάβετε υπόψη ότι δεν μπορείτε να δημιουργήσετε νέα είδωλα αυτή τη στιγμή.
|
||||
settings.mirror_settings.docs.can_still_use=Αν και δεν μπορείτε να τροποποιήσετε τα υπάρχοντα είδωλα ή να δημιουργήσετε νέα, μπορείτε να χρησιμοποιείται ακόμα το υπάρχων είδωλο.
|
||||
settings.mirror_settings.docs.pull_mirror_instructions=Για να ορίσετε έναν είδωλο έλξης, παρακαλούμε συμβουλευθείτε:
|
||||
settings.mirror_settings.docs.more_information_if_disabled=Μπορείτε να μάθετε περισσότερα για τα είδωλα ώθησης και έλξης εδώ:
|
||||
settings.mirror_settings.docs.doc_link_title=Πώς μπορώ να αντιγράψω αποθετήρια;
|
||||
settings.mirror_settings.docs.doc_link_pull_section=το κεφάλαιο "Pulling from a remote repository" της τεκμηρίωσης.
|
||||
settings.mirror_settings.docs.pulling_remote_title=Έλξη από ένα απομακρυσμένο αποθετήριο
|
||||
settings.mirror_settings.docs.pulling_remote_title=Pull από ένα απομακρυσμένο repository
|
||||
settings.mirror_settings.mirrored_repository=Είδωλο αποθετηρίου
|
||||
settings.mirror_settings.direction=Κατεύθυνση
|
||||
settings.mirror_settings.direction.pull=Pull
|
||||
|
@ -2188,33 +2188,33 @@ settings.reindex_button=Προσθήκη στην ουρά Reindex
|
|||
settings.reindex_requested=Αιτήθηκε reindex
|
||||
settings.admin_enable_close_issues_via_commit_in_any_branch=Κλείσιμο ενός ζητήματος μέσω μιας υποβολής που έγινε σε έναν μη προεπιλεγμένο κλάδο
|
||||
settings.danger_zone=Ζώνη κινδύνου
|
||||
settings.new_owner_has_same_repo=Ο νέος ιδιοκτήτης έχει ήδη ένα αποθετήριο με το ίδιο όνομα. Παρακαλώ επιλέξτε ένα άλλο όνομα.
|
||||
settings.convert=Μετατροπή σε κανονικό αποθετήριο
|
||||
settings.convert_desc=Μπορείτε να μετατρέψετε αυτόν το είδωλο σε κανονικό αποθετήριο. Αυτό δεν μπορεί να αναιρεθεί.
|
||||
settings.convert_notices_1=Αυτή η λειτουργία θα μετατρέψει το είδωλο σε ένα κανονικό αποθετήριο και δεν μπορεί να αναιρεθεί.
|
||||
settings.new_owner_has_same_repo=Ο νέος ιδιοκτήτης έχει ήδη ένα repository με το ίδιο όνομα. Παρακαλώ επιλέξτε ένα άλλο όνομα.
|
||||
settings.convert=Μετατροπή σε κανονικό repository
|
||||
settings.convert_desc=Μπορείτε να μετατρέψετε αυτόν το είδωλο σε κανονικό repository. Αυτό δεν μπορεί να αναιρεθεί.
|
||||
settings.convert_notices_1=Αυτή η λειτουργία θα μετατρέψει το είδωλο σε ένα κανονικό repository και δεν μπορεί να αναιρεθεί.
|
||||
settings.convert_confirm=Μετατροπή αποθετηρίου
|
||||
settings.convert_succeed=Το είδωλο έχει μετατραπεί σε κανονικό αποθετήριο.
|
||||
settings.convert_fork=Μετατροπή σε κανονικό αποθετήριο
|
||||
settings.convert_fork_desc=Μπορείτε να μετατρέψετε αυτό το fork σε κανονικό αποθετήριο. Αυτό δεν μπορεί να αναιρεθεί.
|
||||
settings.convert_fork_notices_1=Αυτή η λειτουργία θα μετατρέψει το fork σε ένα κανονικό αποθετήριο και δεν μπορεί να αναιρεθεί.
|
||||
settings.convert_succeed=Το είδωλο έχει μετατραπεί σε κανονικό repository.
|
||||
settings.convert_fork=Μετατροπή σε κανονικό repository
|
||||
settings.convert_fork_desc=Μπορείτε να μετατρέψετε αυτό το fork σε κανονικό repository. Αυτό δεν μπορεί να αναιρεθεί.
|
||||
settings.convert_fork_notices_1=Αυτή η λειτουργία θα μετατρέψει το fork σε ένα κανονικό repository και δεν μπορεί να αναιρεθεί.
|
||||
settings.convert_fork_confirm=Μετατροπή αποθετηρίου
|
||||
settings.convert_fork_succeed=Το fork έχει μετατραπεί σε κανονικό αποθετήριο.
|
||||
settings.convert_fork_succeed=Το fork έχει μετατραπεί σε κανονικό repository.
|
||||
settings.transfer.title=Μεταβίβαση ιδιοκτησίας
|
||||
settings.transfer.rejected=Η μεταβίβαση του αποθετηρίου απορρίφθηκε.
|
||||
settings.transfer.success=Η μεταβίβαση του αποθετηρίου ήταν επιτυχής.
|
||||
settings.transfer_abort=Ακύρωση μεταβίβασης
|
||||
settings.transfer_abort_invalid=Δεν μπορείτε να ακυρώσετε μια ανύπαρκτη μεταβίβαση αποθετηρίου.
|
||||
settings.transfer_abort_success=Η μεταφορά αποθετηρίου στο %s ακυρώθηκε με επιτυχία.
|
||||
settings.transfer_desc=Μεταβιβάστε αυτό το αποθετήριο σε έναν χρήστη ή σε έναν οργανισμό για τον οποίο έχετε δικαιώματα διαχειριστή.
|
||||
settings.transfer_desc=Μεταβιβάστε αυτό το repository σε έναν χρήστη ή σε έναν οργανισμό για τον οποίο έχετε δικαιώματα διαχειριστή.
|
||||
settings.transfer_form_title=Εισάγετε το όνομα του αποθετηρίου ως επιβεβαίωση:
|
||||
settings.transfer_in_progress=Αυτή τη στιγμή υπάρχει μια εν εξελίξει μεταβίβαση. Παρακαλούμε ακυρώστε την αν θέλετε να μεταβιβάσετε αυτό το αποθετήριο σε άλλο χρήστη.
|
||||
settings.transfer_notices_1=- Θα χάσετε την πρόσβαση στο αποθετήριο αν το μεταβιβάσετε σε έναν μεμονωμένο χρήστη.
|
||||
settings.transfer_notices_2=- Θα διατηρήσετε την πρόσβαση στο αποθετήριο αν το μεταβιβάσετε σε έναν οργανισμό που είστε (συν)ιδιοκτήτης.
|
||||
settings.transfer_notices_3=- Εάν το αποθετήριο είναι ιδιωτικό και μεταβιβάζεται σε μεμονωμένο χρήστη, αυτή η ενέργεια εξασφαλίζει ότι ο χρήστης έχει τουλάχιστον άδεια ανάγνωσης (και αλλάζει τα δικαιώματα εάν είναι απαραίτητο).
|
||||
settings.transfer_in_progress=Αυτή τη στιγμή υπάρχει μια εν εξελίξει μεταβίβαση. Παρακαλούμε ακυρώστε την αν θέλετε να μεταβιβάσετε αυτό το repository σε άλλο χρήστη.
|
||||
settings.transfer_notices_1=- Θα χάσετε την πρόσβαση στο repository αν το μεταβιβάσετε σε έναν μεμονωμένο χρήστη.
|
||||
settings.transfer_notices_2=- Θα διατηρήσετε την πρόσβαση στο repository αν το μεταβιβάσετε σε έναν οργανισμό που είστε (συν)ιδιοκτήτης.
|
||||
settings.transfer_notices_3=- Εάν το repository είναι ιδιωτικό και μεταβιβάζεται σε μεμονωμένο χρήστη, αυτή η ενέργεια εξασφαλίζει ότι ο χρήστης έχει τουλάχιστον άδεια ανάγνωσης (και αλλάζει τα δικαιώματα εάν είναι απαραίτητο).
|
||||
settings.transfer_owner=Νέος ιδιοκτήτης
|
||||
settings.transfer_perform=Εκτέλεση μεταφοράς
|
||||
settings.transfer_started=`Αυτό το αποθετήριο έχει επισημανθεί για μεταφορά και αναμένει επιβεβαίωση από το "%s"`
|
||||
settings.transfer_succeed=Το αποθετήριο έχει μεταφερθεί.
|
||||
settings.transfer_started=`Αυτό το repository έχει επισημανθεί για μεταφορά και αναμένει επιβεβαίωση από το "%s"`
|
||||
settings.transfer_succeed=Το repository έχει μεταφερθεί.
|
||||
settings.signing_settings=Ρυθμίσεις επαλήθευσης υπογραφών
|
||||
settings.trust_model=Μοντέλο εμπιστοσύνης υπογραφών
|
||||
settings.trust_model.default=Προεπιλεγμένο μοντέλο εμπιστοσύνης
|
||||
|
@ -2236,33 +2236,33 @@ settings.wiki_deletion_success=Τα δεδομένα wiki του αποθετη
|
|||
settings.delete=Διαγραφή αυτόυ του αποθετηρίου
|
||||
settings.delete_desc=Η διαγραφή ενός αποθετηρίου είναι μόνιμη και δεν μπορεί να αναιρεθεί.
|
||||
settings.delete_notices_1=- Αυτή η ενέργεια <strong>ΔΕΝ ΜΠΟΡΕΙ</strong> να αναιρεθεί.
|
||||
settings.delete_notices_2=- Αυτή η ενέργεια θα διαγράψει μόνιμα το αποθετήριο <strong>%s</strong> μαζί με τον κώδικα, τα ζητημάτα, τα σχόλια, τα δεδομένα των wiki και τις ρυθμίσεις συνεργατών που βρίσκονται μέσα σε αυτό.
|
||||
settings.delete_notices_2=- Αυτή η ενέργεια θα διαγράψει μόνιμα το repository <strong>%s</strong> μαζί με τον κώδικα, τα ζητημάτα, τα σχόλια, τα δεδομένα των wiki και τις ρυθμίσεις συνεργατών που βρίσκονται μέσα σε αυτό.
|
||||
settings.delete_notices_fork_1=- Τα Forks αυτού του αποθετηρίου θα γίνουν ανεξάρτητα μετά τη διαγραφή.
|
||||
settings.deletion_success=Το αποθετήριο έχει διαγραφεί.
|
||||
settings.deletion_success=Το repository έχει διαγραφεί.
|
||||
settings.update_settings_success=Οι ρυθμίσεις του αποθετηρίου έχουν ενημερωθεί.
|
||||
settings.update_settings_no_unit=Το αποθετήριο θα πρέπει να επιτρέπει τουλάχιστον κάποιο είδος αλληλεπίδρασης.
|
||||
settings.update_settings_no_unit=Το repository θα πρέπει να επιτρέπει τουλάχιστον κάποιο είδος αλληλεπίδρασης.
|
||||
settings.confirm_delete=Διαγραφή αποθετηρίου
|
||||
settings.add_collaborator=Προσθήκη συνεργάτη
|
||||
settings.add_collaborator_success=Ο συνεργάτης προστέθηκε.
|
||||
settings.add_collaborator_inactive_user=Δεν είναι δυνατή η προσθήκη ενός ανενεργού χρήστη ως συνεργάτη.
|
||||
settings.add_collaborator_owner=Δεν είναι δυνατή η προσθήκη ενός ιδιοκτήτη σαν συνεργάτη.
|
||||
settings.add_collaborator_duplicate=Ο συνεργάτης έχει ήδη προστεθεί σε αυτό το αποθετήριο.
|
||||
settings.add_collaborator_duplicate=Ο συνεργάτης έχει ήδη προστεθεί σε αυτό το repository.
|
||||
settings.delete_collaborator=Κατάργηση
|
||||
settings.collaborator_deletion=Κατάργηση συνεργάτη
|
||||
settings.collaborator_deletion_desc=Η κατάργηση ενός συνεργάτη θα αφαιρέσει και την πρόσβασή του στο αποθετήριο. Είστε βέβαιοι;
|
||||
settings.collaborator_deletion_desc=Η κατάργηση ενός συνεργάτη θα αφαιρέσει και την πρόσβασή του στο repository. Είστε βέβαιοι;
|
||||
settings.remove_collaborator_success=Ο συνεργάτης έχει καταργηθεί.
|
||||
settings.search_user_placeholder=Αναζήτηση χρήστη…
|
||||
settings.org_not_allowed_to_be_collaborator=Δεν μπορείτε να προσθέσετε έναν οργανισμό ως συνεργάτη.
|
||||
settings.change_team_access_not_allowed=Η αλλαγή της πρόσβασης ομάδας για το αποθετήριο έχει περιοριστεί στον ιδιοκτήτη του οργανισμού
|
||||
settings.team_not_in_organization=Η ομάδα δεν είναι στον ίδιο οργανισμό με το αποθετήριο
|
||||
settings.change_team_access_not_allowed=Η αλλαγή της πρόσβασης ομάδας για το repository έχει περιοριστεί στον ιδιοκτήτη του οργανισμού
|
||||
settings.team_not_in_organization=Η ομάδα δεν είναι στον ίδιο οργανισμό με το repository
|
||||
settings.teams=Ομάδες
|
||||
settings.add_team=Προσθήκη ομάδας
|
||||
settings.add_team_duplicate=Η ομάδα έχει ήδη το αποθετήριο
|
||||
settings.add_team_success=Η ομάδα έχει πλέον πρόσβαση στο αποθετήριο.
|
||||
settings.add_team_duplicate=Η ομάδα έχει ήδη το repository
|
||||
settings.add_team_success=Η ομάδα έχει πλέον πρόσβαση στο repository.
|
||||
settings.search_team=Αναζήτηση Ομάδας…
|
||||
settings.change_team_permission_tip=Τα δικαιώματα της ομάδας έχουν οριστεί στη σελίδα ρυθμίσεων της ομάδας και δεν μπορούν να αλλάξουν ανά αποθετήριο
|
||||
settings.change_team_permission_tip=Τα δικαιώματα της ομάδας έχουν οριστεί στη σελίδα ρυθμίσεων της ομάδας και δεν μπορούν να αλλάξουν ανά repository
|
||||
settings.delete_team_tip=Αυτή η ομάδα έχει πρόσβαση σε όλα τα αποθετήρια και δεν μπορεί να αφαιρεθεί
|
||||
settings.remove_team_success=Έχει αφαιρεθεί η πρόσβαση της ομάδας στο αποθετήριο.
|
||||
settings.remove_team_success=Έχει αφαιρεθεί η πρόσβαση της ομάδας στο repository.
|
||||
settings.add_webhook=Προσθήκη webhook
|
||||
settings.add_webhook.invalid_channel_name=Το όνομα του καναλιού Webhook δεν μπορεί να είναι κενό και δεν μπορεί να περιέχει μόνο έναν χαρακτήρα #.
|
||||
settings.hooks_desc=Τα Webhooks κάνουν αυτόματα αιτήσεις HTTP POST σε ένα διακομιστή όταν ενεργοποιούνται ορισμένα γεγονότα στο Forgejo. Διαβάστε περισσότερα στον <a target="_blank" rel="noopener noreferrer" href="%s">οδηγό webhooks</a>.
|
||||
|
@ -2305,15 +2305,15 @@ settings.event_create_desc=Ο κλάδος ή η ετικέτα δημιουργ
|
|||
settings.event_delete=Διαγραφή
|
||||
settings.event_delete_desc=Ο κλάδος ή η ετικέτα διαγράφηκε.
|
||||
settings.event_fork=Fork
|
||||
settings.event_fork_desc=Το αποθετήριο έγινε fork.
|
||||
settings.event_fork_desc=Το repository έγινε fork.
|
||||
settings.event_wiki=Wiki
|
||||
settings.event_wiki_desc=Η σελίδα Wiki δημιουργήθηκε, μετονομάστηκε, επεξεργάστηκε ή διαγράφηκε.
|
||||
settings.event_release=Κυκλοφορία
|
||||
settings.event_release_desc=Η έκδοση δημοσιεύτηκε, ενημερώθηκε ή διαγράφηκε από ένα αποθετήριο.
|
||||
settings.event_push=Push
|
||||
settings.event_push_desc=Git push σε ένα αποθετήριο.
|
||||
settings.event_repository=Αποθετήριο
|
||||
settings.event_repository_desc=Το αποθετήριο δημιουργήθηκε ή διαγράφηκε.
|
||||
settings.event_push_desc=Git push σε ένα repository.
|
||||
settings.event_repository=Repository
|
||||
settings.event_repository_desc=Το repository δημιουργήθηκε ή διαγράφηκε.
|
||||
settings.event_header_issue=Συμβάντα ζητημάτων
|
||||
settings.event_issues=Ζητήματα
|
||||
settings.event_issues_desc=Το ζήτημα άνοιξε, έκλεισε, ανοίχθηκε εκ νέου ή επεξεργάστηκε.
|
||||
|
@ -2345,7 +2345,7 @@ settings.event_pull_request_review_request_desc=Ζητήθηκε η αξιολό
|
|||
settings.event_pull_request_approvals=Εγκρίσεις pull request
|
||||
settings.event_pull_request_merge=Συγχώνευση pull request
|
||||
settings.event_package=Πακέτο
|
||||
settings.event_package_desc=Το πακέτο δημιουργήθηκε ή διαγράφηκε σε ένα αποθετήριο.
|
||||
settings.event_package_desc=Το πακέτο δημιουργήθηκε ή διαγράφηκε σε ένα repository.
|
||||
settings.branch_filter=Φίλτρο κλάδου
|
||||
settings.branch_filter_desc=Λίστα επιτρεπόμενων κλάδων για ωθήσεις, δημιουργία κλάδων και γεγονότα διαγραφής κλάδων, που ορίζονται ως μοτίβο glob. Εάν είναι κενό ή <code>*</code>, αναφέρονται συμβάντα για όλους τους κλάδους. Δείτε τη τεκμηρίωση<a href="%[1]s">%[2]s</a> για σύνταξη. Παραδείγματα: <code>master</code>, <code>{master,release*}</code>.
|
||||
settings.authorization_header=Κεφαλίδα authorization
|
||||
|
@ -2361,7 +2361,7 @@ settings.hook_type=Είδος hook
|
|||
settings.slack_token=Διακριτικό
|
||||
settings.slack_domain=Domain
|
||||
settings.slack_channel=Κανάλι
|
||||
settings.add_web_hook_desc=Ενσωμάτωσε το <a target="_blank" rel="noreferrer" href="%s">%s</a> στο αποθετήριο σας.
|
||||
settings.add_web_hook_desc=Ενσωμάτωσε το <a target="_blank" rel="noreferrer" href="%s">%s</a> στο repository σας.
|
||||
settings.web_hook_name_gitea=Gitea
|
||||
settings.web_hook_name_forgejo = Forgejo
|
||||
settings.web_hook_name_gogs=Gogs
|
||||
|
@ -2381,9 +2381,9 @@ settings.packagist_api_token=Διακριτικό API
|
|||
settings.packagist_package_url=URL πακέτων Packagist
|
||||
settings.deploy_keys=Κλειδιά διάθεσης
|
||||
settings.add_deploy_key=Προσθήκη κλειδιού διάθεσης
|
||||
settings.deploy_key_desc=Τα κλειδιά διάθεσης έχουν πρόσβαση μόνο-ανάγνωσης στο αποθετήριο.
|
||||
settings.deploy_key_desc=Τα κλειδιά διάθεσης έχουν πρόσβαση μόνο-ανάγνωσης στο repository.
|
||||
settings.is_writable=Ενεργοποίηση πρόσβασης εγγραφής
|
||||
settings.is_writable_info=Επιτρέψτε σε αυτό το κλειδί διάθεσης να <strong>ωθήσει</strong> στο αποθετήριο.
|
||||
settings.is_writable_info=Επιτρέψτε σε αυτό το κλειδί διάθεσης να <strong>ωθήσει</strong> στο repository.
|
||||
settings.no_deploy_keys=Δεν υπάρχουν ακόμα κλειδιά διάθεσης.
|
||||
settings.title=Τίτλος
|
||||
settings.deploy_key_content=Περιεχόμενο
|
||||
|
@ -2391,7 +2391,7 @@ settings.key_been_used=Ένα κλειδί διάθεσης με το ίδιο
|
|||
settings.key_name_used=Ένα κλειδί διάθεσης με το ίδιο όνομα υπάρχει ήδη.
|
||||
settings.add_key_success=Το κλειδί διάθεσης «%s» προστέθηκε.
|
||||
settings.deploy_key_deletion=Αφαίρεση κλειδιού διάθεσης
|
||||
settings.deploy_key_deletion_desc=Η κατάργηση ενός κλειδί διάθεσης θα ανακαλέσει την πρόσβασή του σε αυτό το αποθετήριο. Συνέχεια;
|
||||
settings.deploy_key_deletion_desc=Η κατάργηση ενός κλειδί διάθεσης θα ανακαλέσει την πρόσβασή του σε αυτό το repository. Συνέχεια;
|
||||
settings.deploy_key_deletion_success=Το κλειδί διάθεσης έχει αφαιρεθεί.
|
||||
settings.branches=Κλάδοι
|
||||
settings.protected_branch=Προστασία κλάδου
|
||||
|
@ -2424,7 +2424,7 @@ settings.protect_check_status_contexts=Ενεργοποίηση ελέγχου
|
|||
settings.protect_status_check_patterns=Μοτίβα ελέγχου κατάστασης:
|
||||
settings.protect_status_check_patterns_desc=Ορίστε μοτίβα για να καθορίσετε ποιοι έλεγχοι κατάστασης πρέπει να περάσουν πριν οι κλάδοι να μπορούν να συγχωνευτούν σε έναν κλάδο που ταιριάζει με αυτόν τον κανόνα. Κάθε γραμμή καθορίζει ένα μοτίβο. Τα μοτίβα δεν μπορούν να είναι κενά.
|
||||
settings.protect_check_status_contexts_desc=Απαιτείται έλεγχος κατάστασης για να περάσει το pull request πριν από τη συγχώνευση. Επιλέξτε ποιοι έλεγχοι κατάστασης πρέπει να περάσουν πριν κλάδοι μπορούν να συγχωνευτούν σε έναν κλάδο που ταιριάζει με αυτόν τον κανόνα. Όταν είναι ενεργοποιημένο, οι υποβολές πρέπει πρώτα να γίνονται push σε άλλο κλάδο, στη συνέχεια, να συγχωνεύονται ή γίνονται push απευθείας σε ένα κλάδο που ταιριάζει με αυτόν τον κανόνα, αφού έχουν ολοκληρωθεί οι έλεγχοι κατάστασης. Αν δεν επιλεχθεί κανένα πλαίσιο, η τελευταία υποβολή πρέπει να είναι επιτυχής ανεξάρτητα από το πλαίσιο.
|
||||
settings.protect_check_status_contexts_list=Έλεγχοι κατάστασης που βρέθηκαν την τελευταία εβδομάδα για αυτό το αποθετήριο
|
||||
settings.protect_check_status_contexts_list=Έλεγχοι κατάστασης που βρέθηκαν την τελευταία εβδομάδα για αυτό το repository
|
||||
settings.protect_status_check_matched=Ταιριάζει
|
||||
settings.protect_invalid_status_check_pattern=Μη έγκυρο μοτίβο ελέγχου κατάστασης: "%s".
|
||||
settings.protect_no_valid_status_check_patterns=Μη έγκυρα μοτίβα ελέγχου κατάστασης.
|
||||
|
@ -2486,20 +2486,20 @@ settings.matrix.message_type=Είδος μηνύματος
|
|||
settings.archive.button=Αρχειοθέτηση αποθετηρίου
|
||||
settings.archive.header=Αρχειοθέτηση αποθετηρίου
|
||||
settings.archive.text=Η αρχειοθέτηση του αποθετηρίου θα το αλλάξει σε μόνο για ανάγνωση. Δε θα φαίνεται στον αρχικό πίνακα. Κανείς (ακόμα και εσείς!) δε θα μπορεί να κάνει νέες υποβολές, ή να ανοίξει ζητήματα ή pull request.
|
||||
settings.archive.success=Το αποθετήριο αρχειοθετήθηκε με επιτυχία.
|
||||
settings.archive.success=Το repository αρχειοθετήθηκε με επιτυχία.
|
||||
settings.archive.error=Παρουσιάστηκε σφάλμα κατά την προσπάθεια αρχειοθέτησης του αποθετηρίου. Δείτε το αρχείο καταγραφής για περισσότερες λεπτομέρειες.
|
||||
settings.archive.error_ismirror=Δε μπορείτε να αρχειοθετήσετε ένα είδωλο αποθετηρίου.
|
||||
settings.archive.branchsettings_unavailable=Οι ρυθμίσεις του κλάδου δεν είναι διαθέσιμες αν το αποθετήριο είναι αρχειοθετημένο.
|
||||
settings.archive.tagsettings_unavailable=Οι ρυθμίσεις της ετικέτας δεν είναι διαθέσιμες αν το αποθετήριο είναι αρχειοθετημένο.
|
||||
settings.archive.branchsettings_unavailable=Οι ρυθμίσεις του κλάδου δεν είναι διαθέσιμες αν το repository είναι αρχειοθετημένο.
|
||||
settings.archive.tagsettings_unavailable=Οι ρυθμίσεις της ετικέτας δεν είναι διαθέσιμες αν το repository είναι αρχειοθετημένο.
|
||||
settings.unarchive.button=Αναίρεση αρχειοθέτησης αποθετηρίου
|
||||
settings.unarchive.header=Απο-Αρχειοθέτηση του αποθετηρίου
|
||||
settings.unarchive.text=Η απο-αρχειοθέτηση του αποθετηρίου θα αποκαταστήσει την ικανότητά του να λαμβάνει υποβολές και ωθήσεις, καθώς και νέα ζητήματα και pull-requests.
|
||||
settings.unarchive.success=Το αποθετήριο απο-αρχειοθετήθηκε με επιτυχία.
|
||||
settings.unarchive.success=Το repository απο-αρχειοθετήθηκε με επιτυχία.
|
||||
settings.unarchive.error=Παρουσιάστηκε σφάλμα κατά την προσπάθεια απο-αρχειοθέτησης του αποθετηρίου. Δείτε τις καταγραφές για περισσότερες λεπτομέρειες.
|
||||
settings.update_avatar_success=Η εικόνα του αποθετηρίου έχει ενημερωθεί.
|
||||
settings.lfs=LFS
|
||||
settings.lfs_filelist=Αρχεία LFS σε αυτό το αποθετήριο
|
||||
settings.lfs_no_lfs_files=Δεν υπάρχουν αρχεία LFS σε αυτό το αποθετήριο
|
||||
settings.lfs_filelist=Αρχεία LFS σε αυτό το repository
|
||||
settings.lfs_no_lfs_files=Δεν υπάρχουν αρχεία LFS σε αυτό το repository
|
||||
settings.lfs_findcommits=Εύρεση υποβολών
|
||||
settings.lfs_lfs_file_no_commits=Δεν βρέθηκαν υποβολές για αυτό το αρχείο LFS
|
||||
settings.lfs_noattribute=Αυτή η διαδρομή δεν έχει λειτουργία κλειδώματος στον προεπιλεγμένο κλάδο
|
||||
|
@ -2518,7 +2518,7 @@ settings.lfs_force_unlock=Εξαγκαναστικό ξεκλείδωμα
|
|||
settings.lfs_pointers.found=Βρέθηκαν %d δείκτης(ες) blob - %d συσχετίστηκαν, %d δεν συσχετίστηκαν (%d λείπουν από το χώρο αποθήκευσης)
|
||||
settings.lfs_pointers.sha=Blob hash
|
||||
settings.lfs_pointers.oid=OID
|
||||
settings.lfs_pointers.inRepo=Στο αποθετήριο
|
||||
settings.lfs_pointers.inRepo=Στο repository
|
||||
settings.lfs_pointers.exists=Υπάρχει στο χώρο αποθήκευσης
|
||||
settings.lfs_pointers.accessible=Προσβάσιμο στον χρήστη
|
||||
settings.lfs_pointers.associateAccessible=Συσχετισμός προσιτών %d OID
|
||||
|
@ -2621,7 +2621,7 @@ release.delete_tag=Διαγραφή ετικέτας
|
|||
release.deletion=Διαγραφή κυκλοφορίας
|
||||
release.deletion_desc=Διαγράφοντας μια κυκλοφορία, αυτή αφαιρείται μόνο από το Gitea. Δε θα επηρεάσει την ετικέτα Git, τα περιεχόμενα του αποθετηρίου σας ή το ιστορικό της. Συνέχεια;
|
||||
release.deletion_success=Η κυκλοφορία έχει διαγραφεί.
|
||||
release.deletion_tag_desc=Θα διαγράψει αυτή την ετικέτα από το αποθετήριο. Τα περιεχόμενα του αποθετηρίου και το ιστορικό παραμένουν αμετάβλητα. Συνέχεια;
|
||||
release.deletion_tag_desc=Θα διαγράψει αυτή την ετικέτα από το repository. Τα περιεχόμενα του repository και το ιστορικό δεν θα πειραχτούν. Να γίνει συνέχεια;
|
||||
release.deletion_tag_success=Η ετικέτα έχει διαγραφεί.
|
||||
release.tag_name_already_exist=Υπάρχει ήδη μια έκδοση με αυτό το όνομα ετικέτας.
|
||||
release.tag_name_invalid=Το όνομα της ετικέτας δεν είναι έγκυρο.
|
||||
|
@ -2646,7 +2646,7 @@ branch.delete_branch_has_new_commits=Ο κλάδος «%s» δεν μπορεί
|
|||
branch.create_branch=Δημιουργία κλάδου %s
|
||||
branch.create_from=`από το «%s»`
|
||||
branch.create_success=Ο κλάδος «%s» δημιουργήθηκε.
|
||||
branch.branch_already_exists=Ο κλάδος «%s» υπάρχει ήδη σε αυτό το αποθετήριο.
|
||||
branch.branch_already_exists=Ο κλάδος «%s» υπάρχει ήδη σε αυτό το repository.
|
||||
branch.branch_name_conflict=Το όνομα του κλάδου «%s» συγκρούεται με το ήδη υπάρχον κλάδο «%s».
|
||||
branch.tag_collision=Ο κλάδος «%s» δεν μπορεί να δημιουργηθεί επειδή μια ετικέτα με το ίδιο όνομα υπάρχει ήδη στο αποθετήριο.
|
||||
branch.deleted_by=Διαγράφηκε από %s
|
||||
|
@ -2691,7 +2691,7 @@ error.csv.invalid_field_count=Δεν είναι δυνατή η απόδοση
|
|||
commits.renamed_from = Μετονομάστηκε από %σ
|
||||
settings.wiki_rename_branch_main_desc = Ο κλάδος, ο οποίος χρησιμοποιείται εσωτερικά από το wiki, θα μετονομαστεί (μόνιμα και μη αναστράψιμα) σε «%s».
|
||||
issues.comment.blocked_by_user = Δεν μπορείτε να αφήσετε σχόλιο σε αυτό το ζήτημα, επειδή ο κάτοχος του αποθετηρίου ή το άτομο που δημιούργησε το ζήτημα σας έχει αποκλείσει.
|
||||
pulls.blocked_by_user = Δεν μπορείτε να δημιουργήσετε pull request σε αυτό το αποθετήριο, επειδή ο κάτοχος του αποθετηρίου σας έχει αποκλείσει.
|
||||
pulls.blocked_by_user = Δεν μπορείτε να δημιουργήσετε pull request σε αυτό το repository, επειδή ο κάτοχος του repository σας έχει αποκλείσει.
|
||||
pulls.made_using_agit = AGit
|
||||
wiki.cancel = Ακύρωση
|
||||
settings.units.add_more = Προσθήκη μονάδων...
|
||||
|
@ -2710,13 +2710,13 @@ rss.must_be_on_branch = Για να αποκτήσετε ένα RSS feed, πρέ
|
|||
clone_in_vscodium = Κλωνοποίηση στο VSCodium
|
||||
editor.invalid_commit_mail = Αυτή η διεύθυνση email δεν είναι έγκυρη για την δημιουργία μίας υποβολής.
|
||||
pulls.nothing_to_compare_have_tag = Ο επιλεγμένος κλάδος/tag είναι όμοιος.
|
||||
issues.blocked_by_user = Δεν μπορείτε να δημιουργήσετε ζητήματα σε αυτό το αποθετήριο, επειδή ο κάτοχος του αποθετηρίου σας έχει αποκλείσει.
|
||||
issues.blocked_by_user = Δεν μπορείτε να δημιουργήσετε ζητήματα σε αυτό το repository, επειδή ο κάτοχος του repository σας έχει αποκλείσει.
|
||||
pulls.agit_explanation = Δημιουργημένο μέσω του AGit. Το AGit επιτρέπει σε συνεισφέροντες να προτείνουν αλλαγές χρησιμοποιώντας την εντολή «git push», χωρίς την δημιουργία fork ή έναν νέο κλάδο.
|
||||
activity.navbar.recent_commits = Πρόσφατες υποβολές
|
||||
settings.wiki_globally_editable = Να επιτρέπεται η επεξεργασία του wiki σε όλους
|
||||
admin.manage_flags = Διαχείριση σημάνσεων
|
||||
admin.enabled_flags = Το αποθετήριο έχει τις εξής σημάνσεις:
|
||||
settings.mirror_settings.pushed_repository = Προοριζόμενο αποθετήριο
|
||||
admin.enabled_flags = Το repository έχει τις εξής σημάνσεις:
|
||||
settings.mirror_settings.pushed_repository = Προοριζόμενο repository
|
||||
admin.flags_replaced = Οι σημάνσεις του αποθετηρίου αντικαταστάθηκαν
|
||||
activity.navbar.code_frequency = Συχνότητα κώδικα
|
||||
settings.wiki_branch_rename_success = Το όνομα κλάδου wiki του αποθετηρίου κανονικοποιήθηκε επιτυχώς.
|
||||
|
@ -2767,7 +2767,7 @@ editor.commit_id_not_matching = Το αρχείο άλλαξε όσο το επ
|
|||
settings.sourcehut_builds.visibility = Ορατότητα εργασιών
|
||||
object_format = Μορφή αντικειμένων («object format»)
|
||||
settings.ignore_stale_approvals_desc = Οι εγκρίσεις, οι οποίες αναφέρονται σε παλαιότερες υποβολές, δεν θα προσμετρούνται στο σύνολο των απαιτούμενων εγκρίσεων του pull request. Εφόσον αυτές οι εγκρίσεις έχουν ήδη ανακληθεί, τότε αυτή η ρύθμιση δεν θα παίξει κάποιον ρόλο.
|
||||
settings.archive.mirrors_unavailable = Οι λειτουργίες ειδώλου δεν είναι διαθέσιμες εφόσον το αποθετήριο έχει αρχειοθετηθεί.
|
||||
settings.archive.mirrors_unavailable = Οι λειτουργίες ειδώλου δεν είναι διαθέσιμες εφόσον το repository έχει αρχειοθετηθεί.
|
||||
settings.web_hook_name_sourcehut_builds = SourceHut Builds
|
||||
settings.enforce_on_admins = Εφαρμογή κανόνα σε διαχειριστές του αποθετηρίου
|
||||
object_format_helper = Η μορφή αντικειμένων («object format») του αποθετηρίου. Δεν θα μπορείτε να το αλλάξετε μεταγενέστερα. Η πιο συμβατή μορφή είναι η SHA1.
|
||||
|
@ -2805,7 +2805,7 @@ release.type_attachment = Συνημμένο
|
|||
activity.published_prerelease_label = Προδημοσίευση
|
||||
activity.published_tag_label = Ετικέτα
|
||||
settings.pull_mirror_sync_quota_exceeded = Έχετε υπερβεί τους διαθέσιμους πόρους σας, για αυτό δεν θα γίνει λήψη των πιο πρόσφατων αλλαγών.
|
||||
settings.transfer_quota_exceeded = Ο νέος ιδιοκτήτης (%s) έχει υπερβεί τους διαθέσιμους πόρους του. Το αποθετήριο δεν μπορεί να μεταφερθεί.
|
||||
settings.transfer_quota_exceeded = Ο νέος ιδιοκτήτης (%s) έχει υπερβεί τους διαθέσιμους πόρους του. Το repository δεν μπορεί να μεταφερθεί.
|
||||
release.asset_name = Όνομα αρχείου
|
||||
release.asset_external_url = Εξωτερικό URL
|
||||
release.invalid_external_url = Μη έγκυρο εξωτερικό URL: «%s»
|
||||
|
@ -2901,7 +2901,7 @@ teams.join=Συμμετοχή
|
|||
teams.leave=Αποχώρηση
|
||||
teams.leave.detail=Σίγουρα θέλετε να αποχωρήσετε από την ομάδα %s;
|
||||
teams.can_create_org_repo=Δημιουργία αποθετηρίων
|
||||
teams.can_create_org_repo_helper=Τα μέλη μπορούν να δημιουργήσουν νέα αποθετήρια στον οργανισμό. Ο δημιουργός θα αποκτήσει πρόσβαση διαχειριστή στο νέο αποθετήριο.
|
||||
teams.can_create_org_repo_helper=Τα μέλη μπορούν να δημιουργήσουν νέα αποθετήρια στον οργανισμό. Ο δημιουργός θα αποκτήσει πρόσβαση διαχειριστή στο νέο repository.
|
||||
teams.none_access=Καμία πρόσβαση
|
||||
teams.none_access_helper=Τα μέλη δεν μπορούν να δουν ή να κάνουν οποιαδήποτε άλλη ενέργεια σε αυτή τη μονάδα.
|
||||
teams.general_access=Γενική πρόσβαση
|
||||
|
@ -2922,7 +2922,7 @@ teams.add_team_member=Προσθήκη μέλους ομάδας
|
|||
teams.invite_team_member=Πρόσκληση στην ομάδα %s
|
||||
teams.invite_team_member.list=Εκκρεμείς προσκλήσεις
|
||||
teams.delete_team_title=Διαγραφή ομάδας
|
||||
teams.delete_team_desc=Η διαγραφή μιας ομάδας ανακαλεί τη πρόσβαση στο αποθετήριο από τα μέλη της. Συνέχεια;
|
||||
teams.delete_team_desc=Η διαγραφή μιας ομάδας ανακαλεί τη πρόσβαση στο repository από τα μέλη της. Συνέχεια;
|
||||
teams.delete_team_success=Η ομάδα έχει διαγραφεί.
|
||||
teams.read_permission_desc=Αυτή η ομάδα χορηγεί πρόσβαση <strong>Ανάγνωσης</strong>: τα μέλη μπορούν να δουν και να κλωνοποιήσουν τα αποθετήρια της ομάδας.
|
||||
teams.write_permission_desc=Αυτή η ομάδα χορηγεί πρόσβαση <strong>Εγγραφής</strong>: τα μέλη μπορούν να διαβάσουν και να κάνουν push στα αποθετήρια της ομάδας.
|
||||
|
@ -2934,9 +2934,9 @@ teams.remove_all_repos_title=Αφαίρεση όλων των αποθετηρί
|
|||
teams.remove_all_repos_desc=Αυτό θα αφαιρέσει όλα τα αποθετήρια από την ομάδα.
|
||||
teams.add_all_repos_title=Προσθήκη όλων των αποθετηρίων
|
||||
teams.add_all_repos_desc=Αυτό θα προσθέσει όλα τα αποθετήρια του οργανισμού στην ομάδα.
|
||||
teams.add_nonexistent_repo=Το αποθετήριο που προσπαθείτε να προσθέσετε δεν υπάρχει, παρακαλώ δημιουργήστε το πρώτα.
|
||||
teams.add_nonexistent_repo=Το repository που προσπαθείτε να προσθέσετε δεν υπάρχει, παρακαλώ δημιουργήστε το πρώτα.
|
||||
teams.add_duplicate_users=Ο χρήστης είναι ήδη μέλος της ομάδας.
|
||||
teams.repos.none=Αυτή η ομάδα δεν έχει πρόσβαση σε κανένα αποθετήριο.
|
||||
teams.repos.none=Αυτή η ομάδα δεν έχει πρόσβαση σε κανένα repository.
|
||||
teams.members.none=Δεν υπάρχουν μέλη σε αυτήν την ομάδα.
|
||||
teams.specific_repositories=Συγκεκριμένα αποθετήρια
|
||||
teams.specific_repositories_helper=Τα μέλη θα έχουν πρόσβαση μόνο σε αποθετήρια που προστίθενται ρητά στην ομάδα. Επιλέγοντας το <strong>δεν θα</strong> θα αφαιρεθούν αυτόματα τα αποθετήρια που έχουν ήδη προστεθεί με το <i>Όλα τα αποθετήρια</i>.
|
||||
|
@ -3154,7 +3154,7 @@ packages.creator=Δημιουργός
|
|||
packages.name=Όνομα
|
||||
packages.version=Έκδοση
|
||||
packages.type=Τύπος
|
||||
packages.repository=Αποθετήριο
|
||||
packages.repository=Repository
|
||||
packages.size=Μέγεθος
|
||||
packages.published=Δημοσιευμένα
|
||||
|
||||
|
@ -3473,7 +3473,7 @@ notices.inverse_selection=Αντιστροφή επιλογής
|
|||
notices.delete_selected=Διαγραφή επιλεγμένων
|
||||
notices.delete_all=Διαγραφή όλων των ειδοποιήσεων
|
||||
notices.type=Τύπος
|
||||
notices.type_1=Αποθετήριο
|
||||
notices.type_1=Repository
|
||||
notices.type_2=Εργασία
|
||||
notices.desc=Περιγραφή
|
||||
notices.op=Λειτ.
|
||||
|
@ -3511,7 +3511,7 @@ users.organization_creation.description = Να επιτρέπεται η δημ
|
|||
|
||||
[action]
|
||||
create_repo=δημιούργησε το αποθετήριο <a href="%s">%s</a>
|
||||
rename_repo=μετονόμασε το αποθετήριο από <code>%[1]s</code> σε <a href="%[2]s">%[3]s</a>
|
||||
rename_repo=μετονόμασε το repository από <code>%[1]s</code> σε <a href="%[2]s">%[3]s</a>
|
||||
commit_repo=έκανε push στο <a href="%[2]s">%[3]s</a> του <a href="%[1]s">%[4]s</a>
|
||||
create_issue=`άνοιξε το ζήτημα <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
close_issue=`έκλεισε το ζήτημα <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
|
@ -3523,7 +3523,7 @@ comment_issue=`άφησε σχόλιο στο ζήτημα <a href="%[1]s">%[3]s
|
|||
comment_pull=`σχολίασε στο pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
merge_pull_request=`συγχώνευσε το pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
auto_merge_pull_request=`αυτόματη συγχώνευση του pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
transfer_repo=μετέφερε το αποθετήριο <code>%s</code> σε <a href="%s">%s</a>
|
||||
transfer_repo=μετέφερε το repository <code>%s</code> σε <a href="%s">%s</a>
|
||||
push_tag=ώθησε την ετικέτα <a href="%[2]s">%[3]s</a> σε <a href="%[1]s">%[4]s</a>
|
||||
delete_tag=διέγραψε την ετικέτα %[2]s από <a href="%[1]s">%[3]s</a>
|
||||
delete_branch=διέγραψε το κλάδο %[2]s από <a href="%[1]s">%[3]s</a>
|
||||
|
@ -3603,7 +3603,7 @@ title=Πακέτα
|
|||
desc=Διαχείριση πακέτων μητρώου.
|
||||
empty=Δεν υπάρχουν πακέτα ακόμα.
|
||||
empty.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο πακέτων, συμβουλευτείτε <a target="_blank" rel="noopener noreferrer" href="%s">τον οδηγό</a>.
|
||||
empty.repo=Μήπως ανεβάσατε ένα πακέτο, αλλά δεν εμφανίζεται εδώ; Πηγαίνετε στις <a href="%[1]s">ρυθμίσεις πακέτων</a> και συνδέστε το σε αυτό το αποθετήριο.
|
||||
empty.repo=Μήπως ανεβάσατε ένα πακέτο, αλλά δεν εμφανίζεται εδώ; Πηγαίνετε στις <a href="%[1]s">ρυθμίσεις πακέτων</a> και συνδέστε το σε αυτό το repository.
|
||||
registry.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο %s, συμβουλευτείτε τον <a target="_blank" rel="noopener noreferrer" href="%s">οδηγό</a>.
|
||||
filter.type=Τύπος
|
||||
filter.type.all=Όλα
|
||||
|
@ -3644,10 +3644,10 @@ composer.registry=Ρυθμίστε αυτό το μητρώο στο αρχεί
|
|||
composer.install=Για να εγκαταστήσετε το πακέτο χρησιμοποιώντας το Composer, εκτελέστε την ακόλουθη εντολή:
|
||||
composer.dependencies=Εξαρτήσεις
|
||||
composer.dependencies.development=Εξαρτήσεις Ανάπτυξης
|
||||
conan.details.repository=Αποθετήριο
|
||||
conan.details.repository=Repository
|
||||
conan.registry=Ρυθμίστε αυτό το μητρώο από τη γραμμή εντολών:
|
||||
conan.install=Για να εγκαταστήσετε το πακέτο χρησιμοποιώντας το Conan, εκτελέστε την ακόλουθη εντολή:
|
||||
conda.registry=Ρυθμίστε αυτό το μητρώο ως αποθετήριο Conda στο αρχείο <code>.condarc</code>:
|
||||
conda.registry=Ρυθμίστε αυτό το μητρώο ως repository Conda στο αρχείο <code>.condarc</code>:
|
||||
conda.install=Για να εγκαταστήσετε το πακέτο χρησιμοποιώντας το Conda, εκτελέστε την ακόλουθη εντολή:
|
||||
container.details.type=Τύπος Εικόνας
|
||||
container.details.platform=Πλατφόρμα
|
||||
|
@ -3705,8 +3705,8 @@ swift.registry=Ρυθμίστε αυτό το μητρώο από τη γραμ
|
|||
swift.install=Προσθέστε το πακέτο στο αρχείο <code>Package.swift</code>:
|
||||
swift.install2=και εκτελέστε την ακόλουθη εντολή:
|
||||
vagrant.install=Για προσθήκη ενός κυτίου Vagrant, εκτελέστε την ακόλουθη εντολή:
|
||||
settings.link=Σύνδεση αυτού του πακέτου με ένα αποθετήριο
|
||||
settings.link.description=Εάν συνδέσετε ένα πακέτο με ένα αποθετήριο, το πακέτο περιλαμβάνεται στη λίστα πακέτων του αποθετηρίου.
|
||||
settings.link=Σύνδεση αυτού του πακέτου με ένα repository
|
||||
settings.link.description=Εάν συνδέσετε ένα πακέτο με ένα repository, το πακέτο περιλαμβάνεται στη λίστα πακέτων του repository.
|
||||
settings.link.select=Επιλογή Αποθετηρίου
|
||||
settings.link.button=Ενημέρωση Συνδέσμου Αποθετηρίου
|
||||
settings.link.success=Ο σύνδεσμος αποθετηρίου ενημερώθηκε επιτυχώς.
|
||||
|
@ -3718,7 +3718,7 @@ settings.delete.success=Το πακέτο έχει διαγραφεί.
|
|||
settings.delete.error=Αποτυχία διαγραφής του πακέτου.
|
||||
owner.settings.cargo.title=Ευρετήριο μητρώου Cargo
|
||||
owner.settings.cargo.initialize=Αρχικοποίηση ευρετηρίου
|
||||
owner.settings.cargo.initialize.description=Απαιτείται ένα ειδικό αποθετήριο ευρετηρίου Git για τη χρήση του μητρώου Cargo. Χρησιμοποιώντας αυτή την επιλογή θα δημιουργηθεί ξανά το αποθετήριο και θα ρυθμιστεί αυτόματα.
|
||||
owner.settings.cargo.initialize.description=Απαιτείται ένα ειδικό repository ευρετηρίου Git για τη χρήση του μητρώου Cargo. Χρησιμοποιώντας αυτή την επιλογή θα δημιουργηθεί ξανά το repository και θα ρυθμιστεί αυτόματα.
|
||||
owner.settings.cargo.initialize.error=Αποτυχία αρχικοποίησης ευρετηρίου Cargo: %v
|
||||
owner.settings.cargo.initialize.success=Ο ευρετήριο Cargo δημιουργήθηκε με επιτυχία.
|
||||
owner.settings.cargo.rebuild=Ανανέωση ευρετηρίου
|
||||
|
@ -3811,7 +3811,7 @@ runners.task_list=Πρόσφατες εργασίες στον εκτελεστ
|
|||
runners.task_list.no_tasks=Δεν υπάρχει καμία εργασία ακόμα.
|
||||
runners.task_list.run=Εκτέλεση
|
||||
runners.task_list.status=Κατάσταση
|
||||
runners.task_list.repository=Αποθετήριο
|
||||
runners.task_list.repository=Repository
|
||||
runners.task_list.commit=Υποβολή
|
||||
runners.task_list.done_at=Ολοκλήρωσε Στις
|
||||
runners.edit_runner=Επεξεργασία Εκτελεστή
|
||||
|
|
|
@ -231,7 +231,6 @@ string.desc = Z - A
|
|||
[error]
|
||||
occurred = An error occurred
|
||||
report_message = If you believe that this is a Forgejo bug, please search for issues on <a href="%s" target="_blank">Codeberg</a> or open a new issue if necessary.
|
||||
invalid_csrf = Bad Request: invalid CSRF token
|
||||
not_found = The target couldn't be found.
|
||||
network_error = Network error
|
||||
server_internal = Internal server error
|
||||
|
@ -1441,8 +1440,7 @@ commitstatus.failure = Failure
|
|||
commitstatus.pending = Pending
|
||||
commitstatus.success = Success
|
||||
|
||||
ext_issues = Access to external issues
|
||||
ext_issues.desc = Link to an external issue tracker.
|
||||
ext_issues = External issues
|
||||
|
||||
projects = Projects
|
||||
projects.desc = Manage issues and pulls in project boards.
|
||||
|
@ -1619,9 +1617,9 @@ issues.no_content = No description provided.
|
|||
issues.close = Close issue
|
||||
issues.comment_pull_merged_at = merged commit %[1]s into %[2]s %[3]s
|
||||
issues.comment_manually_pull_merged_at = manually merged commit %[1]s into %[2]s %[3]s
|
||||
issues.close_comment_issue = Comment and close
|
||||
issues.close_comment_issue = Close with comment
|
||||
issues.reopen_issue = Reopen
|
||||
issues.reopen_comment_issue = Comment and reopen
|
||||
issues.reopen_comment_issue = Reopen with comment
|
||||
issues.create_comment = Comment
|
||||
issues.closed_at = `closed this issue <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at = `reopened this issue <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
|
@ -2025,8 +2023,7 @@ signing.wont_sign.commitssigned = The merge will not be signed as all the associ
|
|||
signing.wont_sign.approved = The merge will not be signed as the PR is not approved.
|
||||
signing.wont_sign.not_signed_in = You are not signed in.
|
||||
|
||||
ext_wiki = Access to external Wiki
|
||||
ext_wiki.desc = Link to an external wiki.
|
||||
ext_wiki = External Wiki
|
||||
|
||||
wiki = Wiki
|
||||
wiki.welcome = Welcome to the Wiki.
|
||||
|
@ -2766,6 +2763,26 @@ error.csv.unexpected = Can't render this file because it contains an unexpected
|
|||
error.csv.invalid_field_count = Can't render this file because it has a wrong number of fields in line %d.
|
||||
error.broken_git_hook = Git hooks of this repository seem to be broken. Please follow the <a target="_blank" rel="noreferrer" href="%s">documentation</a> to fix them, then push some commits to refresh the status.
|
||||
|
||||
[repo.permissions]
|
||||
code.read = <b>Read:</b> Access and clone the code of the repository.
|
||||
code.write = <b>Write:</b> Push to the repository, create branches and tags.
|
||||
issues.read = <b>Read:</b> Read and create issues and comments.
|
||||
issues.write = <b>Write:</b> Close issues and manage metadata like labels, milestones, assignees, due dates and dependencies.
|
||||
pulls.read = <b>Read:</b> Reading and create pull requests.
|
||||
pulls.write = <b>Write:</b> Close pull requests and manage metadata like labels, milestones, assignees, due dates and dependencies.
|
||||
releases.read = <b>Read:</b> View and download releases.
|
||||
releases.write = <b>Write:</b> Publish, edit and delete releases and their assets.
|
||||
wiki.read = <b>Read:</b> Read the integrated wiki and it's history.
|
||||
wiki.write = <b>Write:</b> Create, update and delete pages in the integrated wiki.
|
||||
projects.read = <b>Read:</b> Access repository project boards.
|
||||
projects.write = <b>Write:</b> Create projects and columns and edit them.
|
||||
packages.read = <b>Read:</b> View and download packages assigned to the repository.
|
||||
packages.write = <b>Write:</b> Publish and delete packages assigned to the repository.
|
||||
actions.read = <b>Read:</b> View integrated CI/CD pipelines and their logs.
|
||||
actions.write = <b>Write:</b> Manually trigger, restart, cancel or approve pending CI/CD pipelines.
|
||||
ext_issues = Access the link to an external issue tracker. The permissions are managed externally.
|
||||
ext_wiki = Access the link to an external wiki. The permissions are managed externally.
|
||||
|
||||
[graphs]
|
||||
component_loading = Loading %s...
|
||||
component_loading_failed = Could not load %s
|
||||
|
@ -3733,7 +3750,7 @@ management = Manage secrets
|
|||
|
||||
[actions]
|
||||
actions = Actions
|
||||
unit.desc = Manage integrated CI/CD pipelines with Forgejo Actions
|
||||
unit.desc = Manage integrated CI/CD pipelines with Forgejo Actions.
|
||||
|
||||
status.unknown = Unknown
|
||||
status.waiting = Waiting
|
||||
|
|
|
@ -147,6 +147,11 @@ value = Arvo
|
|||
rerun = Suorita uudelleen
|
||||
filter.clear = Tyhjennä suodattimet
|
||||
invalid_data = Virheellistä dataa: %v
|
||||
new_repo.title = Uusi repositorio
|
||||
new_org.title = Uusi organisaatio
|
||||
new_org.link = Uusi organisaatio
|
||||
new_repo.link = Uusi repositorio
|
||||
new_migrate.link = Uusi siirto
|
||||
|
||||
[aria]
|
||||
footer.links = Linkit
|
||||
|
|
|
@ -207,7 +207,7 @@ string.desc=Z - A
|
|||
|
||||
[error]
|
||||
occurred=Une erreur s’est produite
|
||||
report_message=Si vous pensez qu'il s'agit d'un bug Forgejo, veuillez consulter notre board <a href="%s" target="_blank">Codeberg</a> ou ouvrir un nouveau ticket si nécessaire.
|
||||
report_message=Si vous pensez qu'il s'agit d'un bug Forgejo, veuillez consulter les tickets de <a href="%s" target="_blank">Codeberg</a> ou ouvrir un nouveau ticket si nécessaire.
|
||||
missing_csrf=Requête incorrecte : aucun jeton CSRF présent
|
||||
invalid_csrf=Requête incorrecte : jeton CSRF invalide
|
||||
not_found=La cible n'a pu être trouvée.
|
||||
|
@ -223,7 +223,7 @@ platform_desc=Forgejo est confirmé fonctionner sur des systèmes d'exploitation
|
|||
lightweight=Léger
|
||||
lightweight_desc=Forgejo utilise peu de ressources. Il peut même tourner sur un Raspberry Pi très bon marché. Économisez l'énergie de vos serveurs !
|
||||
license=Open Source
|
||||
license_desc=Toutes les sources sont sur <a target="_blank" rel="noopener noreferrer" href="%[1]s">Forgejo</a> ! Rejoignez-nous et <a target="_blank" rel="noopener noreferrer" href="https ://codeberg.org/forgejo/forgejo">contribuez</a> à rendre ce projet encore meilleur !
|
||||
license_desc=Toutes les sources sont sur <a target="_blank" rel="noopener noreferrer" href="%[1]s">Forgejo</a> ! Rejoignez-nous et <a target="_blank" rel="noopener noreferrer" href="https ://codeberg.org/forgejo/forgejo">contribuez</a> à rendre ce projet encore meilleur. Ne craignez pas de devenir un·e contributeur·trice !
|
||||
|
||||
[install]
|
||||
install=Installation
|
||||
|
@ -558,6 +558,11 @@ removed_security_key.subject = Une clé de sécurité a été supprimée
|
|||
totp_disabled.subject = TOTP a été désactivé
|
||||
removed_security_key.no_2fa = Il n'y a plus de méthodes 2FA configurées ce qui signifie qu'il n'est plus nécessaire d'utiliser 2FA pour se connecter à votre compte.
|
||||
account_security_caution.text_1 = Si vous êtes à l’origine de cette action, vous pouvez ignorer ce courriel.
|
||||
totp_enrolled.text_1.no_webauthn = Vous venez d'activer TOTP pour votre compte. Cela signifie que pour toutes les prochaines connexions à votre compte, vous devrez utiliser TOTP comme méthode 2FA.
|
||||
totp_enrolled.subject = Vous avez activé TOTP comme méthode 2FA
|
||||
totp_enrolled.text_1.has_webauthn = Vous venez d'activer TOTP pour votre compte. Cela signifie que pour toutes les prochaines connexions à votre compte, vous pouvez utiliser TOTP comme méthode 2FA ou l'une de vos clés de sécurité.
|
||||
removed_security_key.text_1 = La clé de sécurité « %[1]s » vient d'être supprimée de votre compte.
|
||||
account_security_caution.text_2 = S'il ne s'agissait pas de vous, votre compte est compromis. Veuillez contacter les administrateurs du site.
|
||||
|
||||
[modal]
|
||||
yes=Oui
|
||||
|
@ -823,7 +828,7 @@ add_new_email=Ajouter une nouvelle adresse e-mail
|
|||
add_new_openid=Ajouter une nouvelle URI OpenID
|
||||
add_email=Ajouter une adresse courriel
|
||||
add_openid=Ajouter une URI OpenID
|
||||
add_email_confirmation_sent=Un e-mail de confirmation a été envoyé à "%s". Veuillez vérifier votre boîte de réception dans les %s suivants pour confirmer votre adresse e-mail.
|
||||
add_email_confirmation_sent=Un courriel de confirmation a été envoyé à « %s ». Pour confirmer votre adresse de courriel, veuillez vérifier votre boîte de réception et suivre le lien indiqué dans les prochains %s.
|
||||
add_email_success=La nouvelle adresse e-mail a été ajoutée.
|
||||
email_preference_set_success=L'e-mail de préférence a été défini avec succès.
|
||||
add_openid_success=La nouvelle adresse OpenID a été ajoutée.
|
||||
|
@ -1042,6 +1047,8 @@ pronouns = Pronoms
|
|||
pronouns_unspecified = Non spécifiés
|
||||
language.title = Langue par défaut
|
||||
keep_activity_private.description = Vous seul pourrez voir votre <a href="%s">activité publique</a>, ainsi que les administrateurs de l'instance.
|
||||
language.localization_project = Aidez-nous à traduire Forgejo dans votre langue ! <a href="%s">En savoir plus</a>.
|
||||
language.description = Cette langue sera enregistrée dans votre compte et utilisée comme langue par défaut après votre connexion.
|
||||
|
||||
[repo]
|
||||
new_repo_helper=Un dépôt contient tous les fichiers d’un projet, ainsi que l’historique de leurs modifications. Vous avez déjà ça ailleurs ? <a href="%s">Migrez-le ici.</a>
|
||||
|
@ -1421,7 +1428,7 @@ commitstatus.failure=Échec
|
|||
commitstatus.pending=En attente
|
||||
commitstatus.success=Succès
|
||||
|
||||
ext_issues=Accès aux tickets externes
|
||||
ext_issues=Tickets externes
|
||||
ext_issues.desc=Lien vers un gestionnaire de tickets externe.
|
||||
|
||||
projects=Projets
|
||||
|
@ -1602,9 +1609,9 @@ issues.no_content=Sans contenu.
|
|||
issues.close=Fermer le ticket
|
||||
issues.comment_pull_merged_at=a fusionné la révision %[1]s dans %[2]s %[3]s
|
||||
issues.comment_manually_pull_merged_at=a fusionné manuellement la révision %[1]s dans %[2]s %[3]s
|
||||
issues.close_comment_issue=Commenter et fermer
|
||||
issues.close_comment_issue=Fermer avec le commentaire
|
||||
issues.reopen_issue=Rouvrir
|
||||
issues.reopen_comment_issue=Commenter et réouvrir
|
||||
issues.reopen_comment_issue=Réouvrir avec le commentaire
|
||||
issues.create_comment=Commenter
|
||||
issues.closed_at=`a fermé ce ticket <a id="%[1]s" href="#%[1]s">%[2]s</a>.`
|
||||
issues.reopened_at=`a réouvert ce ticket <a id="%[1]s" href="#%[1]s">%[2]s</a>.`
|
||||
|
@ -1993,7 +2000,7 @@ signing.wont_sign.commitssigned=La fusion ne sera pas signée car ses révisions
|
|||
signing.wont_sign.approved=La fusion ne sera pas signée car la demande d'ajout n'a pas été approuvée.
|
||||
signing.wont_sign.not_signed_in=Vous n'êtes pas connecté.
|
||||
|
||||
ext_wiki=Accès au wiki externe
|
||||
ext_wiki=Wiki externe
|
||||
ext_wiki.desc=Lier un wiki externe.
|
||||
|
||||
wiki=Wiki
|
||||
|
@ -2418,14 +2425,14 @@ settings.protect_enable_merge_desc=Toute personne ayant un accès en écriture s
|
|||
settings.protect_whitelist_committers=Liste blanche des soumissions (push)
|
||||
settings.protect_whitelist_committers_desc=Seuls les utilisateurs ou les équipes autorisés pourront soumettre sur cette branche (sans forcer).
|
||||
settings.protect_whitelist_deploy_keys=Mettez les clés de déploiement sur liste blanche avec accès en écriture pour soumettre.
|
||||
settings.protect_whitelist_users=Utilisateurs sur liste blanche :
|
||||
settings.protect_whitelist_users=Utilisateurs sur liste blanche pour pousser
|
||||
settings.protect_whitelist_search_users=Rechercher des utilisateurs…
|
||||
settings.protect_whitelist_teams=Équipes sur liste blanche :
|
||||
settings.protect_whitelist_teams=Équipes sur liste blanche pour pousser
|
||||
settings.protect_whitelist_search_teams=Rechercher des équipes…
|
||||
settings.protect_merge_whitelist_committers=Activer la liste blanche pour la fusion
|
||||
settings.protect_merge_whitelist_committers_desc=N'autoriser que les utilisateurs et les équipes en liste blanche d'appliquer les demandes de fusion sur cette branche.
|
||||
settings.protect_merge_whitelist_users=Utilisateurs en liste blanche de fusion :
|
||||
settings.protect_merge_whitelist_teams=Équipes en liste blanche de fusion :
|
||||
settings.protect_merge_whitelist_users=Utilisateurs en liste blanche pour fusionner
|
||||
settings.protect_merge_whitelist_teams=Équipes en liste blanche pour fusionner
|
||||
settings.protect_check_status_contexts=Activer le contrôle de status
|
||||
settings.protect_status_check_patterns=Motifs de vérification des statuts :
|
||||
settings.protect_status_check_patterns_desc=Entrez des motifs pour spécifier quelles vérifications doivent réussir avant que des branches puissent être fusionnées. Un motif par ligne. Un motif ne peut être vide.
|
||||
|
@ -2434,12 +2441,12 @@ settings.protect_check_status_contexts_list=Contrôles qualité trouvés au cour
|
|||
settings.protect_status_check_matched=Correspondant
|
||||
settings.protect_invalid_status_check_pattern=Motif de vérification des statuts incorrect : « %s ».
|
||||
settings.protect_no_valid_status_check_patterns=Aucun motif de vérification des statuts valide.
|
||||
settings.protect_required_approvals=Minimum d'approbations requis :
|
||||
settings.protect_required_approvals=Approbations requises
|
||||
settings.protect_required_approvals_desc=Permet de fusionner les demandes d’ajout lorsque suffisamment d’évaluation sont positives.
|
||||
settings.protect_approvals_whitelist_enabled=Restreindre les approbations aux utilisateurs ou aux équipes en liste blanche
|
||||
settings.protect_approvals_whitelist_enabled_desc=Seuls les évaluations des utilisateurs ou des équipes suivantes compteront dans les approbations requises. Si laissé vide, les évaluations de toute personne ayant un accès en écriture seront comptabilisées à la place.
|
||||
settings.protect_approvals_whitelist_users=Évaluateurs autorisés :
|
||||
settings.protect_approvals_whitelist_teams=Équipes d’évaluateurs autorisés :
|
||||
settings.protect_approvals_whitelist_users=Évaluateurs autorisés
|
||||
settings.protect_approvals_whitelist_teams=Équipes d’évaluateurs autorisés
|
||||
settings.dismiss_stale_approvals=Révoquer automatiquement les approbations périmées
|
||||
settings.dismiss_stale_approvals_desc=Lorsque des nouvelles révisions changent le contenu de la demande d’ajout, les approbations existantes sont révoquées.
|
||||
settings.ignore_stale_approvals=Ignorer les approbations obsolètes
|
||||
|
@ -2449,9 +2456,9 @@ settings.require_signed_commits_desc=Rejeter les soumissions sur cette branche l
|
|||
settings.protect_branch_name_pattern=Motif de nom de branche protégé
|
||||
settings.protect_branch_name_pattern_desc=Motifs de nom de branche protégé. Consultez la <a href="%s">documentation</a> pour la syntaxe du motif. Exemples : main, release/**
|
||||
settings.protect_patterns=Motifs
|
||||
settings.protect_protected_file_patterns=Liste des fichiers et motifs protégés (séparés par un point virgule ";") :
|
||||
settings.protect_protected_file_patterns_desc=Les fichiers protégés ne peuvent être modifiés, même si l'utilisateur a le droit d'ajouter, éditer ou supprimer des fichiers dans cette branche. Plusieurs motifs peuvent être séparés par un point-virgule (";"). Veuillez voir <a href="%s">github.com/gobwas/glob</a> la documentation pour la syntaxe des motifs. Exemples : <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||
settings.protect_unprotected_file_patterns=Liste des fichiers et motifs exclus (séparés par un point virgule ";") :
|
||||
settings.protect_protected_file_patterns=Motifs de fichiers protégés (séparés par un point virgule ";")
|
||||
settings.protect_protected_file_patterns_desc=Les fichiers protégés ne peuvent être modifiés, même si l'utilisateur a le droit d'ajouter, éditer ou supprimer des fichiers dans cette branche. Plusieurs motifs peuvent être séparés par un point-virgule (";"). Veuillez voir <a href="%[1]s">%[2]s</a> la documentation pour la syntaxe des motifs. Exemples : <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||
settings.protect_unprotected_file_patterns=Motifs de fichiers non protégés (séparés par un point virgule ";")
|
||||
settings.protect_unprotected_file_patterns_desc=Les fichiers non-protégés qui peuvent être modifiés si l'utilisateur a le droit d'écriture, prenant le pas sur les restrictions de push. Plusieurs motifs peuvent être séparés par un point-virgule (";"). Veuillez voir <a href="%[1]s">%[2]s</a> la documentation pour la syntaxe des motifs. Exemples : <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||
settings.add_protected_branch=Activer la protection
|
||||
settings.delete_protected_branch=Désactiver la protection
|
||||
|
@ -2811,6 +2818,23 @@ settings.federation_following_repos = Les URL des dépôts suivis séparés par
|
|||
settings.federation_not_enabled = La fédération n'est pas activée pour votre instance.
|
||||
comments.edit.already_changed = Impossible de sauvegarder les changements du commentaire car son contenu a déjà été modifié par un autre utilisateur. Veuillez recharger la page et essayer de l'éditer à nouveau pour éviter d'écraser ses changements
|
||||
settings.federation_apapiurl = URL de fédération de ce dépôt. A copier-coller dans les paramètres de fédérations d'un autre dépôt comme URL d'un dépôt à suivre.
|
||||
mirror_denied_combination = Il n'est pas possible de combiner une authentification par clé publique et par mot de passe.
|
||||
mirror_public_key = Clé SSH publique
|
||||
mirror_use_ssh.text = Utiliser l'authentification SSH
|
||||
mirror_use_ssh.helper = Forgejo va créer un miroir du dépôt via Git sur SSH et créer une paire de clés pour vous lorsque vous sélectionnez cette option. Vous devez vous assurer que la clé publique générée est autorisée à pousser dans le dépôt de destination. Il n'est pas possible d'utiliser l'autorisation basée sur un mot de passe si vous choisissez cette option.
|
||||
no_eol.text = Pas d'EOL
|
||||
mirror_use_ssh.not_available = L'authentification par SSH n'est pas disponible.
|
||||
no_eol.tooltip = Ce fichier ne contient pas de caractère final de fin de ligne.
|
||||
release.type_attachment = Pièce jointe
|
||||
settings.transfer_quota_exceeded = Le nouvel utilisateur (%s) a dépassé son quota. Le dépôt n'a pas été transféré.
|
||||
settings.pull_mirror_sync_quota_exceeded = Quota dépassé, les modifications ne sont pas tirées.
|
||||
activity.commit = Activité de commit
|
||||
settings.mirror_settings.push_mirror.copy_public_key = Copier la clé publique
|
||||
release.asset_external_url = URL externe
|
||||
release.invalid_external_url = URL externe non valable : « %s »
|
||||
milestones.filter_sort.name = Nom
|
||||
settings.mirror_settings.push_mirror.none_ssh = Aucun
|
||||
settings.protect_new_rule = Créer une nouvelle règle de protection de branche
|
||||
|
||||
[graphs]
|
||||
component_loading=Chargement de %s…
|
||||
|
|
|
@ -3,7 +3,7 @@ home=Forsíða
|
|||
dashboard=Stjórnborð
|
||||
explore=Vafra
|
||||
help=Hjálp
|
||||
sign_in=Skrá Inn
|
||||
sign_in=Skrá inn
|
||||
sign_in_or=eða
|
||||
sign_out=Skrá Út
|
||||
sign_up=Nýskráning
|
||||
|
@ -15,9 +15,9 @@ page=Síða
|
|||
template=Sniðmát
|
||||
language=Tungumál
|
||||
notifications=Tilkynningar
|
||||
active_stopwatch=Virk Tímamæling
|
||||
active_stopwatch=Virk tímamæling
|
||||
create_new=Skapa…
|
||||
user_profile_and_more=Notandasíða og Stillingar…
|
||||
user_profile_and_more=Notandasíða og stillingar…
|
||||
signed_in_as=Skráð(ur) inn sem
|
||||
toc=Efnisyfirlit
|
||||
licenses=Hugbúnaðarleyfi
|
||||
|
@ -111,6 +111,8 @@ concept_code_repository=Hugbúnaðarsafn
|
|||
|
||||
name=Heiti
|
||||
value=Gildi
|
||||
sign_in_with_provider = Skrá inn með %s
|
||||
enable_javascript = Þessi síða krefst JavaScript.
|
||||
|
||||
[aria]
|
||||
|
||||
|
|
|
@ -471,6 +471,11 @@ openid_signin_desc = Inserisci il tuo URI OpenID. Per esempio: alice.openid.exam
|
|||
password_pwned = La password che hai scelto è in un <a target="_blank" rel="noopener noreferrer" href="%s">elenco di password rubate</a> precedentemente esposte a violazioni di dati pubblici. Riprova con una password diversa e valuta di modificare questa password anche altrove.
|
||||
tab_signup = Registrati
|
||||
tab_signin = Accedi
|
||||
back_to_sign_in = Torna alla schermata d'accesso
|
||||
sign_in_openid = Procedi con OpenID
|
||||
hint_login = Hai già un'utenza? <a href="%s">Accedi!</a>
|
||||
hint_register = Non hai un'utenza? <a href="%s">Registrati ora.</a>
|
||||
sign_up_button = Registrati ora.
|
||||
|
||||
[mail]
|
||||
view_it_on=Visualizza su %s
|
||||
|
@ -539,6 +544,21 @@ activate_email.title = %s, verifica il tuo indirizzo email
|
|||
admin.new_user.text = <a href="%s">Clicca qui</a> per gestire questo utente dal pannello di amministrazione.
|
||||
team_invite.text_1 = %[1]s ti ha invitato a far parte del team %[2]s nell'organizzazione %[3]s.
|
||||
team_invite.text_3 = Nota: Questo invito è destinato a %[1]s. Se non ti aspettavi questo invito, puoi ignorare questa email.
|
||||
primary_mail_change.subject = La tua mail principale è stata cambiata
|
||||
removed_security_key.no_2fa = Non ci sono più altri metodi di autenticazione a due fattori configurati, ergo non c'è più bisogno di accedere alla tua utenza tramite tale autenticazione.
|
||||
primary_mail_change.text_1 = La mail principale della tua utenza è appena stata cambiata in %[1]s. Ciò significa che questo indirizzo di posta elettronica non riceverà più notifiche mail da quest'utenza.
|
||||
totp_disabled.subject = La TOTP è stata disabilitata
|
||||
totp_disabled.no_2fa = Non ci sono più altri metodi d'autenticazione a due fattori configurati, ergo non c'è più bisogno di accedere alla tua utenza con tale autenticazione.
|
||||
removed_security_key.subject = È stata rimossa una chiave di sicurezza
|
||||
removed_security_key.text_1 = La chiave di sicurezza "%[1]s" è appena stata rimossa dalla tua utenza.
|
||||
totp_disabled.text_1 = La password a tempo usa e getta (TOTP) della tua utenza è appena stata disabilitata.
|
||||
totp_enrolled.subject = Hai attivato la TOTP come metodo d'autenticazione a due fattori
|
||||
totp_enrolled.text_1.no_webauthn = Hai appena attivato la TOTP per la tua utenza. Ciò significa che dovrai usarla come metodo d'autenticazione a due fattori per tutti i tuoi accessi futuri.
|
||||
totp_enrolled.text_1.has_webauthn = Hai appena attivato la TOTP per la tua utenza. Ciò significa che dovrai usare come metodo d'autenticazione a due fattori per i tuoi accessi futuri tale TOTP o una delle tue chiavi di sicurezza.
|
||||
password_change.subject = La tua password è stata modificata
|
||||
password_change.text_1 = La password della tua utenza è appena stata modificata.
|
||||
account_security_caution.text_1 = Se sei statə tu, puoi ignorare questa mail.
|
||||
account_security_caution.text_2 = Se non sei statə tu, la tua utenza è compromessa. Contatta l'amministrazione del sito.
|
||||
|
||||
|
||||
[modal]
|
||||
|
@ -1022,6 +1042,8 @@ pronouns = Pronomi
|
|||
pronouns_custom = Personalizzato
|
||||
pronouns_unspecified = Non specificato
|
||||
language.title = Lingua predefinita
|
||||
language.description = Questa lingua verrà salvata nella tua utenza e verrà usata come predefinita ogni volta che farai l'accesso.
|
||||
language.localization_project = Aiutaci a tradurre Forgejo nella tua lingua! <a href="%s">Più informazioni</a>.
|
||||
|
||||
[repo]
|
||||
owner=Proprietario
|
||||
|
@ -3863,6 +3885,7 @@ exact_tooltip = Includi solo i risultati che corrispondono esattamente al termin
|
|||
issue_kind = Cerca segnalazioni...
|
||||
pull_kind = Cerca richieste...
|
||||
exact = Esatto
|
||||
milestone_kind = Ricerca tappe...
|
||||
|
||||
[munits.data]
|
||||
gib = GiB
|
||||
|
|
|
@ -158,6 +158,13 @@ filter.not_template = テンプレートではない
|
|||
invalid_data = 無効なデータ: %v
|
||||
more_items = さらに表示
|
||||
copy_generic = クリップボードへコピー
|
||||
new_repo.title = 新しいリポジトリ
|
||||
new_migrate.title = 新しいマイグレーション
|
||||
new_org.title = 新しい組織
|
||||
new_repo.link = 新しいリポジトリ
|
||||
new_migrate.link = 新しいマイグレーション
|
||||
new_org.link = 新しい組織
|
||||
test = テスト
|
||||
|
||||
[aria]
|
||||
navbar=ナビゲーションバー
|
||||
|
|
|
@ -159,6 +159,18 @@ fuzzy_tooltip = Įtraukti rezultatus, kurie taip pat labai atitinka paieškos te
|
|||
repo_kind = Ieškoti saugyklų...
|
||||
code_search_unavailable = Kodų paieška šiuo metu nepasiekiama. Kreipkis į svetainės administratorių.
|
||||
org_kind = Ieškoti organizacijų...
|
||||
union = Bendrinis
|
||||
code_search_by_git_grep = Dabartiniai kodo paieškos rezultatai pateikiami atliekant „git grep“. Rezultatai gali būti geresni, jei svetainės administratorius įjungs kodo indeksuotoją.
|
||||
package_kind = Ieškoti paketų...
|
||||
project_kind = Ieškoti projektų...
|
||||
commit_kind = Ieškoti įsipareigojimų...
|
||||
runner_kind = Ieškoti vykdyklių...
|
||||
no_results = Nerasta atitinkamų rezultatų.
|
||||
issue_kind = Ieškoti problemų...
|
||||
branch_kind = Ieškoti šakų...
|
||||
milestone_kind = Ieškoti gairių...
|
||||
pull_kind = Ieškoti sujungimų...
|
||||
keyword_search_unavailable = Ieškoti pagal raktažodį šiuo metu nepasiekiamas. Susisiekite su svetainės administratoriumi.
|
||||
|
||||
[actions]
|
||||
workflow.disable = Išjungti darbo eigą
|
||||
|
@ -171,6 +183,9 @@ runs.empty_commit_message = (tuščias įsipareigojimo pranešimas)
|
|||
submodule = Pomodulis
|
||||
changed_filemode = %[1]s → %[2]s
|
||||
symbolic_link = Virtualusis aplankas
|
||||
directory = Katalogas
|
||||
executable_file = Vykdomasis failas
|
||||
normal_file = Įprastas failas
|
||||
|
||||
[projects]
|
||||
deleted.display_name = Ištrintas projektas
|
||||
|
@ -183,3 +198,63 @@ filepreview.truncated = Peržiūra buvo sutrumpinta
|
|||
|
||||
[mail]
|
||||
reset_password.text = Jei tai buvote jūs, spustelėkite toliau esančią nuorodą, kad atkurtumėte savo paskyrą per <b>%s</b>:
|
||||
|
||||
[heatmap]
|
||||
contributions_one = įnašas
|
||||
contributions_few = įnašai
|
||||
less = Mažiau
|
||||
more = Daugiau
|
||||
number_of_contributions_in_the_last_12_months = %s įnašų per pastaruosius 12 mėnesių
|
||||
contributions_zero = Įnašų nėra
|
||||
contributions_format = {contributions} {year} {month} {day}
|
||||
|
||||
[aria]
|
||||
navbar = Naršymo juosta
|
||||
footer = Puslapinė poraštė
|
||||
footer.software = Apie šią programinę įrangą
|
||||
footer.links = Nuorodos
|
||||
|
||||
[editor]
|
||||
buttons.quote.tooltip = Cituoti tekstą
|
||||
buttons.code.tooltip = Pridėti kodą
|
||||
buttons.link.tooltip = Pridėti nuorodą
|
||||
buttons.heading.tooltip = Pridėti antraštę
|
||||
buttons.bold.tooltip = Pridėti pusjuodį tekstą
|
||||
buttons.italic.tooltip = Pridėti kursyvinį tekstą
|
||||
|
||||
[error]
|
||||
network_error = Tinklo klaida
|
||||
server_internal = Vidinio serverio klaida
|
||||
|
||||
[startpage]
|
||||
app_desc = Nesudėtinga, savarankiškai teikiama „Git“ paslauga
|
||||
install = Lengva įdiegti
|
||||
|
||||
[install]
|
||||
path = Kelias
|
||||
err_admin_name_is_reserved = Administratoriaus naudotojo vardas netinkamas. Naudotojo vardas yra rezervuotas.
|
||||
enable_update_checker = Įjungti naujinimų tikrintuvą
|
||||
env_config_keys = Aplinkos konfigūracija
|
||||
db_title = Duomenų bazės nustatymai
|
||||
db_type = Duomenų bazės tipas
|
||||
user = Naudotojo vardas
|
||||
password = Slaptažodis
|
||||
db_name = Duomenų bazės pavadinimas
|
||||
db_schema = Schema
|
||||
ssl_mode = SSL
|
||||
host = Pagrindinis komputeris
|
||||
general_title = Bendrieji nustatymai
|
||||
email_title = El. pašto nustatymai
|
||||
federated_avatar_lookup.description = Ieškokite pseudoportretų naudojant „Libravatar“.
|
||||
db_schema_helper = Palikite tuščią, jei tai numatytoji duomenų bazė („public“).
|
||||
err_empty_admin_password = Administratoriaus slaptažodis negali būti tuščias.
|
||||
err_empty_admin_email = Administratoriaus el. paštas negali būti tuščias.
|
||||
|
||||
[explore]
|
||||
go_to = Eiti į
|
||||
code = Kodas
|
||||
|
||||
[auth]
|
||||
remember_me = Prisiminti šį įrenginį
|
||||
forgot_password_title = Pamirštas slaptažodis
|
||||
forgot_password = Pamiršote slaptažodį?
|
|
@ -24,3 +24,114 @@ notifications = Varslinger
|
|||
create_new = Opprett…
|
||||
user_profile_and_more = Profil og innstillinger…
|
||||
signed_in_as = Logget inn som
|
||||
confirm_delete_selected = Bekreft sletting av alle valgte elementer?
|
||||
dashboard = Dashbord
|
||||
download_logs = Last ned logger
|
||||
copy_hash = Kopier hash
|
||||
more_items = Flere elementer
|
||||
passcode = Adgangskode
|
||||
webauthn_insert_key = Skriv inn din sikkerhetsnøkkel
|
||||
webauthn_use_twofa = Bruk tofaktorkode fra din mobil
|
||||
organization = Organisasjon
|
||||
mirror = Speil
|
||||
new_mirror = Ny speiling
|
||||
repository = Repositorium
|
||||
new_project = Nytt prosjekt
|
||||
new_project_column = Ny kolonne
|
||||
webauthn_error = Klarte ikke lese sikkerhetsnøkkelen.
|
||||
webauthn_unsupported_browser = Nettleseren din støtter ikke WebAuthn.
|
||||
webauthn_error_unknown = En ukjent feil oppstod. Vennligst prøv igjen.
|
||||
webauthn_error_insecure = WebAuhn støtter kun sikre forbindelser. For testing over HTTP kan du bruke verten "localhost" eller "127.0.0.1"
|
||||
admin_panel = Nettsideadministrasjon
|
||||
settings = Innstillinger
|
||||
your_profile = Profil
|
||||
your_starred = Stjernemerket
|
||||
your_settings = Innstillinger
|
||||
new_repo.title = Nytt repositorium
|
||||
new_migrate.title = Ny migrasjon
|
||||
new_org.title = Ny organisasjon
|
||||
new_repo.link = Nytt repositorium
|
||||
new_migrate.link = Ny migrasjon
|
||||
new_org.link = Ny organisasjon
|
||||
all = Alle
|
||||
sources = Kilder
|
||||
mirrors = Speilinger
|
||||
activities = Aktiviteter
|
||||
rss_feed = RSS feed
|
||||
retry = Prøv igjen
|
||||
rerun = Kjør på nytt
|
||||
rerun_all = Kjør alle jobber på nytt
|
||||
save = Lagre
|
||||
cancel = Avbryt
|
||||
forks = Forks
|
||||
milestones = Milepæler
|
||||
ok = OK
|
||||
test = Test
|
||||
loading = Laster inn…
|
||||
error = Feil
|
||||
go_back = Gå tilbake
|
||||
never = Aldri
|
||||
invalid_data = Ugyldig data: %v
|
||||
unknown = Ukjent
|
||||
pin = Pin
|
||||
artifacts = Artefakter
|
||||
archived = Arkivert
|
||||
concept_system_global = Global
|
||||
add = Legg til
|
||||
add_all = Legg til alle
|
||||
remove = Fjern
|
||||
remove_all = Fjern alle
|
||||
remove_label_str = Fjern element "%s"
|
||||
edit = Rediger
|
||||
view = Vis
|
||||
enabled = Aktivert
|
||||
disabled = Deaktivert
|
||||
locked = Låst
|
||||
copy = Kopier
|
||||
copy_generic = Kopier til utklippstavlen
|
||||
copy_url = Kopier URL
|
||||
copy_content = Kopier innhold
|
||||
copy_success = Kopiert!
|
||||
copy_error = Kopiering mislyktes
|
||||
copy_type_unsupported = Denne filtypen kan ikke kopieres
|
||||
write = Skriv
|
||||
preview = Forhåndsvis
|
||||
concept_user_individual = Individuell
|
||||
concept_code_repository = Repositorium
|
||||
concept_user_organization = Organisasjon
|
||||
show_timestamps = Vis tidsstempler
|
||||
show_log_seconds = Vis sekunder
|
||||
show_full_screen = Vis fullskjerm
|
||||
name = Navn
|
||||
value = Verdi
|
||||
filter = Filter
|
||||
filter.clear = Tøm filtre
|
||||
filter.is_archived = Arkivert
|
||||
filter.not_archived = Ikke arkivert
|
||||
filter.is_mirror = Speilinger
|
||||
filter.not_mirror = Ikke speilinger
|
||||
filter.is_template = Maler
|
||||
filter.not_template = Ikke maler
|
||||
filter.public = Offentlig
|
||||
filter.private = Privat
|
||||
explore = Utforsk
|
||||
active_stopwatch = Aktiv tidsregistrering
|
||||
home = Hjem
|
||||
help = Hjelp
|
||||
logo = Logo
|
||||
sign_in = Logg inn
|
||||
sign_in_with_provider = Logg inn med %s
|
||||
sign_in_or = eller
|
||||
sign_out = Logg ut
|
||||
sign_up = Opprett konto
|
||||
confirm_delete_artifact = Er du sikker på at du vil slette artefakten "%s" ?
|
||||
|
||||
[search]
|
||||
search = Søk...
|
||||
type_tooltip = Søketype
|
||||
fuzzy = Fuzzy
|
||||
union = Union
|
||||
|
||||
[auth]
|
||||
verify = Bekreft
|
||||
sign_up_button = Opprett konto nå.
|
|
@ -1406,7 +1406,7 @@ commitstatus.failure=Неудача
|
|||
commitstatus.pending=Ожидание
|
||||
commitstatus.success=Успешно
|
||||
|
||||
ext_issues=Доступ ко внешним задачам
|
||||
ext_issues=Внешние задачи
|
||||
ext_issues.desc=Ссылка на внешнюю систему отслеживания задач.
|
||||
|
||||
projects=Проекты
|
||||
|
@ -1587,9 +1587,9 @@ issues.no_content=Описание отсутствует.
|
|||
issues.close=Закрыть задачу
|
||||
issues.comment_pull_merged_at=коммит %[1]s был добавлен в %[2]s %[3]s
|
||||
issues.comment_manually_pull_merged_at=коммит %[1]s был вручную добавлен в %[2]s %[3]s
|
||||
issues.close_comment_issue=Прокомментировать и закрыть
|
||||
issues.close_comment_issue=Закрыть комментарием
|
||||
issues.reopen_issue=Открыть снова
|
||||
issues.reopen_comment_issue=Прокомментировать и открыть снова
|
||||
issues.reopen_comment_issue=Открыть снова комментарием
|
||||
issues.create_comment=Комментировать
|
||||
issues.closed_at=`задача была закрыта <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at=`задача была открыта снова <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
|
@ -1964,7 +1964,7 @@ signing.wont_sign.commitssigned=Слияние не будет подписан
|
|||
signing.wont_sign.approved=Слияние не будет подписано, так как запрос на слияние не одобрен.
|
||||
signing.wont_sign.not_signed_in=Вы не вошли в систему.
|
||||
|
||||
ext_wiki=Доступ ко внешней вики
|
||||
ext_wiki=Внешняя вики
|
||||
ext_wiki.desc=Ссылка на внешнюю вики.
|
||||
|
||||
wiki=Вики
|
||||
|
@ -3332,7 +3332,7 @@ config.allow_only_external_registration=Регистрация только че
|
|||
config.enable_openid_signup=Саморегистрация через OpenID
|
||||
config.enable_openid_signin=Вход через OpenID
|
||||
config.show_registration_button=Кнопка регистрации
|
||||
config.require_sign_in_view=Для просмотра содержимого необходима авторизация
|
||||
config.require_sign_in_view=Требовать авторизацию для просмотра содержимого
|
||||
config.mail_notify=Уведомления по эл. почте
|
||||
config.enable_captcha=CAPTCHA
|
||||
config.active_code_lives=Срок действия кода активации учётной записи
|
||||
|
@ -3964,3 +3964,7 @@ filepreview.truncated = Предпросмотр был обрезан
|
|||
|
||||
[translation_meta]
|
||||
test = хи-хи!
|
||||
|
||||
[repo.permissions]
|
||||
code.write = <b>Запись:</b> отправка изменений в репозиторий, создание веток и тегов.
|
||||
code.read = <b>Чтение:</b> доступ к исходному коду репозитория и клонированию.
|
|
@ -1427,7 +1427,7 @@ commitstatus.failure=失败
|
|||
commitstatus.pending=待定
|
||||
commitstatus.success=成功
|
||||
|
||||
ext_issues=访问外部工单
|
||||
ext_issues=外部工单
|
||||
ext_issues.desc=链接到外部工单跟踪系统。
|
||||
|
||||
projects=项目
|
||||
|
@ -1608,9 +1608,9 @@ issues.no_content=没有提供说明。
|
|||
issues.close=关闭工单
|
||||
issues.comment_pull_merged_at=已合并提交 %[1]s 到 %[2]s %[3]s
|
||||
issues.comment_manually_pull_merged_at=手动合并提交 %[1]s 到 %[2]s %[3]s
|
||||
issues.close_comment_issue=评论并关闭
|
||||
issues.close_comment_issue=关闭评论
|
||||
issues.reopen_issue=重新开启
|
||||
issues.reopen_comment_issue=评论并重新开启
|
||||
issues.reopen_comment_issue=重新打开评论
|
||||
issues.create_comment=评论
|
||||
issues.closed_at=`于 <a id="%[1]s" href="#%[1]s">%[2]s</a> 关闭此工单`
|
||||
issues.reopened_at=`重新打开此问题 <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
|
@ -1999,7 +1999,7 @@ signing.wont_sign.commitssigned=合并将不会被签名,因为所有相关的
|
|||
signing.wont_sign.approved=合并将不会被签名,因为合并请求未被批准。
|
||||
signing.wont_sign.not_signed_in=您还没有登录。
|
||||
|
||||
ext_wiki=访问外部百科
|
||||
ext_wiki=外部百科
|
||||
ext_wiki.desc=链接到外部 wiki。
|
||||
|
||||
wiki=百科
|
||||
|
@ -2330,7 +2330,7 @@ settings.event_repository_desc=创建或删除仓库
|
|||
settings.event_header_issue=工单事件
|
||||
settings.event_issues=工单
|
||||
settings.event_issues_desc=工单已打开、已关闭、已重新打开或已编辑。
|
||||
settings.event_issue_assign=工单已分配
|
||||
settings.event_issue_assign=工单已指派
|
||||
settings.event_issue_assign_desc=工单已被指派或取消指派。
|
||||
settings.event_issue_label=工单已分类
|
||||
settings.event_issue_label_desc=工单标签被更新或清除。
|
||||
|
@ -3818,7 +3818,7 @@ management=密钥管理
|
|||
[actions]
|
||||
actions=Actions
|
||||
|
||||
unit.desc=使用 Forgejo Actions 管理集成的 CI/CD 管道
|
||||
unit.desc=使用 Forgejo Actions 管理集成的 CI/CD 管道。
|
||||
|
||||
status.unknown=未知
|
||||
status.waiting=等待中
|
||||
|
@ -3981,3 +3981,23 @@ filepreview.truncated = 预览已被截断
|
|||
|
||||
[translation_meta]
|
||||
test = 好的
|
||||
|
||||
[repo.permissions]
|
||||
code.write = <b>写入:</b>推送到仓库,创建分支和标签。
|
||||
code.read = <b>读取:</b>访问并克隆仓库的代码。
|
||||
actions.read = <b>读取:</b>查看集成的 CI/CD 管道及其日志。
|
||||
issues.write = <b>写入:</b>关闭工单并管理元数据,如标签、里程碑、指派成员、截止日期和依赖。
|
||||
releases.write = <b>写入:</b>发布、编辑和删除版本发布及其资产。
|
||||
issues.read = <b>读取:</b>阅读并创建工单和评论。
|
||||
pulls.read = <b>读取:</b>阅读并创建合并请求。
|
||||
releases.read = <b>读取:</b>查看并下载版本发布。
|
||||
wiki.read = <b>读取:</b>阅读集成的百科及其历史。
|
||||
wiki.write = <b>写入:</b>在集成的百科中创建、更新和删除页面。
|
||||
projects.read = <b>读取:</b>访问仓库项目看板。
|
||||
packages.read = <b>读取:</b>查看并下载指派给仓库的软件包。
|
||||
packages.write = <b>写入:</b>发布并删除指派给仓库的软件包。
|
||||
actions.write = <b>写入:</b>手动触发、重启、取消或批准待处理的 CI/CD 管道。
|
||||
ext_issues = 访问外部工单系统的链接。权限由外部管理。
|
||||
ext_wiki = 访问外部百科的链接。权限由外部管理。
|
||||
projects.write = <b>写入:</b>创建项目和列并进行编辑。
|
||||
pulls.write = <b>写入:</b>关闭合并请求并管理元数据,如标签、里程碑、指派成员、截止日期和依赖。
|
2048
package-lock.json
generated
2048
package-lock.json
generated
File diff suppressed because it is too large
Load diff
24
package.json
24
package.json
|
@ -4,8 +4,8 @@
|
|||
"node": ">= 18.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@citation-js/core": "0.7.11",
|
||||
"@citation-js/plugin-bibtex": "0.7.11",
|
||||
"@citation-js/core": "0.7.14",
|
||||
"@citation-js/plugin-bibtex": "0.7.16",
|
||||
"@citation-js/plugin-software-formats": "0.6.1",
|
||||
"@github/markdown-toolbar-element": "2.2.3",
|
||||
"@github/relative-time-element": "4.4.3",
|
||||
|
@ -29,10 +29,10 @@
|
|||
"idiomorph": "0.3.0",
|
||||
"jquery": "3.7.1",
|
||||
"katex": "0.16.11",
|
||||
"mermaid": "11.2.0",
|
||||
"mermaid": "11.2.1",
|
||||
"mini-css-extract-plugin": "2.9.1",
|
||||
"minimatch": "10.0.1",
|
||||
"monaco-editor": "0.50.0",
|
||||
"monaco-editor": "0.51.0",
|
||||
"monaco-editor-webpack-plugin": "7.1.0",
|
||||
"pdfobject": "2.3.0",
|
||||
"postcss": "8.4.47",
|
||||
|
@ -41,7 +41,7 @@
|
|||
"pretty-ms": "9.0.0",
|
||||
"sortablejs": "1.15.3",
|
||||
"swagger-ui-dist": "5.17.14",
|
||||
"tailwindcss": "3.4.11",
|
||||
"tailwindcss": "3.4.13",
|
||||
"temporal-polyfill": "0.2.4",
|
||||
"throttle-debounce": "5.0.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
|
@ -50,11 +50,11 @@
|
|||
"tributejs": "5.1.3",
|
||||
"uint8-to-base64": "0.2.0",
|
||||
"vanilla-colorful": "0.7.2",
|
||||
"vue": "3.5.6",
|
||||
"vue": "3.5.10",
|
||||
"vue-chartjs": "5.3.1",
|
||||
"vue-loader": "17.4.2",
|
||||
"vue3-calendar-heatmap": "2.0.5",
|
||||
"webpack": "5.94.0",
|
||||
"webpack": "5.95.0",
|
||||
"webpack-cli": "5.1.4",
|
||||
"wrap-ansi": "9.0.0"
|
||||
},
|
||||
|
@ -62,11 +62,12 @@
|
|||
"@axe-core/playwright": "4.10.0",
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "4.4.0",
|
||||
"@playwright/test": "1.47.2",
|
||||
"@stoplight/spectral-cli": "6.13.0",
|
||||
"@stoplight/spectral-cli": "6.13.1",
|
||||
"@stylistic/eslint-plugin-js": "2.8.0",
|
||||
"@stylistic/stylelint-plugin": "3.0.1",
|
||||
"@vitejs/plugin-vue": "5.1.3",
|
||||
"@stylistic/stylelint-plugin": "3.1.0",
|
||||
"@vitejs/plugin-vue": "5.1.4",
|
||||
"@vitest/coverage-v8": "2.1.1",
|
||||
"@vitest/eslint-plugin": "1.1.4",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-plugin-array-func": "4.0.0",
|
||||
|
@ -78,14 +79,13 @@
|
|||
"eslint-plugin-regexp": "2.6.0",
|
||||
"eslint-plugin-sonarjs": "2.0.2",
|
||||
"eslint-plugin-unicorn": "55.0.0",
|
||||
"eslint-plugin-vitest": "0.5.4",
|
||||
"eslint-plugin-vitest-globals": "1.5.0",
|
||||
"eslint-plugin-vue": "9.28.0",
|
||||
"eslint-plugin-vue-scoped-css": "2.8.1",
|
||||
"eslint-plugin-wc": "2.1.1",
|
||||
"happy-dom": "15.7.4",
|
||||
"license-checker-rseidelsohn": "4.4.2",
|
||||
"markdownlint-cli": "0.41.0",
|
||||
"markdownlint-cli": "0.42.0",
|
||||
"postcss-html": "1.7.0",
|
||||
"stylelint": "16.9.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||
|
|
191
poetry.lock
generated
191
poetry.lock
generated
|
@ -214,104 +214,105 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2023.12.25"
|
||||
version = "2024.9.11"
|
||||
description = "Alternative regular expression module, to replace re."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"},
|
||||
{file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"},
|
||||
{file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"},
|
||||
{file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"},
|
||||
{file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"},
|
||||
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"},
|
||||
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"},
|
||||
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"},
|
||||
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"},
|
||||
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"},
|
||||
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"},
|
||||
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"},
|
||||
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"},
|
||||
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"},
|
||||
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"},
|
||||
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"},
|
||||
{file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"},
|
||||
{file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"},
|
||||
{file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"},
|
||||
{file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"},
|
||||
{file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0"},
|
||||
{file = "regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9"},
|
||||
{file = "regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776"},
|
||||
{file = "regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8"},
|
||||
{file = "regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:35f4a6f96aa6cb3f2f7247027b07b15a374f0d5b912c0001418d1d55024d5cb4"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b96e7ce3a69a8449a66984c268062fbaa0d8ae437b285428e12797baefce7e"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb130fccd1a37ed894824b8c046321540263013da72745d755f2d35114b81a60"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:323c1f04be6b2968944d730e5c2091c8c89767903ecaa135203eec4565ed2b2b"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be1c8ed48c4c4065ecb19d882a0ce1afe0745dfad8ce48c49586b90a55f02366"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5b029322e6e7b94fff16cd120ab35a253236a5f99a79fb04fda7ae71ca20ae8"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6fff13ef6b5f29221d6904aa816c34701462956aa72a77f1f151a8ec4f56aeb"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d4af3979376652010e400accc30404e6c16b7df574048ab1f581af82065e4"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:079400a8269544b955ffa9e31f186f01d96829110a3bf79dc338e9910f794fca"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f9268774428ec173654985ce55fc6caf4c6d11ade0f6f914d48ef4719eb05ebb"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:23f9985c8784e544d53fc2930fc1ac1a7319f5d5332d228437acc9f418f2f168"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2941333154baff9838e88aa71c1d84f4438189ecc6021a12c7573728b5838e"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e93f1c331ca8e86fe877a48ad64e77882c0c4da0097f2212873a69bbfea95d0c"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:846bc79ee753acf93aef4184c040d709940c9d001029ceb7b7a52747b80ed2dd"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-win32.whl", hash = "sha256:c94bb0a9f1db10a1d16c00880bdebd5f9faf267273b8f5bd1878126e0fbde771"},
|
||||
{file = "regex-2024.9.11-cp38-cp38-win_amd64.whl", hash = "sha256:2b08fce89fbd45664d3df6ad93e554b6c16933ffa9d55cb7e01182baaf971508"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-win32.whl", hash = "sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142"},
|
||||
{file = "regex-2024.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919"},
|
||||
{file = "regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
5
release-notes/5372.md
Normal file
5
release-notes/5372.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
feat: [commit](https://codeberg.org/forgejo/forgejo/commit/9d3473119893ffde0ab36d98e7a0e41c5d0ba9a3) Add bin to Composer Metadata.
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/f709de24039ab7e605d3e09e3b61240836381603) Fix wrong last modify time.
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/2675a24649af2fff34f5c7e416d6ff78591d8d9c) Repo Activity: count new issues that were closed.
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/526054332acb221e061d3900bba2dc6e012da52d) Fix incorrect /tokens api.
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/0cafec4c7a2faf810953e9d522faf5dc019e1522) Do not escape relative path in RPM primary index.
|
|
@ -175,18 +175,20 @@ func CommonRoutes() *web.Route {
|
|||
arch.PushPackage(ctx)
|
||||
return
|
||||
} else if isDelete {
|
||||
if groupLen < 2 {
|
||||
if groupLen < 3 {
|
||||
ctx.Status(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if groupLen == 2 {
|
||||
if groupLen == 3 {
|
||||
ctx.SetParams("group", "")
|
||||
ctx.SetParams("package", pathGroups[0])
|
||||
ctx.SetParams("version", pathGroups[1])
|
||||
ctx.SetParams("arch", pathGroups[2])
|
||||
} else {
|
||||
ctx.SetParams("group", strings.Join(pathGroups[:groupLen-2], "/"))
|
||||
ctx.SetParams("package", pathGroups[groupLen-2])
|
||||
ctx.SetParams("version", pathGroups[groupLen-1])
|
||||
ctx.SetParams("group", strings.Join(pathGroups[:groupLen-3], "/"))
|
||||
ctx.SetParams("package", pathGroups[groupLen-3])
|
||||
ctx.SetParams("version", pathGroups[groupLen-2])
|
||||
ctx.SetParams("arch", pathGroups[groupLen-1])
|
||||
}
|
||||
reqPackageAccess(perm.AccessModeWrite)(ctx)
|
||||
if ctx.Written() {
|
||||
|
|
|
@ -9,12 +9,14 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
arch_module "code.gitea.io/gitea/modules/packages/arch"
|
||||
"code.gitea.io/gitea/modules/sync"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/api/packages/helper"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
|
@ -25,6 +27,8 @@ import (
|
|||
var (
|
||||
archPkgOrSig = regexp.MustCompile(`^.*\.pkg\.tar\.\w+(\.sig)*$`)
|
||||
archDBOrSig = regexp.MustCompile(`^.*.db(\.tar\.gz)*(\.sig)*$`)
|
||||
|
||||
locker = sync.NewExclusivePool()
|
||||
)
|
||||
|
||||
func apiError(ctx *context.Context, status int, obj any) {
|
||||
|
@ -33,6 +37,14 @@ func apiError(ctx *context.Context, status int, obj any) {
|
|||
})
|
||||
}
|
||||
|
||||
func refreshLocker(ctx *context.Context, group string) func() {
|
||||
key := fmt.Sprintf("pkg_%d_arch_pkg_%s", ctx.Package.Owner.ID, group)
|
||||
locker.CheckIn(key)
|
||||
return func() {
|
||||
locker.CheckOut(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetRepositoryKey(ctx *context.Context) {
|
||||
_, pub, err := arch_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID)
|
||||
if err != nil {
|
||||
|
@ -48,7 +60,8 @@ func GetRepositoryKey(ctx *context.Context) {
|
|||
|
||||
func PushPackage(ctx *context.Context) {
|
||||
group := ctx.Params("group")
|
||||
|
||||
releaser := refreshLocker(ctx, group)
|
||||
defer releaser()
|
||||
upload, needToClose, err := ctx.UploadStream()
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
|
@ -154,6 +167,7 @@ func PushPackage(ctx *context.Context) {
|
|||
})
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
if err = arch_service.BuildPacmanDB(ctx, ctx.Package.Owner.ID, group, p.FileMetadata.Arch); err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
|
@ -169,7 +183,7 @@ func GetPackageOrDB(ctx *context.Context) {
|
|||
arch = ctx.Params("arch")
|
||||
)
|
||||
if archPkgOrSig.MatchString(file) {
|
||||
pkg, err := arch_service.GetPackageFile(ctx, group, file, ctx.Package.Owner.ID)
|
||||
pkg, u, pf, err := arch_service.GetPackageFile(ctx, group, file, ctx.Package.Owner.ID)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
apiError(ctx, http.StatusNotFound, err)
|
||||
|
@ -178,15 +192,12 @@ func GetPackageOrDB(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.ServeContent(pkg, &context.ServeHeaderOptions{
|
||||
Filename: file,
|
||||
})
|
||||
helper.ServePackageFile(ctx, pkg, u, pf)
|
||||
return
|
||||
}
|
||||
|
||||
if archDBOrSig.MatchString(file) {
|
||||
pkg, err := arch_service.GetPackageDBFile(ctx, group, arch, ctx.Package.Owner.ID,
|
||||
pkg, u, pf, err := arch_service.GetPackageDBFile(ctx, group, arch, ctx.Package.Owner.ID,
|
||||
strings.HasSuffix(file, ".sig"))
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
|
@ -196,9 +207,7 @@ func GetPackageOrDB(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
ctx.ServeContent(pkg, &context.ServeHeaderOptions{
|
||||
Filename: file,
|
||||
})
|
||||
helper.ServePackageFile(ctx, pkg, u, pf)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -207,10 +216,13 @@ func GetPackageOrDB(ctx *context.Context) {
|
|||
|
||||
func RemovePackage(ctx *context.Context) {
|
||||
var (
|
||||
group = ctx.Params("group")
|
||||
pkg = ctx.Params("package")
|
||||
ver = ctx.Params("version")
|
||||
group = ctx.Params("group")
|
||||
pkg = ctx.Params("package")
|
||||
ver = ctx.Params("version")
|
||||
pkgArch = ctx.Params("arch")
|
||||
)
|
||||
releaser := refreshLocker(ctx, group)
|
||||
defer releaser()
|
||||
pv, err := packages_model.GetVersionByNameAndVersion(
|
||||
ctx, ctx.Package.Owner.ID, packages_model.TypeArch, pkg, ver,
|
||||
)
|
||||
|
@ -229,7 +241,13 @@ func RemovePackage(ctx *context.Context) {
|
|||
}
|
||||
deleted := false
|
||||
for _, file := range files {
|
||||
if file.CompositeKey == group {
|
||||
extName := fmt.Sprintf("-%s.pkg.tar%s", pkgArch, filepath.Ext(file.LowerName))
|
||||
if strings.HasSuffix(file.LowerName, ".sig") {
|
||||
extName = fmt.Sprintf("-%s.pkg.tar%s.sig", pkgArch,
|
||||
filepath.Ext(strings.TrimSuffix(file.LowerName, filepath.Ext(file.LowerName))))
|
||||
}
|
||||
if file.CompositeKey == group &&
|
||||
strings.HasSuffix(file.LowerName, extName) {
|
||||
deleted = true
|
||||
err := packages_service.RemovePackageFileAndVersionIfUnreferenced(ctx, ctx.ContextUser, file)
|
||||
if err != nil {
|
||||
|
@ -242,6 +260,7 @@ func RemovePackage(ctx *context.Context) {
|
|||
err = arch_service.BuildCustomRepositoryFiles(ctx, ctx.Package.Owner.ID, group)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
} else {
|
||||
|
|
|
@ -117,7 +117,9 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
|
|||
xmlMetadataWithHeader := append([]byte(xml.Header), xmlMetadata...)
|
||||
|
||||
latest := pds[len(pds)-1]
|
||||
ctx.Resp.Header().Set("Last-Modified", latest.Version.CreatedUnix.Format(http.TimeFormat))
|
||||
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
|
||||
lastModifed := latest.Version.CreatedUnix.AsTime().UTC().Format(http.TimeFormat)
|
||||
ctx.Resp.Header().Set("Last-Modified", lastModifed)
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(params.Filename))
|
||||
if isChecksumExtension(ext) {
|
||||
|
|
|
@ -839,10 +839,16 @@ func EditIssue(ctx *context.APIContext) {
|
|||
if (form.Deadline != nil || form.RemoveDeadline != nil) && canWrite {
|
||||
var deadlineUnix timeutil.TimeStamp
|
||||
|
||||
if (form.RemoveDeadline == nil || !*form.RemoveDeadline) && !form.Deadline.IsZero() {
|
||||
deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
|
||||
23, 59, 59, 0, form.Deadline.Location())
|
||||
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
|
||||
if form.RemoveDeadline == nil || !*form.RemoveDeadline {
|
||||
if form.Deadline == nil {
|
||||
ctx.Error(http.StatusBadRequest, "", "The due_date cannot be empty")
|
||||
return
|
||||
}
|
||||
if !form.Deadline.IsZero() {
|
||||
deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
|
||||
23, 59, 59, 0, form.Deadline.Location())
|
||||
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
|
||||
}
|
||||
}
|
||||
|
||||
if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {
|
||||
|
|
|
@ -118,6 +118,10 @@ func CreateAccessToken(ctx *context.APIContext) {
|
|||
ctx.Error(http.StatusBadRequest, "AccessTokenScope.Normalize", fmt.Errorf("invalid access token scope provided: %w", err))
|
||||
return
|
||||
}
|
||||
if scope == "" {
|
||||
ctx.Error(http.StatusBadRequest, "AccessTokenScope", "access token must have a scope")
|
||||
return
|
||||
}
|
||||
t.Scope = scope
|
||||
|
||||
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
||||
|
@ -129,6 +133,7 @@ func CreateAccessToken(ctx *context.APIContext) {
|
|||
Token: t.Token,
|
||||
ID: t.ID,
|
||||
TokenLastEight: t.TokenLastEight,
|
||||
Scopes: t.Scope.StringSlice(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -395,7 +395,8 @@ func (h *serviceHandler) sendFile(ctx *context.Context, contentType, file string
|
|||
|
||||
ctx.Resp.Header().Set("Content-Type", contentType)
|
||||
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
|
||||
ctx.Resp.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
|
||||
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
|
||||
ctx.Resp.Header().Set("Last-Modified", fi.ModTime().UTC().Format(http.TimeFormat))
|
||||
http.ServeFile(ctx.Resp, ctx.Req, reqFile)
|
||||
}
|
||||
|
||||
|
|
|
@ -132,6 +132,8 @@ func webAuth(authMethod auth_service.Method) func(*context.Context) {
|
|||
// ensure the session uid is deleted
|
||||
_ = ctx.Session.Delete("uid")
|
||||
}
|
||||
|
||||
ctx.Csrf.PrepareForSessionUser(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -127,10 +127,8 @@ func Contexter() func(next http.Handler) http.Handler {
|
|||
csrfOpts := CsrfOptions{
|
||||
Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()),
|
||||
Cookie: setting.CSRFCookieName,
|
||||
SetCookie: true,
|
||||
Secure: setting.SessionConfig.Secure,
|
||||
CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
|
||||
Header: "X-Csrf-Token",
|
||||
CookieDomain: setting.SessionConfig.Domain,
|
||||
CookiePath: setting.SessionConfig.CookiePath,
|
||||
SameSite: setting.SessionConfig.SameSite,
|
||||
|
@ -156,7 +154,7 @@ func Contexter() func(next http.Handler) http.Handler {
|
|||
ctx.Base.AppendContextValue(WebContextKey, ctx)
|
||||
ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
|
||||
|
||||
ctx.Csrf = PrepareCSRFProtector(csrfOpts, ctx)
|
||||
ctx.Csrf = NewCSRFProtector(csrfOpts)
|
||||
|
||||
// Get the last flash message from cookie
|
||||
lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash)
|
||||
|
@ -193,8 +191,6 @@ func Contexter() func(next http.Handler) http.Handler {
|
|||
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||
|
||||
ctx.Data["SystemConfig"] = setting.Config()
|
||||
ctx.Data["CsrfToken"] = ctx.Csrf.GetToken()
|
||||
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
|
||||
|
||||
// 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
|
||||
|
|
|
@ -20,64 +20,43 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
)
|
||||
|
||||
const (
|
||||
CsrfHeaderName = "X-Csrf-Token"
|
||||
CsrfFormName = "_csrf"
|
||||
CsrfErrorString = "Invalid CSRF token."
|
||||
)
|
||||
|
||||
// CSRFProtector represents a CSRF protector and is used to get the current token and validate the token.
|
||||
type CSRFProtector interface {
|
||||
// GetHeaderName returns HTTP header to search for token.
|
||||
GetHeaderName() string
|
||||
// GetFormName returns form value to search for token.
|
||||
GetFormName() string
|
||||
// GetToken returns the token.
|
||||
GetToken() string
|
||||
// Validate validates the token in http context.
|
||||
// PrepareForSessionUser prepares the csrf protector for the current session user.
|
||||
PrepareForSessionUser(ctx *Context)
|
||||
// Validate validates the csrf token in http context.
|
||||
Validate(ctx *Context)
|
||||
// DeleteCookie deletes the cookie
|
||||
// DeleteCookie deletes the csrf cookie
|
||||
DeleteCookie(ctx *Context)
|
||||
}
|
||||
|
||||
type csrfProtector struct {
|
||||
opt CsrfOptions
|
||||
// Token generated to pass via header, cookie, or hidden form value.
|
||||
Token string
|
||||
// This value must be unique per user.
|
||||
ID string
|
||||
}
|
||||
|
||||
// GetHeaderName returns the name of the HTTP header for csrf token.
|
||||
func (c *csrfProtector) GetHeaderName() string {
|
||||
return c.opt.Header
|
||||
}
|
||||
|
||||
// GetFormName returns the name of the form value for csrf token.
|
||||
func (c *csrfProtector) GetFormName() string {
|
||||
return c.opt.Form
|
||||
}
|
||||
|
||||
// GetToken returns the current token. This is typically used
|
||||
// to populate a hidden form in an HTML template.
|
||||
func (c *csrfProtector) GetToken() string {
|
||||
return c.Token
|
||||
// id must be unique per user.
|
||||
id string
|
||||
// token is the valid one which wil be used by end user and passed via header, cookie, or hidden form value.
|
||||
token string
|
||||
}
|
||||
|
||||
// CsrfOptions maintains options to manage behavior of Generate.
|
||||
type CsrfOptions struct {
|
||||
// The global secret value used to generate Tokens.
|
||||
Secret string
|
||||
// HTTP header used to set and get token.
|
||||
Header string
|
||||
// Form value used to set and get token.
|
||||
Form string
|
||||
// Cookie value used to set and get token.
|
||||
Cookie string
|
||||
// Cookie domain.
|
||||
|
@ -87,103 +66,64 @@ type CsrfOptions struct {
|
|||
CookieHTTPOnly bool
|
||||
// SameSite set the cookie SameSite type
|
||||
SameSite http.SameSite
|
||||
// Key used for getting the unique ID per user.
|
||||
SessionKey string
|
||||
// oldSessionKey saves old value corresponding to SessionKey.
|
||||
oldSessionKey string
|
||||
// If true, send token via X-Csrf-Token header.
|
||||
SetHeader bool
|
||||
// If true, send token via _csrf cookie.
|
||||
SetCookie bool
|
||||
// Set the Secure flag to true on the cookie.
|
||||
Secure bool
|
||||
// Disallow Origin appear in request header.
|
||||
Origin bool
|
||||
// Cookie lifetime. Default is 0
|
||||
CookieLifeTime int
|
||||
// sessionKey is the key used for getting the unique ID per user.
|
||||
sessionKey string
|
||||
// oldSessionKey saves old value corresponding to sessionKey.
|
||||
oldSessionKey string
|
||||
}
|
||||
|
||||
func prepareDefaultCsrfOptions(opt CsrfOptions) CsrfOptions {
|
||||
if opt.Secret == "" {
|
||||
randBytes, err := util.CryptoRandomBytes(8)
|
||||
if err != nil {
|
||||
// this panic can be handled by the recover() in http handlers
|
||||
panic(fmt.Errorf("failed to generate random bytes: %w", err))
|
||||
}
|
||||
opt.Secret = base32.StdEncoding.EncodeToString(randBytes)
|
||||
}
|
||||
if opt.Header == "" {
|
||||
opt.Header = "X-Csrf-Token"
|
||||
}
|
||||
if opt.Form == "" {
|
||||
opt.Form = "_csrf"
|
||||
}
|
||||
if opt.Cookie == "" {
|
||||
opt.Cookie = "_csrf"
|
||||
}
|
||||
if opt.CookiePath == "" {
|
||||
opt.CookiePath = "/"
|
||||
}
|
||||
if opt.SessionKey == "" {
|
||||
opt.SessionKey = "uid"
|
||||
}
|
||||
if opt.CookieLifeTime == 0 {
|
||||
opt.CookieLifeTime = int(CsrfTokenTimeout.Seconds())
|
||||
}
|
||||
|
||||
opt.oldSessionKey = "_old_" + opt.SessionKey
|
||||
return opt
|
||||
}
|
||||
|
||||
func newCsrfCookie(c *csrfProtector, value string) *http.Cookie {
|
||||
func newCsrfCookie(opt *CsrfOptions, value string) *http.Cookie {
|
||||
return &http.Cookie{
|
||||
Name: c.opt.Cookie,
|
||||
Name: opt.Cookie,
|
||||
Value: value,
|
||||
Path: c.opt.CookiePath,
|
||||
Domain: c.opt.CookieDomain,
|
||||
MaxAge: c.opt.CookieLifeTime,
|
||||
Secure: c.opt.Secure,
|
||||
HttpOnly: c.opt.CookieHTTPOnly,
|
||||
SameSite: c.opt.SameSite,
|
||||
Path: opt.CookiePath,
|
||||
Domain: opt.CookieDomain,
|
||||
MaxAge: int(CsrfTokenTimeout.Seconds()),
|
||||
Secure: opt.Secure,
|
||||
HttpOnly: opt.CookieHTTPOnly,
|
||||
SameSite: opt.SameSite,
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareCSRFProtector returns a CSRFProtector to be used for every request.
|
||||
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
|
||||
func PrepareCSRFProtector(opt CsrfOptions, ctx *Context) CSRFProtector {
|
||||
opt = prepareDefaultCsrfOptions(opt)
|
||||
x := &csrfProtector{opt: opt}
|
||||
|
||||
if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
|
||||
return x
|
||||
func NewCSRFProtector(opt CsrfOptions) CSRFProtector {
|
||||
if opt.Secret == "" {
|
||||
panic("CSRF secret is empty but it must be set") // it shouldn't happen because it is always set in code
|
||||
}
|
||||
opt.Cookie = util.IfZero(opt.Cookie, "_csrf")
|
||||
opt.CookiePath = util.IfZero(opt.CookiePath, "/")
|
||||
opt.sessionKey = "uid"
|
||||
opt.oldSessionKey = "_old_" + opt.sessionKey
|
||||
return &csrfProtector{opt: opt}
|
||||
}
|
||||
|
||||
x.ID = "0"
|
||||
uidAny := ctx.Session.Get(opt.SessionKey)
|
||||
if uidAny != nil {
|
||||
func (c *csrfProtector) PrepareForSessionUser(ctx *Context) {
|
||||
c.id = "0"
|
||||
if uidAny := ctx.Session.Get(c.opt.sessionKey); uidAny != nil {
|
||||
switch uidVal := uidAny.(type) {
|
||||
case string:
|
||||
x.ID = uidVal
|
||||
c.id = uidVal
|
||||
case int64:
|
||||
x.ID = strconv.FormatInt(uidVal, 10)
|
||||
c.id = strconv.FormatInt(uidVal, 10)
|
||||
default:
|
||||
log.Error("invalid uid type in session: %T", uidAny)
|
||||
}
|
||||
}
|
||||
|
||||
oldUID := ctx.Session.Get(opt.oldSessionKey)
|
||||
uidChanged := oldUID == nil || oldUID.(string) != x.ID
|
||||
cookieToken := ctx.GetSiteCookie(opt.Cookie)
|
||||
oldUID := ctx.Session.Get(c.opt.oldSessionKey)
|
||||
uidChanged := oldUID == nil || oldUID.(string) != c.id
|
||||
cookieToken := ctx.GetSiteCookie(c.opt.Cookie)
|
||||
|
||||
needsNew := true
|
||||
if uidChanged {
|
||||
_ = ctx.Session.Set(opt.oldSessionKey, x.ID)
|
||||
_ = ctx.Session.Set(c.opt.oldSessionKey, c.id)
|
||||
} else if cookieToken != "" {
|
||||
// If cookie token presents, reuse existing unexpired token, else generate a new one.
|
||||
if issueTime, ok := ParseCsrfToken(cookieToken); ok {
|
||||
dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time.
|
||||
if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval {
|
||||
x.Token = cookieToken
|
||||
c.token = cookieToken
|
||||
needsNew = false
|
||||
}
|
||||
}
|
||||
|
@ -191,42 +131,33 @@ func PrepareCSRFProtector(opt CsrfOptions, ctx *Context) CSRFProtector {
|
|||
|
||||
if needsNew {
|
||||
// FIXME: actionId.
|
||||
x.Token = GenerateCsrfToken(x.opt.Secret, x.ID, "POST", time.Now())
|
||||
if opt.SetCookie {
|
||||
cookie := newCsrfCookie(x, x.Token)
|
||||
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
c.token = GenerateCsrfToken(c.opt.Secret, c.id, "POST", time.Now())
|
||||
cookie := newCsrfCookie(&c.opt, c.token)
|
||||
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
|
||||
if opt.SetHeader {
|
||||
ctx.Resp.Header().Add(opt.Header, x.Token)
|
||||
}
|
||||
return x
|
||||
ctx.Data["CsrfToken"] = c.token
|
||||
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + template.HTMLEscapeString(c.token) + `">`)
|
||||
}
|
||||
|
||||
func (c *csrfProtector) validateToken(ctx *Context, token string) {
|
||||
if !ValidCsrfToken(token, c.opt.Secret, c.ID, "POST", time.Now()) {
|
||||
if !ValidCsrfToken(token, c.opt.Secret, c.id, "POST", time.Now()) {
|
||||
c.DeleteCookie(ctx)
|
||||
if middleware.IsAPIPath(ctx.Req) {
|
||||
// currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
|
||||
http.Error(ctx.Resp, "Invalid CSRF token.", http.StatusBadRequest)
|
||||
} else {
|
||||
ctx.Flash.Error(ctx.Tr("error.invalid_csrf"))
|
||||
ctx.Redirect(setting.AppSubURL + "/")
|
||||
}
|
||||
// currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
|
||||
// FIXME: distinguish what the response is for: HTML (web page) or JSON (fetch)
|
||||
http.Error(ctx.Resp, CsrfErrorString, http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate should be used as a per route middleware. It attempts to get a token from an "X-Csrf-Token"
|
||||
// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated.
|
||||
// If this validation fails, custom Error is sent in the reply.
|
||||
// If neither a header nor form value is found, http.StatusBadRequest is sent.
|
||||
// If this validation fails, http.StatusBadRequest is sent.
|
||||
func (c *csrfProtector) Validate(ctx *Context) {
|
||||
if token := ctx.Req.Header.Get(c.GetHeaderName()); token != "" {
|
||||
if token := ctx.Req.Header.Get(CsrfHeaderName); token != "" {
|
||||
c.validateToken(ctx, token)
|
||||
return
|
||||
}
|
||||
if token := ctx.Req.FormValue(c.GetFormName()); token != "" {
|
||||
if token := ctx.Req.FormValue(CsrfFormName); token != "" {
|
||||
c.validateToken(ctx, token)
|
||||
return
|
||||
}
|
||||
|
@ -234,9 +165,7 @@ func (c *csrfProtector) Validate(ctx *Context) {
|
|||
}
|
||||
|
||||
func (c *csrfProtector) DeleteCookie(ctx *Context) {
|
||||
if c.opt.SetCookie {
|
||||
cookie := newCsrfCookie(c, "")
|
||||
cookie.MaxAge = -1
|
||||
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
cookie := newCsrfCookie(&c.opt, "")
|
||||
cookie.MaxAge = -1
|
||||
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
@ -20,6 +21,7 @@ import (
|
|||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
arch_module "code.gitea.io/gitea/modules/packages/arch"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/sync"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
|
||||
|
@ -28,6 +30,8 @@ import (
|
|||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||
)
|
||||
|
||||
var locker = sync.NewExclusivePool()
|
||||
|
||||
func GetOrCreateRepositoryVersion(ctx context.Context, ownerID int64) (*packages_model.PackageVersion, error) {
|
||||
return packages_service.GetOrCreateInternalPackageVersion(ctx, ownerID, packages_model.TypeArch, arch_module.RepositoryPackage, arch_module.RepositoryVersion)
|
||||
}
|
||||
|
@ -101,6 +105,9 @@ func NewFileSign(ctx context.Context, ownerID int64, input io.Reader) (*packages
|
|||
|
||||
// BuildPacmanDB Create db signature cache
|
||||
func BuildPacmanDB(ctx context.Context, ownerID int64, group, arch string) error {
|
||||
key := fmt.Sprintf("pkg_%d_arch_db_%s", ownerID, group)
|
||||
locker.CheckIn(key)
|
||||
defer locker.CheckOut(key)
|
||||
pv, err := GetOrCreateRepositoryVersion(ctx, ownerID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -173,15 +180,18 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
gw := gzip.NewWriter(db)
|
||||
defer gw.Close()
|
||||
tw := tar.NewWriter(gw)
|
||||
defer tw.Close()
|
||||
count := 0
|
||||
for _, pkg := range pkgs {
|
||||
versions, err := packages_model.GetVersionsByPackageName(
|
||||
ctx, ownerID, packages_model.TypeArch, pkg.Name,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err)
|
||||
return nil, err
|
||||
}
|
||||
sort.Slice(versions, func(i, j int) bool {
|
||||
return versions[i].CreatedUnix > versions[j].CreatedUnix
|
||||
|
@ -190,7 +200,7 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages
|
|||
for _, ver := range versions {
|
||||
files, err := packages_model.GetFilesByVersionID(ctx, ver.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err)
|
||||
return nil, err
|
||||
}
|
||||
var pf *packages_model.PackageFile
|
||||
for _, file := range files {
|
||||
|
@ -213,7 +223,7 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages
|
|||
ctx, packages_model.PropertyTypeFile, pf.ID, arch_module.PropertyDescription,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err)
|
||||
return nil, err
|
||||
}
|
||||
if len(pps) >= 1 {
|
||||
meta := []byte(pps[0].Value)
|
||||
|
@ -223,60 +233,50 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages
|
|||
Mode: int64(os.ModePerm),
|
||||
}
|
||||
if err = tw.WriteHeader(header); err != nil {
|
||||
return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err)
|
||||
return nil, err
|
||||
}
|
||||
if _, err := tw.Write(meta); err != nil {
|
||||
return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err)
|
||||
return nil, err
|
||||
}
|
||||
count++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
defer gw.Close()
|
||||
defer tw.Close()
|
||||
if count == 0 {
|
||||
return nil, errors.Join(db.Close(), io.EOF)
|
||||
return nil, io.EOF
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// GetPackageFile Get data related to provided filename and distribution, for package files
|
||||
// update download counter.
|
||||
func GetPackageFile(ctx context.Context, group, file string, ownerID int64) (io.ReadSeekCloser, error) {
|
||||
pf, err := getPackageFile(ctx, group, file, ownerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func GetPackageFile(ctx context.Context, group, file string, ownerID int64) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
|
||||
fileSplit := strings.Split(file, "-")
|
||||
if len(fileSplit) <= 3 {
|
||||
return nil, nil, nil, errors.New("invalid file format, need <name>-<version>-<release>-<arch>.pkg.<archive>")
|
||||
}
|
||||
|
||||
filestream, _, _, err := packages_service.GetPackageFileStream(ctx, pf)
|
||||
return filestream, err
|
||||
}
|
||||
|
||||
// Ejects parameters required to get package file property from file name.
|
||||
func getPackageFile(ctx context.Context, group, file string, ownerID int64) (*packages_model.PackageFile, error) {
|
||||
var (
|
||||
splt = strings.Split(file, "-")
|
||||
pkgname = strings.Join(splt[0:len(splt)-3], "-")
|
||||
vername = splt[len(splt)-3] + "-" + splt[len(splt)-2]
|
||||
pkgName = strings.Join(fileSplit[0:len(fileSplit)-3], "-")
|
||||
pkgVer = fileSplit[len(fileSplit)-3] + "-" + fileSplit[len(fileSplit)-2]
|
||||
)
|
||||
|
||||
version, err := packages_model.GetVersionByNameAndVersion(ctx, ownerID, packages_model.TypeArch, pkgname, vername)
|
||||
version, err := packages_model.GetVersionByNameAndVersion(ctx, ownerID, packages_model.TypeArch, pkgName, pkgVer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
pkgfile, err := packages_model.GetFileForVersionByName(ctx, version.ID, file, group)
|
||||
pkgFile, err := packages_model.GetFileForVersionByName(ctx, version.ID, file, group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return pkgfile, nil
|
||||
|
||||
return packages_service.GetPackageFileStream(ctx, pkgFile)
|
||||
}
|
||||
|
||||
func GetPackageDBFile(ctx context.Context, group, arch string, ownerID int64, signFile bool) (io.ReadSeekCloser, error) {
|
||||
func GetPackageDBFile(ctx context.Context, group, arch string, ownerID int64, signFile bool) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
|
||||
pv, err := GetOrCreateRepositoryVersion(ctx, ownerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
fileName := fmt.Sprintf("%s.db", arch)
|
||||
if signFile {
|
||||
|
@ -284,10 +284,9 @@ func GetPackageDBFile(ctx context.Context, group, arch string, ownerID int64, si
|
|||
}
|
||||
file, err := packages_model.GetFileForVersionByName(ctx, pv.ID, fileName, group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
filestream, _, _, err := packages_service.GetPackageFileStream(ctx, file)
|
||||
return filestream, err
|
||||
return packages_service.GetPackageFileStream(ctx, file)
|
||||
}
|
||||
|
||||
// GetOrCreateKeyPair gets or creates the PGP keys used to sign repository metadata files
|
||||
|
|
116
services/packages/cleanup/cleanup_sha256_test.go
Normal file
116
services/packages/cleanup/cleanup_sha256_test.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2024 The Forgejo Authors.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package container
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
container_service "code.gitea.io/gitea/services/packages/container"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCleanupSHA256(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
defer test.MockVariableValue(&container_service.SHA256BatchSize, 1)()
|
||||
|
||||
ctx := db.DefaultContext
|
||||
|
||||
createContainer := func(t *testing.T, name, version, digest string, created timeutil.TimeStamp) {
|
||||
t.Helper()
|
||||
|
||||
ownerID := int64(2001)
|
||||
|
||||
p := packages.Package{
|
||||
OwnerID: ownerID,
|
||||
LowerName: name,
|
||||
Type: packages.TypeContainer,
|
||||
}
|
||||
_, err := db.GetEngine(ctx).Insert(&p)
|
||||
// package_version").Where("version = ?", multiTag).Update(&packages_model.PackageVersion{MetadataJSON: `corrupted "manifests":[{ bad`})
|
||||
require.NoError(t, err)
|
||||
|
||||
var metadata string
|
||||
if digest != "" {
|
||||
m := container_module.Metadata{
|
||||
Manifests: []*container_module.Manifest{
|
||||
{
|
||||
Digest: digest,
|
||||
},
|
||||
},
|
||||
}
|
||||
mt, err := json.Marshal(m)
|
||||
require.NoError(t, err)
|
||||
metadata = string(mt)
|
||||
}
|
||||
v := packages.PackageVersion{
|
||||
PackageID: p.ID,
|
||||
LowerVersion: version,
|
||||
MetadataJSON: metadata,
|
||||
CreatedUnix: created,
|
||||
}
|
||||
_, err = db.GetEngine(ctx).NoAutoTime().Insert(&v)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
cleanupAndCheckLogs := func(t *testing.T, olderThan time.Duration, expected ...string) {
|
||||
t.Helper()
|
||||
logChecker, cleanup := test.NewLogChecker(log.DEFAULT, log.TRACE)
|
||||
logChecker.Filter(expected...)
|
||||
logChecker.StopMark(container_service.SHA256LogFinish)
|
||||
defer cleanup()
|
||||
|
||||
require.NoError(t, CleanupExpiredData(ctx, olderThan))
|
||||
|
||||
logFiltered, logStopped := logChecker.Check(5 * time.Second)
|
||||
assert.True(t, logStopped)
|
||||
filtered := make([]bool, 0, len(expected))
|
||||
for range expected {
|
||||
filtered = append(filtered, true)
|
||||
}
|
||||
assert.EqualValues(t, filtered, logFiltered, expected)
|
||||
}
|
||||
|
||||
ancient := 1 * time.Hour
|
||||
|
||||
t.Run("no packages, cleanup nothing", func(t *testing.T) {
|
||||
cleanupAndCheckLogs(t, ancient, "Nothing to cleanup")
|
||||
})
|
||||
|
||||
orphan := "orphan"
|
||||
createdLongAgo := timeutil.TimeStamp(time.Now().Add(-(ancient * 2)).Unix())
|
||||
createdRecently := timeutil.TimeStamp(time.Now().Add(-(ancient / 2)).Unix())
|
||||
|
||||
t.Run("an orphaned package created a long time ago is removed", func(t *testing.T) {
|
||||
createContainer(t, orphan, "sha256:"+orphan, "", createdLongAgo)
|
||||
cleanupAndCheckLogs(t, ancient, "Removing 1 entries from `package_version`")
|
||||
cleanupAndCheckLogs(t, ancient, "Nothing to cleanup")
|
||||
})
|
||||
|
||||
t.Run("a newly created orphaned package is not cleaned up", func(t *testing.T) {
|
||||
createContainer(t, orphan, "sha256:"+orphan, "", createdRecently)
|
||||
cleanupAndCheckLogs(t, ancient, "1 out of 1 container image(s) are not deleted because they were created less than")
|
||||
cleanupAndCheckLogs(t, 0, "Removing 1 entries from `package_version`")
|
||||
cleanupAndCheckLogs(t, 0, "Nothing to cleanup")
|
||||
})
|
||||
|
||||
t.Run("a referenced package is not removed", func(t *testing.T) {
|
||||
referenced := "referenced"
|
||||
digest := "sha256:" + referenced
|
||||
createContainer(t, referenced, digest, "", createdRecently)
|
||||
index := "index"
|
||||
createContainer(t, index, index, digest, createdRecently)
|
||||
cleanupAndCheckLogs(t, ancient, "Nothing to cleanup")
|
||||
})
|
||||
}
|
14
services/packages/cleanup/main_test.go
Normal file
14
services/packages/cleanup/main_test.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2024 The Forgejo Authors.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package container
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m)
|
||||
}
|
|
@ -13,6 +13,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -37,18 +38,24 @@ func cleanupSHA256(outerCtx context.Context, olderThan time.Duration) error {
|
|||
defer committer.Close()
|
||||
|
||||
foundAtLeastOneSHA256 := false
|
||||
shaToVersionID := make(map[string]int64, 100)
|
||||
type packageVersion struct {
|
||||
id int64
|
||||
created timeutil.TimeStamp
|
||||
}
|
||||
shaToPackageVersion := make(map[string]packageVersion, 100)
|
||||
knownSHA := make(map[string]any, 100)
|
||||
|
||||
// compute before making the inventory to not race against ongoing
|
||||
// image creations
|
||||
old := timeutil.TimeStamp(time.Now().Add(-olderThan).Unix())
|
||||
|
||||
log.Debug("Look for all package_version.version that start with sha256:")
|
||||
|
||||
old := time.Now().Add(-olderThan).Unix()
|
||||
|
||||
// Iterate over all container versions in ascending order and store
|
||||
// in shaToVersionID all versions with a sha256: prefix. If an index
|
||||
// in shaToPackageVersion all versions with a sha256: prefix. If an index
|
||||
// manifest is found, the sha256: digest it references are removed
|
||||
// from shaToVersionID. If the sha256: digest found in an index
|
||||
// manifest is not already in shaToVersionID, it is stored in
|
||||
// from shaToPackageVersion. If the sha256: digest found in an index
|
||||
// manifest is not already in shaToPackageVersion, it is stored in
|
||||
// knownSHA to be dealt with later.
|
||||
//
|
||||
// Although it is theoretically possible that a sha256: is uploaded
|
||||
|
@ -56,16 +63,16 @@ func cleanupSHA256(outerCtx context.Context, olderThan time.Duration) error {
|
|||
// normal order of operations. First the sha256: version is uploaded
|
||||
// and then the index manifest. When the iteration completes,
|
||||
// knownSHA will therefore be empty most of the time and
|
||||
// shaToVersionID will only contain unreferenced sha256: versions.
|
||||
// shaToPackageVersion will only contain unreferenced sha256: versions.
|
||||
if err := db.GetEngine(ctx).
|
||||
Select("`package_version`.`id`, `package_version`.`lower_version`, `package_version`.`metadata_json`").
|
||||
Select("`package_version`.`id`, `package_version`.`created_unix`, `package_version`.`lower_version`, `package_version`.`metadata_json`").
|
||||
Join("INNER", "`package`", "`package`.`id` = `package_version`.`package_id`").
|
||||
Where("`package`.`type` = ? AND `package_version`.`created_unix` < ?", packages.TypeContainer, old).
|
||||
Where("`package`.`type` = ?", packages.TypeContainer).
|
||||
OrderBy("`package_version`.`id` ASC").
|
||||
Iterate(new(packages.PackageVersion), func(_ int, bean any) error {
|
||||
v := bean.(*packages.PackageVersion)
|
||||
if strings.HasPrefix(v.LowerVersion, "sha256:") {
|
||||
shaToVersionID[v.LowerVersion] = v.ID
|
||||
shaToPackageVersion[v.LowerVersion] = packageVersion{id: v.ID, created: v.CreatedUnix}
|
||||
foundAtLeastOneSHA256 = true
|
||||
} else if strings.Contains(v.MetadataJSON, `"manifests":[{`) {
|
||||
var metadata container_module.Metadata
|
||||
|
@ -74,8 +81,8 @@ func cleanupSHA256(outerCtx context.Context, olderThan time.Duration) error {
|
|||
return nil
|
||||
}
|
||||
for _, manifest := range metadata.Manifests {
|
||||
if _, ok := shaToVersionID[manifest.Digest]; ok {
|
||||
delete(shaToVersionID, manifest.Digest)
|
||||
if _, ok := shaToPackageVersion[manifest.Digest]; ok {
|
||||
delete(shaToPackageVersion, manifest.Digest)
|
||||
} else {
|
||||
knownSHA[manifest.Digest] = true
|
||||
}
|
||||
|
@ -87,10 +94,10 @@ func cleanupSHA256(outerCtx context.Context, olderThan time.Duration) error {
|
|||
}
|
||||
|
||||
for sha := range knownSHA {
|
||||
delete(shaToVersionID, sha)
|
||||
delete(shaToPackageVersion, sha)
|
||||
}
|
||||
|
||||
if len(shaToVersionID) == 0 {
|
||||
if len(shaToPackageVersion) == 0 {
|
||||
if foundAtLeastOneSHA256 {
|
||||
log.Debug("All container images with a version matching sha256:* are referenced by an index manifest")
|
||||
} else {
|
||||
|
@ -100,15 +107,24 @@ func cleanupSHA256(outerCtx context.Context, olderThan time.Duration) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
found := len(shaToVersionID)
|
||||
found := len(shaToPackageVersion)
|
||||
|
||||
log.Warn("%d container image(s) with a version matching sha256:* are not referenced by an index manifest", found)
|
||||
|
||||
log.Debug("Deleting unreferenced image versions from `package_version`, `package_file` and `package_property` (%d at a time)", SHA256BatchSize)
|
||||
|
||||
packageVersionIDs := make([]int64, 0, SHA256BatchSize)
|
||||
for _, id := range shaToVersionID {
|
||||
packageVersionIDs = append(packageVersionIDs, id)
|
||||
tooYoung := 0
|
||||
for _, p := range shaToPackageVersion {
|
||||
if p.created < old {
|
||||
packageVersionIDs = append(packageVersionIDs, p.id)
|
||||
} else {
|
||||
tooYoung++
|
||||
}
|
||||
}
|
||||
|
||||
if tooYoung > 0 {
|
||||
log.Warn("%d out of %d container image(s) are not deleted because they were created less than %v ago", tooYoung, found, olderThan)
|
||||
}
|
||||
|
||||
for len(packageVersionIDs) > 0 {
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -440,7 +439,7 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []
|
|||
Archive: pd.FileMetadata.ArchiveSize,
|
||||
},
|
||||
Location: Location{
|
||||
Href: fmt.Sprintf("package/%s/%s/%s/%s", url.PathEscape(pd.Package.Name), url.PathEscape(packageVersion), url.PathEscape(pd.FileMetadata.Architecture), url.PathEscape(fmt.Sprintf("%s-%s.%s.rpm", pd.Package.Name, packageVersion, pd.FileMetadata.Architecture))),
|
||||
Href: fmt.Sprintf("package/%s/%s/%s/%s-%s.%s.rpm", pd.Package.Name, packageVersion, pd.FileMetadata.Architecture, pd.Package.Name, packageVersion, pd.FileMetadata.Architecture),
|
||||
},
|
||||
Format: Format{
|
||||
License: pd.VersionMetadata.License,
|
||||
|
|
|
@ -309,8 +309,9 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
|||
}
|
||||
|
||||
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
||||
RepoID: repo.ID,
|
||||
TagNames: tags,
|
||||
RepoID: repo.ID,
|
||||
TagNames: tags,
|
||||
IncludeTags: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("db.Find[repo_model.Release]: %w", err)
|
||||
|
@ -369,14 +370,13 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
|||
return fmt.Errorf("CommitsCount: %w", err)
|
||||
}
|
||||
|
||||
rel, has := relMap[lowerTag]
|
||||
parts := strings.SplitN(tag.Message, "\n", 2)
|
||||
note := ""
|
||||
if len(parts) > 1 {
|
||||
note = parts[1]
|
||||
}
|
||||
|
||||
if !has {
|
||||
parts := strings.SplitN(tag.Message, "\n", 2)
|
||||
note := ""
|
||||
if len(parts) > 1 {
|
||||
note = parts[1]
|
||||
}
|
||||
if rel, has := relMap[lowerTag]; !has {
|
||||
rel = &repo_model.Release{
|
||||
RepoID: repo.ID,
|
||||
Title: parts[0],
|
||||
|
@ -394,13 +394,13 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
|||
if author != nil {
|
||||
rel.PublisherID = author.ID
|
||||
}
|
||||
|
||||
newReleases = append(newReleases, rel)
|
||||
} else {
|
||||
rel.Title = parts[0]
|
||||
rel.Note = note
|
||||
rel.Sha1 = commit.ID.String()
|
||||
rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix())
|
||||
rel.NumCommits = commitsCount
|
||||
rel.IsDraft = false
|
||||
if rel.IsTag && author != nil {
|
||||
rel.PublisherID = author.ID
|
||||
}
|
||||
|
|
|
@ -65,8 +65,8 @@
|
|||
<tr>
|
||||
<th>{{ctx.Locale.Tr "units.unit"}}</th>
|
||||
<th id="access_none">{{ctx.Locale.Tr "org.teams.none_access"}}</th>
|
||||
<th id="access_read">{{ctx.Locale.Tr "org.teams.read_access"}}</th>
|
||||
<th id="access_write">{{ctx.Locale.Tr "org.teams.write_access"}}</th>
|
||||
<th>{{ctx.Locale.Tr "org.teams.read_access"}}</th>
|
||||
<th>{{ctx.Locale.Tr "org.teams.write_access"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -75,25 +75,26 @@
|
|||
<tr>
|
||||
<td>
|
||||
<label {{if $unit.Type.UnitGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
|
||||
{{ctx.Locale.Tr $unit.NameKey}}{{if $unit.Type.UnitGlobalDisabled}} {{ctx.Locale.Tr "org.team_unit_disabled"}}{{end}}
|
||||
<span class="help">{{ctx.Locale.Tr $unit.DescKey}}</span>
|
||||
<span id="help_{{$unit.Type.Value}}_name">{{ctx.Locale.Tr $unit.NameKey}}{{if $unit.Type.UnitGlobalDisabled}} {{ctx.Locale.Tr "org.team_unit_disabled"}}{{end}}</span>
|
||||
<span class="help" id="help_{{$unit.Type.Value}}_r">{{ctx.Locale.Tr (print "repo.permissions." $unit.Name ".read")}}</span>
|
||||
<span class="help" id="help_{{$unit.Type.Value}}_w">{{ctx.Locale.Tr (print "repo.permissions." $unit.Name ".write")}}</span>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<label>
|
||||
<input aria-labelledby="access_none" type="radio" name="unit_{{$unit.Type.Value}}" value="0"{{if or ($unit.Type.UnitGlobalDisabled) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 0)}} checked{{end}}>
|
||||
<input aria-labelledby="help_{{$unit.Type.Value}}_name access_none" type="radio" name="unit_{{$unit.Type.Value}}" value="0"{{if or ($unit.Type.UnitGlobalDisabled) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 0)}} checked{{end}}>
|
||||
<span class="only-mobile">{{ctx.Locale.Tr "org.teams.none_access"}}</span>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<label>
|
||||
<input aria-labelledby="access_read" type="radio" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}>
|
||||
<input aria-labelledby="help_{{$unit.Type.Value}}_name help_{{$unit.Type.Value}}_r" type="radio" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}>
|
||||
<span class="only-mobile">{{ctx.Locale.Tr "org.teams.read_access"}}</span>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<label>
|
||||
<input aria-labelledby="access_write" type="radio" name="unit_{{$unit.Type.Value}}" value="2"{{if (ge ($.Team.UnitAccessMode $.Context $unit.Type) 2)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}>
|
||||
<input aria-labelledby="help_{{$unit.Type.Value}}_name help_{{$unit.Type.Value}}_w" type="radio" name="unit_{{$unit.Type.Value}}" value="2"{{if (ge ($.Team.UnitAccessMode $.Context $unit.Type) 2)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}>
|
||||
<span class="only-mobile">{{ctx.Locale.Tr "org.teams.write_access"}}</span>
|
||||
</label>
|
||||
</td>
|
||||
|
@ -108,7 +109,7 @@
|
|||
<label {{if $unit.Type.UnitGlobalDisabled}}data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
|
||||
<input type="checkbox" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}>
|
||||
{{ctx.Locale.Tr $unit.NameKey}}{{if $unit.Type.UnitGlobalDisabled}} {{ctx.Locale.Tr "org.team_unit_disabled"}}{{end}}
|
||||
<span class="help">{{ctx.Locale.Tr $unit.DescKey}}</span>
|
||||
<span class="help">{{ctx.Locale.Tr (print "repo.permissions." $unit.Name)}}</span>
|
||||
</label>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
@ -16,11 +16,11 @@ pacman-key --lsign-key '{{$.SignMail}}'</code></pre>
|
|||
<pre
|
||||
class="code-block"><code>
|
||||
{{- if gt (len $.Groups) 1 -}}
|
||||
# {{ctx.Locale.Tr "packages.arch.pacman.repo.multi" $.PackageDescriptor.Package.LowerName}}
|
||||
# {{ctx.Locale.Tr "packages.arch.pacman.repo.multi" $.PackageDescriptor.Package.LowerName}}
|
||||
|
||||
{{end -}}
|
||||
{{- $GroupSize := (len .Groups) -}}
|
||||
{{- range $i,$v := .Groups -}}
|
||||
{{- range $i,$v := .Groups -}}
|
||||
{{- if gt $i 0}}
|
||||
{{end -}}{{- if gt $GroupSize 1 -}}
|
||||
# {{ctx.Locale.Tr "packages.arch.pacman.repo.multi.item" .}}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{{if eq .PackageDescriptor.Package.Type "arch"}}
|
||||
{{range .PackageDescriptor.Metadata.License}}<div class="item" title="{{$.locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "gt-mr-3"}} {{.}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.License}}<div class="item" title="{{$.locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.}}</div>{{end}}
|
||||
{{end}}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<div class="issue-card-icon">
|
||||
{{template "shared/issueicon" .}}
|
||||
</div>
|
||||
<a class="issue-card-title muted issue-title tw-break-anywhere" href="{{.Link}}">{{.Title | RenderEmoji ctx | RenderCodeBlock}}</a>
|
||||
<a class="issue-card-title muted issue-title tw-break-anywhere" href="{{.Link}}">{{RenderRefIssueTitle $.Context .Title}}</a>
|
||||
{{if and $.isPinnedIssueCard $.Page.IsRepoAdmin}}
|
||||
<a role="button" class="issue-card-unpin muted tw-flex tw-items-center" data-tooltip-content={{ctx.Locale.Tr "repo.issues.unpin_issue"}} data-issue-id="{{.ID}}" data-unpin-url="{{$.Page.Link}}/unpin/{{.Index}}">
|
||||
{{svg "octicon-x" 16}}
|
||||
|
|
|
@ -149,7 +149,7 @@
|
|||
{{if eq .RefAction 3}}</del>{{end}}
|
||||
|
||||
<div class="detail flex-text-block">
|
||||
<span class="text grey muted-links"><a href="{{.RefIssueLink ctx}}"><b>{{.RefIssueTitle ctx}}</b> {{.RefIssueIdent ctx}}</a></span>
|
||||
<span class="text grey muted-links"><a href="{{.RefIssueLink ctx}}"><b>{{.RefIssueTitle ctx | RenderEmoji $.Context | RenderCodeBlock}}</b> {{.RefIssueIdent ctx}}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
{{else if eq .Type 4}}
|
||||
|
@ -226,7 +226,7 @@
|
|||
{{template "shared/user/avatarlink" dict "user" .Poster}}
|
||||
<span class="text grey muted-links">
|
||||
{{template "shared/user/authorlink" .Poster}}
|
||||
{{ctx.Locale.Tr "repo.issues.change_title_at" (.OldTitle|RenderEmoji $.Context) (.NewTitle|RenderEmoji $.Context) $createdStr}}
|
||||
{{ctx.Locale.Tr "repo.issues.change_title_at" (RenderRefIssueTitle $.Context .OldTitle) (RenderRefIssueTitle $.Context .NewTitle) $createdStr}}
|
||||
</span>
|
||||
</div>
|
||||
{{else if eq .Type 11}}
|
||||
|
@ -339,10 +339,11 @@
|
|||
{{svg "octicon-plus"}}
|
||||
<span class="text grey muted-links">
|
||||
<a href="{{.DependentIssue.Link}}">
|
||||
{{$strTitle := RenderRefIssueTitle $.Context .DependentIssue.Title}}
|
||||
{{if eq .DependentIssue.RepoID .Issue.RepoID}}
|
||||
#{{.DependentIssue.Index}} {{.DependentIssue.Title}}
|
||||
#{{.DependentIssue.Index}} {{$strTitle}}
|
||||
{{else}}
|
||||
{{.DependentIssue.Repo.FullName}}#{{.DependentIssue.Index}} - {{.DependentIssue.Title}}
|
||||
{{.DependentIssue.Repo.FullName}}#{{.DependentIssue.Index}} - {{$strTitle}}
|
||||
{{end}}
|
||||
</a>
|
||||
</span>
|
||||
|
@ -362,10 +363,11 @@
|
|||
{{svg "octicon-trash"}}
|
||||
<span class="text grey muted-links">
|
||||
<a href="{{.DependentIssue.Link}}">
|
||||
{{$strTitle := RenderRefIssueTitle $.Context .DependentIssue.Title}}
|
||||
{{if eq .DependentIssue.RepoID .Issue.RepoID}}
|
||||
#{{.DependentIssue.Index}} {{.DependentIssue.Title}}
|
||||
#{{.DependentIssue.Index}} {{$strTitle}}
|
||||
{{else}}
|
||||
{{.DependentIssue.Repo.FullName}}#{{.DependentIssue.Index}} - {{.DependentIssue.Title}}
|
||||
{{.DependentIssue.Repo.FullName}}#{{.DependentIssue.Index}} - {{$strTitle}}
|
||||
{{end}}
|
||||
</a>
|
||||
</span>
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
{{range .BlockingDependencies}}
|
||||
<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
|
||||
<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
|
||||
<a class="title muted" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}">
|
||||
#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}
|
||||
<a class="title muted" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{RenderRefIssueTitle $.Context .Issue.Title}}}">
|
||||
#{{.Issue.Index}} {{RenderRefIssueTitle $.Context .Issue.Title}}}
|
||||
</a>
|
||||
<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
|
||||
{{.Repository.OwnerName}}/{{.Repository.Name}}
|
||||
|
@ -51,8 +51,9 @@
|
|||
{{range .BlockedByDependencies}}
|
||||
<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
|
||||
<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
|
||||
<a class="title muted" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}">
|
||||
#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}
|
||||
{{$title := RenderRefIssueTitle $.Context .Issue.Title}}
|
||||
<a class="title muted" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{RenderRefIssueTitle $.Context .Issue.Title}}">
|
||||
#{{.Issue.Index}} {{RenderRefIssueTitle $.Context .Issue.Title}}
|
||||
</a>
|
||||
<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
|
||||
{{.Repository.OwnerName}}/{{.Repository.Name}}
|
||||
|
@ -73,8 +74,8 @@
|
|||
<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
|
||||
<div class="gt-ellipsis">
|
||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.no_permission.can_remove"}}">{{svg "octicon-lock" 16}}</span>
|
||||
<span class="title" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}">
|
||||
#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}
|
||||
<span class="title" data-tooltip-content="#{{.Issue.Index}} {{RenderRefIssueTitle $.Context .DependentIssue.Title}}">
|
||||
#{{.Issue.Index}} {{RenderRefIssueTitle $.Context .DependentIssue.Title}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
{{$canEditIssueTitle := and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}}
|
||||
<div class="issue-title" id="issue-title-display">
|
||||
<h1 class="tw-break-anywhere">
|
||||
{{RenderIssueTitle $.Context .Issue.Title ($.Repository.ComposeMetas ctx) | RenderCodeBlock}}
|
||||
<span class="index">#{{.Issue.Index}}</span>
|
||||
{{RenderIssueTitle $.Context .Issue.Title ($.Repository.ComposeMetas ctx)}} <span class="index">#{{.Issue.Index}}</span>
|
||||
</h1>
|
||||
<div class="button-row">
|
||||
{{if $canEditIssueTitle}}
|
||||
|
|
|
@ -153,7 +153,7 @@
|
|||
{{range .Activity.MergedPRs}}
|
||||
<p class="desc">
|
||||
<span class="ui purple label">{{ctx.Locale.Tr "repo.activity.merged_prs_label"}}</span>
|
||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
|
||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{RenderRefIssueTitle $.Context .Issue.Title}}</a>
|
||||
{{TimeSinceUnix .MergedUnix ctx.Locale}}
|
||||
</p>
|
||||
{{end}}
|
||||
|
@ -172,7 +172,7 @@
|
|||
{{range .Activity.OpenedPRs}}
|
||||
<p class="desc">
|
||||
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.opened_prs_label"}}</span>
|
||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
|
||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{RenderRefIssueTitle $.Context .Issue.Title}}</a>
|
||||
{{TimeSinceUnix .Issue.CreatedUnix ctx.Locale}}
|
||||
</p>
|
||||
{{end}}
|
||||
|
@ -191,7 +191,7 @@
|
|||
{{range .Activity.ClosedIssues}}
|
||||
<p class="desc">
|
||||
<span class="ui red label">{{ctx.Locale.Tr "repo.activity.closed_issue_label"}}</span>
|
||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
|
||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{RenderRefIssueTitle $.Context .Title}}</a>
|
||||
{{TimeSinceUnix .ClosedUnix ctx.Locale}}
|
||||
</p>
|
||||
{{end}}
|
||||
|
@ -210,7 +210,7 @@
|
|||
{{range .Activity.OpenedIssues}}
|
||||
<p class="desc">
|
||||
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.new_issue_label"}}</span>
|
||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
|
||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{RenderRefIssueTitle $.Context .Title}}</a>
|
||||
{{TimeSinceUnix .CreatedUnix ctx.Locale}}
|
||||
</p>
|
||||
{{end}}
|
||||
|
@ -228,9 +228,9 @@
|
|||
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.unresolved_conv_label"}}</span>
|
||||
#{{.Index}}
|
||||
{{if .IsPull}}
|
||||
<a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
|
||||
<a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{RenderRefIssueTitle $.Context .Title}}</a>
|
||||
{{else}}
|
||||
<a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
|
||||
<a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{RenderRefIssueTitle $.Context .Title}}</a>
|
||||
{{end}}
|
||||
{{TimeSinceUnix .UpdatedUnix ctx.Locale}}
|
||||
</p>
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
<div class="notifications-bottom-row tw-text-16 tw-py-0.5">
|
||||
<span class="issue-title tw-break-anywhere">
|
||||
{{if .Issue}}
|
||||
{{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}}
|
||||
{{RenderRefIssueTitle $.Context .Issue.Title}}
|
||||
{{else}}
|
||||
{{.Repository.FullName}}
|
||||
{{end}}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {expect} from '@playwright/test';
|
||||
import AxeBuilder from '@axe-core/playwright';
|
||||
import {AxeBuilder} from '@axe-core/playwright';
|
||||
|
||||
export async function validate_form({page}, scope) {
|
||||
scope ??= 'form';
|
||||
|
@ -21,10 +21,24 @@ export async function validate_form({page}, scope) {
|
|||
await expect(b).toHaveCSS('margin-top', '0px');
|
||||
await expect(b).toHaveCSS('vertical-align', 'baseline');
|
||||
}
|
||||
|
||||
// assert no (trailing) colon is used in labels
|
||||
// might be necessary to adjust in case colons are strictly necessary in help text
|
||||
for (const l of await page.locator('label').all()) {
|
||||
const str = await l.textContent();
|
||||
await expect(str.split('\n')[0]).not.toContain(':');
|
||||
}
|
||||
|
||||
// check that multiple help text are correctly aligned to each other
|
||||
// used for example to separate read/write permissions in team permission matrix
|
||||
for (const l of await page.locator('label:has(.help + .help)').all()) {
|
||||
const helpLabels = await l.locator('.help').all();
|
||||
const boxes = await Promise.all(helpLabels.map((help) => help.boundingBox()));
|
||||
for (let i = 1; i < boxes.length; i++) {
|
||||
// help texts vertically aligned on top of each other
|
||||
await expect(boxes[i].x).toBe(boxes[0].x);
|
||||
// help texts don't horizontally intersect each other
|
||||
await expect(boxes[i].y + boxes[i].height).toBeGreaterThanOrEqual(boxes[i - 1].y + boxes[i - 1].height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
|
||||
|
@ -258,11 +259,15 @@ HMhNSS1IzUsBcpJAPFAwwUXSM0u4BjoaR8EoGAWjgGQAAILFeyQADAAA
|
|||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
req = NewRequestWithBody(t, "DELETE", rootURL+"/base/notfound/1.0.0-1", nil).
|
||||
req = NewRequestWithBody(t, "DELETE", rootURL+"/base/notfound/1.0.0-1/any", nil).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1", nil).
|
||||
req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1/x86_64", nil).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1/any", nil).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
|
@ -270,12 +275,22 @@ HMhNSS1IzUsBcpJAPFAwwUXSM0u4BjoaR8EoGAWjgGQAAILFeyQADAAA
|
|||
respPkg := MakeRequest(t, req, http.StatusOK)
|
||||
files, err := listTarGzFiles(respPkg.Body.Bytes())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, files, 1) // other pkg in L225
|
||||
require.Len(t, files, 1)
|
||||
|
||||
req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1", nil).
|
||||
req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1/any", nil).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
req = NewRequest(t, "GET", groupURL+"/x86_64/base.db")
|
||||
|
||||
req = NewRequest(t, "GET", groupURL+"/x86_64/base.db").
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1/aarch64", nil).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
req = NewRequest(t, "GET", groupURL+"/aarch64/base.db").
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
|
@ -294,12 +309,33 @@ HMhNSS1IzUsBcpJAPFAwwUXSM0u4BjoaR8EoGAWjgGQAAILFeyQADAAA
|
|||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
require.Equal(t, pkgs[key], resp.Body.Bytes())
|
||||
|
||||
req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1", nil).
|
||||
req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1/any", nil).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
})
|
||||
}
|
||||
}
|
||||
t.Run("Concurrent Upload", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
var wg sync.WaitGroup
|
||||
|
||||
targets := []string{"any", "aarch64", "x86_64"}
|
||||
for _, tag := range targets {
|
||||
wg.Add(1)
|
||||
go func(i string) {
|
||||
defer wg.Done()
|
||||
req := NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgs[i])).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
}(tag)
|
||||
}
|
||||
wg.Wait()
|
||||
for _, target := range targets {
|
||||
req := NewRequestWithBody(t, "DELETE", rootURL+"/test/1.0.0-1/"+target, nil).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func getProperty(data, key string) string {
|
||||
|
@ -318,10 +354,10 @@ func getProperty(data, key string) string {
|
|||
|
||||
func listTarGzFiles(data []byte) (fstest.MapFS, error) {
|
||||
reader, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
defer reader.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer reader.Close()
|
||||
tarRead := tar.NewReader(reader)
|
||||
files := make(fstest.MapFS)
|
||||
for {
|
||||
|
|
|
@ -37,6 +37,7 @@ func TestPackageComposer(t *testing.T) {
|
|||
packageType := "composer-plugin"
|
||||
packageAuthor := "Gitea Authors"
|
||||
packageLicense := "MIT"
|
||||
packageBin := "./bin/script"
|
||||
|
||||
var buf bytes.Buffer
|
||||
archive := zip.NewWriter(&buf)
|
||||
|
@ -50,6 +51,9 @@ func TestPackageComposer(t *testing.T) {
|
|||
{
|
||||
"name": "` + packageAuthor + `"
|
||||
}
|
||||
],
|
||||
"bin": [
|
||||
"` + packageBin + `"
|
||||
]
|
||||
}`))
|
||||
archive.Close()
|
||||
|
@ -211,6 +215,8 @@ func TestPackageComposer(t *testing.T) {
|
|||
assert.Len(t, pkgs[0].Authors, 1)
|
||||
assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name)
|
||||
assert.Equal(t, "zip", pkgs[0].Dist.Type)
|
||||
assert.Equal(t, "7b40bfd6da811b2b78deec1e944f156dbb2c747b", pkgs[0].Dist.Checksum)
|
||||
assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum)
|
||||
assert.Len(t, pkgs[0].Bin, 1)
|
||||
assert.Equal(t, packageBin, pkgs[0].Bin[0])
|
||||
})
|
||||
}
|
||||
|
|
|
@ -23,10 +23,10 @@ func TestAPICreateAndDeleteToken(t *testing.T) {
|
|||
defer tests.PrepareTestEnv(t)()
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, nil)
|
||||
newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
|
||||
deleteAPIAccessToken(t, newAccessToken, user)
|
||||
|
||||
newAccessToken = createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user, nil)
|
||||
newAccessToken = createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
|
||||
deleteAPIAccessToken(t, newAccessToken, user)
|
||||
}
|
||||
|
||||
|
@ -72,19 +72,19 @@ func TestAPIDeleteTokensPermission(t *testing.T) {
|
|||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
// admin can delete tokens for other users
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user2, nil)
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
|
||||
req := NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-1").
|
||||
AddBasicAuth(admin.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// non-admin can delete tokens for himself
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user2, nil)
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
|
||||
req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-2").
|
||||
AddBasicAuth(user2.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// non-admin can't delete tokens for other users
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-3", user2, nil)
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-3", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
|
||||
req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-3").
|
||||
AddBasicAuth(user4.Name)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
|
@ -520,7 +520,7 @@ func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model
|
|||
unauthorizedScopes = append(unauthorizedScopes, cateogoryUnauthorizedScopes...)
|
||||
}
|
||||
|
||||
accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-token", user, &unauthorizedScopes)
|
||||
accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-token", user, unauthorizedScopes)
|
||||
defer deleteAPIAccessToken(t, accessToken, user)
|
||||
|
||||
// Request the endpoint. Verify that permission is denied.
|
||||
|
@ -532,20 +532,12 @@ func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model
|
|||
|
||||
// createAPIAccessTokenWithoutCleanUp Create an API access token and assert that
|
||||
// creation succeeded. The caller is responsible for deleting the token.
|
||||
func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *user_model.User, scopes *[]auth_model.AccessTokenScope) api.AccessToken {
|
||||
func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *user_model.User, scopes []auth_model.AccessTokenScope) api.AccessToken {
|
||||
payload := map[string]any{
|
||||
"name": tokenName,
|
||||
}
|
||||
if scopes != nil {
|
||||
for _, scope := range *scopes {
|
||||
scopes, scopesExists := payload["scopes"].([]string)
|
||||
if !scopesExists {
|
||||
scopes = make([]string, 0)
|
||||
}
|
||||
scopes = append(scopes, string(scope))
|
||||
payload["scopes"] = scopes
|
||||
}
|
||||
"name": tokenName,
|
||||
"scopes": scopes,
|
||||
}
|
||||
|
||||
log.Debug("Requesting creation of token with scopes: %v", scopes)
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", payload).
|
||||
AddBasicAuth(user.Name)
|
||||
|
@ -563,8 +555,7 @@ func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *us
|
|||
return newAccessToken
|
||||
}
|
||||
|
||||
// createAPIAccessTokenWithoutCleanUp Delete an API access token and assert that
|
||||
// deletion succeeded.
|
||||
// deleteAPIAccessToken deletes an API access token and assert that deletion succeeded.
|
||||
func deleteAPIAccessToken(t *testing.T, accessToken api.AccessToken, user *user_model.User) {
|
||||
req := NewRequestf(t, "DELETE", "/api/v1/users/"+user.LoginName+"/tokens/%d", accessToken.ID).
|
||||
AddBasicAuth(user.Name)
|
||||
|
|
|
@ -60,7 +60,8 @@ func createAttachment(t *testing.T, session *TestSession, repoURL, filename stri
|
|||
func TestCreateAnonymousAttachment(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
session := emptyTestSession(t)
|
||||
createAttachment(t, session, "user2/repo1", "image.png", generateImg(), http.StatusSeeOther)
|
||||
// this test is not right because it just doesn't pass the CSRF validation
|
||||
createAttachment(t, session, "user2/repo1", "image.png", generateImg(), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateIssueAttachment(t *testing.T) {
|
||||
|
|
|
@ -5,12 +5,10 @@ package integration
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -25,28 +23,12 @@ func TestCsrfProtection(t *testing.T) {
|
|||
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
|
||||
"_csrf": "fake_csrf",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
resp := session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
loc := resp.Header().Get("Location")
|
||||
assert.Equal(t, setting.AppSubURL+"/", loc)
|
||||
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
assert.Equal(t, "Bad Request: invalid CSRF token",
|
||||
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
|
||||
)
|
||||
resp := session.MakeRequest(t, req, http.StatusBadRequest)
|
||||
assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
|
||||
|
||||
// test web form csrf via header. TODO: should use an UI api to test
|
||||
req = NewRequest(t, "POST", "/user/settings")
|
||||
req.Header.Add("X-Csrf-Token", "fake_csrf")
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
loc = resp.Header().Get("Location")
|
||||
assert.Equal(t, setting.AppSubURL+"/", loc)
|
||||
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
|
||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||
assert.Equal(t, "Bad Request: invalid CSRF token",
|
||||
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
|
||||
)
|
||||
resp = session.MakeRequest(t, req, http.StatusBadRequest)
|
||||
assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
forgejo_context "code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -190,11 +191,6 @@ func TestRedirectsWebhooks(t *testing.T) {
|
|||
{from: "/user/settings/hooks/" + kind + "/new", to: "/user/login", verb: "GET"},
|
||||
{from: "/admin/system-hooks/" + kind + "/new", to: "/user/login", verb: "GET"},
|
||||
{from: "/admin/default-hooks/" + kind + "/new", to: "/user/login", verb: "GET"},
|
||||
{from: "/user2/repo1/settings/hooks/" + kind + "/new", to: "/", verb: "POST"},
|
||||
{from: "/admin/system-hooks/" + kind + "/new", to: "/", verb: "POST"},
|
||||
{from: "/admin/default-hooks/" + kind + "/new", to: "/", verb: "POST"},
|
||||
{from: "/user2/repo1/settings/hooks/1", to: "/", verb: "POST"},
|
||||
{from: "/admin/hooks/1", to: "/", verb: "POST"},
|
||||
}
|
||||
for _, info := range redirects {
|
||||
req := NewRequest(t, info.verb, info.from)
|
||||
|
@ -202,6 +198,24 @@ func TestRedirectsWebhooks(t *testing.T) {
|
|||
assert.EqualValues(t, path.Join(setting.AppSubURL, info.to), test.RedirectURL(resp), info.from)
|
||||
}
|
||||
}
|
||||
|
||||
for _, kind := range []string{"forgejo", "gitea"} {
|
||||
csrf := []struct {
|
||||
from string
|
||||
verb string
|
||||
}{
|
||||
{from: "/user2/repo1/settings/hooks/" + kind + "/new", verb: "POST"},
|
||||
{from: "/admin/hooks/1", verb: "POST"},
|
||||
{from: "/admin/system-hooks/" + kind + "/new", verb: "POST"},
|
||||
{from: "/admin/default-hooks/" + kind + "/new", verb: "POST"},
|
||||
{from: "/user2/repo1/settings/hooks/1", verb: "POST"},
|
||||
}
|
||||
for _, info := range csrf {
|
||||
req := NewRequest(t, info.verb, info.from)
|
||||
resp := MakeRequest(t, req, http.StatusBadRequest)
|
||||
assert.Contains(t, resp.Body.String(), forgejo_context.CsrfErrorString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoLinks(t *testing.T) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -24,6 +25,7 @@ import (
|
|||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/routers/web/auth"
|
||||
forgejo_context "code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/markbates/goth"
|
||||
|
@ -803,6 +805,16 @@ func TestOAuthIntrospection(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func requireCookieCSRF(t *testing.T, resp http.ResponseWriter) string {
|
||||
for _, c := range resp.(*httptest.ResponseRecorder).Result().Cookies() {
|
||||
if c.Name == "_csrf" {
|
||||
return c.Value
|
||||
}
|
||||
}
|
||||
require.True(t, false, "_csrf not found in cookies")
|
||||
return ""
|
||||
}
|
||||
|
||||
func TestOAuth_GrantScopesReadUser(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
|
@ -840,19 +852,18 @@ func TestOAuth_GrantScopesReadUser(t *testing.T) {
|
|||
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
|
||||
|
||||
authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&")[0]
|
||||
htmlDoc := NewHTMLParser(t, authorizeResp.Body)
|
||||
grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"_csrf": requireCookieCSRF(t, authorizeResp),
|
||||
"client_id": app.ClientID,
|
||||
"redirect_uri": "a",
|
||||
"state": "thestate",
|
||||
"granted": "true",
|
||||
})
|
||||
grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther)
|
||||
htmlDocGrant := NewHTMLParser(t, grantResp.Body)
|
||||
grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest)
|
||||
assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString)
|
||||
|
||||
accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"_csrf": htmlDocGrant.GetCSRF(),
|
||||
"_csrf": requireCookieCSRF(t, authorizeResp),
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": app.ClientID,
|
||||
"client_secret": app.ClientSecret,
|
||||
|
@ -921,19 +932,18 @@ func TestOAuth_GrantScopesFailReadRepository(t *testing.T) {
|
|||
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
|
||||
|
||||
authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&")[0]
|
||||
htmlDoc := NewHTMLParser(t, authorizeResp.Body)
|
||||
grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"_csrf": requireCookieCSRF(t, authorizeResp),
|
||||
"client_id": app.ClientID,
|
||||
"redirect_uri": "a",
|
||||
"state": "thestate",
|
||||
"granted": "true",
|
||||
})
|
||||
grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther)
|
||||
htmlDocGrant := NewHTMLParser(t, grantResp.Body)
|
||||
grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest)
|
||||
assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString)
|
||||
|
||||
accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"_csrf": htmlDocGrant.GetCSRF(),
|
||||
"_csrf": requireCookieCSRF(t, authorizeResp),
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": app.ClientID,
|
||||
"client_secret": app.ClientSecret,
|
||||
|
@ -1000,19 +1010,18 @@ func TestOAuth_GrantScopesReadRepository(t *testing.T) {
|
|||
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
|
||||
|
||||
authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&")[0]
|
||||
htmlDoc := NewHTMLParser(t, authorizeResp.Body)
|
||||
grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"_csrf": requireCookieCSRF(t, authorizeResp),
|
||||
"client_id": app.ClientID,
|
||||
"redirect_uri": "a",
|
||||
"state": "thestate",
|
||||
"granted": "true",
|
||||
})
|
||||
grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther)
|
||||
htmlDocGrant := NewHTMLParser(t, grantResp.Body)
|
||||
grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest)
|
||||
assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString)
|
||||
|
||||
accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"_csrf": htmlDocGrant.GetCSRF(),
|
||||
"_csrf": requireCookieCSRF(t, authorizeResp),
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": app.ClientID,
|
||||
"client_secret": app.ClientSecret,
|
||||
|
@ -1082,19 +1091,18 @@ func TestOAuth_GrantScopesReadPrivateGroups(t *testing.T) {
|
|||
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
|
||||
|
||||
authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&")[0]
|
||||
htmlDoc := NewHTMLParser(t, authorizeResp.Body)
|
||||
grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"_csrf": requireCookieCSRF(t, authorizeResp),
|
||||
"client_id": app.ClientID,
|
||||
"redirect_uri": "a",
|
||||
"state": "thestate",
|
||||
"granted": "true",
|
||||
})
|
||||
grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther)
|
||||
htmlDocGrant := NewHTMLParser(t, grantResp.Body)
|
||||
grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest)
|
||||
assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString)
|
||||
|
||||
accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"_csrf": htmlDocGrant.GetCSRF(),
|
||||
"_csrf": requireCookieCSRF(t, authorizeResp),
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": app.ClientID,
|
||||
"client_secret": app.ClientSecret,
|
||||
|
@ -1164,19 +1172,18 @@ func TestOAuth_GrantScopesReadOnlyPublicGroups(t *testing.T) {
|
|||
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
|
||||
|
||||
authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&")[0]
|
||||
htmlDoc := NewHTMLParser(t, authorizeResp.Body)
|
||||
grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"_csrf": requireCookieCSRF(t, authorizeResp),
|
||||
"client_id": app.ClientID,
|
||||
"redirect_uri": "a",
|
||||
"state": "thestate",
|
||||
"granted": "true",
|
||||
})
|
||||
grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther)
|
||||
htmlDocGrant := NewHTMLParser(t, grantResp.Body)
|
||||
grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest)
|
||||
assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString)
|
||||
|
||||
accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"_csrf": htmlDocGrant.GetCSRF(),
|
||||
"_csrf": requireCookieCSRF(t, authorizeResp),
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": app.ClientID,
|
||||
"client_secret": app.ClientSecret,
|
||||
|
@ -1260,19 +1267,18 @@ func TestOAuth_GrantScopesReadPublicGroupsWithTheReadScope(t *testing.T) {
|
|||
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
|
||||
|
||||
authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&")[0]
|
||||
htmlDoc := NewHTMLParser(t, authorizeResp.Body)
|
||||
grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"_csrf": requireCookieCSRF(t, authorizeResp),
|
||||
"client_id": app.ClientID,
|
||||
"redirect_uri": "a",
|
||||
"state": "thestate",
|
||||
"granted": "true",
|
||||
})
|
||||
grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther)
|
||||
htmlDocGrant := NewHTMLParser(t, grantResp.Body)
|
||||
grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest)
|
||||
assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString)
|
||||
|
||||
accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"_csrf": htmlDocGrant.GetCSRF(),
|
||||
"_csrf": requireCookieCSRF(t, authorizeResp),
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": app.ClientID,
|
||||
"client_secret": app.ClientSecret,
|
||||
|
|
|
@ -18,7 +18,6 @@ import (
|
|||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
|
@ -157,15 +156,8 @@ func TestCreateBranchInvalidCSRF(t *testing.T) {
|
|||
"_csrf": "fake_csrf",
|
||||
"new_branch_name": "test",
|
||||
})
|
||||
resp := session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
loc := resp.Header().Get("Location")
|
||||
assert.Equal(t, setting.AppSubURL+"/", loc)
|
||||
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
assert.Equal(t,
|
||||
"Bad Request: invalid CSRF token",
|
||||
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
|
||||
)
|
||||
resp := session.MakeRequest(t, req, http.StatusBadRequest)
|
||||
assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
|
||||
}
|
||||
|
||||
func TestDatabaseMissingABranch(t *testing.T) {
|
||||
|
|
162
tests/integration/repo_issue_title_test.go
Normal file
162
tests/integration/repo_issue_title_test.go
Normal file
|
@ -0,0 +1,162 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIssueTitles(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
repo, _, f := tests.CreateDeclarativeRepo(t, user, "issue-titles", nil, nil, nil)
|
||||
defer f()
|
||||
|
||||
session := loginUser(t, user.LoginName)
|
||||
|
||||
title := "Title :+1: `code`"
|
||||
issue1 := createIssue(t, user, repo, title, "Test issue")
|
||||
issue2 := createIssue(t, user, repo, title, "Ref #1")
|
||||
|
||||
titleHTML := []string{
|
||||
"Title",
|
||||
`<span class="emoji" aria-label="thumbs up">👍</span>`,
|
||||
`<code class="inline-code-block">code</code>`,
|
||||
}
|
||||
|
||||
t.Run("Main issue title", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
html := extractHTML(t, session, issue1, "div.issue-title-header > * > h1")
|
||||
assertContainsAll(t, titleHTML, html)
|
||||
})
|
||||
|
||||
t.Run("Referenced issue comment", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
html := extractHTML(t, session, issue1, "div.timeline > div.timeline-item:nth-child(3) > div.detail > * > a")
|
||||
assertContainsAll(t, titleHTML, html)
|
||||
})
|
||||
|
||||
t.Run("Dependent issue comment", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
err := issues_model.CreateIssueDependency(db.DefaultContext, user, issue1, issue2)
|
||||
require.NoError(t, err)
|
||||
|
||||
html := extractHTML(t, session, issue1, "div.timeline > div:nth-child(3) > div.detail > * > a")
|
||||
assertContainsAll(t, titleHTML, html)
|
||||
})
|
||||
|
||||
t.Run("Dependent issue sidebar", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
html := extractHTML(t, session, issue1, "div.item.dependency > * > a.title")
|
||||
assertContainsAll(t, titleHTML, html)
|
||||
})
|
||||
|
||||
t.Run("Referenced pull comment", func(t *testing.T) {
|
||||
_, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user, &files_service.ChangeRepoFilesOptions{
|
||||
Files: []*files_service.ChangeRepoFile{
|
||||
{
|
||||
Operation: "update",
|
||||
TreePath: "README.md",
|
||||
ContentReader: strings.NewReader("Update README"),
|
||||
},
|
||||
},
|
||||
Message: "Update README",
|
||||
OldBranch: "main",
|
||||
NewBranch: "branch",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
Committer: time.Now(),
|
||||
},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
pullIssue := &issues_model.Issue{
|
||||
RepoID: repo.ID,
|
||||
Title: title,
|
||||
Content: "Closes #1",
|
||||
PosterID: user.ID,
|
||||
Poster: user,
|
||||
IsPull: true,
|
||||
}
|
||||
|
||||
pullRequest := &issues_model.PullRequest{
|
||||
HeadRepoID: repo.ID,
|
||||
BaseRepoID: repo.ID,
|
||||
HeadBranch: "branch",
|
||||
BaseBranch: "main",
|
||||
HeadRepo: repo,
|
||||
BaseRepo: repo,
|
||||
Type: issues_model.PullRequestGitea,
|
||||
}
|
||||
|
||||
err = pull_service.NewPullRequest(git.DefaultContext, repo, pullIssue, nil, nil, pullRequest, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
html := extractHTML(t, session, issue1, "div.timeline > div:nth-child(4) > div.detail > * > a")
|
||||
assertContainsAll(t, titleHTML, html)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func createIssue(t *testing.T, user *user_model.User, repo *repo_model.Repository, title, content string) *issues_model.Issue {
|
||||
issue := &issues_model.Issue{
|
||||
RepoID: repo.ID,
|
||||
Title: title,
|
||||
Content: content,
|
||||
PosterID: user.ID,
|
||||
Poster: user,
|
||||
}
|
||||
|
||||
err := issue_service.NewIssue(db.DefaultContext, repo, issue, nil, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
return issue
|
||||
}
|
||||
|
||||
func extractHTML(t *testing.T, session *TestSession, issue *issues_model.Issue, query string) string {
|
||||
req := NewRequest(t, "GET", issue.HTMLURL())
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
res, err := doc.doc.Find(query).Html()
|
||||
require.NoError(t, err)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func assertContainsAll(t *testing.T, expected []string, actual string) {
|
||||
for i := range expected {
|
||||
assert.Contains(t, actual, expected[i])
|
||||
}
|
||||
}
|
|
@ -108,6 +108,40 @@ func TestCreateNewTagProtected(t *testing.T) {
|
|||
})
|
||||
})
|
||||
|
||||
t.Run("GitTagForce", func(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
httpContext := NewAPITestContext(t, owner.Name, repo.Name)
|
||||
|
||||
dstPath := t.TempDir()
|
||||
|
||||
u.Path = httpContext.GitPath()
|
||||
u.User = url.UserPassword(owner.Name, userPassword)
|
||||
|
||||
doGitClone(dstPath, u)(t)
|
||||
|
||||
_, _, err := git.NewCommand(git.DefaultContext, "tag", "v-1.1", "-m", "force update", "--force").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "tag", "v-1.1", "-m", "force update v2", "--force").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "the tag already exists in the remote")
|
||||
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "--tags", "--force").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
req := NewRequestf(t, "GET", "/%s/releases/tag/v-1.1", repo.FullName())
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
tagsTab := htmlDoc.Find(".release-list-title")
|
||||
assert.Contains(t, tagsTab.Text(), "force update v2")
|
||||
})
|
||||
})
|
||||
|
||||
// Cleanup
|
||||
releases, err := db.Find[repo_model.Release](db.DefaultContext, repo_model.FindReleasesOptions{
|
||||
IncludeTags: true,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {defineConfig} from 'vitest/config';
|
||||
import vuePlugin from '@vitejs/plugin-vue';
|
||||
import {stringPlugin} from 'vite-string-plugin';
|
||||
import {resolve} from 'node:path';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
|
@ -13,6 +14,9 @@ export default defineConfig({
|
|||
passWithNoTests: true,
|
||||
globals: true,
|
||||
watch: false,
|
||||
alias: {
|
||||
'monaco-editor': resolve(import.meta.dirname, '/node_modules/monaco-editor/esm/vs/editor/editor.api'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
stringPlugin(),
|
||||
|
|
125
web_src/fomantic/package-lock.json
generated
125
web_src/fomantic/package-lock.json
generated
|
@ -44,9 +44,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
|
@ -198,9 +198,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@octokit/core/node_modules/@octokit/request-error": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.4.tgz",
|
||||
"integrity": "sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg==",
|
||||
"version": "6.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
|
||||
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
@ -211,9 +211,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@octokit/core/node_modules/@octokit/types": {
|
||||
"version": "13.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz",
|
||||
"integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==",
|
||||
"version": "13.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.0.tgz",
|
||||
"integrity": "sha512-CrooV/vKCXqwLa+osmHLIMUb87brpgUqlqkPGc6iE2wCkUvTrHiXFMhAKoDDaAAYJrtKtrFTgSQTg5nObBEaew==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
@ -304,9 +304,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@octokit/graphql/node_modules/@octokit/request-error": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.4.tgz",
|
||||
"integrity": "sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg==",
|
||||
"version": "6.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
|
||||
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
@ -317,9 +317,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@octokit/graphql/node_modules/@octokit/types": {
|
||||
"version": "13.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz",
|
||||
"integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==",
|
||||
"version": "13.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.0.tgz",
|
||||
"integrity": "sha512-CrooV/vKCXqwLa+osmHLIMUb87brpgUqlqkPGc6iE2wCkUvTrHiXFMhAKoDDaAAYJrtKtrFTgSQTg5nObBEaew==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
@ -492,9 +492,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.4.0.tgz",
|
||||
"integrity": "sha512-49AbMDwYUz7EXxKU/r7mXOsxwFr4BYbvB7tWYxVuLdb2ibd30ijjXINSMAHiEEZk5PCRBmW1gUeisn2VMKt3cQ==",
|
||||
"version": "22.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
|
||||
"integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
|
@ -1115,9 +1115,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.23.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
|
||||
"integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
|
||||
"version": "4.24.0",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz",
|
||||
"integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -1134,8 +1134,8 @@
|
|||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001646",
|
||||
"electron-to-chromium": "^1.5.4",
|
||||
"caniuse-lite": "^1.0.30001663",
|
||||
"electron-to-chromium": "^1.5.28",
|
||||
"node-releases": "^2.0.18",
|
||||
"update-browserslist-db": "^1.1.0"
|
||||
},
|
||||
|
@ -1219,9 +1219,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001651",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
|
||||
"integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==",
|
||||
"version": "1.0.30001664",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz",
|
||||
"integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -1277,7 +1277,6 @@
|
|||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
|
||||
"integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
|
||||
"deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "^2.0.0",
|
||||
|
@ -1962,9 +1961,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.11",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.11.tgz",
|
||||
"integrity": "sha512-R1CccCDYqndR25CaXFd6hp/u9RaaMcftMkphmvuepXr5b1vfLkRml6aWVeBhXJ7rbevHkKEMJtz8XqPf7ffmew==",
|
||||
"version": "1.5.29",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz",
|
||||
"integrity": "sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
|
@ -2078,9 +2077,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
|
@ -2619,7 +2618,7 @@
|
|||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2",
|
||||
"deprecated": "Upgrade to fsevents v2 to mitigate potential security issues",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
|
@ -2700,9 +2699,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/get-stream/node_modules/pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
||||
"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
|
@ -4105,9 +4104,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/gulp-uglify/node_modules/uglify-js": {
|
||||
"version": "3.19.2",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.2.tgz",
|
||||
"integrity": "sha512-S8KA6DDI47nQXJSi2ctQ629YzwOVs+bQML6DAtvy0wgNdpi+0ySpQK0g2pxBq2xfF2z3YCscu7NNA8nXT9PlIQ==",
|
||||
"version": "3.19.3",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
|
||||
"integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"bin": {
|
||||
"uglifyjs": "bin/uglifyjs"
|
||||
|
@ -4620,9 +4619,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.15.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz",
|
||||
"integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==",
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
||||
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
|
@ -5836,9 +5835,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/native-request": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/native-request/-/native-request-1.1.0.tgz",
|
||||
"integrity": "sha512-uZ5rQaeRn15XmpgE0xoPL8YWqcX90VtCFglYwAgkvKM5e8fog+vePLAhHxuuv/gRkrQxIeh5U3q9sMNUrENqWw==",
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/native-request/-/native-request-1.1.2.tgz",
|
||||
"integrity": "sha512-/etjwrK0J4Ebbcnt35VMWnfiUX/B04uwGJxyJInagxDqf2z5drSt/lsOvEMWGYunz1kaLZAFrV4NDAbOoDKvAQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
|
@ -6240,9 +6239,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
|
||||
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/parse-filepath": {
|
||||
|
@ -7415,9 +7414,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/spdx-license-ids": {
|
||||
"version": "3.0.18",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz",
|
||||
"integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==",
|
||||
"version": "3.0.20",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz",
|
||||
"integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==",
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/split-string": {
|
||||
|
@ -8132,9 +8131,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.6",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.6.tgz",
|
||||
"integrity": "sha512-e/vggGopEfTKSvj4ihnOLTsqhrKRN3LeO6qSN/GxohhuRv8qH9bNQ4B8W7e/vFL+0XTnmHPB4/kegunZGA4Org==",
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/union-value": {
|
||||
|
@ -8230,9 +8229,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
|
||||
"integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
|
||||
"integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -8249,8 +8248,8 @@
|
|||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"escalade": "^3.1.2",
|
||||
"picocolors": "^1.0.1"
|
||||
"escalade": "^3.2.0",
|
||||
"picocolors": "^1.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"update-browserslist-db": "cli.js"
|
||||
|
@ -8260,9 +8259,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db/node_modules/picocolors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
|
||||
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
|
||||
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/urix": {
|
||||
|
|
|
@ -8,6 +8,7 @@ import {toAbsoluteUrl} from '../utils.js';
|
|||
import {initDropzone} from './common-global.js';
|
||||
import {POST, GET} from '../modules/fetch.js';
|
||||
import {showErrorToast} from '../modules/toast.js';
|
||||
import {emojiHTML} from './emoji.js';
|
||||
|
||||
const {appSubUrl} = window.config;
|
||||
|
||||
|
@ -124,7 +125,7 @@ export function initRepoIssueSidebarList() {
|
|||
return;
|
||||
}
|
||||
filteredResponse.results.push({
|
||||
name: `#${issue.number} ${htmlEscape(issue.title)
|
||||
name: `#${issue.number} ${issueTitleHTML(htmlEscape(issue.title))
|
||||
}<div class="text small tw-break-anywhere">${htmlEscape(issue.repository.full_name)}</div>`,
|
||||
value: issue.id,
|
||||
});
|
||||
|
@ -731,3 +732,9 @@ export function initArchivedLabelHandler() {
|
|||
toggleElem(label, label.classList.contains('checked'));
|
||||
}
|
||||
}
|
||||
|
||||
// Render the issue's title. It converts emojis and code blocks syntax into their respective HTML equivalent.
|
||||
export function issueTitleHTML(title) {
|
||||
return title.replaceAll(/:[-+\w]+:/g, (emoji) => emojiHTML(emoji.substring(1, emoji.length - 1)))
|
||||
.replaceAll(/`[^`]+`/g, (code) => `<code class="inline-code-block">${code.substring(1, code.length - 1)}</code>`);
|
||||
}
|
||||
|
|
24
web_src/js/features/repo-issue.test.js
Normal file
24
web_src/js/features/repo-issue.test.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import {vi} from 'vitest';
|
||||
|
||||
import {issueTitleHTML} from './repo-issue.js';
|
||||
|
||||
// monaco-editor does not have any exports fields, which trips up vitest
|
||||
vi.mock('./comp/ComboMarkdownEditor.js', () => ({}));
|
||||
// jQuery is missing
|
||||
vi.mock('./common-global.js', () => ({}));
|
||||
|
||||
test('Convert issue title to html', () => {
|
||||
expect(issueTitleHTML('')).toEqual('');
|
||||
expect(issueTitleHTML('issue title')).toEqual('issue title');
|
||||
|
||||
const expected_thumbs_up = `<span class="emoji" title=":+1:">👍</span>`;
|
||||
expect(issueTitleHTML(':+1:')).toEqual(expected_thumbs_up);
|
||||
expect(issueTitleHTML(':invalid emoji:')).toEqual(':invalid emoji:');
|
||||
|
||||
const expected_code_block = `<code class="inline-code-block">code</code>`;
|
||||
expect(issueTitleHTML('`code`')).toEqual(expected_code_block);
|
||||
expect(issueTitleHTML('`invalid code')).toEqual('`invalid code');
|
||||
expect(issueTitleHTML('invalid code`')).toEqual('invalid code`');
|
||||
|
||||
expect(issueTitleHTML('issue title :+1: `code`')).toEqual(`issue title ${expected_thumbs_up} ${expected_code_block}`);
|
||||
});
|
Loading…
Reference in a new issue