mirror of
https://codeberg.org/forgejo/forgejo
synced 2024-11-28 04:36:11 +01:00
Compare commits
136 commits
6dcf3690aa
...
01402068e7
Author | SHA1 | Date | |
---|---|---|---|
01402068e7 | |||
4b7354a1af | |||
f7d217b695 | |||
252426f992 | |||
8b5ff23996 | |||
cf6ea2dbc1 | |||
e553ae901e | |||
f42df16c9a | |||
a057b7f582 | |||
a625a0b307 | |||
fbc8b2790e | |||
b4a0f47d2c | |||
5303190eeb | |||
c184aff53f | |||
a0d1f75f74 | |||
4d73c51a26 | |||
d47509191f | |||
3674e90c93 | |||
0a39ee3bbe | |||
8636a8b228 | |||
f5c0570533 | |||
b7c15f7b70 | |||
298863c701 | |||
f90928507a | |||
71ff98d61d | |||
1f4be5baad | |||
cff7754735 | |||
0fb3e45ca5 | |||
1806db31d1 | |||
7c1f3a7594 | |||
3c3c9b22a9 | |||
73cb6c9204 | |||
25354c03a5 | |||
18cecf124f | |||
2f1b1d8b80 | |||
c3653e0eaa | |||
4a3f8f3004 | |||
6cfaebf043 | |||
e0b37253c8 | |||
b9697f5227 | |||
6553148de9 | |||
387ed7e072 | |||
289c22cc4a | |||
4163402f5e | |||
662e9c53d9 | |||
e31090cf4b | |||
2cc3278791 | |||
693f7731f9 | |||
02f4d3bd2d | |||
b6869d643e | |||
abec2442b7 | |||
64a89c8d33 | |||
76f172b080 | |||
a5363a539b | |||
b5161325ef | |||
9701e5e0ff | |||
da40383cf4 | |||
8e94947ed9 | |||
c01a03e93d | |||
ca0cd42d7a | |||
01c9c19536 | |||
1b9d1240eb | |||
d2dc4fae3a | |||
e434ecdaca | |||
569a67327c | |||
146824badc | |||
eaa66f85f6 | |||
e4eb82b738 | |||
969a6ab24a | |||
7d59060dc6 | |||
308812a82e | |||
fc26becba4 | |||
02a2dbef69 | |||
013cc1dee4 | |||
6d0f2c1b82 | |||
2cccc02e76 | |||
356aa6521b | |||
cf323a3d55 | |||
6bab3c374c | |||
570e8cec9e | |||
bf810fa8d3 | |||
66dfb2813c | |||
95a8987844 | |||
9fd2df6e30 | |||
7f707b2a6f | |||
5406310f3e | |||
b21cc70dd7 | |||
4a5d9d4b78 | |||
1e1b162cbe | |||
b1bc294955 | |||
01ab0583f5 | |||
786dfc7fb8 | |||
061abe6004 | |||
3e3ef76808 | |||
7067cc7da4 | |||
e6bbecb02d | |||
b70196653f | |||
9508aa7713 | |||
1ce33aa38d | |||
0fa436c373 | |||
296935b0d7 | |||
1c25bbe773 | |||
c8d97e5594 | |||
e426a52a87 | |||
faa796feb9 | |||
cdc38ace39 | |||
4043377377 | |||
c226b4d00a | |||
19c9e0a0c2 | |||
ef9a0c8d3d | |||
d1ad4dd561 | |||
b92863b024 | |||
91fda7ee81 | |||
8a4407ef72 | |||
a8beeff422 | |||
eda83cc7ed | |||
aea3c7d6e8 | |||
24028747d3 | |||
5bd682b59d | |||
969027e3f2 | |||
019e38a746 | |||
1f7a648057 | |||
c17b4bdaeb | |||
634519e891 | |||
bd42f677b4 | |||
75a8b83946 | |||
e600fe97a3 | |||
d1520cf08d | |||
f9169eac96 | |||
f8cbd6301e | |||
b86f6cae03 | |||
9e95f80d94 | |||
c7b0132a78 | |||
8206d509fc | |||
a5ba7cadf7 | |||
19a27ded86 |
|
@ -1,59 +0,0 @@
|
|||
# Copyright 2024 The Forgejo Authors
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# To modify this workflow:
|
||||
#
|
||||
# - change pull_request_target: to pull_request:
|
||||
# so that it runs from a pull request instead of the default branch
|
||||
#
|
||||
# - push it to the wip-ci-backport branch on the forgejo repository
|
||||
# otherwise it will not have access to the secrets required to push
|
||||
# the PR
|
||||
#
|
||||
# - open a pull request targetting wip-ci-backport that includes a change
|
||||
# that can be backported without conflict in v1.21 and set the
|
||||
# `backport/v1.21` label.
|
||||
#
|
||||
# - once it works, open a pull request for the sake of keeping track
|
||||
# of the change even if the PR won't run it because it will use
|
||||
# whatever is in the default branch instead
|
||||
#
|
||||
# - after it is merged, double check it works by setting a
|
||||
# `backport/v1.21` label on a merged pull request that can be backported
|
||||
# without conflict.
|
||||
#
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
backporting:
|
||||
if: >
|
||||
( vars.ROLE == 'forgejo-coding' ) && (
|
||||
github.event.pull_request.merged
|
||||
&&
|
||||
contains(toJSON(github.event.pull_request.labels), 'backport/v')
|
||||
)
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
steps:
|
||||
- name: event info
|
||||
run: |
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
- uses: https://code.forgejo.org/actions/git-backporting@v4.8.4
|
||||
with:
|
||||
target-branch-pattern: "^backport/(?<target>(v.*))$"
|
||||
strategy: ort
|
||||
strategy-option: find-renames
|
||||
cherry-pick-options: -x
|
||||
auth: ${{ secrets.BACKPORT_TOKEN }}
|
||||
pull-request: ${{ github.event.pull_request.url }}
|
||||
auto-no-squash: true
|
||||
enable-err-notification: true
|
||||
git-user: forgejo-backport-action
|
||||
git-email: forgejo-backport-action@noreply.codeberg.org
|
|
@ -1,75 +0,0 @@
|
|||
# Copyright 2024 The Forgejo Authors
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# To modify this workflow:
|
||||
#
|
||||
# - push it to the wip-ci-end-to-end branch on the forgejo repository
|
||||
# otherwise it will not have access to the secrets required to push
|
||||
# the cascading PR
|
||||
#
|
||||
# - once it works, open a pull request for the sake of keeping track
|
||||
# of the change even if the PR won't run it because it will use
|
||||
# whatever is in the default branch instead
|
||||
#
|
||||
# - after it is merged, double check it works by setting the
|
||||
# run-end-to-end-test on a pull request (any pull request will do)
|
||||
#
|
||||
name: end-to-end
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'wip-ci-end-to-end'
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
info:
|
||||
if: vars.ROLE == 'forgejo-coding'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: code.forgejo.org/oci/node:20-bookworm
|
||||
steps:
|
||||
- name: event
|
||||
run: |
|
||||
echo github.event.pull_request.head.repo.fork = ${{ github.event.pull_request.head.repo.fork }}
|
||||
echo github.event.action = ${{ github.event.action }}
|
||||
echo github.event.label
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github.event.label) }}
|
||||
EOF
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github.event) }}
|
||||
EOF
|
||||
|
||||
cascade:
|
||||
if: >
|
||||
vars.ROLE == 'forgejo-coding' && (
|
||||
github.event_name == 'push' ||
|
||||
(
|
||||
github.event.action == 'label_updated' && github.event.label.name == 'run-end-to-end-tests'
|
||||
)
|
||||
)
|
||||
runs-on: docker
|
||||
container:
|
||||
image: code.forgejo.org/oci/node:20-bookworm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
show-progress: 'false'
|
||||
- uses: actions/cascading-pr@v2
|
||||
with:
|
||||
origin-url: ${{ env.GITHUB_SERVER_URL }}
|
||||
origin-repo: ${{ github.repository }}
|
||||
origin-token: ${{ secrets.END_TO_END_CASCADING_PR_ORIGIN }}
|
||||
origin-pr: ${{ github.event.pull_request.number }}
|
||||
origin-ref: ${{ github.event_name == 'push' && github.event.ref || '' }}
|
||||
destination-url: https://code.forgejo.org
|
||||
destination-fork-repo: cascading-pr/end-to-end
|
||||
destination-repo: forgejo/end-to-end
|
||||
destination-branch: main
|
||||
destination-token: ${{ secrets.END_TO_END_CASCADING_PR_DESTINATION }}
|
||||
close-merge: true
|
||||
update: .forgejo/cascading-pr-end-to-end
|
205
.forgejo/workflows/issue-labels.yml
Normal file
205
.forgejo/workflows/issue-labels.yml
Normal file
|
@ -0,0 +1,205 @@
|
|||
# Copyright 2024 The Forgejo Authors
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# To modify the pull_request_target jobs:
|
||||
#
|
||||
# - push it to the wip-ci-issue-labels branch on the forgejo repository
|
||||
# otherwise it will not have access to the required secrets.
|
||||
#
|
||||
# - once it works, open a pull request for the sake of keeping track
|
||||
# of the change even if the PR won't run it because it will use
|
||||
# whatever is in the default branch instead
|
||||
#
|
||||
# - after it is merged, double check it works by changing the labels
|
||||
# to trigger the job.
|
||||
#
|
||||
name: issue-labels
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'wip-ci-issue-labels'
|
||||
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
- edited
|
||||
- labeled
|
||||
- synchronize
|
||||
|
||||
pull_request:
|
||||
types:
|
||||
- edited
|
||||
- labeled
|
||||
- opened
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
info:
|
||||
if: vars.ROLE == 'forgejo-coding'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: code.forgejo.org/oci/node:20-bookworm
|
||||
steps:
|
||||
- name: Debug info
|
||||
run: |
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
|
||||
end-to-end:
|
||||
if: >
|
||||
vars.ROLE == 'forgejo-coding' &&
|
||||
|
||||
secrets.END_TO_END_CASCADING_PR_DESTINATION != '' &&
|
||||
secrets.END_TO_END_CASCADING_PR_ORIGIN != '' &&
|
||||
|
||||
(
|
||||
github.event_name == 'push' ||
|
||||
(
|
||||
github.event_name == 'pull_request_target' &&
|
||||
github.event.action == 'label_updated' &&
|
||||
github.event.label.name == 'run-end-to-end-tests'
|
||||
)
|
||||
)
|
||||
runs-on: docker
|
||||
container:
|
||||
image: code.forgejo.org/oci/node:20-bookworm
|
||||
steps:
|
||||
- name: Debug info
|
||||
run: |
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
show-progress: 'false'
|
||||
- uses: actions/cascading-pr@v2
|
||||
with:
|
||||
origin-url: ${{ env.GITHUB_SERVER_URL }}
|
||||
origin-repo: ${{ github.repository }}
|
||||
origin-token: ${{ secrets.END_TO_END_CASCADING_PR_ORIGIN }}
|
||||
origin-pr: ${{ github.event.pull_request.number }}
|
||||
origin-ref: ${{ github.event_name == 'push' && github.event.ref || '' }}
|
||||
destination-url: https://code.forgejo.org
|
||||
destination-fork-repo: cascading-pr/end-to-end
|
||||
destination-repo: forgejo/end-to-end
|
||||
destination-branch: main
|
||||
destination-token: ${{ secrets.END_TO_END_CASCADING_PR_DESTINATION }}
|
||||
close-merge: true
|
||||
update: .forgejo/cascading-pr-end-to-end
|
||||
|
||||
backporting:
|
||||
if: >
|
||||
vars.ROLE == 'forgejo-coding' &&
|
||||
|
||||
secrets.BACKPORT_TOKEN != '' &&
|
||||
|
||||
github.event_name == 'pull_request_target' &&
|
||||
(
|
||||
github.event.pull_request.merged &&
|
||||
contains(toJSON(github.event.pull_request.labels), 'backport/v')
|
||||
)
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
steps:
|
||||
- name: Debug info
|
||||
run: |
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
- uses: https://code.forgejo.org/actions/git-backporting@v4.8.4
|
||||
with:
|
||||
target-branch-pattern: "^backport/(?<target>(v.*))$"
|
||||
strategy: ort
|
||||
strategy-option: find-renames
|
||||
cherry-pick-options: -x
|
||||
auth: ${{ secrets.BACKPORT_TOKEN }}
|
||||
pull-request: ${{ github.event.pull_request.url }}
|
||||
auto-no-squash: true
|
||||
enable-err-notification: true
|
||||
git-user: forgejo-backport-action
|
||||
git-email: forgejo-backport-action@noreply.codeberg.org
|
||||
|
||||
merge-conditions:
|
||||
if: >
|
||||
vars.ROLE == 'forgejo-coding' &&
|
||||
|
||||
github.event_name == 'pull_request' &&
|
||||
(
|
||||
github.event.action == 'label_updated' ||
|
||||
github.event.action == 'edited' ||
|
||||
github.event.action == 'opened'
|
||||
)
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
steps:
|
||||
- name: Debug info
|
||||
run: |
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
- name: Missing test label
|
||||
if: >
|
||||
!(
|
||||
contains(toJSON(github.event.pull_request.labels), 'test/present')
|
||||
|| contains(toJSON(github.event.pull_request.labels), 'test/not-needed')
|
||||
|| contains(toJSON(github.event.pull_request.labels), 'test/manual')
|
||||
)
|
||||
run: |
|
||||
echo "Test label must be set to either 'present', 'not-needed' or 'manual'."
|
||||
exit 1
|
||||
- name: Missing manual test instructions
|
||||
if: >
|
||||
(
|
||||
contains(toJSON(github.event.pull_request.labels), 'test/manual')
|
||||
&& !contains(toJSON(github.event.pull_request.body), '# Test')
|
||||
)
|
||||
run: |
|
||||
echo "Manual test label is set. The PR description needs to contain test steps introduced by a heading like:"
|
||||
echo "# Testing"
|
||||
exit 1
|
||||
|
||||
release-notes:
|
||||
if: >
|
||||
vars.ROLE == 'forgejo-coding' &&
|
||||
|
||||
secrets.RELEASE_NOTES_ASSISTANT_TOKEN != '' &&
|
||||
|
||||
github.event_name == 'pull_request_target' &&
|
||||
contains(github.event.pull_request.labels.*.name, 'worth a release-note') &&
|
||||
(
|
||||
github.event.action == 'label_updated' ||
|
||||
github.event.action == 'edited' ||
|
||||
github.event.action == 'synchronized'
|
||||
)
|
||||
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
steps:
|
||||
- name: Debug info
|
||||
run: |
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache: false
|
||||
|
||||
- name: apt install jq
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get -q install -y -qq jq
|
||||
|
||||
- name: release-notes-assistant preview
|
||||
run: |
|
||||
go run code.forgejo.org/forgejo/release-notes-assistant@v1.1.1 --config .release-notes-assistant.yaml --storage pr --storage-location ${{ github.event.pull_request.number }} --forgejo-url $GITHUB_SERVER_URL --repository $GITHUB_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} preview ${{ github.event.pull_request.number }}
|
|
@ -1,45 +0,0 @@
|
|||
# Copyright 2024 The Forgejo Authors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: requirements
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- labeled
|
||||
- edited
|
||||
- opened
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
merge-conditions:
|
||||
if: vars.ROLE == 'forgejo-coding'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
steps:
|
||||
- name: Debug output
|
||||
run: |
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github.event) }}
|
||||
EOF
|
||||
- name: Missing test label
|
||||
if: >
|
||||
!(
|
||||
contains(toJSON(github.event.pull_request.labels), 'test/present')
|
||||
|| contains(toJSON(github.event.pull_request.labels), 'test/not-needed')
|
||||
|| contains(toJSON(github.event.pull_request.labels), 'test/manual')
|
||||
)
|
||||
run: |
|
||||
echo "Test label must be set to either 'present', 'not-needed' or 'manual'."
|
||||
exit 1
|
||||
- name: Missing manual test instructions
|
||||
if: >
|
||||
(
|
||||
contains(toJSON(github.event.pull_request.labels), 'test/manual')
|
||||
&& !contains(toJSON(github.event.pull_request.body), '# Test')
|
||||
)
|
||||
run: |
|
||||
echo "Manual test label is set. The PR description needs to contain test steps introduced by a heading like:"
|
||||
echo "# Testing"
|
||||
exit 1
|
|
@ -1,39 +0,0 @@
|
|||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- edited
|
||||
- synchronize
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
release-notes:
|
||||
if: ( vars.ROLE == 'forgejo-coding' ) && contains(github.event.pull_request.labels.*.name, 'worth a release-note')
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
|
||||
- name: event
|
||||
run: |
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github.event.pull_request.labels.*.name) }}
|
||||
EOF
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github.event) }}
|
||||
EOF
|
||||
|
||||
- uses: https://code.forgejo.org/actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache: false
|
||||
|
||||
- name: apt install jq
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get -q install -y -qq jq
|
||||
|
||||
- name: release-notes-assistant preview
|
||||
run: |
|
||||
go run code.forgejo.org/forgejo/release-notes-assistant@v1.1.1 --config .release-notes-assistant.yaml --storage pr --storage-location ${{ github.event.pull_request.number }} --forgejo-url $GITHUB_SERVER_URL --repository $GITHUB_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} preview ${{ github.event.pull_request.number }}
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
|
||||
runs-on: docker-runner-one
|
||||
container:
|
||||
image: code.forgejo.org/forgejo-contrib/renovate:39.9.1
|
||||
image: code.forgejo.org/forgejo-contrib/renovate:39.19.1
|
||||
|
||||
steps:
|
||||
- name: Load renovate repo cache
|
||||
|
|
|
@ -56,14 +56,15 @@ jobs:
|
|||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
services:
|
||||
elasticsearch:
|
||||
image: docker.io/bitnami/elasticsearch:7
|
||||
image: code.forgejo.org/oci/bitnami/elasticsearch:7
|
||||
options: --tmpfs /bitnami/elasticsearch/data
|
||||
env:
|
||||
discovery.type: single-node
|
||||
ES_JAVA_OPTS: "-Xms512m -Xmx512m"
|
||||
minio:
|
||||
image: docker.io/bitnami/minio:2024.8.17
|
||||
image: code.forgejo.org/oci/bitnami/minio:2024.8.17
|
||||
options: >-
|
||||
--hostname gitea.minio
|
||||
--hostname gitea.minio --tmpfs /bitnami/minio/data
|
||||
env:
|
||||
MINIO_DOMAIN: minio
|
||||
MINIO_ROOT_USER: 123456
|
||||
|
@ -121,27 +122,35 @@ jobs:
|
|||
USE_REPO_TEST_DIR: 1
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
CHANGED_FILES: ${{steps.changed-files.outputs.all_changed_files}}
|
||||
- name: Upload test artifacts on failure
|
||||
if: failure()
|
||||
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
|
||||
with:
|
||||
name: test-artifacts.zip
|
||||
path: tests/e2e/test-artifacts/
|
||||
retention-days: 3
|
||||
test-remote-cacher:
|
||||
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
||||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks, test-unit]
|
||||
container:
|
||||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
name: ${{ format('test-remote-cacher ({0})', matrix.cacher.name) }}
|
||||
strategy:
|
||||
matrix:
|
||||
cacher:
|
||||
# redis
|
||||
- image: docker.io/bitnami/redis:7.2
|
||||
port: 6379
|
||||
# redict
|
||||
- image: registry.redict.io/redict:7.3.0-scratch
|
||||
port: 6379
|
||||
# valkey
|
||||
- image: docker.io/bitnami/valkey:7.2
|
||||
port: 6379
|
||||
# garnet
|
||||
- image: ghcr.io/microsoft/garnet-alpine:1.0.14
|
||||
port: 6379
|
||||
- name: redis
|
||||
image: code.forgejo.org/oci/bitnami/redis:7.2
|
||||
options: --tmpfs /bitnami/redis/data
|
||||
- name: redict
|
||||
image: registry.redict.io/redict:7.3.0-scratch
|
||||
options: --tmpfs /data
|
||||
- name: valkey
|
||||
image: code.forgejo.org/oci/bitnami/valkey:7.2
|
||||
options: --tmpfs /bitnami/redis/data
|
||||
- name: garnet
|
||||
image: ghcr.io/microsoft/garnet-alpine:1.0.14
|
||||
options: --tmpfs /data
|
||||
services:
|
||||
cacher:
|
||||
image: ${{ matrix.cacher.image }}
|
||||
|
@ -169,14 +178,15 @@ jobs:
|
|||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
services:
|
||||
mysql:
|
||||
image: 'docker.io/bitnami/mysql:8.4'
|
||||
image: 'code.forgejo.org/oci/bitnami/mysql:8.4'
|
||||
env:
|
||||
ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_DATABASE: testgitea
|
||||
#
|
||||
# See also https://codeberg.org/forgejo/forgejo/issues/976
|
||||
#
|
||||
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
|
||||
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 --disable-log-bin
|
||||
options: --tmpfs /bitnami/mysql/data
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
|
@ -198,17 +208,21 @@ jobs:
|
|||
image: 'code.forgejo.org/oci/node:20-bookworm'
|
||||
services:
|
||||
minio:
|
||||
image: docker.io/bitnami/minio:2024.8.17
|
||||
image: code.forgejo.org/oci/bitnami/minio:2024.8.17
|
||||
env:
|
||||
MINIO_ROOT_USER: 123456
|
||||
MINIO_ROOT_PASSWORD: 12345678
|
||||
options: --tmpfs /bitnami/minio/data
|
||||
ldap:
|
||||
image: docker.io/gitea/test-openldap:latest
|
||||
image: code.forgejo.org/oci/test-openldap:latest
|
||||
pgsql:
|
||||
image: 'code.forgejo.org/oci/postgres:15'
|
||||
image: code.forgejo.org/oci/bitnami/postgresql:15
|
||||
env:
|
||||
POSTGRES_DB: test
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRESQL_DATABASE: test
|
||||
POSTGRESQL_PASSWORD: postgres
|
||||
POSTGRESQL_FSYNC: off
|
||||
POSTGRESQL_EXTRA_FLAGS: -c full_page_writes=off
|
||||
options: --tmpfs /bitnami/postgresql
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx
|
||||
FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/xx AS xx
|
||||
|
||||
FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/golang:1.23-alpine3.20 as build-env
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx
|
||||
FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/xx AS xx
|
||||
|
||||
FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/golang:1.23-alpine3.20 as build-env
|
||||
|
||||
|
|
2
Makefile
2
Makefile
|
@ -49,7 +49,7 @@ GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasour
|
|||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.26.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@39.9.1 # renovate: datasource=docker packageName=code.forgejo.org/forgejo-contrib/renovate
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@39.19.1 # renovate: datasource=docker packageName=code.forgejo.org/forgejo-contrib/renovate
|
||||
|
||||
ifeq ($(HAS_GO), yes)
|
||||
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766
|
||||
|
|
|
@ -6,6 +6,10 @@ A [patch or minor release](https://semver.org/spec/v2.0.0.html) (e.g. upgrading
|
|||
|
||||
The release notes of each release [are available in the corresponding milestone](https://codeberg.org/forgejo/forgejo/milestones), starting with [Forgejo 7.0.7](https://codeberg.org/forgejo/forgejo/milestone/7683) and [Forgejo 8.0.1](https://codeberg.org/forgejo/forgejo/milestone/7682).
|
||||
|
||||
## 9.0.2
|
||||
|
||||
The Forgejo v9.0.2 release notes are [available in the v9.0.2 milestone](https://codeberg.org/forgejo/forgejo/milestone/8610).
|
||||
|
||||
## 9.0.1
|
||||
|
||||
The Forgejo v9.0.1 release notes are [available in the v9.0.1 milestone](https://codeberg.org/forgejo/forgejo/milestone/8544).
|
||||
|
@ -163,6 +167,10 @@ A [companion blog post](https://forgejo.org/2024-07-release-v8-0/) provides addi
|
|||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/2937): <!--number 2937 --><!--number--><!--description -->31 March updates<!--description-->
|
||||
<!--end release-notes-assistant-->
|
||||
|
||||
## 7.0.11
|
||||
|
||||
The Forgejo v7.0.11 release notes are [available in the v7.0.11 milestone](https://codeberg.org/forgejo/forgejo/milestone/8609).
|
||||
|
||||
## 7.0.10
|
||||
|
||||
The Forgejo v7.0.10 release notes are [available in the v7.0.10 milestone](https://codeberg.org/forgejo/forgejo/milestone/8286).
|
||||
|
|
|
@ -62,7 +62,7 @@ func initRemoveTags() {
|
|||
"user", "utente", "lietotājs", "gebruiker", "usuário", "Benutzer", "Bruker",
|
||||
"server", "servidor", "kiszolgáló", "serveris",
|
||||
"label", "etichetta", "etiķete", "rótulo", "Label", "utilizador",
|
||||
"filename", "bestandsnaam", "dosyaadi", "fails", "nome do arquivo",
|
||||
"filename", "bestandsnaam", "dosyaadi", "fails", "nome do arquivo", "datnes nosaukums",
|
||||
} {
|
||||
oldnew = append(oldnew, "<"+el+">", "REPLACED-TAG")
|
||||
}
|
||||
|
|
|
@ -1938,7 +1938,7 @@ LEVEL = Info
|
|||
;ENABLED = true
|
||||
;;
|
||||
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||
;ALLOWED_TYPES = .cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip
|
||||
;ALLOWED_TYPES = .avif,.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip
|
||||
;;
|
||||
;; Max size of each file. Defaults to 2048MB
|
||||
;MAX_SIZE = 2048
|
||||
|
|
8
go.mod
8
go.mod
|
@ -107,7 +107,7 @@ require (
|
|||
golang.org/x/sys v0.27.0
|
||||
golang.org/x/text v0.20.0
|
||||
golang.org/x/tools v0.26.0
|
||||
google.golang.org/grpc v1.67.1
|
||||
google.golang.org/grpc v1.68.0
|
||||
google.golang.org/protobuf v1.35.1
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
|
@ -283,7 +283,7 @@ require (
|
|||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/sync v0.9.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // 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,8 +293,8 @@ 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.5
|
||||
replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.22.0
|
||||
|
||||
replace github.com/mholt/archiver/v3 => code.forgejo.org/forgejo/archiver/v3 v3.5.1
|
||||
|
||||
replace github.com/goccy/go-json => github.com/grafana/go-json v0.0.0-20241106155216-71a03f133f5c
|
||||
replace github.com/goccy/go-json => github.com/grafana/go-json v0.0.0-20241115232854-f14426c40ff2
|
||||
|
|
16
go.sum
16
go.sum
|
@ -4,8 +4,8 @@ code.forgejo.org/f3/gof3/v3 v3.7.0 h1:ZfuCP8CGm8ZJbWmL+V0pUu3E0X4FCAA7GfRDy/y5/K
|
|||
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.5 h1:rWI+bhClocogdNwjRrM836rZYY7JBcHY3VUAwkYqEtw=
|
||||
code.forgejo.org/forgejo/act v1.21.5/go.mod h1:+PcvJ9iv+NTFeJSh79ra9Jbk9l0vvyA9D9me5/dbxYM=
|
||||
code.forgejo.org/forgejo/act v1.22.0 h1:NbUf0+vQ48+ddwe4zVkINqnxKYl/to+NUvW7iisPA60=
|
||||
code.forgejo.org/forgejo/act v1.22.0/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=
|
||||
|
@ -381,8 +381,8 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX
|
|||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||
github.com/grafana/go-json v0.0.0-20241106155216-71a03f133f5c h1:yKBKEC347YZpgii1KazRCfxHsTaxMqWZzoivM1OTT50=
|
||||
github.com/grafana/go-json v0.0.0-20241106155216-71a03f133f5c/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/grafana/go-json v0.0.0-20241115232854-f14426c40ff2 h1:8xGrYqQ1GM4aaMk7pNDfecBdL/VGhEbpvvGBoqO6BIY=
|
||||
github.com/grafana/go-json v0.0.0-20241115232854-f14426c40ff2/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
||||
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
|
@ -843,10 +843,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-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/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
|
||||
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
|
||||
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=
|
||||
|
|
|
@ -15,12 +15,31 @@ import (
|
|||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
type AuthorizationPurpose string
|
||||
|
||||
var (
|
||||
// Used to store long term authorization tokens.
|
||||
LongTermAuthorization AuthorizationPurpose = "long_term_authorization"
|
||||
|
||||
// Used to activate a user account.
|
||||
UserActivation AuthorizationPurpose = "user_activation"
|
||||
|
||||
// Used to reset the password.
|
||||
PasswordReset AuthorizationPurpose = "password_reset"
|
||||
)
|
||||
|
||||
// Used to activate the specified email address for a user.
|
||||
func EmailActivation(email string) AuthorizationPurpose {
|
||||
return AuthorizationPurpose("email_activation:" + email)
|
||||
}
|
||||
|
||||
// AuthorizationToken represents a authorization token to a user.
|
||||
type AuthorizationToken struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"INDEX"`
|
||||
LookupKey string `xorm:"INDEX UNIQUE"`
|
||||
HashedValidator string
|
||||
Purpose AuthorizationPurpose `xorm:"NOT NULL DEFAULT 'long_term_authorization'"`
|
||||
Expiry timeutil.TimeStamp
|
||||
}
|
||||
|
||||
|
@ -41,7 +60,7 @@ func (authToken *AuthorizationToken) IsExpired() bool {
|
|||
// GenerateAuthToken generates a new authentication token for the given user.
|
||||
// It returns the lookup key and validator values that should be passed to the
|
||||
// user via a long-term cookie.
|
||||
func GenerateAuthToken(ctx context.Context, userID int64, expiry timeutil.TimeStamp) (lookupKey, validator string, err error) {
|
||||
func GenerateAuthToken(ctx context.Context, userID int64, expiry timeutil.TimeStamp, purpose AuthorizationPurpose) (lookupKey, validator string, err error) {
|
||||
// Request 64 random bytes. The first 32 bytes will be used for the lookupKey
|
||||
// and the other 32 bytes will be used for the validator.
|
||||
rBytes, err := util.CryptoRandomBytes(64)
|
||||
|
@ -56,14 +75,15 @@ func GenerateAuthToken(ctx context.Context, userID int64, expiry timeutil.TimeSt
|
|||
Expiry: expiry,
|
||||
LookupKey: lookupKey,
|
||||
HashedValidator: HashValidator(rBytes[32:]),
|
||||
Purpose: purpose,
|
||||
})
|
||||
return lookupKey, validator, err
|
||||
}
|
||||
|
||||
// FindAuthToken will find a authorization token via the lookup key.
|
||||
func FindAuthToken(ctx context.Context, lookupKey string) (*AuthorizationToken, error) {
|
||||
func FindAuthToken(ctx context.Context, lookupKey string, purpose AuthorizationPurpose) (*AuthorizationToken, error) {
|
||||
var authToken AuthorizationToken
|
||||
has, err := db.GetEngine(ctx).Where("lookup_key = ?", lookupKey).Get(&authToken)
|
||||
has, err := db.GetEngine(ctx).Where("lookup_key = ? AND purpose = ?", lookupKey, purpose).Get(&authToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
target_url: https://example.com/builds/
|
||||
description: My awesome CI-service
|
||||
context: ci/awesomeness
|
||||
context_hash: c65f4d64a3b14a3eced0c9b36799e66e1bd5ced7
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
|
@ -18,6 +19,7 @@
|
|||
target_url: https://example.com/coverage/
|
||||
description: My awesome Coverage service
|
||||
context: cov/awesomeness
|
||||
context_hash: 3929ac7bccd3fa1bf9b38ddedb77973b1b9a8cfe
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
|
@ -29,6 +31,7 @@
|
|||
target_url: https://example.com/coverage/
|
||||
description: My awesome Coverage service
|
||||
context: cov/awesomeness
|
||||
context_hash: 3929ac7bccd3fa1bf9b38ddedb77973b1b9a8cfe
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
|
@ -40,6 +43,7 @@
|
|||
target_url: https://example.com/builds/
|
||||
description: My awesome CI-service
|
||||
context: ci/awesomeness
|
||||
context_hash: c65f4d64a3b14a3eced0c9b36799e66e1bd5ced7
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
|
@ -51,15 +55,41 @@
|
|||
target_url: https://example.com/builds/
|
||||
description: My awesome deploy service
|
||||
context: deploy/awesomeness
|
||||
context_hash: ae9547713a6665fc4261d0756904932085a41cf2
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
id: 6
|
||||
index: 6
|
||||
index: 1
|
||||
repo_id: 62
|
||||
state: "failure"
|
||||
sha: "774f93df12d14931ea93259ae93418da4482fcc1"
|
||||
target_url: "/user2/test_workflows/actions"
|
||||
description: My awesome deploy service
|
||||
context: deploy/awesomeness
|
||||
context_hash: ae9547713a6665fc4261d0756904932085a41cf2
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
id: 7
|
||||
index: 6
|
||||
repo_id: 1
|
||||
state: "pending"
|
||||
sha: "1234123412341234123412341234123412341234"
|
||||
target_url: https://example.com/builds/
|
||||
description: My awesome deploy service
|
||||
context: deploy/awesomeness
|
||||
context_hash: ae9547713a6665fc4261d0756904932085a41cf2
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
id: 8
|
||||
index: 2
|
||||
repo_id: 62
|
||||
state: "error"
|
||||
sha: "774f93df12d14931ea93259ae93418da4482fcc1"
|
||||
target_url: "/user2/test_workflows/actions"
|
||||
description: "My awesome deploy service - v2"
|
||||
context: deploy/awesomeness
|
||||
context_hash: ae9547713a6665fc4261d0756904932085a41cf2
|
||||
creator_id: 2
|
||||
|
|
|
@ -84,6 +84,8 @@ var migrations = []*Migration{
|
|||
NewMigration("Add `legacy` to `web_authn_credential` table", AddLegacyToWebAuthnCredential),
|
||||
// v23 -> v24
|
||||
NewMigration("Add `delete_branch_after_merge` to `auto_merge` table", AddDeleteBranchAfterMergeToAutoMerge),
|
||||
// v24 -> v25
|
||||
NewMigration("Add `purpose` column to `forgejo_auth_token` table", AddPurposeToForgejoAuthToken),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current Forgejo database version.
|
||||
|
|
19
models/forgejo_migrations/v24.go
Normal file
19
models/forgejo_migrations/v24.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_migrations //nolint:revive
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
||||
func AddPurposeToForgejoAuthToken(x *xorm.Engine) error {
|
||||
type ForgejoAuthToken struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Purpose string `xorm:"NOT NULL DEFAULT 'long_term_authorization'"`
|
||||
}
|
||||
if err := x.Sync(new(ForgejoAuthToken)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := x.Exec("UPDATE `forgejo_auth_token` SET purpose = 'long_term_authorization' WHERE purpose = ''")
|
||||
return err
|
||||
}
|
|
@ -288,27 +288,18 @@ func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOp
|
|||
|
||||
// GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs
|
||||
func GetLatestCommitStatusForPairs(ctx context.Context, repoSHAs []RepoSHA) (map[int64][]*CommitStatus, error) {
|
||||
type result struct {
|
||||
Index int64
|
||||
RepoID int64
|
||||
SHA string
|
||||
}
|
||||
|
||||
results := make([]result, 0, len(repoSHAs))
|
||||
|
||||
getBase := func() *xorm.Session {
|
||||
return db.GetEngine(ctx).Table(&CommitStatus{})
|
||||
}
|
||||
results := []*CommitStatus{}
|
||||
|
||||
// Create a disjunction of conditions for each repoID and SHA pair
|
||||
conds := make([]builder.Cond, 0, len(repoSHAs))
|
||||
for _, repoSHA := range repoSHAs {
|
||||
conds = append(conds, builder.Eq{"repo_id": repoSHA.RepoID, "sha": repoSHA.SHA})
|
||||
}
|
||||
sess := getBase().Where(builder.Or(conds...)).
|
||||
Select("max( `index` ) as `index`, repo_id, sha").
|
||||
GroupBy("context_hash, repo_id, sha").OrderBy("max( `index` ) desc")
|
||||
|
||||
sess := db.GetEngine(ctx).Table(&CommitStatus{}).
|
||||
Select("MAX(`index`) AS `index`, *").
|
||||
Where(builder.Or(conds...)).
|
||||
GroupBy("context_hash, repo_id, sha").OrderBy("MAX(`index`) DESC")
|
||||
err := sess.Find(&results)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -316,27 +307,9 @@ func GetLatestCommitStatusForPairs(ctx context.Context, repoSHAs []RepoSHA) (map
|
|||
|
||||
repoStatuses := make(map[int64][]*CommitStatus)
|
||||
|
||||
if len(results) > 0 {
|
||||
statuses := make([]*CommitStatus, 0, len(results))
|
||||
|
||||
conds = make([]builder.Cond, 0, len(results))
|
||||
for _, result := range results {
|
||||
cond := builder.Eq{
|
||||
"`index`": result.Index,
|
||||
"repo_id": result.RepoID,
|
||||
"sha": result.SHA,
|
||||
}
|
||||
conds = append(conds, cond)
|
||||
}
|
||||
err = getBase().Where(builder.Or(conds...)).Find(&statuses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Group the statuses by repo ID
|
||||
for _, status := range statuses {
|
||||
repoStatuses[status.RepoID] = append(repoStatuses[status.RepoID], status)
|
||||
}
|
||||
// Group the statuses by repo ID
|
||||
for _, status := range results {
|
||||
repoStatuses[status.RepoID] = append(repoStatuses[status.RepoID], status)
|
||||
}
|
||||
|
||||
return repoStatuses, nil
|
||||
|
|
|
@ -35,8 +35,8 @@ func TestGetCommitStatuses(t *testing.T) {
|
|||
SHA: sha1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 5, int(maxResults))
|
||||
assert.Len(t, statuses, 5)
|
||||
assert.EqualValues(t, 6, maxResults)
|
||||
assert.Len(t, statuses, 6)
|
||||
|
||||
assert.Equal(t, "ci/awesomeness", statuses[0].Context)
|
||||
assert.Equal(t, structs.CommitStatusPending, statuses[0].State)
|
||||
|
@ -58,13 +58,17 @@ func TestGetCommitStatuses(t *testing.T) {
|
|||
assert.Equal(t, structs.CommitStatusError, statuses[4].State)
|
||||
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[4].APIURL(db.DefaultContext))
|
||||
|
||||
assert.Equal(t, "deploy/awesomeness", statuses[5].Context)
|
||||
assert.Equal(t, structs.CommitStatusPending, statuses[5].State)
|
||||
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[5].APIURL(db.DefaultContext))
|
||||
|
||||
statuses, maxResults, err = db.FindAndCount[git_model.CommitStatus](db.DefaultContext, &git_model.CommitStatusOptions{
|
||||
ListOptions: db.ListOptions{Page: 2, PageSize: 50},
|
||||
RepoID: repo1.ID,
|
||||
SHA: sha1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 5, int(maxResults))
|
||||
assert.EqualValues(t, 6, maxResults)
|
||||
assert.Empty(t, statuses)
|
||||
}
|
||||
|
||||
|
@ -265,3 +269,148 @@ func TestCommitStatusesHideActionsURL(t *testing.T) {
|
|||
assert.Empty(t, statuses[0].TargetURL)
|
||||
assert.Equal(t, "https://mycicd.org/1", statuses[1].TargetURL)
|
||||
}
|
||||
|
||||
func TestGetLatestCommitStatusForPairs(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
t.Run("All", func(t *testing.T) {
|
||||
pairs, err := git_model.GetLatestCommitStatusForPairs(db.DefaultContext, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, map[int64][]*git_model.CommitStatus{
|
||||
1: {
|
||||
{
|
||||
ID: 7,
|
||||
Index: 6,
|
||||
RepoID: 1,
|
||||
State: structs.CommitStatusPending,
|
||||
SHA: "1234123412341234123412341234123412341234",
|
||||
TargetURL: "https://example.com/builds/",
|
||||
Description: "My awesome deploy service",
|
||||
ContextHash: "ae9547713a6665fc4261d0756904932085a41cf2",
|
||||
Context: "deploy/awesomeness",
|
||||
CreatorID: 2,
|
||||
},
|
||||
{
|
||||
ID: 4,
|
||||
Index: 4,
|
||||
State: structs.CommitStatusFailure,
|
||||
TargetURL: "https://example.com/builds/",
|
||||
Description: "My awesome CI-service",
|
||||
Context: "ci/awesomeness",
|
||||
CreatorID: 2,
|
||||
RepoID: 1,
|
||||
SHA: "1234123412341234123412341234123412341234",
|
||||
ContextHash: "c65f4d64a3b14a3eced0c9b36799e66e1bd5ced7",
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Index: 3,
|
||||
State: structs.CommitStatusSuccess,
|
||||
TargetURL: "https://example.com/coverage/",
|
||||
Description: "My awesome Coverage service",
|
||||
Context: "cov/awesomeness",
|
||||
CreatorID: 2,
|
||||
RepoID: 1,
|
||||
SHA: "1234123412341234123412341234123412341234",
|
||||
ContextHash: "3929ac7bccd3fa1bf9b38ddedb77973b1b9a8cfe",
|
||||
},
|
||||
},
|
||||
62: {
|
||||
{
|
||||
ID: 8,
|
||||
Index: 2,
|
||||
RepoID: 62,
|
||||
State: structs.CommitStatusError,
|
||||
TargetURL: "/user2/test_workflows/actions",
|
||||
Description: "My awesome deploy service - v2",
|
||||
Context: "deploy/awesomeness",
|
||||
SHA: "774f93df12d14931ea93259ae93418da4482fcc1",
|
||||
ContextHash: "ae9547713a6665fc4261d0756904932085a41cf2",
|
||||
CreatorID: 2,
|
||||
},
|
||||
},
|
||||
}, pairs)
|
||||
})
|
||||
|
||||
t.Run("Repo 1", func(t *testing.T) {
|
||||
pairs, err := git_model.GetLatestCommitStatusForPairs(db.DefaultContext, []git_model.RepoSHA{{1, "1234123412341234123412341234123412341234"}})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, map[int64][]*git_model.CommitStatus{
|
||||
1: {
|
||||
{
|
||||
ID: 7,
|
||||
Index: 6,
|
||||
RepoID: 1,
|
||||
State: structs.CommitStatusPending,
|
||||
SHA: "1234123412341234123412341234123412341234",
|
||||
TargetURL: "https://example.com/builds/",
|
||||
Description: "My awesome deploy service",
|
||||
ContextHash: "ae9547713a6665fc4261d0756904932085a41cf2",
|
||||
Context: "deploy/awesomeness",
|
||||
CreatorID: 2,
|
||||
},
|
||||
{
|
||||
ID: 4,
|
||||
Index: 4,
|
||||
State: structs.CommitStatusFailure,
|
||||
TargetURL: "https://example.com/builds/",
|
||||
Description: "My awesome CI-service",
|
||||
Context: "ci/awesomeness",
|
||||
CreatorID: 2,
|
||||
RepoID: 1,
|
||||
SHA: "1234123412341234123412341234123412341234",
|
||||
ContextHash: "c65f4d64a3b14a3eced0c9b36799e66e1bd5ced7",
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Index: 3,
|
||||
State: structs.CommitStatusSuccess,
|
||||
TargetURL: "https://example.com/coverage/",
|
||||
Description: "My awesome Coverage service",
|
||||
Context: "cov/awesomeness",
|
||||
CreatorID: 2,
|
||||
RepoID: 1,
|
||||
SHA: "1234123412341234123412341234123412341234",
|
||||
ContextHash: "3929ac7bccd3fa1bf9b38ddedb77973b1b9a8cfe",
|
||||
},
|
||||
},
|
||||
}, pairs)
|
||||
})
|
||||
t.Run("Repo 62", func(t *testing.T) {
|
||||
pairs, err := git_model.GetLatestCommitStatusForPairs(db.DefaultContext, []git_model.RepoSHA{{62, "774f93df12d14931ea93259ae93418da4482fcc1"}})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, map[int64][]*git_model.CommitStatus{
|
||||
62: {
|
||||
{
|
||||
ID: 8,
|
||||
Index: 2,
|
||||
RepoID: 62,
|
||||
State: structs.CommitStatusError,
|
||||
TargetURL: "/user2/test_workflows/actions",
|
||||
Description: "My awesome deploy service - v2",
|
||||
Context: "deploy/awesomeness",
|
||||
SHA: "774f93df12d14931ea93259ae93418da4482fcc1",
|
||||
ContextHash: "ae9547713a6665fc4261d0756904932085a41cf2",
|
||||
CreatorID: 2,
|
||||
},
|
||||
},
|
||||
}, pairs)
|
||||
})
|
||||
|
||||
t.Run("Repo 62 nonexistant sha", func(t *testing.T) {
|
||||
pairs, err := git_model.GetLatestCommitStatusForPairs(db.DefaultContext, []git_model.RepoSHA{{62, "774f93df12d14931ea93259ae93418da4482fcc"}})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, map[int64][]*git_model.CommitStatus{}, pairs)
|
||||
})
|
||||
|
||||
t.Run("SHA with non existant repo id", func(t *testing.T) {
|
||||
pairs, err := git_model.GetLatestCommitStatusForPairs(db.DefaultContext, []git_model.RepoSHA{{1, "774f93df12d14931ea93259ae93418da4482fcc1"}})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, map[int64][]*git_model.CommitStatus{}, pairs)
|
||||
})
|
||||
}
|
||||
|
|
11
models/issues/TestGetUIDsAndStopwatch/stopwatch.yml
Normal file
11
models/issues/TestGetUIDsAndStopwatch/stopwatch.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
-
|
||||
id: 3
|
||||
user_id: 1
|
||||
issue_id: 2
|
||||
created_unix: 1500988004
|
||||
|
||||
-
|
||||
id: 4
|
||||
user_id: 3
|
||||
issue_id: 0
|
||||
created_unix: 1500988003
|
|
@ -60,34 +60,19 @@ func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, ex
|
|||
return sw, exists, err
|
||||
}
|
||||
|
||||
// UserIDCount is a simple coalition of UserID and Count
|
||||
type UserStopwatch struct {
|
||||
UserID int64
|
||||
StopWatches []*Stopwatch
|
||||
}
|
||||
|
||||
// GetUIDsAndNotificationCounts between the two provided times
|
||||
func GetUIDsAndStopwatch(ctx context.Context) ([]*UserStopwatch, error) {
|
||||
func GetUIDsAndStopwatch(ctx context.Context) (map[int64][]*Stopwatch, error) {
|
||||
sws := []*Stopwatch{}
|
||||
if err := db.GetEngine(ctx).Where("issue_id != 0").Find(&sws); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := map[int64][]*Stopwatch{}
|
||||
if len(sws) == 0 {
|
||||
return []*UserStopwatch{}, nil
|
||||
return res, nil
|
||||
}
|
||||
|
||||
lastUserID := int64(-1)
|
||||
res := []*UserStopwatch{}
|
||||
for _, sw := range sws {
|
||||
if lastUserID == sw.UserID {
|
||||
lastUserStopwatch := res[len(res)-1]
|
||||
lastUserStopwatch.StopWatches = append(lastUserStopwatch.StopWatches, sw)
|
||||
} else {
|
||||
res = append(res, &UserStopwatch{
|
||||
UserID: sw.UserID,
|
||||
StopWatches: []*Stopwatch{sw},
|
||||
})
|
||||
}
|
||||
res[sw.UserID] = append(res[sw.UserID], sw)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
package issues_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -77,3 +79,41 @@ func TestCreateOrStopIssueStopwatch(t *testing.T) {
|
|||
unittest.AssertNotExistsBean(t, &issues_model.Stopwatch{UserID: 2, IssueID: 2})
|
||||
unittest.AssertExistsAndLoadBean(t, &issues_model.TrackedTime{UserID: 2, IssueID: 2})
|
||||
}
|
||||
|
||||
func TestGetUIDsAndStopwatch(t *testing.T) {
|
||||
defer unittest.OverrideFixtures(
|
||||
unittest.FixturesOptions{
|
||||
Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"),
|
||||
Base: setting.AppWorkPath,
|
||||
Dirs: []string{"models/issues/TestGetUIDsAndStopwatch/"},
|
||||
},
|
||||
)()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
uidStopwatches, err := issues_model.GetUIDsAndStopwatch(db.DefaultContext)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, map[int64][]*issues_model.Stopwatch{
|
||||
1: {
|
||||
{
|
||||
ID: 1,
|
||||
UserID: 1,
|
||||
IssueID: 1,
|
||||
CreatedUnix: timeutil.TimeStamp(1500988001),
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
UserID: 1,
|
||||
IssueID: 2,
|
||||
CreatedUnix: timeutil.TimeStamp(1500988004),
|
||||
},
|
||||
},
|
||||
2: {
|
||||
{
|
||||
ID: 2,
|
||||
UserID: 2,
|
||||
IssueID: 2,
|
||||
CreatedUnix: timeutil.TimeStamp(1500988002),
|
||||
},
|
||||
},
|
||||
}, uidStopwatches)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
-
|
||||
id: 1001
|
||||
owner_id: 33
|
||||
owner_name: user33
|
||||
lower_name: repo1001
|
||||
name: repo1001
|
||||
default_branch: main
|
||||
num_watches: 0
|
||||
num_stars: 0
|
||||
num_forks: 0
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 0
|
||||
num_closed_pulls: 0
|
||||
num_milestones: 0
|
||||
num_closed_milestones: 0
|
||||
num_projects: 0
|
||||
num_closed_projects: 0
|
||||
is_private: false
|
||||
is_empty: false
|
||||
is_archived: false
|
||||
is_mirror: false
|
||||
status: 0
|
||||
is_fork: false
|
||||
fork_id: 0
|
||||
is_template: false
|
||||
template_id: 0
|
||||
size: 0
|
||||
is_fsck_enabled: true
|
||||
close_issues_via_commit_in_any_branch: false
|
|
@ -7,6 +7,7 @@ import (
|
|||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"xorm.io/builder"
|
||||
|
@ -54,9 +55,9 @@ func GetUserFork(ctx context.Context, repoID, userID int64) (*Repository, error)
|
|||
return &forkedRepo, nil
|
||||
}
|
||||
|
||||
// GetForks returns all the forks of the repository
|
||||
func GetForks(ctx context.Context, repo *Repository, listOptions db.ListOptions) ([]*Repository, error) {
|
||||
sess := db.GetEngine(ctx)
|
||||
// GetForks returns all the forks of the repository that are visible to the user.
|
||||
func GetForks(ctx context.Context, repo *Repository, user *user_model.User, listOptions db.ListOptions) ([]*Repository, int64, error) {
|
||||
sess := db.GetEngine(ctx).Where(AccessibleRepositoryCondition(user, unit.TypeInvalid))
|
||||
|
||||
var forks []*Repository
|
||||
if listOptions.Page == 0 {
|
||||
|
@ -66,7 +67,8 @@ func GetForks(ctx context.Context, repo *Repository, listOptions db.ListOptions)
|
|||
sess = db.SetSessionPagination(sess, &listOptions)
|
||||
}
|
||||
|
||||
return forks, sess.Find(&forks, &Repository{ForkID: repo.ID})
|
||||
count, err := sess.FindAndCount(&forks, &Repository{ForkID: repo.ID})
|
||||
return forks, count, err
|
||||
}
|
||||
|
||||
// IncrementRepoForkNum increment repository fork number
|
||||
|
|
|
@ -641,12 +641,9 @@ func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) bu
|
|||
// 1. Be able to see all non-private repositories that either:
|
||||
cond = cond.Or(builder.And(
|
||||
builder.Eq{"`repository`.is_private": false},
|
||||
// 2. Aren't in an private organisation or limited organisation if we're not logged in
|
||||
// 2. Aren't in an private organisation/user or limited organisation/user if the doer is not logged in.
|
||||
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(
|
||||
builder.And(
|
||||
builder.Eq{"type": user_model.UserTypeOrganization},
|
||||
builder.In("visibility", orgVisibilityLimit)),
|
||||
))))
|
||||
builder.In("visibility", orgVisibilityLimit)))))
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
|
|
|
@ -4,13 +4,18 @@
|
|||
package repo_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -403,3 +408,43 @@ func TestSearchRepositoryByTopicName(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchRepositoryIDsByCondition(t *testing.T) {
|
||||
defer unittest.OverrideFixtures(
|
||||
unittest.FixturesOptions{
|
||||
Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"),
|
||||
Base: setting.AppWorkPath,
|
||||
Dirs: []string{"models/repo/TestSearchRepositoryIDsByCondition/"},
|
||||
},
|
||||
)()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
// Sanity check of the database
|
||||
limitedUser := unittest.AssertExistsAndLoadBean(t, &user.User{ID: 33, Visibility: structs.VisibleTypeLimited})
|
||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1001, OwnerID: limitedUser.ID})
|
||||
|
||||
testCases := []struct {
|
||||
user *user.User
|
||||
repoIDs []int64
|
||||
}{
|
||||
{
|
||||
user: nil,
|
||||
repoIDs: []int64{1, 4, 8, 9, 10, 11, 12, 14, 17, 18, 21, 23, 25, 27, 29, 32, 33, 34, 35, 36, 37, 42, 44, 45, 46, 47, 48, 49, 50, 51, 53, 57, 58, 60, 61, 62, 1059},
|
||||
},
|
||||
{
|
||||
user: unittest.AssertExistsAndLoadBean(t, &user.User{ID: 4}),
|
||||
repoIDs: []int64{1, 3, 4, 8, 9, 10, 11, 12, 14, 17, 18, 21, 23, 25, 27, 29, 32, 33, 34, 35, 36, 37, 38, 40, 42, 44, 45, 46, 47, 48, 49, 50, 51, 53, 57, 58, 60, 61, 62, 1001, 1059},
|
||||
},
|
||||
{
|
||||
user: unittest.AssertExistsAndLoadBean(t, &user.User{ID: 5}),
|
||||
repoIDs: []int64{1, 4, 8, 9, 10, 11, 12, 14, 17, 18, 21, 23, 25, 27, 29, 32, 33, 34, 35, 36, 37, 38, 40, 42, 44, 45, 46, 47, 48, 49, 50, 51, 53, 57, 58, 60, 61, 62, 1001, 1059},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
repoIDs, err := repo_model.FindUserCodeAccessibleRepoIDs(db.DefaultContext, testCase.user)
|
||||
require.NoError(t, err)
|
||||
|
||||
slices.Sort(repoIDs)
|
||||
assert.EqualValues(t, testCase.repoIDs, repoIDs)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,26 +75,28 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
|
|||
return nil, err
|
||||
}
|
||||
|
||||
additionalUserIDs := make([]int64, 0, 10)
|
||||
if err = e.Table("team_user").
|
||||
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
|
||||
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
|
||||
Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
|
||||
repo.ID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
|
||||
Distinct("`team_user`.uid").
|
||||
Select("`team_user`.uid").
|
||||
Find(&additionalUserIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uniqueUserIDs := make(container.Set[int64])
|
||||
uniqueUserIDs.AddMultiple(userIDs...)
|
||||
uniqueUserIDs.AddMultiple(additionalUserIDs...)
|
||||
|
||||
if repo.Owner.IsOrganization() {
|
||||
additionalUserIDs := make([]int64, 0, 10)
|
||||
if err = e.Table("team_user").
|
||||
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
|
||||
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
|
||||
Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
|
||||
repo.ID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
|
||||
Distinct("`team_user`.uid").
|
||||
Select("`team_user`.uid").
|
||||
Find(&additionalUserIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uniqueUserIDs.AddMultiple(additionalUserIDs...)
|
||||
}
|
||||
|
||||
// Leave a seat for owner itself to append later, but if owner is an organization
|
||||
// and just waste 1 unit is cheaper than re-allocate memory once.
|
||||
users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
|
||||
if len(userIDs) > 0 {
|
||||
if len(uniqueUserIDs) > 0 {
|
||||
if err = e.In("id", uniqueUserIDs.Values()).
|
||||
Where(builder.Eq{"`user`.is_active": true}).
|
||||
OrderBy(user_model.GetOrderByName()).
|
||||
|
|
|
@ -8,10 +8,8 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -246,23 +244,6 @@ func updateActivation(ctx context.Context, email *EmailAddress, activate bool) e
|
|||
return UpdateUserCols(ctx, user, "rands")
|
||||
}
|
||||
|
||||
// VerifyActiveEmailCode verifies active email code when active account
|
||||
func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress {
|
||||
if user := GetVerifyUser(ctx, code); user != nil {
|
||||
// time limit code
|
||||
prefix := code[:base.TimeLimitCodeLength]
|
||||
data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands)
|
||||
|
||||
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
|
||||
emailAddress := &EmailAddress{UID: user.ID, Email: email}
|
||||
if has, _ := db.GetEngine(ctx).Get(emailAddress); has {
|
||||
return emailAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SearchEmailOrderBy is used to sort the results from SearchEmails()
|
||||
type SearchEmailOrderBy string
|
||||
|
||||
|
|
|
@ -7,7 +7,9 @@ package user
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
|
@ -318,15 +320,14 @@ func (u *User) OrganisationLink() string {
|
|||
return setting.AppSubURL + "/org/" + url.PathEscape(u.Name)
|
||||
}
|
||||
|
||||
// GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
|
||||
func (u *User) GenerateEmailActivateCode(email string) string {
|
||||
code := base.CreateTimeLimitCode(
|
||||
fmt.Sprintf("%d%s%s%s%s", u.ID, email, u.LowerName, u.Passwd, u.Rands),
|
||||
setting.Service.ActiveCodeLives, time.Now(), nil)
|
||||
|
||||
// Add tail hex username
|
||||
code += hex.EncodeToString([]byte(u.LowerName))
|
||||
return code
|
||||
// GenerateEmailAuthorizationCode generates an activation code based for the user for the specified purpose.
|
||||
// The standard expiry is ActiveCodeLives minutes.
|
||||
func (u *User) GenerateEmailAuthorizationCode(ctx context.Context, purpose auth.AuthorizationPurpose) (string, error) {
|
||||
lookup, validator, err := auth.GenerateAuthToken(ctx, u.ID, timeutil.TimeStampNow().Add(int64(setting.Service.ActiveCodeLives)*60), purpose)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return lookup + ":" + validator, nil
|
||||
}
|
||||
|
||||
// GetUserFollowers returns range of user's followers.
|
||||
|
@ -838,35 +839,50 @@ func countUsers(ctx context.Context, opts *CountUserFilter) int64 {
|
|||
return count
|
||||
}
|
||||
|
||||
// GetVerifyUser get user by verify code
|
||||
func GetVerifyUser(ctx context.Context, code string) (user *User) {
|
||||
if len(code) <= base.TimeLimitCodeLength {
|
||||
return nil
|
||||
// VerifyUserActiveCode verifies that the code is valid for the given purpose for this user.
|
||||
// If delete is specified, the token will be deleted.
|
||||
func VerifyUserAuthorizationToken(ctx context.Context, code string, purpose auth.AuthorizationPurpose, delete bool) (*User, error) {
|
||||
lookupKey, validator, found := strings.Cut(code, ":")
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// use tail hex username query user
|
||||
hexStr := code[base.TimeLimitCodeLength:]
|
||||
if b, err := hex.DecodeString(hexStr); err == nil {
|
||||
if user, err = GetUserByName(ctx, string(b)); user != nil {
|
||||
return user
|
||||
authToken, err := auth.FindAuthToken(ctx, lookupKey, purpose)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
log.Error("user.getVerifyUser: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
if authToken.IsExpired() {
|
||||
return nil, auth.DeleteAuthToken(ctx, authToken)
|
||||
}
|
||||
|
||||
// VerifyUserActiveCode verifies active code when active account
|
||||
func VerifyUserActiveCode(ctx context.Context, code string) (user *User) {
|
||||
if user = GetVerifyUser(ctx, code); user != nil {
|
||||
// time limit code
|
||||
prefix := code[:base.TimeLimitCodeLength]
|
||||
data := fmt.Sprintf("%d%s%s%s%s", user.ID, user.Email, user.LowerName, user.Passwd, user.Rands)
|
||||
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
|
||||
return user
|
||||
rawValidator, err := hex.DecodeString(validator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if subtle.ConstantTimeCompare([]byte(authToken.HashedValidator), []byte(auth.HashValidator(rawValidator))) == 0 {
|
||||
return nil, errors.New("validator doesn't match")
|
||||
}
|
||||
|
||||
u, err := GetUserByID(ctx, authToken.UID)
|
||||
if err != nil {
|
||||
if IsErrUserNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if delete {
|
||||
if err := auth.DeleteAuthToken(ctx, authToken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// ValidateUser check if user is valid to insert / update into database
|
||||
|
|
|
@ -7,6 +7,7 @@ package user_test
|
|||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -21,7 +22,9 @@ import (
|
|||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
|
@ -700,3 +703,66 @@ func TestDisabledUserFeatures(t *testing.T) {
|
|||
assert.True(t, user_model.IsFeatureDisabledWithLoginType(user, f))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateEmailAuthorizationCode(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Service.ActiveCodeLives, 2)()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
|
||||
code, err := user.GenerateEmailAuthorizationCode(db.DefaultContext, auth.UserActivation)
|
||||
require.NoError(t, err)
|
||||
|
||||
lookupKey, validator, ok := strings.Cut(code, ":")
|
||||
assert.True(t, ok)
|
||||
|
||||
rawValidator, err := hex.DecodeString(validator)
|
||||
require.NoError(t, err)
|
||||
|
||||
authToken, err := auth.FindAuthToken(db.DefaultContext, lookupKey, auth.UserActivation)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, authToken.IsExpired())
|
||||
assert.EqualValues(t, authToken.HashedValidator, auth.HashValidator(rawValidator))
|
||||
|
||||
authToken.Expiry = authToken.Expiry.Add(-int64(setting.Service.ActiveCodeLives) * 60)
|
||||
assert.True(t, authToken.IsExpired())
|
||||
}
|
||||
|
||||
func TestVerifyUserAuthorizationToken(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Service.ActiveCodeLives, 2)()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
|
||||
code, err := user.GenerateEmailAuthorizationCode(db.DefaultContext, auth.UserActivation)
|
||||
require.NoError(t, err)
|
||||
|
||||
lookupKey, _, ok := strings.Cut(code, ":")
|
||||
assert.True(t, ok)
|
||||
|
||||
t.Run("Wrong purpose", func(t *testing.T) {
|
||||
u, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.PasswordReset, false)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, u)
|
||||
})
|
||||
|
||||
t.Run("No delete", func(t *testing.T) {
|
||||
u, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.UserActivation, false)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, user.ID, u.ID)
|
||||
|
||||
authToken, err := auth.FindAuthToken(db.DefaultContext, lookupKey, auth.UserActivation)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, authToken)
|
||||
})
|
||||
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
u, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.UserActivation, true)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, user.ID, u.ID)
|
||||
|
||||
authToken, err := auth.FindAuthToken(db.DefaultContext, lookupKey, auth.UserActivation)
|
||||
require.ErrorIs(t, err, util.ErrNotExist)
|
||||
assert.Nil(t, authToken)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4,26 +4,20 @@
|
|||
package base
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
@ -54,66 +48,6 @@ func BasicAuthDecode(encoded string) (string, string, error) {
|
|||
return "", "", errors.New("invalid basic authentication")
|
||||
}
|
||||
|
||||
// VerifyTimeLimitCode verify time limit code
|
||||
func VerifyTimeLimitCode(now time.Time, data string, minutes int, code string) bool {
|
||||
if len(code) <= 18 {
|
||||
return false
|
||||
}
|
||||
|
||||
startTimeStr := code[:12]
|
||||
aliveTimeStr := code[12:18]
|
||||
aliveTime, _ := strconv.Atoi(aliveTimeStr) // no need to check err, if anything wrong, the following code check will fail soon
|
||||
|
||||
// check code
|
||||
retCode := CreateTimeLimitCode(data, aliveTime, startTimeStr, nil)
|
||||
if subtle.ConstantTimeCompare([]byte(retCode), []byte(code)) != 1 {
|
||||
retCode = CreateTimeLimitCode(data, aliveTime, startTimeStr, sha1.New()) // TODO: this is only for the support of legacy codes, remove this in/after 1.23
|
||||
if subtle.ConstantTimeCompare([]byte(retCode), []byte(code)) != 1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// check time is expired or not: startTime <= now && now < startTime + minutes
|
||||
startTime, _ := time.ParseInLocation("200601021504", startTimeStr, time.Local)
|
||||
return (startTime.Before(now) || startTime.Equal(now)) && now.Before(startTime.Add(time.Minute*time.Duration(minutes)))
|
||||
}
|
||||
|
||||
// TimeLimitCodeLength default value for time limit code
|
||||
const TimeLimitCodeLength = 12 + 6 + 40
|
||||
|
||||
// CreateTimeLimitCode create a time-limited code.
|
||||
// Format: 12 length date time string + 6 minutes string (not used) + 40 hash string, some other code depends on this fixed length
|
||||
// If h is nil, then use the default hmac hash.
|
||||
func CreateTimeLimitCode[T time.Time | string](data string, minutes int, startTimeGeneric T, h hash.Hash) string {
|
||||
const format = "200601021504"
|
||||
|
||||
var start time.Time
|
||||
var startTimeAny any = startTimeGeneric
|
||||
if t, ok := startTimeAny.(time.Time); ok {
|
||||
start = t
|
||||
} else {
|
||||
var err error
|
||||
start, err = time.ParseInLocation(format, startTimeAny.(string), time.Local)
|
||||
if err != nil {
|
||||
return "" // return an invalid code because the "parse" failed
|
||||
}
|
||||
}
|
||||
startStr := start.Format(format)
|
||||
end := start.Add(time.Minute * time.Duration(minutes))
|
||||
|
||||
if h == nil {
|
||||
h = hmac.New(sha1.New, setting.GetGeneralTokenSigningSecret())
|
||||
}
|
||||
_, _ = fmt.Fprintf(h, "%s%s%s%s%d", data, hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), startStr, end.Format(format), minutes)
|
||||
encoded := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
|
||||
if len(code) != TimeLimitCodeLength {
|
||||
panic("there is a hard requirement for the length of time-limited code") // it shouldn't happen
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
// FileSize calculates the file size and generate user-friendly string.
|
||||
func FileSize(s int64) string {
|
||||
return humanize.IBytes(uint64(s))
|
||||
|
|
|
@ -4,13 +4,7 @@
|
|||
package base
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -46,57 +40,6 @@ func TestBasicAuthDecode(t *testing.T) {
|
|||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestVerifyTimeLimitCode(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.InstallLock, true)()
|
||||
initGeneralSecret := func(secret string) {
|
||||
setting.InstallLock = true
|
||||
setting.CfgProvider, _ = setting.NewConfigProviderFromData(fmt.Sprintf(`
|
||||
[oauth2]
|
||||
JWT_SECRET = %s
|
||||
`, secret))
|
||||
setting.LoadCommonSettings()
|
||||
}
|
||||
|
||||
initGeneralSecret("KZb_QLUd4fYVyxetjxC4eZkrBgWM2SndOOWDNtgUUko")
|
||||
now := time.Now()
|
||||
|
||||
t.Run("TestGenericParameter", func(t *testing.T) {
|
||||
time2000 := time.Date(2000, 1, 2, 3, 4, 5, 0, time.Local)
|
||||
assert.Equal(t, "2000010203040000026fa5221b2731b7cf80b1b506f5e39e38c115fee5", CreateTimeLimitCode("test-sha1", 2, time2000, sha1.New()))
|
||||
assert.Equal(t, "2000010203040000026fa5221b2731b7cf80b1b506f5e39e38c115fee5", CreateTimeLimitCode("test-sha1", 2, "200001020304", sha1.New()))
|
||||
assert.Equal(t, "2000010203040000024842227a2f87041ff82025199c0187410a9297bf", CreateTimeLimitCode("test-hmac", 2, time2000, nil))
|
||||
assert.Equal(t, "2000010203040000024842227a2f87041ff82025199c0187410a9297bf", CreateTimeLimitCode("test-hmac", 2, "200001020304", nil))
|
||||
})
|
||||
|
||||
t.Run("TestInvalidCode", func(t *testing.T) {
|
||||
assert.False(t, VerifyTimeLimitCode(now, "data", 2, ""))
|
||||
assert.False(t, VerifyTimeLimitCode(now, "data", 2, "invalid code"))
|
||||
})
|
||||
|
||||
t.Run("TestCreateAndVerify", func(t *testing.T) {
|
||||
code := CreateTimeLimitCode("data", 2, now, nil)
|
||||
assert.False(t, VerifyTimeLimitCode(now.Add(-time.Minute), "data", 2, code)) // not started yet
|
||||
assert.True(t, VerifyTimeLimitCode(now, "data", 2, code))
|
||||
assert.True(t, VerifyTimeLimitCode(now.Add(time.Minute), "data", 2, code))
|
||||
assert.False(t, VerifyTimeLimitCode(now.Add(time.Minute), "DATA", 2, code)) // invalid data
|
||||
assert.False(t, VerifyTimeLimitCode(now.Add(2*time.Minute), "data", 2, code)) // expired
|
||||
})
|
||||
|
||||
t.Run("TestDifferentSecret", func(t *testing.T) {
|
||||
// use another secret to ensure the code is invalid for different secret
|
||||
verifyDataCode := func(c string) bool {
|
||||
return VerifyTimeLimitCode(now, "data", 2, c)
|
||||
}
|
||||
code1 := CreateTimeLimitCode("data", 2, now, sha1.New())
|
||||
code2 := CreateTimeLimitCode("data", 2, now, nil)
|
||||
assert.True(t, verifyDataCode(code1))
|
||||
assert.True(t, verifyDataCode(code2))
|
||||
initGeneralSecret("000_QLUd4fYVyxetjxC4eZkrBgWM2SndOOWDNtgUUko")
|
||||
assert.False(t, verifyDataCode(code1))
|
||||
assert.False(t, verifyDataCode(code2))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFileSize(t *testing.T) {
|
||||
var size int64 = 512
|
||||
assert.Equal(t, "512 B", FileSize(size))
|
||||
|
|
|
@ -90,8 +90,8 @@ loop:
|
|||
return
|
||||
}
|
||||
|
||||
for _, userStopwatches := range usersStopwatches {
|
||||
apiSWs, err := convert.ToStopWatches(ctx, userStopwatches.StopWatches)
|
||||
for uid, stopwatches := range usersStopwatches {
|
||||
apiSWs, err := convert.ToStopWatches(ctx, stopwatches)
|
||||
if err != nil {
|
||||
if !issues_model.IsErrIssueNotExist(err) {
|
||||
log.Error("Unable to APIFormat stopwatches: %v", err)
|
||||
|
@ -103,7 +103,7 @@ loop:
|
|||
log.Error("Unable to marshal stopwatches: %v", err)
|
||||
continue
|
||||
}
|
||||
m.SendMessage(userStopwatches.UserID, &Event{
|
||||
m.SendMessage(uid, &Event{
|
||||
Name: "stopwatches",
|
||||
Data: string(dataBs),
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ package git
|
|||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
@ -97,3 +98,41 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetNote(ctx context.Context, repo *Repository, commitID, notes, doerName, doerEmail string) error {
|
||||
_, err := repo.GetCommit(commitID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env := append(os.Environ(),
|
||||
"GIT_AUTHOR_NAME="+doerName,
|
||||
"GIT_AUTHOR_EMAIL="+doerEmail,
|
||||
"GIT_COMMITTER_NAME="+doerName,
|
||||
"GIT_COMMITTER_EMAIL="+doerEmail,
|
||||
)
|
||||
|
||||
cmd := NewCommand(ctx, "notes", "add", "-f", "-m")
|
||||
cmd.AddDynamicArguments(notes, commitID)
|
||||
|
||||
_, stderr, err := cmd.RunStdString(&RunOpts{Dir: repo.Path, Env: env})
|
||||
if err != nil {
|
||||
log.Error("Error while running git notes add: %s", stderr)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RemoveNote(ctx context.Context, repo *Repository, commitID string) error {
|
||||
cmd := NewCommand(ctx, "notes", "remove")
|
||||
cmd.AddDynamicArguments(commitID)
|
||||
|
||||
_, stderr, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
|
||||
if err != nil {
|
||||
log.Error("Error while running git notes remove: %s", stderr)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,25 +1,38 @@
|
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
package git_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
testReposDir = "tests/repos/"
|
||||
)
|
||||
|
||||
// openRepositoryWithDefaultContext opens the repository at the given path with DefaultContext.
|
||||
func openRepositoryWithDefaultContext(repoPath string) (*git.Repository, error) {
|
||||
return git.OpenRepository(git.DefaultContext, repoPath)
|
||||
}
|
||||
|
||||
func TestGetNotes(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
|
||||
require.NoError(t, err)
|
||||
defer bareRepo1.Close()
|
||||
|
||||
note := Note{}
|
||||
err = GetNote(context.Background(), bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653", ¬e)
|
||||
note := git.Note{}
|
||||
err = git.GetNote(context.Background(), bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653", ¬e)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("Note contents\n"), note.Message)
|
||||
assert.Equal(t, "Vladimir Panteleev", note.Commit.Author.Name)
|
||||
|
@ -31,11 +44,11 @@ func TestGetNestedNotes(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
defer repo.Close()
|
||||
|
||||
note := Note{}
|
||||
err = GetNote(context.Background(), repo, "3e668dbfac39cbc80a9ff9c61eb565d944453ba4", ¬e)
|
||||
note := git.Note{}
|
||||
err = git.GetNote(context.Background(), repo, "3e668dbfac39cbc80a9ff9c61eb565d944453ba4", ¬e)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("Note 2"), note.Message)
|
||||
err = GetNote(context.Background(), repo, "ba0a96fa63532d6c5087ecef070b0250ed72fa47", ¬e)
|
||||
err = git.GetNote(context.Background(), repo, "ba0a96fa63532d6c5087ecef070b0250ed72fa47", ¬e)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("Note 1"), note.Message)
|
||||
}
|
||||
|
@ -46,8 +59,48 @@ func TestGetNonExistentNotes(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
defer bareRepo1.Close()
|
||||
|
||||
note := Note{}
|
||||
err = GetNote(context.Background(), bareRepo1, "non_existent_sha", ¬e)
|
||||
note := git.Note{}
|
||||
err = git.GetNote(context.Background(), bareRepo1, "non_existent_sha", ¬e)
|
||||
require.Error(t, err)
|
||||
assert.IsType(t, ErrNotExist{}, err)
|
||||
assert.IsType(t, git.ErrNotExist{}, err)
|
||||
}
|
||||
|
||||
func TestSetNote(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
require.NoError(t, unittest.CopyDir(bareRepo1Path, filepath.Join(tempDir, "repo1")))
|
||||
|
||||
bareRepo1, err := openRepositoryWithDefaultContext(filepath.Join(tempDir, "repo1"))
|
||||
require.NoError(t, err)
|
||||
defer bareRepo1.Close()
|
||||
|
||||
require.NoError(t, git.SetNote(context.Background(), bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653", "This is a new note", "Test", "test@test.com"))
|
||||
|
||||
note := git.Note{}
|
||||
err = git.GetNote(context.Background(), bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653", ¬e)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("This is a new note\n"), note.Message)
|
||||
assert.Equal(t, "Test", note.Commit.Author.Name)
|
||||
}
|
||||
|
||||
func TestRemoveNote(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
|
||||
tempDir := t.TempDir()
|
||||
|
||||
require.NoError(t, unittest.CopyDir(bareRepo1Path, filepath.Join(tempDir, "repo1")))
|
||||
|
||||
bareRepo1, err := openRepositoryWithDefaultContext(filepath.Join(tempDir, "repo1"))
|
||||
require.NoError(t, err)
|
||||
defer bareRepo1.Close()
|
||||
|
||||
require.NoError(t, git.RemoveNote(context.Background(), bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653"))
|
||||
|
||||
note := git.Note{}
|
||||
err = git.GetNote(context.Background(), bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653", ¬e)
|
||||
require.Error(t, err)
|
||||
assert.IsType(t, git.ErrNotExist{}, err)
|
||||
}
|
||||
|
|
|
@ -254,7 +254,7 @@ func TestGitAttributeCheckerError(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
_, err = ac.CheckPath("i-am-a-python.p")
|
||||
require.ErrorIs(t, err, context.Canceled)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Cancelled/DuringRun", func(t *testing.T) {
|
||||
|
|
|
@ -97,7 +97,7 @@ func createDefaultPolicy() *bluemonday.Policy {
|
|||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(ref-issue( ref-external-issue)?|mention)$`)).OnElements("a")
|
||||
|
||||
// Allow classes for task lists
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`task-list-item`)).OnElements("li")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^task-list-item$`)).OnElements("li")
|
||||
|
||||
// Allow classes for org mode list item status.
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(unchecked|checked|indeterminate)$`)).OnElements("li")
|
||||
|
@ -106,7 +106,7 @@ func createDefaultPolicy() *bluemonday.Policy {
|
|||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^icon(\s+[\p{L}\p{N}_-]+)+$`)).OnElements("i")
|
||||
|
||||
// Allow classes for emojis
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`emoji`)).OnElements("img")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^emoji$`)).OnElements("img")
|
||||
|
||||
// Allow icons, emojis, chroma syntax and keyword markup on span
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^((icon(\s+[\p{L}\p{N}_-]+)+)|(emoji)|(language-math display)|(language-math inline))$|^([a-z][a-z0-9]{0,2})$|^` + keywordClass + `$`)).OnElements("span")
|
||||
|
@ -123,13 +123,13 @@ func createDefaultPolicy() *bluemonday.Policy {
|
|||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^header$")).OnElements("div")
|
||||
policy.AllowAttrs("data-line-number").Matching(regexp.MustCompile("^[0-9]+$")).OnElements("span")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^text small grey$")).OnElements("span")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^file-preview*")).OnElements("table")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^file-preview$")).OnElements("table")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^lines-escape$")).OnElements("td")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^toggle-escape-button btn interact-bg$")).OnElements("button")
|
||||
policy.AllowAttrs("title").OnElements("button")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ambiguous-code-point$")).OnElements("span")
|
||||
policy.AllowAttrs("data-tooltip-content").OnElements("span")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("muted|(text black)")).OnElements("a")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^muted|(text black)$")).OnElements("a")
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui warning message tw-text-left$")).OnElements("div")
|
||||
|
||||
// Allow generally safe attributes
|
||||
|
|
|
@ -12,7 +12,7 @@ var Attachment = struct {
|
|||
Enabled bool
|
||||
}{
|
||||
Storage: &Storage{},
|
||||
AllowedTypes: ".cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip",
|
||||
AllowedTypes: ".avif,.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip",
|
||||
MaxSize: 2048,
|
||||
MaxFiles: 5,
|
||||
Enabled: true,
|
||||
|
@ -25,7 +25,7 @@ func loadAttachmentFrom(rootCfg ConfigProvider) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip")
|
||||
Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".avif,.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip")
|
||||
Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(2048)
|
||||
Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5)
|
||||
Attachment.Enabled = sec.Key("ENABLED").MustBool(true)
|
||||
|
|
|
@ -37,6 +37,10 @@ type MarkupOption struct {
|
|||
//
|
||||
// in: body
|
||||
FilePath string
|
||||
// The current branch path where the form gets posted
|
||||
//
|
||||
// in: body
|
||||
BranchPath string
|
||||
}
|
||||
|
||||
// MarkupRender is a rendered markup document
|
||||
|
|
|
@ -8,3 +8,7 @@ type Note struct {
|
|||
Message string `json:"message"`
|
||||
Commit *Commit `json:"commit"`
|
||||
}
|
||||
|
||||
type NoteOptions struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
|
|
@ -486,7 +486,7 @@ func PrintCurrentTest(t testing.TB, skip ...int) func() {
|
|||
|
||||
if err := WriterCloser.popT(); err != nil {
|
||||
// disable test failure for now (too flacky)
|
||||
_, _ = fmt.Fprintf(os.Stdout, "testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err)
|
||||
_, _ = fmt.Fprintf(os.Stdout, "testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v\n", err)
|
||||
// t.Errorf("testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ const sniffLen = 1024
|
|||
const (
|
||||
// SvgMimeType MIME type of SVG images.
|
||||
SvgMimeType = "image/svg+xml"
|
||||
// AvifMimeType MIME type of AVIF images
|
||||
AvifMimeType = "image/avif"
|
||||
// ApplicationOctetStream MIME type of binary files.
|
||||
ApplicationOctetStream = "application/octet-stream"
|
||||
)
|
||||
|
@ -106,6 +108,12 @@ func DetectContentType(data []byte) SniffedType {
|
|||
}
|
||||
}
|
||||
|
||||
// AVIF is unsuported by http.DetectContentType
|
||||
// Signature taken from https://stackoverflow.com/a/68322450
|
||||
if bytes.Index(data, []byte("ftypavif")) == 4 {
|
||||
ct = AvifMimeType
|
||||
}
|
||||
|
||||
if strings.HasPrefix(ct, "audio/") && bytes.HasPrefix(data, []byte("ID3")) {
|
||||
// The MP3 detection is quite inaccurate, any content with "ID3" prefix will result in "audio/mpeg".
|
||||
// So remove the "ID3" prefix and detect again, if result is text, then it must be text content.
|
||||
|
|
|
@ -135,3 +135,13 @@ func TestDetectContentTypeOgg(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.True(t, st.IsVideo())
|
||||
}
|
||||
|
||||
func TestDetectContentTypeAvif(t *testing.T) {
|
||||
avifImage, err := hex.DecodeString("000000206674797061766966")
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := DetectContentTypeFromReader(bytes.NewReader(avifImage))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, st.IsImage())
|
||||
}
|
||||
|
|
|
@ -2859,6 +2859,7 @@ issues.review.add_remove_review_requests = požádal/a o kontroly od %[1]s a ods
|
|||
pulls.delete_after_merge.head_branch.is_default = Větev hlavy, kterou chcete odstranit, je výchozí větví a nelze ji odstranit.
|
||||
pulls.delete_after_merge.head_branch.is_protected = Větev hlavy, kterou chcete odstranit, je chráněnou větví a nelze ji odstranit.
|
||||
pulls.delete_after_merge.head_branch.insufficient_branch = Nemáte oprávnění k odstranění větve hlavy.
|
||||
issues.filter_sort.relevance = Relevance
|
||||
|
||||
[graphs]
|
||||
component_loading_info = Tohle může chvíli trvat…
|
||||
|
|
|
@ -2844,6 +2844,7 @@ issues.review.add_remove_review_requests = hat Reviews von %[1]s angefragt und h
|
|||
pulls.delete_after_merge.head_branch.is_default = Der Head-Branch, den Sie löschen wollen, ist der Standardbranch und kann nicht gelöscht werden.
|
||||
pulls.delete_after_merge.head_branch.is_protected = Der Head-Branch, den Sie löschen wollen, ist ein geschützter Branch und kann nicht gelöscht werden.
|
||||
pulls.delete_after_merge.head_branch.insufficient_branch = Sie haben keine Erlaubnis, den Head-Branch zu löschen.
|
||||
issues.filter_sort.relevance = Relevanz
|
||||
|
||||
[graphs]
|
||||
|
||||
|
|
|
@ -2622,6 +2622,9 @@ diff.browse_source = Browse source
|
|||
diff.parent = parent
|
||||
diff.commit = commit
|
||||
diff.git-notes = Notes
|
||||
diff.git-notes.add = Add note
|
||||
diff.git-notes.remove-header = Remove note
|
||||
diff.git-notes.remove-body = This note will be removed.
|
||||
diff.data_not_available = Diff content is not available
|
||||
diff.options_button = Diff options
|
||||
diff.show_diff_stats = Show stats
|
||||
|
@ -3837,8 +3840,8 @@ runs.actors_no_select = All actors
|
|||
runs.status_no_select = All status
|
||||
runs.no_results = No results matched.
|
||||
runs.no_workflows = There are no workflows yet.
|
||||
runs.no_workflows.quick_start = Don't know how to start with Forgejo Actions? See <a target="_blank" rel="noopener noreferrer" href="%s">the quick start guide</a>.
|
||||
runs.no_workflows.documentation = For more information on Forgejo Actions, see <a target="_blank" rel="noopener noreferrer" href="%s">the documentation</a>.
|
||||
runs.no_workflows.help_write_access = Don't know how to start with Forgejo Actions? Check out the <a target="_blank" rel="noopener noreferrer" href="%s">quick start in the user documentation</a> to write your first workflow, then <a target="_blank" rel="noopener noreferrer" href="%s">set up a Forgejo runner</a> to execute your jobs.
|
||||
runs.no_workflows.help_no_write_access = To learn about Forgejo Actions, see <a target="_blank" rel="noopener noreferrer" href="%s">the documentation</a>.
|
||||
runs.no_runs = The workflow has no runs yet.
|
||||
runs.empty_commit_message = (empty commit message)
|
||||
runs.expire_log_message = Logs have been purged because they were too old.
|
||||
|
|
|
@ -199,6 +199,11 @@ buttons.enable_monospace_font=Activar fuente monoespaciada
|
|||
buttons.disable_monospace_font=Desactivar fuente monoespaciada
|
||||
buttons.unindent.tooltip = Desanidar elementos por un nivel
|
||||
buttons.indent.tooltip = Anidar elementos por un nivel
|
||||
buttons.new_table.tooltip = Añadir tabla
|
||||
table_modal.header = Añadir tabla
|
||||
table_modal.placeholder.header = Cabecera
|
||||
table_modal.label.rows = Filas
|
||||
table_modal.label.columns = Columnas
|
||||
|
||||
[filter]
|
||||
string.asc=A - Z
|
||||
|
@ -475,6 +480,7 @@ back_to_sign_in = Volver a Iniciar sesión
|
|||
sign_in_openid = Proceder con OpenID
|
||||
remember_me.compromised = El identificador de inicio de sesión ya no es válido, lo que puede indicar una cuenta comprometida. Por favor, verifica si tu cuenta presenta actividades sospechosas.
|
||||
unauthorized_credentials = Las credenciales son incorrectas o han expirado. Reintenta el comando o visita %s para más información
|
||||
use_onetime_code = Usar código de un solo uso
|
||||
|
||||
[mail]
|
||||
view_it_on=Ver en %s
|
||||
|
@ -1036,7 +1042,7 @@ update_hints = Actualizar sugerencias
|
|||
pronouns = Pronombres
|
||||
pronouns_custom = Personalizados
|
||||
additional_repo_units_hint = Sugerir la habilitación de unidades de repositorio adicionales
|
||||
additional_repo_units_hint_description = Mostrar un botón "Añadir más unidades..." para los repositorios que no tengan habilitadas todas las unidades disponibles.
|
||||
additional_repo_units_hint_description = Mostrar la sugerencia "Habilitar más" para los repositorios que no tengan habilitadas todas las unidades disponibles.
|
||||
language.title = Idioma por defecto
|
||||
update_hints_success = Se han actualizado las sugerencias.
|
||||
pronouns_unspecified = No especificados
|
||||
|
@ -1769,8 +1775,8 @@ issues.review.left_comment=dejó un comentario
|
|||
issues.review.content.empty=Es necesario dejar un comentario indicando los cambios solicitados.
|
||||
issues.review.reject=cambios solicitados %s
|
||||
issues.review.wait=fue solicitado para revisión %s
|
||||
issues.review.add_review_request=solicitó revisión de %s %s
|
||||
issues.review.remove_review_request=eliminó la solicitud de revisión para %s %s
|
||||
issues.review.add_review_request=solicitó revisión de %[1]s %[2]s
|
||||
issues.review.remove_review_request=eliminó la solicitud de revisión para %[1]s %[2]s
|
||||
issues.review.remove_review_request_self=se negó a revisar %s
|
||||
issues.review.pending=Pendiente
|
||||
issues.review.pending.tooltip=Este comentario no es visible actualmente para otros usuarios. Para enviar sus comentarios pendientes, seleccione "%s" -> "%s/%s/%s" en la parte superior de la página.
|
||||
|
@ -2750,7 +2756,7 @@ pulls.cmd_instruction_merge_title = Fusionar
|
|||
contributors.contribution_type.deletions = Eliminaciones
|
||||
contributors.contribution_type.filter_label = Tipo de contribución:
|
||||
contributors.contribution_type.additions = Adiciones
|
||||
settings.units.add_more = Añadir más...
|
||||
settings.units.add_more = Habilitar más
|
||||
wiki.cancel = Cancelar
|
||||
activity.published_prerelease_label = Pre-lanzamiento
|
||||
activity.published_tag_label = Etiqueta
|
||||
|
@ -2775,6 +2781,16 @@ settings.mirror_settings.push_mirror.copy_public_key = Copiar clave pública
|
|||
issues.new.assign_to_me = Asignar a mi
|
||||
issues.all_title = Todos
|
||||
settings.wiki_globally_editable = Permitir a cualquiera editar la wiki
|
||||
settings.new_owner_blocked_doer = El nuevo propietario te ha bloqueado.
|
||||
settings.add_collaborator_blocked_our = No se puede añadir al colaborador debido a que el propietario del repositorio lo ha bloqueado.
|
||||
settings.discord_icon_url.exceeds_max_length = La URL del icono debe tener una longitud menor o igual a 2048 caracteres
|
||||
settings.add_webhook.invalid_path = La ruta no debe contener una parte que sea "." o ".." o la cadena vacía. No puede empezar o acabar con una barra oblicua.
|
||||
settings.wiki_rename_branch_main_notices_1 = Esta operación <strong>NO SE PUEDE</strong> deshacer.
|
||||
settings.add_collaborator_blocked_them = No se puede añadir al colaborador debido a que este ha bloqueado al propietario del repositorio.
|
||||
settings.transfer.button = Transferir la propiedad
|
||||
settings.transfer.modal.title = Transferir la propiedad
|
||||
settings.enter_repo_name = Introduce el nombre del propietario y del repositorio exactamente como se muestra:
|
||||
settings.confirmation_string = Cadena de confirmación
|
||||
|
||||
[graphs]
|
||||
|
||||
|
@ -3814,6 +3830,7 @@ exact_tooltip = Incluir sólo los resultados que corresponden al término de bú
|
|||
issue_kind = Buscar incidencias…
|
||||
fuzzy = Difusa
|
||||
runner_kind = Buscar ejecutores…
|
||||
regexp_tooltip = Interpretar los términos de búsqueda como una expresión regular
|
||||
|
||||
[markup]
|
||||
filepreview.lines = Líneas %[1]d a %[2]d en %[3]s
|
||||
|
|
|
@ -567,6 +567,7 @@ followers.title.few = seuraajaa
|
|||
following.title.one = Seurataan
|
||||
following.title.few = Seurataan
|
||||
joined_on = Liittynyt %s
|
||||
public_activity.visibility_hint.self_private = Toimintasi näkyy vain sinulle ja instanssin ylläpitäjille. <a href="%s">Määritä</a>.
|
||||
|
||||
|
||||
[settings]
|
||||
|
@ -801,6 +802,10 @@ change_password_success = Salasanasi on päivitetty. Kirjaudu jatkossa käyttäe
|
|||
manage_oauth2_applications = Hallitse OAuth2-sovelluksia
|
||||
change_password = Vaihda salasana
|
||||
webauthn_key_loss_warning = Jos kadotat turva-avaimesi, menetät pääsyn tilillesi.
|
||||
keep_activity_private.description = <a href="%s">Julkinen toimintasi</a> näkyy vain sinulle ja instanssin ylläpitäjille.
|
||||
email_desc = Ensisijaista sähköpostiosoitettasi käytetään ilmoituksiin, salasanan palautukseen ja jos sähköpostiosoite ei ole piilotettu, web-pohjaisiin Git-toimenpiteisiin.
|
||||
tokens_desc = Nämä poletit mahdollistavat pääsyn tilillesi Forgejon rajapintaa vasten.
|
||||
keep_email_private_popup = Tämä piilottaa sähköpostiosoitteesi profiilistasi. Se ei ole enää oletus verkkosivukäyttöliittymän kautta tehdyissä kommiteissa, kuten tiedostojen lähetyksissä ja muokkauksissa, eikä sitä käytetä yhdistämiskommiteissa. Sen sijaan erikoisosoitetta %s voidaan käyttää kommittien liittämisessä tiliisi. Ota huomioon, ettei tämän asetuksen muuttaminen vaikuta olemassa oleviin kommitteihin.
|
||||
|
||||
[repo]
|
||||
owner=Omistaja
|
||||
|
@ -2384,6 +2389,7 @@ conan.registry = Määritä tämä rekisteri komentoriviltä:
|
|||
conda.install = Asenna paketti Condalla suorittamalla seuraava komento:
|
||||
helm.registry = Määritä tämä rekisteri komentoriviltä:
|
||||
pub.install = Asenna paketti Dartilla suorittamalla seuraava komento:
|
||||
owner.settings.cargo.title = Cargon rekisteri-indeksi
|
||||
|
||||
[secrets]
|
||||
creation.failed = Salaisuuden lisääminen epäonnistui.
|
||||
|
|
|
@ -201,6 +201,11 @@ buttons.disable_monospace_font=Désactiver la police à chasse fixe
|
|||
buttons.indent.tooltip = Indenter les éléments d'un niveau
|
||||
buttons.unindent.tooltip = Supprimer un niveau d'indentation
|
||||
buttons.new_table.tooltip = Ajouter une table
|
||||
table_modal.header = Ajouter une table
|
||||
table_modal.placeholder.header = Entête
|
||||
table_modal.placeholder.content = Contenu
|
||||
table_modal.label.rows = Lignes
|
||||
table_modal.label.columns = Colonnes
|
||||
|
||||
[filter]
|
||||
string.asc=A - Z
|
||||
|
@ -480,6 +485,7 @@ hint_login = Vous avez déjà un compte ? <a href="%s">Connectez vous maintena
|
|||
back_to_sign_in = Retour à la connexion
|
||||
sign_in_openid = Continuer avec OpenID
|
||||
unauthorized_credentials = Vos identifiants sont invalides ou ont expiré. Réessayez votre commande, ou allez à %s pour plus d'informations
|
||||
use_onetime_code = Utiliser un code a usage unique
|
||||
|
||||
[mail]
|
||||
view_it_on=Voir sur %s
|
||||
|
@ -743,7 +749,7 @@ uid=UID
|
|||
webauthn=Clés de sécurité à deux facteurs
|
||||
|
||||
public_profile=Profil public
|
||||
biography_placeholder=Parlez-nous un peu de vous ! (Vous pouvez utiliser Markdown)
|
||||
biography_placeholder=Parlez-nous un peu de vous ! (Markdown est interprété)
|
||||
location_placeholder=Partagez votre position approximative avec d'autres personnes
|
||||
profile_desc=Contrôlez comment votre profil est affiché aux autres utilisateurs. Votre adresse courriel principale sera utilisée pour les notifications, la récupération de mot de passe et les opérations Git basées sur le Web.
|
||||
password_username_disabled=Les utilisateurs externes ne sont pas autorisés à modifier leur nom d'utilisateur. Veuillez contacter l'administrateur de votre site pour plus de détails.
|
||||
|
@ -939,7 +945,7 @@ select_permissions=Sélectionner les autorisations
|
|||
permission_no_access=Aucun accès
|
||||
permission_read=Lecture
|
||||
permission_write=Lecture et écriture
|
||||
access_token_desc=Les autorisations des jetons sélectionnées se limitent aux <a href="%[1]s" target="_blank">routes API</a> correspondantes. Lisez la <a href="%[2]s" target="_blank">documentation</a> pour plus d’informations.
|
||||
access_token_desc=Les autorisations des jetons sélectionnées se limitent aux <a href="%[1]s" target="_blank">routes API</a> correspondantes. Lisez la <a href="%[2]s" target="_blank">documentation</a> pour plus d’information.
|
||||
at_least_one_permission=Vous devez sélectionner au moins une permission pour créer un jeton
|
||||
permissions_list=Autorisations :
|
||||
|
||||
|
@ -1781,8 +1787,8 @@ issues.review.left_comment=laisser un commentaire
|
|||
issues.review.content.empty=Vous devez laisser un commentaire indiquant le(s) changement(s) demandé(s).
|
||||
issues.review.reject=a requis les changements %s
|
||||
issues.review.wait=a été sollicité pour évaluer cette demande d’ajout %s
|
||||
issues.review.add_review_request=a demandé à %s une évaluation %s
|
||||
issues.review.remove_review_request=a retiré la demande d’évaluation pour %s %s
|
||||
issues.review.add_review_request=demande d'évaluation de %[1]s %[2]s
|
||||
issues.review.remove_review_request=demande d’évaluation retirée pour %[1]s %[2]s
|
||||
issues.review.remove_review_request_self=a refusé d’évaluer cette demande d’ajout %s
|
||||
issues.review.pending=En attente
|
||||
issues.review.pending.tooltip=Ce commentaire n'est pas encore visible par les autres utilisateurs. Pour soumettre vos commentaires en attente, sélectionnez "%s" → "%s/%s/%s" en haut de la page.
|
||||
|
@ -2583,7 +2589,7 @@ diff.generated=générée
|
|||
diff.vendored=externe
|
||||
diff.comment.add_line_comment=Commenter cette ligne
|
||||
diff.comment.placeholder=Laisser un commentaire
|
||||
diff.comment.markdown_info=Formater avec Markdown.
|
||||
diff.comment.markdown_info=Formater avec Markdown est autorisé.
|
||||
diff.comment.add_single_comment=Commenter (simple)
|
||||
diff.comment.add_review_comment=Commenter
|
||||
diff.comment.start_review=Débuter une évaluation
|
||||
|
@ -2834,12 +2840,12 @@ settings.pull_mirror_sync_quota_exceeded = Quota dépassé, les modifications ne
|
|||
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 »
|
||||
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
|
||||
pulls.cmd_instruction_merge_warning = <b>Avertissement :</b> Le paramètre "détection automatique de la fusion manuelle" n'est pas activé pour ce dépôt, vous devrez marquer cette demande d'ajout comme manuellement fusionnée après.
|
||||
release.type_external_asset = Actif Externe
|
||||
release.type_external_asset = Actif externe
|
||||
activity.published_prerelease_label = Pré-version
|
||||
activity.published_tag_label = Étiquette
|
||||
release.asset_name = Nom de l'actif
|
||||
|
@ -2847,6 +2853,13 @@ release.add_external_asset = Ajouter un actif externe
|
|||
issues.new.assign_to_me = Assigner à moi-même
|
||||
issues.all_title = Tous
|
||||
settings.discord_icon_url.exceeds_max_length = L'URL de l’icône ne doit pas dépasser 2048 caractères
|
||||
issues.review.add_review_requests = demandes d'évaluation de %[1]s %[2]s
|
||||
issues.review.remove_review_requests = demandes d’évaluation retirée pour %[1]s %[2]s
|
||||
issues.review.add_remove_review_requests = demandes d'évaluation pour %[1]s et demandes d'évaluation retirées pour %[2]s %[3]s
|
||||
pulls.delete_after_merge.head_branch.is_protected = La branche head que vous voulez supprimer est une branche protégée et ne peut pas être supprimée.
|
||||
pulls.delete_after_merge.head_branch.is_default = La branche head que vous voulez supprimer est la branche par défaut et ne peut pas être supprimée.
|
||||
pulls.delete_after_merge.head_branch.insufficient_branch = Vous n'avez pas le droit de supprimer la branche head.
|
||||
issues.filter_sort.relevance = Pertinence
|
||||
|
||||
[graphs]
|
||||
component_loading=Chargement de %s…
|
||||
|
|
|
@ -1172,20 +1172,20 @@ invisible_runes_header=`Šīs fails satur neredzamus unikoda simbolus`
|
|||
invisible_runes_description=`Šis fails satur neredzamus unikoda simbolus, kas ir neatšķirami cilvēkiem, bet dators tās var atstrādāt atšķirīgi. Ja šķiet, ka tas ir ar nolūku, šo brīdinājumu var droši neņemt vērā. Jāizmanto atsoļa taustiņš (Esc), lai atklātu tās.`
|
||||
ambiguous_runes_header=`Šis fails satur neviennozīmīgus unikoda simbolus`
|
||||
ambiguous_runes_description=`Šis fails satur unikoda simbolus, kas var tikt sajauktas ar citām rakstzīmēm. Ja šķiet, ka tas ir ar nolūku, šo brīdinājumu var droši neņemt vērā. Jāizmanto atsoļa taustiņš (Esc), lai atklātu tās.`
|
||||
invisible_runes_line=`Šī līnija satur neredzamus unikoda simbolus`
|
||||
ambiguous_runes_line=`Šī līnija satur neviennozīmīgus unikoda simbolus`
|
||||
invisible_runes_line=`Šajā rindā ir neredzamas unikoda rakstzīmes`
|
||||
ambiguous_runes_line=`Šajā rindā ir neviennozīmīgas unikoda rakstzīmes`
|
||||
ambiguous_character=`%[1]c [U+%04[1]X] var tikt sajaukts ar %[2]c [U+%04[2]X]`
|
||||
|
||||
escape_control_characters=Kodēt
|
||||
unescape_control_characters=Atkodēt
|
||||
file_copy_permalink=Kopēt saiti
|
||||
view_git_blame=Aplūkot Git vainīgos
|
||||
video_not_supported_in_browser=Jūsu pārlūks neatbalsta HTML5 video.
|
||||
audio_not_supported_in_browser=Jūsu pārlūks neatbalsta HTML5 audio.
|
||||
file_copy_permalink=Ievietot pastāvīgo saiti starpliktuvē
|
||||
view_git_blame=Apskatīt Git izmaiņu veicējus
|
||||
video_not_supported_in_browser=Pārlūks neatbalsta HTML5 tagu "video".
|
||||
audio_not_supported_in_browser=Pārlūks neatbalsta HTML5 tagu "audio".
|
||||
stored_lfs=Saglabāts Git LFS
|
||||
symbolic_link=Simboliska saite
|
||||
executable_file=Izpildāmais fails
|
||||
commit_graph=Revīziju grafs
|
||||
executable_file=Izpildāma datne
|
||||
commit_graph=Iesūtījumu karte
|
||||
commit_graph.select=Izvēlieties atzarus
|
||||
commit_graph.hide_pr_refs=Paslēpt izmaiņu pieprasījumus
|
||||
commit_graph.monochrome=Melnbalts
|
||||
|
@ -1200,27 +1200,27 @@ line=rinda
|
|||
lines=rindas
|
||||
from_comment=(komentārs)
|
||||
|
||||
editor.add_file=Pievienot
|
||||
editor.add_file=Pievienot datni
|
||||
editor.new_file=Jauna datne
|
||||
editor.upload_file=Augšupielādēt failu
|
||||
editor.edit_file=Labot failu
|
||||
editor.upload_file=Augšupielādēt datni
|
||||
editor.edit_file=Labot datni
|
||||
editor.preview_changes=Priekšskatīt izmaiņas
|
||||
editor.cannot_edit_lfs_files=LFS failus nevar labot no tīmekļa saskarnes.
|
||||
editor.cannot_edit_non_text_files=Nav iespējams labot bināros failus no pārlūka saskarnes.
|
||||
editor.edit_this_file=Labot failu
|
||||
editor.edit_this_file=Labot datni
|
||||
editor.this_file_locked=Fails ir bloķēts
|
||||
editor.must_be_on_a_branch=Ir jābūt izvēlētam atzaram, lai varētu veikt vai piedāvāt izmaiņas šim failam.
|
||||
editor.fork_before_edit=Lai varētu labot failu, ir nepieciešams atdalīt repozitoriju.
|
||||
editor.delete_this_file=Dzēst failu
|
||||
editor.delete_this_file=Izdzēst datni
|
||||
editor.must_have_write_access=Jums ir jābūt rakstīšanas tiesībām, lai varētu veikt vai piedāvāt izmaiņas šim failam.
|
||||
editor.file_delete_success=Fails "%s" tika izdzēsts.
|
||||
editor.name_your_file=Ievadiet faila nosaukumu…
|
||||
editor.filename_help=Lai pievienotu direktoriju, ierakstiet tās nosaukumu un slīpsvītru ('/'). Lai noņemtu direktoriju, ielieciet kursoru pirms faila nosaukuma un nospiediet atpakaļatkāpes taustiņu.
|
||||
editor.filename_help=Mapi var pievienot, ja ieraksta tās nosaukumu, aiz kura ir slīpsvītra ("/"). Mapi var noņemt ar atpakaļatkāpes taustiņa nospiešanu ievades lauka sākumā.
|
||||
editor.or=vai
|
||||
editor.cancel_lower=Atcelt
|
||||
editor.commit_signed_changes=Apstiprināt parakstītu revīziju
|
||||
editor.commit_changes=Pabeigt revīziju
|
||||
editor.add_tmpl=Pievienot '<fails>'
|
||||
editor.commit_signed_changes=Iesūtīt parakstītas izmaiņas
|
||||
editor.commit_changes=Iesūtīt izmaiņas
|
||||
editor.add_tmpl=Pievienot '<datnes nosaukums>'
|
||||
editor.add=Pievienot %s
|
||||
editor.update=Atjaunot %s
|
||||
editor.delete=Dzēst %s
|
||||
|
@ -1246,15 +1246,15 @@ editor.file_is_a_symlink=Fails "%s" ir norāde, kuru nav iespējams labot no tī
|
|||
editor.filename_is_a_directory=Faila nosaukums "%s" sakrīt ar direktorijas nosaukumu šajā repozitorijā.
|
||||
editor.file_editing_no_longer_exists=Fails "%s", ko labojat, vairs neeksistē šajā repozitorijā.
|
||||
editor.file_deleting_no_longer_exists=Fails "%s", ko dzēšat, vairs neeksistē šajā repozitorijā.
|
||||
editor.file_changed_while_editing=Faila saturs ir mainījies kopš sākāt to labot. Noklikšķiniet <a target="_blank" rel="noopener noreferrer" href="%s">šeit</a>, lai apskatītu, vai <strong>Nosūtiet izmaiņas atkārtoti</strong>, lai pārrakstītu.
|
||||
editor.file_changed_while_editing=Datnes saturs ir mainījies kopš labošanas uzsākšanas. <a target="_blank" rel="noopener noreferrer" href="%s">Klikšķināt šeit</a>, lai apskatītu vai <strong>atkārtoti iesūtītu izmaiņas</strong>, lai tās pārrakstītu.
|
||||
editor.file_already_exists=Fails ar nosaukumu "%s" šajā repozitorijā jau eksistē.
|
||||
editor.commit_empty_file_header=Iesūtīt tukšu failu
|
||||
editor.commit_empty_file_text=Fails, ko vēlaties iesūtīt, ir tukšs. Vai turpināt?
|
||||
editor.no_changes_to_show=Nav izmaiņu, ko rādīt.
|
||||
editor.fail_to_update_file=Neizdevās atjaunot/izveidot failu "%s".
|
||||
editor.fail_to_update_file_summary=Kļūdas ziņojums:
|
||||
editor.push_rejected_no_message=Izmaiņu iesūtīšana tika noraidīta, bet serveris neatgrieza paziņojumu. Pārbaudiet git āķus šim repozitorijam.
|
||||
editor.push_rejected=Serveris noraidīja šo izmaiņu. Pārbaudiet git āķus.
|
||||
editor.push_rejected_no_message=Serveris noraidīja izmaiņas bez paziņojuma. Lūgums pārbaudīt Git aizķeres.
|
||||
editor.push_rejected=Serveris noraidīja izmaiņas. Lūgums pārbaudīt Git aizķeres.
|
||||
editor.push_rejected_summary=Pilns noraidīšanas ziņojums:
|
||||
editor.add_subdir=Pievienot direktoriju…
|
||||
editor.unable_to_upload_files=Neizdevās augšupielādēt failus uz direktoriju "%s", kļūda: %v
|
||||
|
@ -1274,7 +1274,7 @@ commits.nothing_to_compare=Atzari ir vienādi.
|
|||
commits.search=Meklēt revīzijas…
|
||||
commits.search.tooltip=Jūs varat izmantot atslēgas vārdus "author:", "committer:", "after:" vai "before:", piemēram, "revert author:Alice before:2019-01-13".
|
||||
commits.find=Meklēt
|
||||
commits.search_all=Visi atzari
|
||||
commits.search_all=Visi zari
|
||||
commits.author=Autors
|
||||
commits.message=Ziņojums
|
||||
commits.date=Datums
|
||||
|
@ -1283,8 +1283,8 @@ commits.newer=Jaunāki
|
|||
commits.signed_by=Parakstījis
|
||||
commits.signed_by_untrusted_user=Parakstījis neuzticams lietotājs
|
||||
commits.signed_by_untrusted_user_unmatched=Parakstījis neuzticams lietotājs, kas neatbilst izmaiņu autoram
|
||||
commits.gpg_key_id=GPG atslēgas ID
|
||||
commits.ssh_key_fingerprint=SSH atslēgas identificējošā zīmju virkne
|
||||
commits.gpg_key_id=GPG atslēgas Id
|
||||
commits.ssh_key_fingerprint=SSH atslēgas nospiedums
|
||||
commits.view_path=Skatīt šajā vēstures punktā
|
||||
|
||||
commit.operations=Darbības
|
||||
|
@ -1300,7 +1300,7 @@ commitstatus.failure=Kļūme
|
|||
commitstatus.pending=Nav iesūtīts
|
||||
commitstatus.success=Pabeigts
|
||||
|
||||
ext_issues=Piekļuve ārējām problēmām
|
||||
ext_issues=Piekļuve ārējiem pieteikumiem
|
||||
ext_issues.desc=Saite uz ārējo problēmu sekotāju.
|
||||
|
||||
projects=Projekti
|
||||
|
@ -1312,35 +1312,35 @@ projects.title=Nosaukums
|
|||
projects.new=Jauns projekts
|
||||
projects.new_subheader=Koordinē, seko un atjauno savu darbu centralizēti, lai projekts būtu izsekojams un vienmēr laikā.
|
||||
projects.create_success=Projekts "%s" tika izveidots.
|
||||
projects.deletion=Dzēst projektu
|
||||
projects.deletion=Izdzēst projektu
|
||||
projects.deletion_desc=Dzēšot projektu no tā tiks atsaistītās visas tam piesaistītās problēmas. Vai turpināt?
|
||||
projects.deletion_success=Šis projekts tika izdzēsts.
|
||||
projects.edit=Labot projektu
|
||||
projects.edit_subheader=Projekti organizē problēmas un ļauj izsekot to progresam.
|
||||
projects.modify=Mainīt projektu
|
||||
projects.modify=Labot projektu
|
||||
projects.edit_success=Projekta "%s" izmaiņas tika saglabātas.
|
||||
projects.type.none=Nav
|
||||
projects.type.basic_kanban=`Vienkāršots "Kanban"`
|
||||
projects.type.basic_kanban=Pamata "Kanban"
|
||||
projects.type.bug_triage=Kļūdu šķirošana
|
||||
projects.template.desc=Projekta sagatave
|
||||
projects.template.desc_helper=Izvēlieties projekta sagatavi, lai sāktu darbu
|
||||
projects.template.desc=Sagatave
|
||||
projects.template.desc_helper=Jāatlasa projekta sagatave, lai uzsāktu
|
||||
projects.type.uncategorized=Bez kategorijas
|
||||
projects.column.edit=Rediģēt kolonnas
|
||||
projects.column.edit=Labot kolonnu
|
||||
projects.column.edit_title=Nosaukums
|
||||
projects.column.new_title=Nosaukums
|
||||
projects.column.new_submit=Izveidot kolonnu
|
||||
projects.column.new=Jauna kolonna
|
||||
projects.column.set_default=Izvēlēties kā noklusēto
|
||||
projects.column.set_default_desc=Izvēlēties šo kolonnu kā noklusēto nekategorizētām problēmām un izmaiņu pieteikumiem
|
||||
projects.column.set_default=Iestatīt kā noklusējumu
|
||||
projects.column.set_default_desc=Iestatīt šo kolonnu kā noklusējumu neapkopotiem pieteikumiem un izmaiņu pieprasījumiem
|
||||
projects.column.unset_default=Atiestatīt noklusēto
|
||||
projects.column.unset_default_desc=Noņemt šo kolonnu kā noklusēto
|
||||
projects.column.delete=Dzēst kolonnu
|
||||
projects.column.deletion_desc=Dzēšot projekta kolonnu visas tam piesaistītās problēmas tiks pārliktas kā nekategorizētas. Vai turpināt?
|
||||
projects.column.delete=Izdzēst kolonnu
|
||||
projects.column.deletion_desc=Projekta kolonnas izdzēšana pārvietos visus saistītos pieteikumus uz noklusējuma kolonnu. Turpināt?
|
||||
projects.column.color=Krāsa
|
||||
projects.open=Aktīvie
|
||||
projects.close=Pabeigtie
|
||||
projects.column.assigned_to=Piešķirts
|
||||
projects.card_type.desc=Kartītes priekšskatījums
|
||||
projects.card_type.desc=Kartīšu priekšskatījumi
|
||||
projects.card_type.images_and_text=Attēli un teksts
|
||||
projects.card_type.text_only=Tikai teksts
|
||||
|
||||
|
@ -1350,40 +1350,40 @@ issues.filter_milestones=Filtrēt pēc atskaites punkta
|
|||
issues.filter_projects=Filtrēt pēc projekta
|
||||
issues.filter_labels=Filtrēt pēc etiķetēm
|
||||
issues.filter_reviewers=Filtrēt pēc recenzentiem
|
||||
issues.new=Jauna problēma
|
||||
issues.new=Jauns pieteikums
|
||||
issues.new.title_empty=Nosaukums nevar būt tukšs
|
||||
issues.new.labels=Etiķetes
|
||||
issues.new.no_label=Nav etiķešu
|
||||
issues.new.no_label=Nav iezīmju
|
||||
issues.new.clear_labels=Noņemt etiķetes
|
||||
issues.new.projects=Projekti
|
||||
issues.new.clear_projects=Notīrīt projektus
|
||||
issues.new.no_projects=Nav projektu
|
||||
issues.new.open_projects=Aktīvie projekti
|
||||
issues.new.closed_projects=Pabeigtie projekti
|
||||
issues.new.open_projects=Atvērtie projekti
|
||||
issues.new.closed_projects=Aizvērtie projekti
|
||||
issues.new.no_items=Nav neviena ieraksta
|
||||
issues.new.milestone=Atskaites punkts
|
||||
issues.new.no_milestone=Nav atskaites punktu
|
||||
issues.new.clear_milestone=Notīrīt atskaites punktus
|
||||
issues.new.open_milestone=Atvērtie atskaites punktus
|
||||
issues.new.open_milestone=Atvērtie atskaites punkti
|
||||
issues.new.closed_milestone=Aizvērtie atskaites punkti
|
||||
issues.new.assignees=Atbildīgie
|
||||
issues.new.clear_assignees=Noņemt atbildīgo
|
||||
issues.new.no_assignees=Nav atbildīgo
|
||||
issues.new.no_reviewers=Nav recenzentu
|
||||
issues.choose.get_started=Sākt darbu
|
||||
issues.choose.get_started=Uzsākt darbu
|
||||
issues.choose.open_external_link=Atvērt
|
||||
issues.choose.blank=Noklusējuma
|
||||
issues.choose.blank_about=Izveidot problēmu ar noklusējuma sagatavi.
|
||||
issues.choose.ignore_invalid_templates=Kļūdainās sagataves tika izlaistas
|
||||
issues.choose.invalid_templates=%v ķļūdaina sagatave(s) atrastas
|
||||
issues.choose.invalid_config=Problēmu konfigurācija satur kļūdas:
|
||||
issues.no_ref=Nav norādīts atzars/tags
|
||||
issues.create=Pieteikt problēmu
|
||||
issues.new_label=Jauna etiķete
|
||||
issues.no_ref=Nav norādīts zars/birka
|
||||
issues.create=Izveidot pieteikumu
|
||||
issues.new_label=Jauna iezīme
|
||||
issues.new_label_placeholder=Etiķetes nosaukums
|
||||
issues.new_label_desc_placeholder=Apraksts
|
||||
issues.create_label=Izveidot etiķeti
|
||||
issues.label_templates.title=Ielādēt sākotnēji noteiktu etiķešu kopu
|
||||
issues.create_label=Izveidot iezīmi
|
||||
issues.label_templates.title=Ielādēt iepriekš noteiktu iezīmju kopu
|
||||
issues.label_templates.info=Nav izveidota neviena etiķete. Jūs varat noklikšķināt uz "Jauna etiķete" augstāk, lai to izveidotu vai izmantot zemāk piedāvātās etiķetes:
|
||||
issues.label_templates.helper=Izvēlieties etiķešu kopu
|
||||
issues.label_templates.use=Izmantot etiķešu kopu
|
||||
|
@ -1752,10 +1752,10 @@ pulls.num_conflicting_files_n=%d faili ar konfliktiem
|
|||
pulls.approve_count_1=%d apstiprinājums
|
||||
pulls.approve_count_n=%d apstiprinājumi
|
||||
pulls.reject_count_1=%d izmaiņu pieprasījums
|
||||
pulls.reject_count_n=%d pieprasītas izmaiņas
|
||||
pulls.waiting_count_1=nepieciešama %d recenzija
|
||||
pulls.waiting_count_n=nepieciešamas %d recenzijas
|
||||
pulls.wrong_commit_id=revīzijas identifikātoram ir jābūt revīzijas identifikatoram no mērķa atzara
|
||||
pulls.reject_count_n=%d izmaiņu pieprasījumi
|
||||
pulls.waiting_count_1=nepieciešama %d izskatīšana
|
||||
pulls.waiting_count_n=nepieciešamas %d izskatīšanas
|
||||
pulls.wrong_commit_id=iesūtījuma identifikatoram jābūt mērķa zara iesūtījuma identifikatoram
|
||||
|
||||
pulls.no_merge_desc=Šo izmaiņu pieprasījumu nav iespējams sapludināt, jo nav atļauts neviens sapludināšanas veids.
|
||||
pulls.no_merge_helper=Lai sapludinātu šo izmaiņu pieprasījumu, iespējojiet vismaz vienu sapludināšanas veidu repozitorija iestatījumos vai sapludiniet to manuāli.
|
||||
|
@ -1771,17 +1771,17 @@ pulls.merge_commit_id=Sapludināšanas revīzijas ID
|
|||
pulls.require_signed_wont_sign=Atzarā var iesūtīt tikai parakstītas revīzijas, bet sapludināšanas revīzijas netiks parakstīta
|
||||
|
||||
pulls.invalid_merge_option=Nav iespējams izmantot šādu sapludināšanas veidu šim izmaiņu pieprasījumam.
|
||||
pulls.merge_conflict=Sapludināšana neizdevās: Veicot sapludināšanu, radās konflikts. Mēģiniet izmantot citu sapludināšanas stratēģiju
|
||||
pulls.merge_conflict_summary=Kļūdas paziņojums
|
||||
pulls.rebase_conflict=Sapludināšana neizdevās: Veicot pārbāzēšanu uz revīziju %[1]s, radās konflikts. Mēģiniet izmantot citu sapludināšanas stratēģiju
|
||||
pulls.rebase_conflict_summary=Kļūdas paziņojums
|
||||
pulls.unrelated_histories=Sapludināšana neizdevās: mērķa un bāzes atzariem nav kopējas vēstures. Ieteikums: izvēlieties citu sapludināšanas stratēģiju
|
||||
pulls.merge_out_of_date=Sapludināšana neizdevās: sapludināšanas laikā, bāzes atzarā tika iesūtītas izmaiņas. Ieteikums: mēģiniet atkārtoti.
|
||||
pulls.head_out_of_date=Sapludināšana neizdevās: sapludināšanas laikā, bāzes atzarā tika iesūtītas izmaiņas. Ieteikums: mēģiniet atkārtoti.
|
||||
pulls.merge_conflict=Apvienošana neizdevās: iekļaušanas laikā radās nesaderības. Norāde: jāmēģina cita pieeja
|
||||
pulls.merge_conflict_summary=Kļūdas ziņojums
|
||||
pulls.rebase_conflict=Apvienošana neizdevās: iesūtījuma %[1]s pārbāzēšanas laikā radās nesaderība. Norāde: jāmēģina cita pieeja
|
||||
pulls.rebase_conflict_summary=Kļūdas ziņojums
|
||||
pulls.unrelated_histories=Apvienošana neizdevās: apvienošanas galotnei un pamatam nav kopējas vēstures. Norāde: jāmēģina cita pieeja
|
||||
pulls.merge_out_of_date=Apvienošana neizdevās: apvienošanas laikā pamata zars tika atjaunināts. Norāde: jāmēģina vēlreiz.
|
||||
pulls.head_out_of_date=Apvienošana neizdevās: apvienošanas laikā galotne tika atjaunināta. Norāde: jāmēģina vēlreiz.
|
||||
pulls.has_merged=Neizdevās: izmaiņu pieprasījums jau ir sapludināts, nevar to darīt atkārtoti vai mainīt mērķa atzaru.
|
||||
pulls.push_rejected=Sapludināšana neizdevās: iesūtīšana tika noraidīta. Pārbaudiet git āķus šim repozitorijam.
|
||||
pulls.push_rejected=Aizgādāšana neizdevās: aizgādāšana tika noraidīta. Jāpārskata šīs glabātavas Git aizķeres.
|
||||
pulls.push_rejected_summary=Pilns noraidīšanas ziņojums
|
||||
pulls.push_rejected_no_message=Sapludināšana neizdevās: Izmaiņu iesūtīšana tika noraidīta, bet serveris neatgrieza paziņojumu.<br>Pārbaudiet git āķus šim repozitorijam
|
||||
pulls.push_rejected_no_message=Apvienošana neizdevās: aizgādāšana tika noraidīta, bet serveris neatgrieza ziņojumu. Jāpārskata šīs glabātavas Git aizķeres
|
||||
pulls.open_unmerged_pull_exists=`Jūs nevarat veikt atkārtotas atvēršanas darbību, jo jau eksistē izmaiņu pieprasījums (#%d) ar šādu sapludināšanas informāciju.`
|
||||
pulls.status_checking=Dažas pārbaudes vēl tiek veiktas
|
||||
pulls.status_checks_success=Visas pārbaudes ir veiksmīgas
|
||||
|
@ -1800,7 +1800,7 @@ pulls.outdated_with_base_branch=Atzars ir novecojis salīdzinot ar bāzes atzaru
|
|||
pulls.close=Aizvērt izmaiņu pieprasījumu
|
||||
pulls.closed_at=`aizvēra šo izmaiņu pieprasījumu <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
pulls.reopened_at=`atkārtoti atvēra šo izmaiņu pieprasījumu <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
pulls.cmd_instruction_hint=`Apskatīt komandrindas izmantošanas norādes.`
|
||||
pulls.cmd_instruction_hint=Apskatīt komandrindas izmantošanas norādes
|
||||
pulls.cmd_instruction_checkout_title=Paņemt
|
||||
pulls.cmd_instruction_checkout_desc=Projekta repozitorijā jāizveido jauns atzars un jāpārbauda izmaiņas.
|
||||
pulls.cmd_instruction_merge_title=Sapludināt
|
||||
|
@ -1838,20 +1838,20 @@ milestones.completeness=%d%% pabeigti
|
|||
milestones.create=Izveidot atskaites punktu
|
||||
milestones.title=Virsraksts
|
||||
milestones.desc=Apraksts
|
||||
milestones.due_date=Termiņš (neobligāts)
|
||||
milestones.due_date=Beigu datums (pēc izvēles)
|
||||
milestones.clear=Notīrīt
|
||||
milestones.invalid_due_date_format=Izpildes termiņam ir jābūt formāta 'yyyy-mm-dd'.
|
||||
milestones.invalid_due_date_format=Beigu datuma pierakstam ir jābūt 'yyyy-mm-dd'.
|
||||
milestones.create_success=Atskaites punkts "%s" tika veiksmīgi izveidots.
|
||||
milestones.edit=Labot atskaites punktu
|
||||
milestones.edit_subheader=Atskaites punkti, ļauj organizēt problēmas un sekot to progresam.
|
||||
milestones.cancel=Atcelt
|
||||
milestones.modify=Labot atskaites punktu
|
||||
milestones.modify=Atjaunināt atskaites punktu
|
||||
milestones.edit_success=Izmaiņas atskaites punktā "%s" tika veiksmīgi saglabātas.
|
||||
milestones.deletion=Dzēst atskaites punktu
|
||||
milestones.deletion=Izdzēst atskaites punktu
|
||||
milestones.deletion_desc=Dzēšot šo atskaites punktu, tas tiks noņemts no visām saistītajām problēmām un izmaiņu pieprasījumiem. Vai turpināt?
|
||||
milestones.deletion_success=Atskaites punkts tika veiksmīgi izdzēsts.
|
||||
milestones.filter_sort.earliest_due_data=Agrākais izpildes laiks
|
||||
milestones.filter_sort.latest_due_date=Vēlākais izpildes laiks
|
||||
milestones.filter_sort.earliest_due_data=Tuvākais izpildes datums
|
||||
milestones.filter_sort.latest_due_date=Tālākais izpildes datums
|
||||
milestones.filter_sort.least_complete=Vismazāk pabeigtais
|
||||
milestones.filter_sort.most_complete=Visvairāk pabeigtais
|
||||
milestones.filter_sort.most_issues=Visvairāk problēmu
|
||||
|
@ -1859,7 +1859,7 @@ milestones.filter_sort.least_issues=Vismazāk problēmu
|
|||
|
||||
signing.will_sign=Šī revīzija tiks parakstīta ar atslēgu "%s".
|
||||
signing.wont_sign.error=Notika kļūda pārbaudot vai revīzija var tikt parakstīta.
|
||||
signing.wont_sign.nokey=Nav pieejamas atslēgas, ar ko parakstīt šo revīziju.
|
||||
signing.wont_sign.nokey=Nav pieejamas atslēgas, ar ko parakstīt šo iesūtījumu.
|
||||
signing.wont_sign.never=Revīzijas nekad netiek parakstītas.
|
||||
signing.wont_sign.always=Revīzijas vienmēr tiek parakstītas.
|
||||
signing.wont_sign.pubkey=Revīzija netiks parakstīta, jo kontam nav piesaistīta publiskā atslēga.
|
||||
|
|
|
@ -2542,6 +2542,7 @@ issues.review.remove_review_requests = hett %[2]s de Nakieken-Anfragen för %[1]
|
|||
pulls.delete_after_merge.head_branch.is_protected = De Kopp-Twieg, wat du lösken willst, is een schütt Twieg un kann nich lösket worden.
|
||||
pulls.delete_after_merge.head_branch.insufficient_branch = Du hest nich dat Recht, de Kopp-Twieg to lösken.
|
||||
pulls.delete_after_merge.head_branch.is_default = De Kopp-Twieg, wat du lösken willst, is de Höövd-Twieg un kann nich lösket worden.
|
||||
issues.filter_sort.relevance = Belang
|
||||
|
||||
[repo.permissions]
|
||||
code.read = <b>Lesen:</b> De Quelltext vun deesem Repositorium ankieken un klonen.
|
||||
|
|
|
@ -199,6 +199,12 @@ buttons.list.task.tooltip = Een lijst met taken toevoegen
|
|||
buttons.disable_monospace_font = Lettertype monospace uitschakelen
|
||||
buttons.indent.tooltip = Items één niveau lager plaatsen
|
||||
buttons.unindent.tooltip = Items één niveau hoger plaatsen
|
||||
buttons.new_table.tooltip = Tabel toevoegen
|
||||
table_modal.header = Tabel toevoegen
|
||||
table_modal.placeholder.header = Kop
|
||||
table_modal.placeholder.content = Inhoud
|
||||
table_modal.label.rows = Rijen
|
||||
table_modal.label.columns = Kolommen
|
||||
|
||||
[filter]
|
||||
string.asc = A - Z
|
||||
|
@ -250,7 +256,7 @@ no_admin_and_disable_registration=U kunt zelf-registratie van de gebruiker niet
|
|||
err_empty_admin_password=Het administrator-wachtwoord mag niet leeg zijn.
|
||||
err_empty_admin_email=Het e-mailadres van Het beheerder mag niet leeg zijn.
|
||||
err_admin_name_is_reserved=Gebruikersnaam van beheerder is ongeldig, gebruikersnaam is gereserveerd
|
||||
err_admin_name_pattern_not_allowed=Gebruikersnaam van beheerder is ongeldig, de gebruikersnaam is gereserveerd
|
||||
err_admin_name_pattern_not_allowed=Gebruikersnaam van beheerder is ongeldig, de gebruikersnaam komt overeen met een gereserveerd patroon
|
||||
err_admin_name_is_invalid=Gebruikersnaam van beheerder is ongeldig
|
||||
|
||||
general_title=Algemene instellingen
|
||||
|
@ -269,7 +275,7 @@ http_port=HTTP luisterpoort
|
|||
http_port_helper=Poortnummer dat zal worden gebruikt door de Forgejo webserver.
|
||||
app_url=Basis URL
|
||||
app_url_helper=Basisadres voor HTTP(S) kloon URL's en e-mailmeldingen.
|
||||
log_root_path=Log-pad
|
||||
log_root_path=Logboek-pad
|
||||
log_root_path_helper=Logboekbestanden worden geschreven naar deze map.
|
||||
|
||||
optional_title=Optionele instellingen
|
||||
|
@ -477,6 +483,7 @@ sign_up_button = Registreer nu.
|
|||
back_to_sign_in = Terug naar aanmelden
|
||||
sign_in_openid = Ga verder met OpenID
|
||||
unauthorized_credentials = Je inloggegevens zijn foutief of vervallen. Probeer opnieuw of zie %s voor meer informatie
|
||||
use_onetime_code = Gebruik een eenmalige code
|
||||
|
||||
[mail]
|
||||
view_it_on=Bekijk het op %s
|
||||
|
@ -978,7 +985,7 @@ visibility.limited=Beperkt
|
|||
visibility.private=Privé
|
||||
blocked_users = Geblokkeerde gebruikers
|
||||
uid = UID
|
||||
biography_placeholder = Vertel ons iets over uzelf! (U kunt van Markdown gebruik maken)
|
||||
biography_placeholder = Vertel anderen een beetje over uzelf! (Markdown is ondersteund)
|
||||
profile_desc = Controleer hoe uw profiel aan andere gebruikers wordt getoond. Uw primaire e-mailadres zal worden gebruikt voor notificaties, wachtwoord herstel en web-gebaseerde Git-operaties.
|
||||
update_language_not_found = Taal "%s" is niet beschikbaar.
|
||||
change_username_prompt = Opmerking: Het veranderen van uw gebruikersnaam zal ook de URL van uw account veranderen.
|
||||
|
@ -1309,7 +1316,7 @@ editor.patching=Patchen:
|
|||
editor.new_patch=Nieuwe patch
|
||||
editor.commit_message_desc=Voeg een optionele uitgebreide omschrijving toe…
|
||||
editor.signoff_desc=Voeg een Signed-off-by toe aan het einde van het commit logbericht.
|
||||
editor.commit_directly_to_this_branch=Commit direct naar de branch '<strong class="%[2]s">%[1]s</strong>'.
|
||||
editor.commit_directly_to_this_branch=Commit direct naar de branch <strong class="%[2]s">%[1]s</strong>.
|
||||
editor.create_new_branch=Maak een <strong>nieuwe branch</strong> voor deze commit en start van een pull request.
|
||||
editor.create_new_branch_np=Maak een <strong>nieuwe branch</strong> voor deze commit.
|
||||
editor.propose_file_change=Stel bestandswijziging voor
|
||||
|
@ -1648,9 +1655,9 @@ issues.review.left_comment=heeft een reactie achtergelaten
|
|||
issues.review.content.empty=Je moet een reactie achterlaten die de gewenste verandering(en) beschrijft.
|
||||
issues.review.reject=aangevraagde wijzigingen %s
|
||||
issues.review.wait=is gevraagd voor review %s
|
||||
issues.review.add_review_request=heeft een review aangevraagd van %s %s
|
||||
issues.review.remove_review_request=beoordelingsaanvraag voor %s %s verwijderd
|
||||
issues.review.remove_review_request_self=beoordeling geweigerd %s
|
||||
issues.review.add_review_request=beoordeling gevraagd van %[1]s %[2]s
|
||||
issues.review.remove_review_request=beoordelingsaanvraag voor %[1]s %[2]s verwijderd
|
||||
issues.review.remove_review_request_self=weigerde te beoordelen %s
|
||||
issues.review.pending=In behandeling
|
||||
issues.review.review=Review
|
||||
issues.review.reviewers=Beoordelaars
|
||||
|
@ -2826,6 +2833,12 @@ mirror_use_ssh.not_available = SSH-authenticatie is niet beschikbaar.
|
|||
issues.new.assign_to_me = Aan mij toewijzen
|
||||
issues.all_title = Alles
|
||||
settings.discord_icon_url.exceeds_max_length = Icoon-URL moet 2048 tekens of minder zijn
|
||||
issues.review.add_review_requests = beoordelingen gevraagd van %[1]s %[2]s
|
||||
issues.review.remove_review_requests = verwijderde beoordelingsverzoeken voor %[1]s %[2]s
|
||||
issues.review.add_remove_review_requests = vraagde beoordelingen van %[1]s en verwijderde beoordelingsverzoeken voor %[2]s %[3]s
|
||||
pulls.delete_after_merge.head_branch.is_default = De hoofdbranch die u wilt verwijderen is de standaard branch en kan niet verwijderd worden.
|
||||
pulls.delete_after_merge.head_branch.is_protected = De hoofdbranch die u wilt verwijderen is een beschermde branch en kan niet verwijderd worden.
|
||||
pulls.delete_after_merge.head_branch.insufficient_branch = Je hebt geen toestemming om de hoofdbranch te verwijderen.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -198,6 +198,12 @@ buttons.switch_to_legacy.tooltip = Zamiast tego użyj starego edytora
|
|||
buttons.disable_monospace_font = Wyłącz czcionkę monospace
|
||||
buttons.enable_monospace_font = Włącz czcionkę monospace
|
||||
buttons.indent.tooltip = Zagnieżdż elementy o jeden poziom
|
||||
buttons.new_table.tooltip = Dodaj tabelę
|
||||
table_modal.header = Dodaj tabelę
|
||||
table_modal.placeholder.header = Nagłówek
|
||||
table_modal.placeholder.content = Zawartość
|
||||
table_modal.label.rows = Wiersze
|
||||
table_modal.label.columns = Kolumny
|
||||
|
||||
[filter]
|
||||
string.asc = A - Z
|
||||
|
@ -470,6 +476,7 @@ back_to_sign_in = Wróć do logowania
|
|||
sign_in_openid = Kontynuuj z OpenID
|
||||
hint_login = Masz już konto? <a href="%s">Zaloguj się teraz!</a>
|
||||
sign_up_button = Zarejestruj się.
|
||||
use_onetime_code = Użyj kodu jednorazowego
|
||||
|
||||
[mail]
|
||||
view_it_on=Zobacz na %s
|
||||
|
@ -655,6 +662,9 @@ must_use_public_key = Podany klucz jest kluczem prywatnym. Nie przesyłaj nigdzi
|
|||
Location = Lokalizacja
|
||||
username_error_no_dots = ` może zawierać tylko znaki alfanumeryczne ("0-9", "a-z", "A-Z"), myślnik ("-") oraz podkreślenie ("_"). Nie może zaczynać się ani kończyć znakami niealfanumerycznymi, a znaki niealfanumeryczne występujące po sobie są również zabronione.`
|
||||
username_error = ` może zawierać tylko znaki alfanumeryczne ("0-9", "a-z", "A-Z"), myślnik ("-") oraz podkreślenie ("_"). Nie może zaczynać się ani kończyć znakami niealfanumerycznymi, a znaki niealfanumeryczne występujące po sobie są również zabronione.`
|
||||
still_has_org = Twoje konto jest członkiem jednej bądź wielu organizacji, musisz je najpierw opuścić.
|
||||
org_still_own_repo = Ta organizacja nadal jest właścicielem jednego lub wielu repozytoriów. Najpierw je usuń lub przenieś.
|
||||
admin_cannot_delete_self = Nie możesz usunąć siebie, gdy jesteś administratorem. Proszę najpierw usunąć swoje uprawnienia administratora.
|
||||
|
||||
|
||||
[user]
|
||||
|
@ -674,6 +684,20 @@ disabled_public_activity=Ten użytkownik wyłączył publiczne wyświetlanie jeg
|
|||
code = Kod
|
||||
block = Zablokuj
|
||||
unblock = Odblokuj
|
||||
block_user.detail = Pamiętaj, że zablokowanie użytkownika powoduje inne skutki, takie jak:
|
||||
block_user.detail_2 = Ten użytkownik nie będzie mógł wchodzić w interakcję z repozytoriami, których jesteś właścicielem, ani z problemami i komentarzami, które utworzyłeś.
|
||||
settings = Ustawienia użytkownika
|
||||
followers_one = %d obserwujących
|
||||
following_one = %d obserwowanych
|
||||
followers.title.one = Obserwujący
|
||||
followers.title.few = Obserwujący
|
||||
following.title.one = Obserwowani
|
||||
following.title.few = Obserwowani
|
||||
email_visibility.limited = Twój adres e-mail jest widoczny dla wszystkich uwierzytelnionych użytkowników
|
||||
block_user = Zablokuj użytkownika
|
||||
block_user.detail_1 = Przestaniecie się wzajemnie obserwować i nie będziecie mogli się wzajemnie obserwować.
|
||||
follow_blocked_user = Nie możesz obserwować tego użytkownika, ponieważ go zablokowałeś lub ten użytkownik zablokował Ciebie.
|
||||
show_on_map = Pokaż to mejsce na mapie
|
||||
|
||||
|
||||
[settings]
|
||||
|
@ -2869,3 +2893,4 @@ issue_kind = Wyszukaj problemy...
|
|||
pull_kind = Wyszukaj pull requesty...
|
||||
union = Unia
|
||||
regexp = RegExp
|
||||
regexp_tooltip = Interpretuj wyszukiwane hasło jako wyrażenie regularne
|
|
@ -2840,6 +2840,7 @@ issues.review.add_remove_review_requests = solicitou revisões de %[1]s e remove
|
|||
pulls.delete_after_merge.head_branch.is_default = O branch head que você quer excluir é o branch padrão e não pode ser excluído.
|
||||
pulls.delete_after_merge.head_branch.is_protected = O branch head que você quer excluir é um branch protegido e não pode ser excluído.
|
||||
pulls.delete_after_merge.head_branch.insufficient_branch = Você não tem permissão para excluir o branch head.
|
||||
issues.filter_sort.relevance = Relevância
|
||||
|
||||
[graphs]
|
||||
|
||||
|
|
|
@ -160,10 +160,10 @@ invalid_data = Неверные данные: %v
|
|||
copy_generic = Копировать в буфер обмена
|
||||
test = Проверить
|
||||
error413 = Ваша квота исчерпана.
|
||||
new_migrate.link = Выполнить миграцию
|
||||
new_migrate.link = Выполнить перенос
|
||||
new_org.link = Создать организацию
|
||||
new_repo.title = Новый репозиторий
|
||||
new_migrate.title = Новая миграция
|
||||
new_migrate.title = Новый перенос
|
||||
new_org.title = Новая организация
|
||||
new_repo.link = Создать репозиторий
|
||||
|
||||
|
@ -255,9 +255,9 @@ err_empty_db_path=Путь к базе данных SQLite3 не может бы
|
|||
no_admin_and_disable_registration=Вы не можете отключить регистрацию до создания учётной записи администратора.
|
||||
err_empty_admin_password=Пароль администратора не может быть пустым.
|
||||
err_empty_admin_email=Адрес эл. почты администратора не может быть пустым.
|
||||
err_admin_name_is_reserved=Неверное имя администратора, это имя зарезервировано
|
||||
err_admin_name_pattern_not_allowed=Неверное имя администратора, имя попадает под зарезервированный шаблон
|
||||
err_admin_name_is_invalid=Неверное имя администратора
|
||||
err_admin_name_is_reserved=Неподходящее имя администратора, оно зарезервировано
|
||||
err_admin_name_pattern_not_allowed=Неподходящее имя администратора, оно попадает под шаблон зарезервированных
|
||||
err_admin_name_is_invalid=Неподходящее имя администратора
|
||||
|
||||
general_title=Основные настройки
|
||||
app_name=Название сервера
|
||||
|
@ -745,7 +745,7 @@ uid=UID
|
|||
webauthn=Двухфакторная аутентификация (ключами безопасности)
|
||||
|
||||
public_profile=Публичный профиль
|
||||
biography_placeholder=Расскажите немного о себе! (Можно использовать Markdown)
|
||||
biography_placeholder=Кратко расскажите о себе другим! (Можно использовать Markdown)
|
||||
location_placeholder=Пусть все знают, откуда вы
|
||||
profile_desc=Как ваш профиль будет отображаться для других пользователей. Ваш основной адрес эл. почты будет использоваться для уведомлений, восстановления пароля и веб-операций с Git.
|
||||
password_username_disabled=Нелокальным пользователям запрещено изменение их имени пользователя. Для получения более подробной информации обратитесь к администратору сайта.
|
||||
|
@ -1195,7 +1195,7 @@ migrate_items_releases=Выпуски
|
|||
migrate_repo=Перенос репозитория
|
||||
migrate.clone_address=Перенос / Клонирование по URL
|
||||
migrate.clone_address_desc=HTTP/HTTPS или Git адрес существующего репозитория
|
||||
migrate.github_token_desc=Вы можете поместить один или несколько токенов, разделенных запятыми, чтобы ускорить миграцию, обходом ограничений скорости API GitHub. ПРЕДУПРЕЖДЕНИЕ: злоупотребление этой функцией может нарушить политику поставщика услуг и привести к блокировке учётной записи.
|
||||
migrate.github_token_desc=Вы можете указать один или несколько разделенных запятыми токенов, чтобы ускорить перенос за счёт обхода ограничений частоты обращений к API GitHub. ПРЕДУПРЕЖДЕНИЕ: злоупотребление этой функцией может нарушить условия предоставления услуг и привести к блокировке учётной записи.
|
||||
migrate.clone_local_path=или локальный путь на сервере
|
||||
migrate.permission_denied=У вас нет прав на импорт локальных репозиториев.
|
||||
migrate.permission_denied_blocked=Вы не можете импортировать с запрещённых хостов, пожалуйста, попросите администратора проверить настройки ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS.
|
||||
|
@ -1208,7 +1208,7 @@ migrated_from_fake=Перенесено из %[1]s
|
|||
migrate.migrate=Перенос из %s
|
||||
migrate.migrating=Перенос из <b>%s</b>...
|
||||
migrate.migrating_failed=Перенос из <b>%s</b> не удался.
|
||||
migrate.migrating_failed.error=Не удалось мигрировать: %s
|
||||
migrate.migrating_failed.error=Не удалось перенести: %s
|
||||
migrate.migrating_failed_no_addr=Перенос не удался.
|
||||
migrate.github.description=Перенесите данные с github.com или сервера GitHub Enterprise.
|
||||
migrate.git.description=Перенести только репозиторий из любого Git сервиса.
|
||||
|
@ -1226,7 +1226,7 @@ migrate.migrating_releases=Перенос выпусков
|
|||
migrate.migrating_issues=Перенос задач
|
||||
migrate.migrating_pulls=Перенос запросов на слияние
|
||||
migrate.cancel_migrating_title=Отменить перенос
|
||||
migrate.cancel_migrating_confirm=Вы хотите отменить эту миграцию?
|
||||
migrate.cancel_migrating_confirm=Вы хотите отменить перенос?
|
||||
|
||||
mirror_from=зеркало из
|
||||
forked_from=ответвлён от
|
||||
|
@ -2097,7 +2097,7 @@ settings.mirror_settings=Зеркалирование
|
|||
settings.mirror_settings.docs=Настройте свой репозиторий для автоматической синхронизации коммитов, тегов и ветвей с другим репозиторием.
|
||||
settings.mirror_settings.docs.disabled_pull_mirror.instructions=Настройте свой проект для автоматической отправки коммитов, тегов и ветвей в другой репозиторий. Pull-зеркала были отключены администратором сайта.
|
||||
settings.mirror_settings.docs.disabled_push_mirror.instructions=Настройте свой проект, чтобы автоматически получать коммиты, теги и ветви из другого репозитория.
|
||||
settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=В настоящее время это можно сделать только в меню «Новая миграция». Для получения дополнительной информации, пожалуйста, ознакомьтесь:
|
||||
settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=В настоящее время это можно сделать только через меню «Выполнить перенос». Для получения дополнительной информации, пожалуйста, ознакомьтесь:
|
||||
settings.mirror_settings.docs.disabled_push_mirror.info=Push-зеркала отключены администратором сайта.
|
||||
settings.mirror_settings.docs.no_new_mirrors=Ваш репозиторий зеркалирует изменения в другой репозиторий или из него. Пожалуйста, имейте в виду, что в данный момент невозможно создавать новые зеркала.
|
||||
settings.mirror_settings.docs.can_still_use=Хотя вы не можете изменять существующие зеркала или создавать новые, вы можете по-прежнему использовать существующее зеркало.
|
||||
|
@ -2536,7 +2536,7 @@ diff.load=Показать различия
|
|||
diff.generated=сгенерированный
|
||||
diff.vendored=предоставленный
|
||||
diff.comment.placeholder=Оставить комментарий
|
||||
diff.comment.markdown_info=Поддерживается синтаксис Markdown.
|
||||
diff.comment.markdown_info=Поддерживается форматирование с Markdown.
|
||||
diff.comment.add_single_comment=Добавить простой комментарий
|
||||
diff.comment.add_review_comment=Добавить комментарий
|
||||
diff.comment.start_review=Начать рецензию
|
||||
|
@ -2661,7 +2661,7 @@ error.csv.unexpected=Не удается отобразить этот файл,
|
|||
error.csv.invalid_field_count=Не удается отобразить этот файл, потому что он имеет неправильное количество полей в строке %d.
|
||||
mirror_address_protocol_invalid = Эта ссылка недействительна. Для зеркалирования можно использовать только расположения http(s):// и git:// .
|
||||
fork_no_valid_owners = Невозможно создать ответвление этого репозитория, т.к. здесь нет действующих владельцев.
|
||||
new_repo_helper = Репозиторий содержит все файлы проекта и историю изменений. Уже где-то есть репозиторий? <a href="%s">Выполните миграцию.</a>
|
||||
new_repo_helper = Репозиторий содержит все файлы проекта и историю изменений. Уже где-то есть репозиторий? <a href="%s">Выполните перенос.</a>
|
||||
mirror_address_url_invalid = Эта ссылка недействительна. Необходимо правильно указать все части адреса.
|
||||
issues.comment.blocked_by_user = Вы не можете комментировать под этой задачей, т.к. вы заблокированы владельцем репозитория или автором задачи.
|
||||
pulls.blocked_by_user = Невозможно создать запрос на слияние в этом репозитории, т.к. вы заблокированы его владельцем.
|
||||
|
@ -2843,6 +2843,7 @@ issues.review.add_remove_review_requests = запрошены рецензии
|
|||
pulls.delete_after_merge.head_branch.is_default = Головная ветвь, которую вы попытались удалить, является ветвью по умолчанию и не может быть удалена.
|
||||
pulls.delete_after_merge.head_branch.is_protected = Головная ветвь, которую вы попытались удалить, защищена от этого и не может быть удалена.
|
||||
pulls.delete_after_merge.head_branch.insufficient_branch = Отсутствует разрешение для удаления головной ветви.
|
||||
issues.filter_sort.relevance = По соответствию
|
||||
|
||||
[graphs]
|
||||
|
||||
|
@ -3418,7 +3419,7 @@ config.git_max_diff_lines=Макс. количество строк в файл
|
|||
config.git_max_diff_line_characters=Макс. количество символов в строке при сравнении
|
||||
config.git_max_diff_files=Макс. отображаемое количество файлов при сравнении
|
||||
config.git_gc_args=Аргументы сборщика мусора
|
||||
config.git_migrate_timeout=Ограничение времени миграций
|
||||
config.git_migrate_timeout=Ограничение времени переносов
|
||||
config.git_mirror_timeout=Ограничение времени на синхронизацию зеркала
|
||||
config.git_clone_timeout=Ограничение времени операций клонирования
|
||||
config.git_pull_timeout=Ограничение времени на получение изменений
|
||||
|
|
|
@ -108,7 +108,7 @@ enable_javascript = Цей вебсайт потребує JavaScript.
|
|||
webauthn_press_button = Натисніть кнопку на ключі безпеки…
|
||||
webauthn_use_twofa = Введіть код підтвердження з телефону
|
||||
webauthn_error = Не вдалося розпізнати ключ безпеки.
|
||||
webauthn_error_unknown = Трапилась невідома помилка. Будь ласка, повторіть спробу.
|
||||
webauthn_error_unknown = Сталася невідома помилка. Будь ласка, повторіть спробу.
|
||||
webauthn_error_unable_to_process = Сервер не зміг обробити запит.
|
||||
webauthn_error_duplicated = Запит із наданим ключем безпеки відхилено. Впевніться, що цього ключа ще не зареєстровано.
|
||||
webauthn_error_empty = Ключ слід якось назвати.
|
||||
|
@ -468,7 +468,7 @@ openid_signin_desc = Введіть ваше посилання OpenID. Напр
|
|||
invalid_password = Ваш пароль не відповідає тому, що був заданий при створенні облікового запису.
|
||||
hint_login = Вже маєте обліковий запис? <a href="%s">Увійдіть зараз!</a>
|
||||
hint_register = Потрібен обліковий запис? <a href="%s">Зареєструйтеся зараз.</a>
|
||||
sign_up_button = Зареєструватись зараз.
|
||||
sign_up_button = Зареєструватися.
|
||||
sign_up_successful = Обліковий запис успішно створений. Вітаємо!
|
||||
|
||||
[mail]
|
||||
|
@ -492,7 +492,7 @@ register_notify.text_3=Якщо цей обліковий запис було с
|
|||
|
||||
reset_password=Відновлення вашого облікового запису
|
||||
reset_password.title=%s, ви відправили запит на відновлення облікового запису
|
||||
reset_password.text=Перейдіть за цим посиланням, щоб відновити ваш обліковий запис в <b>%s</b>:
|
||||
reset_password.text=Перейдіть за цим посиланням, щоб відновити свій обліковий запис в <b>%s</b>:
|
||||
|
||||
register_success=Реєстрація успішна
|
||||
|
||||
|
@ -532,12 +532,14 @@ repo.collaborator.added.text=Вас додано в якості співавт
|
|||
primary_mail_change.subject = Ваша основна пошта була змінена
|
||||
totp_disabled.subject = TOTP було вимкнено
|
||||
totp_disabled.text_1 = Тимчасовий одноразовий пароль (TOTP) на вашому обліковому записі було вимкнено.
|
||||
password_change.subject = Ваш пароль було успішно змінено
|
||||
password_change.text_1 = Пароль до вашого облікового запису щойно був змінений.
|
||||
password_change.subject = Ваш пароль успішно змінено
|
||||
password_change.text_1 = Пароль до вашого облікового запису було щойно змінено.
|
||||
reply = чи відповісти напряму з електронної адреси
|
||||
admin.new_user.user_info = Інформація користувача
|
||||
admin.new_user.text = Будь ласка, <a href="%s">натисніть тут</a>, щоб керувати цим користувачем із панелі адміністрації.
|
||||
admin.new_user.subject = Новий користувач %s щойно ввійшов
|
||||
removed_security_key.text_1 = Ключ безпеки «%[1]s» було щойно видалено з вашого облікового запису.
|
||||
removed_security_key.subject = Ключ безпеки видалено
|
||||
|
||||
|
||||
[modal]
|
||||
|
@ -748,7 +750,7 @@ manage_ssh_keys=Керувати ключами SSH
|
|||
manage_ssh_principals=Управління SSH сертифікатами користувачів
|
||||
manage_gpg_keys=Керувати ключами GPG
|
||||
add_key=Додати ключ
|
||||
ssh_desc=Ці відкриті SSH-ключі пов'язані з вашим обліковим записом. Відповідні приватні ключі дозволяють отримати повний доступ до ваших репозиторіїв.
|
||||
ssh_desc=Ці відкриті ключі SSH повʼязані з вашим обліковим записом. Відповідні приватні ключі дозволяють отримати повний доступ до ваших репозиторіїв. Підтверджені ключі можна використати для підтвердження комітів Git, підписані з SSH.
|
||||
principal_desc=Ці настройки SSH сертифікатів вказані у вашому обліковому записі та надають повний доступ до ваших репозиторіїв.
|
||||
gpg_desc=Ці публічні ключі GPG пов'язані з вашим обліковим записом. Тримайте свої приватні ключі в безпеці, оскільки вони дозволяють здійснювати перевірку комітів.
|
||||
ssh_helper=<strong>Потрібна допомога?</strong> Дивіться гід на GitHub з <a href="%s"> генерації ключів SSH</a> або виправлення <a href="%s">типових неполадок SSH</a>.
|
||||
|
@ -917,6 +919,20 @@ webauthn_delete_key_desc = Якщо ви видалите ключ безпек
|
|||
change_password = Зміна пароля
|
||||
email_notifications.andyourown = І ваші власні сповіщення
|
||||
visibility.public_tooltip = Видимий(а) для всіх
|
||||
update_language_not_found = Мова «%s» недоступна.
|
||||
pronouns = Займенники
|
||||
pronouns_unspecified = Не вказані
|
||||
hints = Підказки
|
||||
language.title = Мова за замовчуванням
|
||||
update_hints = Оновити підказки
|
||||
update_hints_success = Підказки оновлено.
|
||||
additional_repo_units_hint = Пропонувати увімкнути додаткові розділи репозиторію
|
||||
additional_repo_units_hint_description = Показувати підказку «Увімкнути ще» для репозиторіїв, у яких увімкнено не всі доступні розділи.
|
||||
language.description = Цю мову буде збережено у вашому обліковому записі, вона використовуватиметься після того, як ви ввійдете в систему.
|
||||
language.localization_project = Допоможіть нам перекласти Forgejo вашою мовою! <a href="%s">Дізнатися більше</a>.
|
||||
permissions_list = Дозволи:
|
||||
comment_type_group_dependency = Залежність
|
||||
comment_type_group_pull_request_push = Додані коміти
|
||||
|
||||
[repo]
|
||||
owner=Власник
|
||||
|
@ -1181,7 +1197,7 @@ commits.commits=Коміти
|
|||
commits.nothing_to_compare=Ці гілки однакові.
|
||||
commits.search=Знайти коміт…
|
||||
commits.find=Пошук
|
||||
commits.search_all=Усі гілки
|
||||
commits.search_all=У всіх гілках
|
||||
commits.author=Автор
|
||||
commits.message=Повідомлення
|
||||
commits.date=Дата
|
||||
|
@ -1744,9 +1760,9 @@ settings.site=Веб-сайт
|
|||
settings.update_settings=Зберегти налаштування
|
||||
settings.branches.update_default_branch=Оновити гілку за замовчуванням
|
||||
settings.advanced_settings=Додаткові налаштування
|
||||
settings.wiki_desc=Увімкнути репозиторії Вікі
|
||||
settings.use_internal_wiki=Використовувати вбудовані Вікі
|
||||
settings.use_external_wiki=Використовувати зовнішні Вікі
|
||||
settings.wiki_desc=Увімкнути вікі репозиторію
|
||||
settings.use_internal_wiki=Використовувати вбудовану вікі
|
||||
settings.use_external_wiki=Використовувати зовнішню вікі
|
||||
settings.external_wiki_url=URL зовнішньої вікі
|
||||
settings.external_wiki_url_error=Зовнішня URL-адреса wiki не є допустимою URL-адресою.
|
||||
settings.external_wiki_url_desc=Відвідувачі будуть перенаправлені на URL-адресу, коли вони клацають по вкладці.
|
||||
|
@ -1796,7 +1812,7 @@ settings.transfer_notices_1=- Ви втратите доступ до репоз
|
|||
settings.transfer_notices_2=- Ви збережете доступ, якщо новим власником стане організація, власником якої ви є.
|
||||
settings.transfer_notices_3=- Якщо репозиторій є приватним і передається окремому користувачеві, ця дія гарантує, що користувач має хоча б дозвіл на читаня репозитарію (і при необхідності змінює права дозволів).
|
||||
settings.transfer_owner=Новий власник
|
||||
settings.transfer_perform=Здіснити перенесення
|
||||
settings.transfer_perform=Здійснити перенесення
|
||||
settings.transfer_started=`Цей репозиторій чекає підтвердження перенесення від "%s"`
|
||||
settings.transfer_succeed=Репозиторій був перенесений.
|
||||
settings.signing_settings=Параметри перевірки підпису
|
||||
|
@ -1906,7 +1922,7 @@ settings.event_pull_request_desc=Запит до злиття відкрито,
|
|||
settings.event_pull_request_assign=Призначення
|
||||
settings.event_pull_request_assign_desc=Запит про злиття призначено або скасовано.
|
||||
settings.event_pull_request_label=Мітки
|
||||
settings.event_pull_request_label_desc=Мітка запиту на злиття оновлена або очищена.
|
||||
settings.event_pull_request_label_desc=Мітки запиту на злиття оновлено або очищено.
|
||||
settings.event_pull_request_milestone=Запит на злиття призначений на етап
|
||||
settings.event_pull_request_milestone_desc=Запит на злиття призначений на етап або видалений з етапу.
|
||||
settings.event_pull_request_comment=Коментарі
|
||||
|
@ -1928,8 +1944,8 @@ settings.hook_type=Тип хука
|
|||
settings.slack_token=Токен
|
||||
settings.slack_domain=Домен
|
||||
settings.slack_channel=Канал
|
||||
settings.deploy_keys=Ключі для розгортування
|
||||
settings.add_deploy_key=Додати ключ для розгортування
|
||||
settings.deploy_keys=Ключі для розгортання
|
||||
settings.add_deploy_key=Додати ключ для розгортання
|
||||
settings.deploy_key_desc=Ключі розгортання доступні тільки для читання. Це не те ж саме що і SSH-ключі аккаунта.
|
||||
settings.is_writable=Увімкнути доступ для запису
|
||||
settings.is_writable_info=Чи може цей ключ бути використаний для виконання <strong>push</strong> в репозиторій? Ключі розгортання завжди мають доступ на pull.
|
||||
|
@ -1938,7 +1954,7 @@ settings.title=Заголовок
|
|||
settings.deploy_key_content=Зміст
|
||||
settings.key_been_used=Зміст ключа розгортання вже використовується.
|
||||
settings.key_name_used=Ключ розгортання з таким заголовком вже існує.
|
||||
settings.deploy_key_deletion=Видалити ключ для розгортування
|
||||
settings.deploy_key_deletion=Видалити ключ для розгортання
|
||||
settings.deploy_key_deletion_desc=Видалення ключа розгортки унеможливить доступ до репозиторія з його допомогою. Ви впевнені?
|
||||
settings.deploy_key_deletion_success=Ключі розгортання було видалено.
|
||||
settings.branches=Гілки
|
||||
|
@ -1949,7 +1965,7 @@ settings.protected_branch_can_push_no=Ви не можете виконуват
|
|||
settings.branch_protection=Правила захисту для гілки «<b>%s</b>»
|
||||
settings.protect_this_branch=Захистити цю гілку
|
||||
settings.protect_this_branch_desc=Запобігає видаленню гілки та обмежує виконання в ній push та злиття.
|
||||
settings.protect_disable_push=Заборонити Push
|
||||
settings.protect_disable_push=Заборонити push
|
||||
settings.protect_disable_push_desc=Для цієї гілки буде заборонено виконання push.
|
||||
settings.protect_enable_push=Дозволити push
|
||||
settings.protect_enable_push_desc=Будь-хто із правом запису зможе виконувати push для цієї гілки (за виключенням force push).
|
||||
|
@ -2273,9 +2289,86 @@ no_eol.tooltip = У цьому файлі відсутній символ зак
|
|||
settings.trust_model.committer.desc = Допустимі підписи будуть позначатися як «довірені», тільки якщо вони відповідають автору коміта, в іншому випадку вони позначатимуться як «невідповідні». Це змусить Forgejo бути автором підписаних комітів, а фактичного автора зазначати в трейлерах «Co-authored-by» і «Co-committed-by» в описі коміта. Типовий ключ Forgejo повинен відповідати користувачу в базі даних.
|
||||
pulls.clear_merge_message_hint = Очищення повідомлення про об'єднання видалить лише вміст повідомлення коміту і збереже згенеровані git-трейлери, такі як «Co-Authored-By…».
|
||||
branch.delete_branch_has_new_commits = Гілку «%s» не можна видалити, оскільки після об'єднання було додано нові коміти.
|
||||
settings.graphql_url = Посилання GraphQL
|
||||
settings.packagist_api_token = Токен API
|
||||
settings.archive.text = Архівування репозиторію зробить його доступним тільки для читання. Він буде прихований з панелі управління. Ніхто (навіть ви!) не зможе робити нові коміти, створювати задачі чи запити на злиття.
|
||||
settings.protected_branch.delete_rule = Видалити правило
|
||||
settings.branches.add_new_rule = Додати нове правило
|
||||
settings.add_key_success = Ключ для розгортання «%s» успішно додано.
|
||||
settings.update_settings_no_unit = Репозиторій повинен дозволяти хоча б якусь взаємодію.
|
||||
settings.packagist_package_url = Посилання на пакунок Packagist
|
||||
settings.transfer.modal.title = Передати новому власнику
|
||||
settings.transfer.button = Передати новому власнику
|
||||
settings.event_package = Пакунок
|
||||
settings.event_package_desc = Пакунок у репозиторії створено або видалено.
|
||||
settings.new_owner_blocked_doer = Новий власник заблокував вас.
|
||||
settings.transfer_quota_exceeded = Новий власник (%s) перевищив квоту. Репозиторій не передано.
|
||||
release.title_empty = Заголовок не може бути порожнім.
|
||||
issues.role.member_helper = Цей користувач є членом організації, що володіє цим репозиторієм.
|
||||
wiki.page_content = Вміст сторінки
|
||||
wiki.page_title = Заголовок сторінки
|
||||
pulls.close = Закрити запит на злиття
|
||||
branch.delete = Видалити гілку «%s»
|
||||
diff.comment.add_line_comment = Додати коментар до рядка
|
||||
issues.review.option.hide_outdated_comments = Приховати застарілі коментарі
|
||||
issues.num_participants_one = %d учасник
|
||||
issues.review.option.show_outdated_comments = Показати застарілі коментарі
|
||||
pulls.delete.title = Видалити цей запит на злиття?
|
||||
issues.author.tooltip.pr = Автор цього запиту на злиття.
|
||||
branch.deletion_failed = Не вдалося видалити гілку «%s».
|
||||
pulls.status_checks_show_all = Показати всі перевірки
|
||||
wiki.cancel = Скасувати
|
||||
issues.role.first_time_contributor_helper = Це перший внесок цього користувача до репозиторію.
|
||||
pulls.filter_changes_by_commit = Фільтрувати за комітом
|
||||
pulls.is_empty = Зміни з цієї гілки вже є в цільовій гілці. Коміт буде порожній.
|
||||
issues.author.tooltip.issue = Автор цієї задачі.
|
||||
pulls.made_using_agit = AGit
|
||||
activity.navbar.recent_commits = Нещодавні коміти
|
||||
branch.deletion_success = Гілку «%s» видалено.
|
||||
pulls.show_all_commits = Показати всі коміти
|
||||
pull.deleted_branch = (видалено): %s
|
||||
milestones.update_ago = Оновлено %s
|
||||
size_format = %[1]s: %[2]s; %[3]s: %[4]s
|
||||
settings.units.add_more = Увімкнути ще
|
||||
migrate.cancel_migrating_title = Скасувати перенесення
|
||||
settings.units.units = Розділи
|
||||
settings.units.overview = Огляд
|
||||
projects.create_success = Проєкт «%s» створено.
|
||||
issues.no_content = Немає опису.
|
||||
settings.mirror_settings.docs.doc_link_title = Як дзеркалювати репозиторії?
|
||||
n_commit_one = %s коміт
|
||||
n_commit_few = %s комітів
|
||||
signing.will_sign = Коміт буде підписано ключем «%s».
|
||||
signing.wont_sign.error = Під час перевірки можливості підписати коміт сталася помилка.
|
||||
commits.search_branch = У цій гілці
|
||||
ext_wiki = Зовнішня вікі
|
||||
pulls.commit_ref_at = `послався на цей запит на злиття в коміті <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
pulls.cmd_instruction_hint = Переглянути інструкції для командного рядка
|
||||
issues.max_pinned = Неможливо закріпити більше задач
|
||||
issues.unpin_comment = відкріпив %s
|
||||
issues.pin_comment = закріпив %s
|
||||
project = Проєкти
|
||||
issues.review.outdated_description = Вміст змінився з моменту написання цього коментаря
|
||||
commits.browse_further = Дивитися далі
|
||||
issues.unpin_issue = Відкріпити задачу
|
||||
n_branch_one = %s гілка
|
||||
n_branch_few = %s гілок
|
||||
executable_file = Виконуваний файл
|
||||
migrate_options_mirror_helper = Цей репозиторій буде дзеркалом
|
||||
projects.edit_success = Проєкт «%s» оновлено.
|
||||
wiki.search = Пошук по вікі
|
||||
wiki.no_search_results = Нічого не знайдено
|
||||
pulls.closed = Запит на злиття закрито
|
||||
signing.wont_sign.not_signed_in = Ви не ввійшли в систему.
|
||||
settings.wiki_globally_editable = Дозволити всім користувачам редагувати вікі
|
||||
|
||||
[graphs]
|
||||
contributors.what = внески
|
||||
component_loading_info = Це може зайняти деякий час…
|
||||
component_loading = Завантаження %s...
|
||||
component_loading_failed = Не вдалося завантажити %s
|
||||
recent_commits.what = нещодавні коміти
|
||||
component_failed_to_load = Сталася несподівана помилка.
|
||||
|
||||
[org]
|
||||
org_name_holder=Назва організації
|
||||
|
@ -2384,6 +2477,7 @@ teams.all_repositories_read_permission_desc=Ця команда надає до
|
|||
teams.all_repositories_write_permission_desc=Ця команда надає дозвіл <strong>Запис</strong> для <strong>всіх репозиторіїв</strong>: учасники можуть переглядати та виконувати push в репозиторіях.
|
||||
teams.all_repositories_admin_permission_desc=Ця команда надає дозвіл <strong>Адміністрування</strong> для <strong>всіх репозиторіїв</strong>: учасники можуть переглядати, виконувати push та додавати співробітників.
|
||||
code = Код
|
||||
open_dashboard = Відкрити панель управління
|
||||
|
||||
[admin]
|
||||
dashboard=Панель управління
|
||||
|
@ -2844,6 +2938,17 @@ monitor.last_execution_result = Результат
|
|||
repos.lfs_size = Розмір LFS
|
||||
config.allow_dots_in_usernames = Дозволити використання крапки в іменах користувачів. Не впливає на існуючі облікові записи.
|
||||
config.mailer_enable_helo = Увімкнути HELO
|
||||
users.organization_creation.description = Дозволити створення нових організацій.
|
||||
users.cannot_delete_self = Ви не можете видалити себе
|
||||
monitor.processes_count = %d процесів
|
||||
monitor.stacktrace = Траса стека
|
||||
config.send_test_mail_submit = Надіслати
|
||||
users.bot = Бот
|
||||
monitor.stats = Статистика
|
||||
users.new_success = Обліковий запис «%s» створено.
|
||||
config_settings = Налаштування
|
||||
self_check.no_problem_found = Проблем поки що не виявлено.
|
||||
config_summary = Підсумок
|
||||
|
||||
|
||||
[action]
|
||||
|
@ -2969,7 +3074,7 @@ desc = Керувати пакунками репозиторію.
|
|||
requirements = Вимоги
|
||||
dependencies = Залежності
|
||||
empty.repo = Ви опублікували пакунок, але він не показаний тут? Перейдіть до <a href="%[1]s">налаштувань пакунків</a> та привʼяжіть його до цього репозиторію.
|
||||
alpine.repository = Інформація репозиторію
|
||||
alpine.repository = Про репозиторій
|
||||
alpine.install = Аби встановити цей пакунок, запустіть команду:
|
||||
cran.install = Аби встановити пакунок, запустіть команду:
|
||||
composer.dependencies.development = Залежності розробки
|
||||
|
@ -2992,7 +3097,7 @@ container.pull = Завантажити світлину з командного
|
|||
details.repository_site = Вебсторінка репозиторію
|
||||
composer.dependencies = Залежності
|
||||
debian.install = Аби встановити пакунок, запустіть команду:
|
||||
debian.repository = Інформація репозиторію
|
||||
debian.repository = Про репозиторій
|
||||
debian.repository.distributions = Дистрибутиви
|
||||
alpine.repository.architectures = Архітектури
|
||||
arch.version.depends = Залежить
|
||||
|
@ -3006,6 +3111,12 @@ dependency.version = Версія
|
|||
container.labels = Мітки
|
||||
filter.no_result = Ваш фільтр не видав жодних результатів.
|
||||
dependency.id = ID
|
||||
rpm.repository = Про репозиторій
|
||||
rpm.repository.architectures = Архітектури
|
||||
settings.delete.error = Не вдалося видалити пакунок.
|
||||
settings.delete.success = Пакунок видалено.
|
||||
npm.dependencies = Залежності
|
||||
settings.delete = Видалити пакунок
|
||||
|
||||
[secrets]
|
||||
deletion = Видалити секрет
|
||||
|
@ -3018,6 +3129,7 @@ deletion.description = Видалення секрету є остаточним
|
|||
creation = Додати секрет
|
||||
none = Секретів ще немає.
|
||||
creation.name_placeholder = без урахування регістру, тільки літерно-цифрові символи або підкреслення, не може починатися з GITEA_ або GITHUB_
|
||||
secrets = Секрети
|
||||
|
||||
[actions]
|
||||
|
||||
|
@ -3065,6 +3177,8 @@ runs.status_no_select = Усі стани
|
|||
runs.status = Стан
|
||||
runners.task_list.status = Стан
|
||||
runners.status = Стан
|
||||
runs.no_workflows.documentation = Докладніше про Дії Forgejo читайте в <a target="_blank" rel="noopener noreferrer" href="%s">документації</a>.
|
||||
runners.reset_registration_token = Скинути токен реєстрації
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ your_starred=点赞
|
|||
your_settings=设置
|
||||
|
||||
all=所有
|
||||
sources=自建
|
||||
sources=来源
|
||||
mirrors=镜像
|
||||
collaborative=协作
|
||||
forks=派生
|
||||
|
@ -2585,7 +2585,7 @@ diff.file_suppressed_line_too_long=文件差异因一行或多行过长而隐藏
|
|||
diff.too_many_files=某些文件未显示,因为此 diff 中更改的文件太多
|
||||
diff.show_more=显示更多
|
||||
diff.load=加载差异
|
||||
diff.generated=自动生成的
|
||||
diff.generated=自动生成
|
||||
diff.vendored=Vendored
|
||||
diff.comment.add_line_comment=添加行内评论
|
||||
diff.comment.placeholder=留下评论
|
||||
|
|
896
package-lock.json
generated
896
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -17,7 +17,7 @@
|
|||
"asciinema-player": "3.8.0",
|
||||
"chart.js": "4.4.5",
|
||||
"chartjs-adapter-dayjs-4": "1.0.4",
|
||||
"chartjs-plugin-zoom": "2.0.1",
|
||||
"chartjs-plugin-zoom": "2.1.0",
|
||||
"clippie": "4.1.1",
|
||||
"css-loader": "7.0.0",
|
||||
"dayjs": "1.11.12",
|
||||
|
@ -42,7 +42,7 @@
|
|||
"pretty-ms": "9.0.0",
|
||||
"sortablejs": "1.15.3",
|
||||
"swagger-ui-dist": "5.17.14",
|
||||
"tailwindcss": "3.4.13",
|
||||
"tailwindcss": "3.4.15",
|
||||
"throttle-debounce": "5.0.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tippy.js": "6.3.7",
|
||||
|
@ -50,7 +50,7 @@
|
|||
"tributejs": "5.1.3",
|
||||
"uint8-to-base64": "0.2.0",
|
||||
"vanilla-colorful": "0.7.2",
|
||||
"vue": "3.5.12",
|
||||
"vue": "3.5.13",
|
||||
"vue-chartjs": "5.3.1",
|
||||
"vue-loader": "17.4.2",
|
||||
"vue3-calendar-heatmap": "2.0.5",
|
||||
|
|
214
poetry.lock
generated
214
poetry.lock
generated
|
@ -126,15 +126,18 @@ six = ">=1.13.0"
|
|||
|
||||
[[package]]
|
||||
name = "json5"
|
||||
version = "0.9.25"
|
||||
version = "0.9.28"
|
||||
description = "A Python implementation of the JSON5 data format."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "json5-0.9.25-py3-none-any.whl", hash = "sha256:34ed7d834b1341a86987ed52f3f76cd8ee184394906b6e22a1e0deb9ab294e8f"},
|
||||
{file = "json5-0.9.25.tar.gz", hash = "sha256:548e41b9be043f9426776f05df8635a00fe06104ea51ed24b67f908856e151ae"},
|
||||
{file = "json5-0.9.28-py3-none-any.whl", hash = "sha256:29c56f1accdd8bc2e037321237662034a7e07921e2b7223281a5ce2c46f0c4df"},
|
||||
{file = "json5-0.9.28.tar.gz", hash = "sha256:1f82f36e615bc5b42f1bbd49dbc94b12563c56408c6ffa06414ea310890e9a6e"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["build (==1.2.2.post1)", "coverage (==7.5.3)", "mypy (==1.13.0)", "pip (==24.3.1)", "pylint (==3.2.3)", "ruff (==0.7.3)", "twine (==5.1.1)", "uv (==0.5.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.12.1"
|
||||
|
@ -210,105 +213,105 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2024.9.11"
|
||||
version = "2024.11.6"
|
||||
description = "Alternative regular expression module, to replace re."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{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"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"},
|
||||
{file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -324,24 +327,24 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.2"
|
||||
version = "2.1.0"
|
||||
description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"},
|
||||
{file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"},
|
||||
{file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"},
|
||||
{file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tqdm"
|
||||
version = "4.66.6"
|
||||
version = "4.67.0"
|
||||
description = "Fast, Extensible Progress Meter"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tqdm-4.66.6-py3-none-any.whl", hash = "sha256:223e8b5359c2efc4b30555531f09e9f2f3589bcd7fdd389271191031b49b7a63"},
|
||||
{file = "tqdm-4.66.6.tar.gz", hash = "sha256:4bdd694238bef1485ce839d67967ab50af8f9272aab687c0d7702a01da0be090"},
|
||||
{file = "tqdm-4.67.0-py3-none-any.whl", hash = "sha256:0cd8af9d56911acab92182e88d763100d4788bdf421d251616040cc4d44863be"},
|
||||
{file = "tqdm-4.67.0.tar.gz", hash = "sha256:fe5a6f95e6fe0b9755e9469b77b9c3cf850048224ecaa8293d7d2d31f97d869a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -349,6 +352,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
|||
|
||||
[package.extras]
|
||||
dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"]
|
||||
discord = ["requests"]
|
||||
notebook = ["ipywidgets (>=6)"]
|
||||
slack = ["slack-sdk"]
|
||||
telegram = ["requests"]
|
||||
|
|
|
@ -7,6 +7,7 @@ label_bug=bug
|
|||
label_feature=feature
|
||||
label_ui=forgejo/ui
|
||||
label_breaking=breaking
|
||||
label_security=security
|
||||
label_localization=forgejo/i18n
|
||||
|
||||
payload=$(mktemp)
|
||||
|
@ -17,50 +18,71 @@ function test_main() {
|
|||
set -ex
|
||||
PS4='${BASH_SOURCE[0]}:$LINENO: ${FUNCNAME[0]}: '
|
||||
|
||||
test_payload_labels $label_worth $label_breaking $label_security $label_bug
|
||||
test "$(categorize)" = 'AA Breaking security bug fixes'
|
||||
|
||||
test_payload_labels $label_worth $label_security $label_bug
|
||||
test "$(categorize)" = 'AB Security bug fixes'
|
||||
|
||||
test_payload_labels $label_worth $label_breaking $label_security $label_feature
|
||||
test "$(categorize)" = 'AC Breaking security features'
|
||||
|
||||
test_payload_labels $label_worth $label_security $label_feature
|
||||
test "$(categorize)" = 'AD Security features'
|
||||
|
||||
test_payload_labels $label_worth $label_security
|
||||
test "$(categorize)" = 'ZA Security changes without a feature or bug label'
|
||||
|
||||
test_payload_labels $label_worth $label_breaking $label_feature
|
||||
test "$(categorize)" = 'AA Breaking features'
|
||||
test "$(categorize)" = 'BA Breaking features'
|
||||
|
||||
test_payload_labels $label_worth $label_breaking $label_bug
|
||||
test "$(categorize)" = 'AB Breaking bug fixes'
|
||||
test "$(categorize)" = 'BB Breaking bug fixes'
|
||||
|
||||
test_payload_labels $label_worth $label_breaking
|
||||
test "$(categorize)" = 'ZC Breaking changes without a feature or bug label'
|
||||
test "$(categorize)" = 'ZB Breaking changes without a feature or bug label'
|
||||
|
||||
test_payload_labels $label_worth $label_ui $label_feature
|
||||
test "$(categorize)" = 'BA User Interface features'
|
||||
test "$(categorize)" = 'CA User Interface features'
|
||||
|
||||
test_payload_labels $label_worth $label_ui $label_bug
|
||||
test "$(categorize)" = 'BB User Interface bug fixes'
|
||||
test "$(categorize)" = 'CB User Interface bug fixes'
|
||||
|
||||
test_payload_labels $label_worth $label_ui
|
||||
test "$(categorize)" = 'ZD User Interface changes without a feature or bug label'
|
||||
|
||||
test_payload_labels $label_worth $label_feature
|
||||
test "$(categorize)" = 'CA Features'
|
||||
|
||||
test_payload_labels $label_worth $label_bug
|
||||
test "$(categorize)" = 'CB Bug fixes'
|
||||
test "$(categorize)" = 'ZC User Interface changes without a feature or bug label'
|
||||
|
||||
test_payload_labels $label_worth $label_localization
|
||||
test "$(categorize)" = 'DA Localization'
|
||||
|
||||
test_payload_labels $label_worth $label_feature
|
||||
test "$(categorize)" = 'EA Features'
|
||||
|
||||
test_payload_labels $label_worth $label_bug
|
||||
test "$(categorize)" = 'EB Bug fixes'
|
||||
|
||||
test_payload_labels $label_worth
|
||||
test "$(categorize)" = 'ZE Other changes without a feature or bug label'
|
||||
|
||||
test_payload_labels
|
||||
test "$(categorize)" = 'ZF Included for completeness but not worth a release note'
|
||||
|
||||
test_payload_draft "fix(security)!: breaking security bug fix"
|
||||
test "$(categorize)" = 'AA Breaking security bug fixes'
|
||||
|
||||
test_payload_draft "fix(security): security bug fix"
|
||||
test "$(categorize)" = 'AB Security bug fixes'
|
||||
|
||||
test_payload_draft "feat!: breaking feature"
|
||||
test "$(categorize)" = 'AA Breaking features'
|
||||
test "$(categorize)" = 'BA Breaking features'
|
||||
|
||||
test_payload_draft "fix!: breaking bug fix"
|
||||
test "$(categorize)" = 'AB Breaking bug fixes'
|
||||
test "$(categorize)" = 'BB Breaking bug fixes'
|
||||
|
||||
test_payload_draft "feat: feature"
|
||||
test "$(categorize)" = 'CA Features'
|
||||
test "$(categorize)" = 'EA Features'
|
||||
|
||||
test_payload_draft "fix: bug fix"
|
||||
test "$(categorize)" = 'CB Bug fixes'
|
||||
test "$(categorize)" = 'EB Bug fixes'
|
||||
|
||||
test_payload_draft "something with no prefix"
|
||||
test "$(categorize)" = 'ZE Other changes without a feature or bug label'
|
||||
|
@ -109,6 +131,7 @@ function categorize() {
|
|||
is_feature=false
|
||||
is_localization=false
|
||||
is_breaking=false
|
||||
is_security=false
|
||||
|
||||
#
|
||||
# first try to figure out the category from the labels
|
||||
|
@ -125,6 +148,12 @@ function categorize() {
|
|||
;;
|
||||
esac
|
||||
|
||||
case "$labels" in
|
||||
*$label_security*)
|
||||
is_security=true
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$labels" in
|
||||
*$label_breaking*)
|
||||
is_breaking=true
|
||||
|
@ -143,6 +172,15 @@ function categorize() {
|
|||
if ! $is_bug && ! $is_feature; then
|
||||
draft="$(jq --raw-output .Draft <$payload)"
|
||||
case "$draft" in
|
||||
fix\(security\)!:*)
|
||||
is_bug=true
|
||||
is_breaking=true
|
||||
is_security=true
|
||||
;;
|
||||
fix\(security\):*)
|
||||
is_bug=true
|
||||
is_security=true
|
||||
;;
|
||||
fix!:*)
|
||||
is_bug=true
|
||||
is_breaking=true
|
||||
|
@ -171,29 +209,45 @@ function categorize() {
|
|||
fi
|
||||
fi
|
||||
|
||||
if $is_breaking; then
|
||||
if $is_feature; then
|
||||
echo -n AA Breaking features
|
||||
elif $is_bug; then
|
||||
echo -n AB Breaking bug fixes
|
||||
if $is_security; then
|
||||
if $is_bug; then
|
||||
if $is_breaking; then
|
||||
echo -n AA Breaking security bug fixes
|
||||
else
|
||||
echo -n AB Security bug fixes
|
||||
fi
|
||||
elif $is_feature; then
|
||||
if $is_breaking; then
|
||||
echo -n AC Breaking security features
|
||||
else
|
||||
echo -n AD Security features
|
||||
fi
|
||||
else
|
||||
echo -n ZC Breaking changes without a feature or bug label
|
||||
echo -n ZA Security changes without a feature or bug label
|
||||
fi
|
||||
elif $is_breaking; then
|
||||
if $is_feature; then
|
||||
echo -n BA Breaking features
|
||||
elif $is_bug; then
|
||||
echo -n BB Breaking bug fixes
|
||||
else
|
||||
echo -n ZB Breaking changes without a feature or bug label
|
||||
fi
|
||||
elif $is_ui; then
|
||||
if $is_feature; then
|
||||
echo -n BA User Interface features
|
||||
echo -n CA User Interface features
|
||||
elif $is_bug; then
|
||||
echo -n BB User Interface bug fixes
|
||||
echo -n CB User Interface bug fixes
|
||||
else
|
||||
echo -n ZD User Interface changes without a feature or bug label
|
||||
echo -n ZC User Interface changes without a feature or bug label
|
||||
fi
|
||||
elif $is_localization; then
|
||||
echo -n DA Localization
|
||||
else
|
||||
if $is_feature; then
|
||||
echo -n CA Features
|
||||
echo -n EA Features
|
||||
elif $is_bug; then
|
||||
echo -n CB Bug fixes
|
||||
echo -n EB Bug fixes
|
||||
else
|
||||
echo -n ZE Other changes without a feature or bug label
|
||||
fi
|
||||
|
|
8
release-notes/5974.md
Normal file
8
release-notes/5974.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/1ce33aa38d1d258d14523ff2c7c2dbf339f22b74) it was possible to use a token sent via email for secondary email validation to reset the password instead. In other words, a token sent for a given action (registration, password reset or secondary email validation) could be used to perform a different action. It is no longer possible to use a token for an action that is different from its original purpose.
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/061abe60045212acf8c3f5c49b5cc758b4cbcde9) a fork of a public repository would show in the list of forks, even if its owner was not a public user or organization. Such a fork is now hidden from the list of forks of the public repository.
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/3e3ef76808100cb1c853378733d0f6a910324ac6) the members of an organization team with read access to a repository (e.g. to read issues) but no read access to the code could read the RSS or atom feeds which include the commit activity. Reading the RSS or atom feeds is now denied unless the team has read permissions on the code.
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/9508aa7713632ed40124a933d91d5766cf2369c2) the tokens used when [replying by email to issues or pull requests](https://forgejo.org/docs/v9.0/user/incoming/) were weaker than the [rfc2104 recommendations](https://datatracker.ietf.org/doc/html/rfc2104#section-5). The tokens are now truncated to 128 bits instead of 80 bits. It is no longer possible to reply to emails sent before the upgrade because the weaker tokens are invalid.
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/786dfc7fb81ee76d4292ca5fcb33e6ea7bdccc29) a registered user could modify the update frequency of any push mirror (e.g. every 4h instead of every 8h). They are now only able to do that if they have administrative permissions on the repository.
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/e6bbecb02d47730d3cc630d419fe27ef2fb5cb39) it was possible to use basic authorization (i.e. user:password) for requests to the API even when security keys were enrolled for a user. It is no longer possible, an application token must be used instead.
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/7067cc7da4f144cc8a2fd2ae6e5307e0465ace7f) some markup sanitation rules were not as strong as they could be (e.g. allowing `emoji somethingelse` as well as `emoji`). The rules are now stricter and do not allow for such cases.
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/b70196653f9d7d3b9d4e72d114e5cc6f472988c4) when Forgejo is configured to enable instance wide search (e.g. with [bleve](https://blevesearch.com/)), results found in the repositories of private or limited users were displayed to anonymous visitors. The results found in private or limited organizations were not displayed. The search results found in the repositories of private or limited user are no longer displayed to anonymous visitors.
|
1
release-notes/5988.md
Normal file
1
release-notes/5988.md
Normal file
|
@ -0,0 +1 @@
|
|||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/fc26becba4b08877a726f2e7e453992310245fe5) when a tag was removed and a release existed for that tag, it would be broken. The release is no longer broken the tag can be added again.
|
|
@ -1316,7 +1316,11 @@ func Routes() *web.Route {
|
|||
m.Get("/trees/{sha}", repo.GetTree)
|
||||
m.Get("/blobs/{sha}", repo.GetBlob)
|
||||
m.Get("/tags/{sha}", repo.GetAnnotatedTag)
|
||||
m.Get("/notes/{sha}", repo.GetNote)
|
||||
m.Group("/notes/{sha}", func() {
|
||||
m.Get("", repo.GetNote)
|
||||
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), bind(api.NoteOptions{}), repo.SetNote)
|
||||
m.Delete("", reqToken(), reqRepoWriter(unit.TypeCode), repo.RemoveNote)
|
||||
})
|
||||
}, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode))
|
||||
m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), mustNotBeArchived, context.EnforceQuotaAPI(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetRepo), repo.ApplyDiffPatch)
|
||||
m.Group("/contents", func() {
|
||||
|
|
|
@ -41,7 +41,16 @@ func Markup(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
common.RenderMarkup(ctx.Base, ctx.Repo, form.Mode, form.Text, form.Context, form.FilePath, form.Wiki)
|
||||
re := common.Renderer{
|
||||
Mode: form.Mode,
|
||||
Text: form.Text,
|
||||
URLPrefix: form.Context,
|
||||
FilePath: form.FilePath,
|
||||
BranchPath: form.BranchPath,
|
||||
IsWiki: form.Wiki,
|
||||
}
|
||||
|
||||
re.RenderMarkup(ctx.Base, ctx.Repo)
|
||||
}
|
||||
|
||||
// Markdown render markdown document to HTML
|
||||
|
@ -76,7 +85,14 @@ func Markdown(ctx *context.APIContext) {
|
|||
mode = form.Mode
|
||||
}
|
||||
|
||||
common.RenderMarkup(ctx.Base, ctx.Repo, mode, form.Text, form.Context, "", form.Wiki)
|
||||
re := common.Renderer{
|
||||
Mode: mode,
|
||||
Text: form.Text,
|
||||
URLPrefix: form.Context,
|
||||
IsWiki: form.Wiki,
|
||||
}
|
||||
|
||||
re.RenderMarkup(ctx.Base, ctx.Repo)
|
||||
}
|
||||
|
||||
// MarkdownRaw render raw markdown HTML
|
||||
|
|
|
@ -56,7 +56,7 @@ func ListForks(ctx *context.APIContext) {
|
|||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
forks, err := repo_model.GetForks(ctx, ctx.Repo.Repository, utils.GetListOptions(ctx))
|
||||
forks, total, err := repo_model.GetForks(ctx, ctx.Repo.Repository, ctx.Doer, utils.GetListOptions(ctx))
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetForks", err)
|
||||
return
|
||||
|
@ -71,7 +71,7 @@ func ListForks(ctx *context.APIContext) {
|
|||
apiForks[i] = convert.ToRepo(ctx, fork, permission)
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(int64(ctx.Repo.Repository.NumForks))
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, apiForks)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
|
@ -102,3 +103,107 @@ func getNote(ctx *context.APIContext, identifier string) {
|
|||
apiNote := api.Note{Message: string(note.Message), Commit: cmt}
|
||||
ctx.JSON(http.StatusOK, apiNote)
|
||||
}
|
||||
|
||||
// SetNote Sets a note corresponding to a single commit from a repository
|
||||
func SetNote(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/git/notes/{sha} repository repoSetNote
|
||||
// ---
|
||||
// summary: Set a note corresponding to a single commit from a repository
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: sha
|
||||
// in: path
|
||||
// description: a git ref or commit sha
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/NoteOptions"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Note"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
sha := ctx.Params(":sha")
|
||||
if !git.IsValidRefPattern(sha) {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha))
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*api.NoteOptions)
|
||||
|
||||
err := git.SetNote(ctx, ctx.Repo.GitRepo, sha, form.Message, ctx.Doer.Name, ctx.Doer.GetEmail())
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.NotFound(sha)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "SetNote", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
getNote(ctx, sha)
|
||||
}
|
||||
|
||||
// RemoveNote Removes a note corresponding to a single commit from a repository
|
||||
func RemoveNote(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /repos/{owner}/{repo}/git/notes/{sha} repository repoRemoveNote
|
||||
// ---
|
||||
// summary: Removes a note corresponding to a single commit from a repository
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: sha
|
||||
// in: path
|
||||
// description: a git ref or commit sha
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
sha := ctx.Params(":sha")
|
||||
if !git.IsValidRefPattern(sha) {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha))
|
||||
return
|
||||
}
|
||||
|
||||
err := git.RemoveNote(ctx, ctx.Repo.GitRepo, sha)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.NotFound(sha)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "RemoveNote", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
|
|
@ -1110,10 +1110,19 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
|||
|
||||
ctx.Repo.PullRequest.SameRepo = isSameRepo
|
||||
log.Trace("Repo path: %q, base branch: %q, head branch: %q", ctx.Repo.GitRepo.Path, baseBranch, headBranch)
|
||||
|
||||
// Check if base branch is valid.
|
||||
if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) && !ctx.Repo.GitRepo.IsTagExist(baseBranch) {
|
||||
ctx.NotFound("BaseNotExist")
|
||||
return nil, nil, nil, "", ""
|
||||
baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(baseBranch)
|
||||
baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(baseBranch)
|
||||
baseIsTag := ctx.Repo.GitRepo.IsTagExist(baseBranch)
|
||||
if !baseIsCommit && !baseIsBranch && !baseIsTag {
|
||||
// Check for short SHA usage
|
||||
if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(baseBranch); baseCommit != nil {
|
||||
baseBranch = baseCommit.ID.String()
|
||||
} else {
|
||||
ctx.NotFound("BaseNotExist")
|
||||
return nil, nil, nil, "", ""
|
||||
}
|
||||
}
|
||||
|
||||
// Check if current user has fork of repository or in the same repository.
|
||||
|
@ -1186,13 +1195,34 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
|||
}
|
||||
|
||||
// Check if head branch is valid.
|
||||
if !headGitRepo.IsBranchExist(headBranch) && !headGitRepo.IsTagExist(headBranch) {
|
||||
headGitRepo.Close()
|
||||
ctx.NotFound()
|
||||
return nil, nil, nil, "", ""
|
||||
headIsCommit := headGitRepo.IsBranchExist(headBranch)
|
||||
headIsBranch := headGitRepo.IsTagExist(headBranch)
|
||||
headIsTag := headGitRepo.IsCommitExist(baseBranch)
|
||||
if !headIsCommit && !headIsBranch && !headIsTag {
|
||||
// Check if headBranch is short sha commit hash
|
||||
if headCommit, _ := headGitRepo.GetCommit(headBranch); headCommit != nil {
|
||||
headBranch = headCommit.ID.String()
|
||||
} else {
|
||||
headGitRepo.Close()
|
||||
ctx.NotFound("IsRefExist", nil)
|
||||
return nil, nil, nil, "", ""
|
||||
}
|
||||
}
|
||||
|
||||
compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch, false, false)
|
||||
baseBranchRef := baseBranch
|
||||
if baseIsBranch {
|
||||
baseBranchRef = git.BranchPrefix + baseBranch
|
||||
} else if baseIsTag {
|
||||
baseBranchRef = git.TagPrefix + baseBranch
|
||||
}
|
||||
headBranchRef := headBranch
|
||||
if headIsBranch {
|
||||
headBranchRef = headBranch
|
||||
} else if headIsTag {
|
||||
headBranchRef = headBranch
|
||||
}
|
||||
|
||||
compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranchRef, headBranchRef, false, false)
|
||||
if err != nil {
|
||||
headGitRepo.Close()
|
||||
ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err)
|
||||
|
|
|
@ -231,4 +231,7 @@ type swaggerParameterBodies struct {
|
|||
|
||||
// in:body
|
||||
SetUserQuotaGroupsOptions api.SetUserQuotaGroupsOptions
|
||||
|
||||
// in:body
|
||||
NoteOptions api.NoteOptions
|
||||
}
|
||||
|
|
|
@ -18,26 +18,31 @@ import (
|
|||
"mvdan.cc/xurls/v2"
|
||||
)
|
||||
|
||||
type Renderer struct {
|
||||
Mode, Text, URLPrefix, FilePath, BranchPath string
|
||||
IsWiki bool
|
||||
}
|
||||
|
||||
// RenderMarkup renders markup text for the /markup and /markdown endpoints
|
||||
func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPrefix, filePath string, wiki bool) {
|
||||
func (re *Renderer) RenderMarkup(ctx *context.Base, repo *context.Repository) {
|
||||
var markupType string
|
||||
relativePath := ""
|
||||
|
||||
if len(text) == 0 {
|
||||
if len(re.Text) == 0 {
|
||||
_, _ = ctx.Write([]byte(""))
|
||||
return
|
||||
}
|
||||
|
||||
switch mode {
|
||||
switch re.Mode {
|
||||
case "markdown":
|
||||
// Raw markdown
|
||||
if err := markdown.RenderRaw(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
AbsolutePrefix: true,
|
||||
Base: urlPrefix,
|
||||
Base: re.URLPrefix,
|
||||
},
|
||||
}, strings.NewReader(text), ctx.Resp); err != nil {
|
||||
}, strings.NewReader(re.Text), ctx.Resp); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return
|
||||
|
@ -50,30 +55,30 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
|
|||
case "file":
|
||||
// File as document based on file extension
|
||||
markupType = ""
|
||||
relativePath = filePath
|
||||
relativePath = re.FilePath
|
||||
default:
|
||||
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
|
||||
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", re.Mode))
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(setting.AppSubURL+"/", urlPrefix) {
|
||||
if !strings.HasPrefix(setting.AppSubURL+"/", re.URLPrefix) {
|
||||
// check if urlPrefix is already set to a URL
|
||||
linkRegex, _ := xurls.StrictMatchingScheme("https?://")
|
||||
m := linkRegex.FindStringIndex(urlPrefix)
|
||||
m := linkRegex.FindStringIndex(re.URLPrefix)
|
||||
if m == nil {
|
||||
urlPrefix = util.URLJoin(setting.AppURL, urlPrefix)
|
||||
re.URLPrefix = util.URLJoin(setting.AppURL, re.URLPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
meta := map[string]string{}
|
||||
if repo != nil && repo.Repository != nil {
|
||||
if mode == "comment" {
|
||||
if re.Mode == "comment" {
|
||||
meta = repo.Repository.ComposeMetas(ctx)
|
||||
} else {
|
||||
meta = repo.Repository.ComposeDocumentMetas(ctx)
|
||||
}
|
||||
}
|
||||
if mode != "comment" {
|
||||
if re.Mode != "comment" {
|
||||
meta["mode"] = "document"
|
||||
}
|
||||
|
||||
|
@ -81,13 +86,14 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
|
|||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
AbsolutePrefix: true,
|
||||
Base: urlPrefix,
|
||||
Base: re.URLPrefix,
|
||||
BranchPath: re.BranchPath,
|
||||
},
|
||||
Metas: meta,
|
||||
IsWiki: wiki,
|
||||
IsWiki: re.IsWiki,
|
||||
Type: markupType,
|
||||
RelativePath: relativePath,
|
||||
}, strings.NewReader(text), ctx.Resp); err != nil {
|
||||
}, strings.NewReader(re.Text), ctx.Resp); err != nil {
|
||||
if markup.IsErrUnsupportedRenderExtension(err) {
|
||||
ctx.Error(http.StatusUnprocessableEntity, err.Error())
|
||||
} else {
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
@ -63,38 +61,11 @@ func autoSignIn(ctx *context.Context) (bool, error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
lookupKey, validator, found := strings.Cut(authCookie, ":")
|
||||
if !found {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
authToken, err := auth.FindAuthToken(ctx, lookupKey)
|
||||
u, err := user_model.VerifyUserAuthorizationToken(ctx, authCookie, auth.LongTermAuthorization, false)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
return false, fmt.Errorf("VerifyUserAuthorizationToken: %w", err)
|
||||
}
|
||||
|
||||
if authToken.IsExpired() {
|
||||
err = auth.DeleteAuthToken(ctx, authToken)
|
||||
return false, err
|
||||
}
|
||||
|
||||
rawValidator, err := hex.DecodeString(validator)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if subtle.ConstantTimeCompare([]byte(authToken.HashedValidator), []byte(auth.HashValidator(rawValidator))) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
u, err := user_model.GetUserByID(ctx, authToken.UID)
|
||||
if err != nil {
|
||||
if !user_model.IsErrUserNotExist(err) {
|
||||
return false, fmt.Errorf("GetUserByID: %w", err)
|
||||
}
|
||||
if u == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
|
@ -633,7 +604,10 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
|
|||
return false
|
||||
}
|
||||
|
||||
mailer.SendActivateAccountMail(ctx.Locale, u)
|
||||
if err := mailer.SendActivateAccountMail(ctx, u); err != nil {
|
||||
ctx.ServerError("SendActivateAccountMail", err)
|
||||
return false
|
||||
}
|
||||
|
||||
ctx.Data["IsSendRegisterMail"] = true
|
||||
ctx.Data["Email"] = u.Email
|
||||
|
@ -674,7 +648,10 @@ func Activate(ctx *context.Context) {
|
|||
ctx.Data["ResendLimited"] = true
|
||||
} else {
|
||||
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
|
||||
mailer.SendActivateAccountMail(ctx.Locale, ctx.Doer)
|
||||
if err := mailer.SendActivateAccountMail(ctx, ctx.Doer); err != nil {
|
||||
ctx.ServerError("SendActivateAccountMail", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ctx.Cache.Put(cacheKey+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
|
||||
log.Error("Set cache(MailResendLimit) fail: %v", err)
|
||||
|
@ -687,7 +664,12 @@ func Activate(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
user := user_model.VerifyUserActiveCode(ctx, code)
|
||||
user, err := user_model.VerifyUserAuthorizationToken(ctx, code, auth.UserActivation, false)
|
||||
if err != nil {
|
||||
ctx.ServerError("VerifyUserAuthorizationToken", err)
|
||||
return
|
||||
}
|
||||
|
||||
// if code is wrong
|
||||
if user == nil {
|
||||
ctx.Data["IsCodeInvalid"] = true
|
||||
|
@ -751,7 +733,12 @@ func ActivatePost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
user := user_model.VerifyUserActiveCode(ctx, code)
|
||||
user, err := user_model.VerifyUserAuthorizationToken(ctx, code, auth.UserActivation, true)
|
||||
if err != nil {
|
||||
ctx.ServerError("VerifyUserAuthorizationToken", err)
|
||||
return
|
||||
}
|
||||
|
||||
// if code is wrong
|
||||
if user == nil {
|
||||
ctx.Data["IsCodeInvalid"] = true
|
||||
|
@ -835,23 +822,32 @@ func ActivateEmail(ctx *context.Context) {
|
|||
code := ctx.FormString("code")
|
||||
emailStr := ctx.FormString("email")
|
||||
|
||||
// Verify code.
|
||||
if email := user_model.VerifyActiveEmailCode(ctx, code, emailStr); email != nil {
|
||||
if err := user_model.ActivateEmail(ctx, email); err != nil {
|
||||
ctx.ServerError("ActivateEmail", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Email activated: %s", email.Email)
|
||||
ctx.Flash.Success(ctx.Tr("settings.add_email_success"))
|
||||
|
||||
if u, err := user_model.GetUserByID(ctx, email.UID); err != nil {
|
||||
log.Warn("GetUserByID: %d", email.UID)
|
||||
} else {
|
||||
// Allow user to validate more emails
|
||||
_ = ctx.Cache.Delete("MailResendLimit_" + u.LowerName)
|
||||
}
|
||||
u, err := user_model.VerifyUserAuthorizationToken(ctx, code, auth.EmailActivation(emailStr), true)
|
||||
if err != nil {
|
||||
ctx.ServerError("VerifyUserAuthorizationToken", err)
|
||||
return
|
||||
}
|
||||
if u == nil {
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
|
||||
return
|
||||
}
|
||||
|
||||
email, err := user_model.GetEmailAddressOfUser(ctx, emailStr, u.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetEmailAddressOfUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := user_model.ActivateEmail(ctx, email); err != nil {
|
||||
ctx.ServerError("ActivateEmail", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Email activated: %s", email.Email)
|
||||
ctx.Flash.Success(ctx.Tr("settings.add_email_success"))
|
||||
|
||||
// Allow user to validate more emails
|
||||
_ = ctx.Cache.Delete("MailResendLimit_" + u.LowerName)
|
||||
|
||||
// FIXME: e-mail verification does not require the user to be logged in,
|
||||
// so this could be redirecting to the login page.
|
||||
|
|
|
@ -86,7 +86,10 @@ func ForgotPasswdPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
mailer.SendResetPasswordMail(u)
|
||||
if err := mailer.SendResetPasswordMail(ctx, u); err != nil {
|
||||
ctx.ServerError("SendResetPasswordMail", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
|
||||
log.Error("Set cache(MailResendLimit) fail: %v", err)
|
||||
|
@ -97,7 +100,7 @@ func ForgotPasswdPost(ctx *context.Context) {
|
|||
ctx.HTML(http.StatusOK, tplForgotPassword)
|
||||
}
|
||||
|
||||
func commonResetPassword(ctx *context.Context) (*user_model.User, *auth.TwoFactor) {
|
||||
func commonResetPassword(ctx *context.Context, shouldDeleteToken bool) (*user_model.User, *auth.TwoFactor) {
|
||||
code := ctx.FormString("code")
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("auth.reset_password")
|
||||
|
@ -113,7 +116,12 @@ func commonResetPassword(ctx *context.Context) (*user_model.User, *auth.TwoFacto
|
|||
}
|
||||
|
||||
// Fail early, don't frustrate the user
|
||||
u := user_model.VerifyUserActiveCode(ctx, code)
|
||||
u, err := user_model.VerifyUserAuthorizationToken(ctx, code, auth.PasswordReset, shouldDeleteToken)
|
||||
if err != nil {
|
||||
ctx.ServerError("VerifyUserAuthorizationToken", err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if u == nil {
|
||||
ctx.Flash.Error(ctx.Tr("auth.invalid_code_forgot_password", fmt.Sprintf("%s/user/forgot_password", setting.AppSubURL)), true)
|
||||
return nil, nil
|
||||
|
@ -145,7 +153,7 @@ func commonResetPassword(ctx *context.Context) (*user_model.User, *auth.TwoFacto
|
|||
func ResetPasswd(ctx *context.Context) {
|
||||
ctx.Data["IsResetForm"] = true
|
||||
|
||||
commonResetPassword(ctx)
|
||||
commonResetPassword(ctx, false)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
@ -155,7 +163,7 @@ func ResetPasswd(ctx *context.Context) {
|
|||
|
||||
// ResetPasswdPost response from account recovery request
|
||||
func ResetPasswdPost(ctx *context.Context) {
|
||||
u, twofa := commonResetPassword(ctx)
|
||||
u, twofa := commonResetPassword(ctx, true)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -14,5 +14,15 @@ import (
|
|||
// Markup render markup document to HTML
|
||||
func Markup(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*api.MarkupOption)
|
||||
common.RenderMarkup(ctx.Base, ctx.Repo, form.Mode, form.Text, form.Context, form.FilePath, form.Wiki)
|
||||
|
||||
re := common.Renderer{
|
||||
Mode: form.Mode,
|
||||
Text: form.Text,
|
||||
URLPrefix: form.Context,
|
||||
FilePath: form.FilePath,
|
||||
BranchPath: form.BranchPath,
|
||||
IsWiki: form.Wiki,
|
||||
}
|
||||
|
||||
re.RenderMarkup(ctx.Base, ctx.Repo)
|
||||
}
|
||||
|
|
|
@ -27,7 +27,9 @@ import (
|
|||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
"code.gitea.io/gitea/services/gitdiff"
|
||||
git_service "code.gitea.io/gitea/services/repository"
|
||||
)
|
||||
|
@ -467,3 +469,29 @@ func processGitCommits(ctx *context.Context, gitCommits []*git.Commit) []*git_mo
|
|||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
func SetCommitNotes(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.CommitNotesForm)
|
||||
|
||||
commitID := ctx.Params(":sha")
|
||||
|
||||
err := git.SetNote(ctx, ctx.Repo.GitRepo, commitID, form.Notes, ctx.Doer.Name, ctx.Doer.GetEmail())
|
||||
if err != nil {
|
||||
ctx.ServerError("SetNote", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Redirect(fmt.Sprintf("%s/commit/%s", ctx.Repo.Repository.HTMLURL(), commitID))
|
||||
}
|
||||
|
||||
func RemoveCommitNotes(ctx *context.Context) {
|
||||
commitID := ctx.Params(":sha")
|
||||
|
||||
err := git.RemoveNote(ctx, ctx.Repo.GitRepo, commitID)
|
||||
if err != nil {
|
||||
ctx.ServerError("RemoveNotes", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Redirect(fmt.Sprintf("%s/commit/%s", ctx.Repo.Repository.HTMLURL(), commitID))
|
||||
}
|
||||
|
|
|
@ -211,6 +211,7 @@ func editFile(ctx *context.Context, isNewFile bool) {
|
|||
ctx.Data["TreeNames"] = treeNames
|
||||
ctx.Data["TreePaths"] = treePaths
|
||||
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
||||
ctx.Data["BranchPath"] = ctx.Repo.BranchNameSubURL()
|
||||
ctx.Data["commit_summary"] = ""
|
||||
ctx.Data["commit_message"] = ""
|
||||
if canCommit {
|
||||
|
|
|
@ -457,16 +457,16 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
|||
ctx.Data["OpenCount"] = issueStats.OpenCount
|
||||
ctx.Data["ClosedCount"] = issueStats.ClosedCount
|
||||
ctx.Data["AllCount"] = issueStats.AllCount
|
||||
linkStr := "%s?q=%s&type=%s&sort=%s&state=%s&labels=%s&milestone=%d&project=%d&assignee=%d&poster=%d&archived=%t"
|
||||
ctx.Data["AllStatesLink"] = fmt.Sprintf(linkStr, ctx.Link,
|
||||
linkStr := "?q=%s&type=%s&sort=%s&state=%s&labels=%s&milestone=%d&project=%d&assignee=%d&poster=%d&fuzzy=%t&archived=%t"
|
||||
ctx.Data["AllStatesLink"] = fmt.Sprintf(linkStr,
|
||||
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "all", url.QueryEscape(selectLabels),
|
||||
milestoneID, projectID, assigneeID, posterID, archived)
|
||||
ctx.Data["OpenLink"] = fmt.Sprintf(linkStr, ctx.Link,
|
||||
milestoneID, projectID, assigneeID, posterID, isFuzzy, archived)
|
||||
ctx.Data["OpenLink"] = fmt.Sprintf(linkStr,
|
||||
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "open", url.QueryEscape(selectLabels),
|
||||
milestoneID, projectID, assigneeID, posterID, archived)
|
||||
ctx.Data["ClosedLink"] = fmt.Sprintf(linkStr, ctx.Link,
|
||||
milestoneID, projectID, assigneeID, posterID, isFuzzy, archived)
|
||||
ctx.Data["ClosedLink"] = fmt.Sprintf(linkStr,
|
||||
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "closed", url.QueryEscape(selectLabels),
|
||||
milestoneID, projectID, assigneeID, posterID, archived)
|
||||
milestoneID, projectID, assigneeID, posterID, isFuzzy, archived)
|
||||
ctx.Data["SelLabelIDs"] = labelIDs
|
||||
ctx.Data["SelectLabels"] = selectLabels
|
||||
ctx.Data["ViewType"] = viewType
|
||||
|
|
|
@ -566,21 +566,19 @@ func SettingsPost(ctx *context.Context) {
|
|||
// as an error on the UI for this action
|
||||
ctx.Data["Err_RepoName"] = nil
|
||||
|
||||
m, err := selectPushMirrorByForm(ctx, form, repo)
|
||||
if err != nil {
|
||||
ctx.NotFound("", nil)
|
||||
return
|
||||
}
|
||||
|
||||
interval, err := time.ParseDuration(form.PushMirrorInterval)
|
||||
if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &forms.RepoSettingForm{})
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(form.PushMirrorID, 10, 64)
|
||||
if err != nil {
|
||||
ctx.ServerError("UpdatePushMirrorIntervalPushMirrorID", err)
|
||||
return
|
||||
}
|
||||
m := &repo_model.PushMirror{
|
||||
ID: id,
|
||||
Interval: interval,
|
||||
}
|
||||
m.Interval = interval
|
||||
if err := repo_model.UpdatePushMirrorInterval(ctx, m); err != nil {
|
||||
ctx.ServerError("UpdatePushMirrorInterval", err)
|
||||
return
|
||||
|
|
|
@ -1232,11 +1232,8 @@ func Forks(ctx *context.Context) {
|
|||
page = 1
|
||||
}
|
||||
|
||||
pager := context.NewPagination(ctx.Repo.Repository.NumForks, setting.MaxForksPerPage, page, 5)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
forks, err := repo_model.GetForks(ctx, ctx.Repo.Repository, db.ListOptions{
|
||||
Page: pager.Paginater.Current(),
|
||||
forks, total, err := repo_model.GetForks(ctx, ctx.Repo.Repository, ctx.Doer, db.ListOptions{
|
||||
Page: page,
|
||||
PageSize: setting.MaxForksPerPage,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -1244,6 +1241,9 @@ func Forks(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
pager := context.NewPagination(int(total), setting.MaxForksPerPage, page, 5)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
for _, fork := range forks {
|
||||
if err = fork.LoadOwner(ctx); err != nil {
|
||||
ctx.ServerError("LoadOwner", err)
|
||||
|
|
|
@ -155,9 +155,15 @@ func EmailPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
// Only fired when the primary email is inactive (Wrong state)
|
||||
mailer.SendActivateAccountMail(ctx.Locale, ctx.Doer)
|
||||
if err := mailer.SendActivateAccountMail(ctx, ctx.Doer); err != nil {
|
||||
ctx.ServerError("SendActivateAccountMail", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
mailer.SendActivateEmailMail(ctx.Doer, email.Email)
|
||||
if err := mailer.SendActivateEmailMail(ctx, ctx.Doer, email.Email); err != nil {
|
||||
ctx.ServerError("SendActivateEmailMail", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
address = email.Email
|
||||
|
||||
|
@ -218,7 +224,10 @@ func EmailPost(ctx *context.Context) {
|
|||
|
||||
// Send confirmation email
|
||||
if setting.Service.RegisterEmailConfirm {
|
||||
mailer.SendActivateEmailMail(ctx.Doer, form.Email)
|
||||
if err := mailer.SendActivateEmailMail(ctx, ctx.Doer, form.Email); err != nil {
|
||||
ctx.ServerError("SendActivateEmailMail", err)
|
||||
return
|
||||
}
|
||||
if err := ctx.Cache.Put("MailResendLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
|
||||
log.Error("Set cache(MailResendLimit) fail: %v", err)
|
||||
}
|
||||
|
|
|
@ -1559,11 +1559,17 @@ func registerRoutes(m *web.Route) {
|
|||
m.Get("/graph", repo.Graph)
|
||||
m.Get("/commit/{sha:([a-f0-9]{4,64})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
|
||||
m.Get("/commit/{sha:([a-f0-9]{4,64})$}/load-branches-and-tags", repo.LoadBranchesAndTags)
|
||||
m.Group("/commit/{sha:([a-f0-9]{4,64})$}/notes", func() {
|
||||
m.Post("", web.Bind(forms.CommitNotesForm{}), repo.SetCommitNotes)
|
||||
m.Post("/remove", repo.RemoveCommitNotes)
|
||||
}, reqSignIn, reqRepoCodeWriter)
|
||||
m.Get("/cherry-pick/{sha:([a-f0-9]{4,64})$}", repo.SetEditorconfigIfExists, repo.CherryPick)
|
||||
}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
|
||||
|
||||
m.Get("/rss/branch/*", repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed("rss"))
|
||||
m.Get("/atom/branch/*", repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed("atom"))
|
||||
m.Group("", func() {
|
||||
m.Get("/rss/branch/*", feed.RenderBranchFeed("rss"))
|
||||
m.Get("/atom/branch/*", feed.RenderBranchFeed("atom"))
|
||||
}, repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefBranch), reqRepoCodeReader, feedEnabled)
|
||||
|
||||
m.Group("/src", func() {
|
||||
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
|
@ -132,6 +133,16 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
|||
return nil, err
|
||||
}
|
||||
|
||||
hashWebAuthn, err := auth_model.HasWebAuthnRegistrationsByUID(req.Context(), u.ID)
|
||||
if err != nil {
|
||||
log.Error("HasWebAuthnRegistrationsByUID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if hashWebAuthn {
|
||||
return nil, errors.New("Basic authorization is not allowed while having security keys enrolled")
|
||||
}
|
||||
|
||||
if skipper, ok := source.Cfg.(LocalTwoFASkipper); !ok || !skipper.IsSkipLocalTwoFA() {
|
||||
if err := validateTOTP(req, u); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -347,12 +347,30 @@ func loadOrCreateAsymmetricKey() (any, error) {
|
|||
key, err := func() (any, error) {
|
||||
switch {
|
||||
case strings.HasPrefix(setting.OAuth2.JWTSigningAlgorithm, "RS"):
|
||||
return rsa.GenerateKey(rand.Reader, 4096)
|
||||
var bits int
|
||||
switch setting.OAuth2.JWTSigningAlgorithm {
|
||||
case "RS256":
|
||||
bits = 2048
|
||||
case "RS384":
|
||||
bits = 3072
|
||||
case "RS512":
|
||||
bits = 4096
|
||||
}
|
||||
return rsa.GenerateKey(rand.Reader, bits)
|
||||
case setting.OAuth2.JWTSigningAlgorithm == "EdDSA":
|
||||
_, pk, err := ed25519.GenerateKey(rand.Reader)
|
||||
return pk, err
|
||||
default:
|
||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
var curve elliptic.Curve
|
||||
switch setting.OAuth2.JWTSigningAlgorithm {
|
||||
case "ES256":
|
||||
curve = elliptic.P256()
|
||||
case "ES384":
|
||||
curve = elliptic.P384()
|
||||
case "ES512":
|
||||
curve = elliptic.P521()
|
||||
}
|
||||
return ecdsa.GenerateKey(curve, rand.Reader)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
|
|
116
services/auth/source/oauth2/jwtsigningkey_test.go
Normal file
116
services/auth/source/oauth2/jwtsigningkey_test.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLoadOrCreateAsymmetricKey(t *testing.T) {
|
||||
loadKey := func(t *testing.T) any {
|
||||
t.Helper()
|
||||
loadOrCreateAsymmetricKey()
|
||||
|
||||
fileContent, err := os.ReadFile(setting.OAuth2.JWTSigningPrivateKeyFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
block, _ := pem.Decode(fileContent)
|
||||
assert.NotNil(t, block)
|
||||
assert.EqualValues(t, "PRIVATE KEY", block.Type)
|
||||
|
||||
parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
return parsedKey
|
||||
}
|
||||
t.Run("RSA-2048", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningPrivateKeyFile, filepath.Join(t.TempDir(), "jwt-rsa-2048.priv"))()
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "RS256")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
|
||||
rsaPrivateKey := parsedKey.(*rsa.PrivateKey)
|
||||
assert.EqualValues(t, 2048, rsaPrivateKey.N.BitLen())
|
||||
|
||||
t.Run("Load key with differ specified algorithm", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "EdDSA")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
rsaPrivateKey := parsedKey.(*rsa.PrivateKey)
|
||||
assert.EqualValues(t, 2048, rsaPrivateKey.N.BitLen())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("RSA-3072", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningPrivateKeyFile, filepath.Join(t.TempDir(), "jwt-rsa-3072.priv"))()
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "RS384")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
|
||||
rsaPrivateKey := parsedKey.(*rsa.PrivateKey)
|
||||
assert.EqualValues(t, 3072, rsaPrivateKey.N.BitLen())
|
||||
})
|
||||
|
||||
t.Run("RSA-4096", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningPrivateKeyFile, filepath.Join(t.TempDir(), "jwt-rsa-4096.priv"))()
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "RS512")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
|
||||
rsaPrivateKey := parsedKey.(*rsa.PrivateKey)
|
||||
assert.EqualValues(t, 4096, rsaPrivateKey.N.BitLen())
|
||||
})
|
||||
|
||||
t.Run("ECDSA-256", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningPrivateKeyFile, filepath.Join(t.TempDir(), "jwt-ecdsa-256.priv"))()
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "ES256")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
|
||||
ecdsaPrivateKey := parsedKey.(*ecdsa.PrivateKey)
|
||||
assert.EqualValues(t, 256, ecdsaPrivateKey.Params().BitSize)
|
||||
})
|
||||
|
||||
t.Run("ECDSA-384", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningPrivateKeyFile, filepath.Join(t.TempDir(), "jwt-ecdsa-384.priv"))()
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "ES384")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
|
||||
ecdsaPrivateKey := parsedKey.(*ecdsa.PrivateKey)
|
||||
assert.EqualValues(t, 384, ecdsaPrivateKey.Params().BitSize)
|
||||
})
|
||||
|
||||
t.Run("ECDSA-512", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningPrivateKeyFile, filepath.Join(t.TempDir(), "jwt-ecdsa-512.priv"))()
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "ES512")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
|
||||
ecdsaPrivateKey := parsedKey.(*ecdsa.PrivateKey)
|
||||
assert.EqualValues(t, 521, ecdsaPrivateKey.Params().BitSize)
|
||||
})
|
||||
|
||||
t.Run("EdDSA", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningPrivateKeyFile, filepath.Join(t.TempDir(), "jwt-eddsa.priv"))()
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTSigningAlgorithm, "EdDSA")()
|
||||
|
||||
parsedKey := loadKey(t)
|
||||
|
||||
assert.NotNil(t, parsedKey.(ed25519.PrivateKey))
|
||||
})
|
||||
}
|
|
@ -47,7 +47,7 @@ func (ctx *Context) GetSiteCookie(name string) string {
|
|||
// SetLTACookie will generate a LTA token and add it as an cookie.
|
||||
func (ctx *Context) SetLTACookie(u *user_model.User) error {
|
||||
days := 86400 * setting.LogInRememberDays
|
||||
lookup, validator, err := auth_model.GenerateAuthToken(ctx, u.ID, timeutil.TimeStampNow().Add(int64(days)))
|
||||
lookup, validator, err := auth_model.GenerateAuthToken(ctx, u.ID, timeutil.TimeStampNow().Add(int64(days)), auth_model.LongTermAuthorization)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -243,6 +243,9 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
|
|||
// find archive download count without existing release
|
||||
genericOrphanCheck("Archive download count without existing Release",
|
||||
"repo_archive_download_count", "release", "repo_archive_download_count.release_id=release.id"),
|
||||
// find authorization tokens without existing user
|
||||
genericOrphanCheck("Authorization token without existing User",
|
||||
"forgejo_auth_token", "user", "forgejo_auth_token.uid=user.id"),
|
||||
)
|
||||
|
||||
for _, c := range consistencyChecks {
|
||||
|
|
|
@ -749,3 +749,7 @@ func (f *DeadlineForm) Validate(req *http.Request, errs binding.Errors) binding.
|
|||
ctx := context.GetValidateContext(req)
|
||||
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type CommitNotesForm struct {
|
||||
Notes string
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
org_model "code.gitea.io/gitea/models/organization"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
|
@ -117,7 +119,11 @@ func PullRequestCodeOwnersReview(ctx context.Context, issue *issues_model.Issue,
|
|||
}
|
||||
|
||||
for _, u := range uniqUsers {
|
||||
if u.ID != issue.Poster.ID {
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, issue.Repo, u)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetUserRepoPermission: %w", err)
|
||||
}
|
||||
if u.ID != issue.Poster.ID && permission.CanRead(unit.TypePullRequests) {
|
||||
comment, err := issues_model.AddReviewRequest(ctx, issue, u, issue.Poster)
|
||||
if err != nil {
|
||||
log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err)
|
||||
|
|
|
@ -70,7 +70,7 @@ func SendTestMail(email string) error {
|
|||
}
|
||||
|
||||
// sendUserMail sends a mail to the user
|
||||
func sendUserMail(language string, u *user_model.User, tpl base.TplName, code, subject, info string) {
|
||||
func sendUserMail(language string, u *user_model.User, tpl base.TplName, code, subject, info string) error {
|
||||
locale := translation.NewLocale(language)
|
||||
data := map[string]any{
|
||||
"locale": locale,
|
||||
|
@ -84,47 +84,66 @@ func sendUserMail(language string, u *user_model.User, tpl base.TplName, code, s
|
|||
var content bytes.Buffer
|
||||
|
||||
if err := bodyTemplates.ExecuteTemplate(&content, string(tpl), data); err != nil {
|
||||
log.Error("Template: %v", err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
msg := NewMessage(u.EmailTo(), subject, content.String())
|
||||
msg.Info = fmt.Sprintf("UID: %d, %s", u.ID, info)
|
||||
|
||||
SendAsync(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendActivateAccountMail sends an activation mail to the user (new user registration)
|
||||
func SendActivateAccountMail(locale translation.Locale, u *user_model.User) {
|
||||
func SendActivateAccountMail(ctx context.Context, u *user_model.User) error {
|
||||
if setting.MailService == nil {
|
||||
// No mail service configured
|
||||
return
|
||||
return nil
|
||||
}
|
||||
sendUserMail(locale.Language(), u, mailAuthActivate, u.GenerateEmailActivateCode(u.Email), locale.TrString("mail.activate_account"), "activate account")
|
||||
|
||||
locale := translation.NewLocale(u.Language)
|
||||
code, err := u.GenerateEmailAuthorizationCode(ctx, auth_model.UserActivation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sendUserMail(locale.Language(), u, mailAuthActivate, code, locale.TrString("mail.activate_account"), "activate account")
|
||||
}
|
||||
|
||||
// SendResetPasswordMail sends a password reset mail to the user
|
||||
func SendResetPasswordMail(u *user_model.User) {
|
||||
func SendResetPasswordMail(ctx context.Context, u *user_model.User) error {
|
||||
if setting.MailService == nil {
|
||||
// No mail service configured
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
locale := translation.NewLocale(u.Language)
|
||||
sendUserMail(u.Language, u, mailAuthResetPassword, u.GenerateEmailActivateCode(u.Email), locale.TrString("mail.reset_password"), "recover account")
|
||||
code, err := u.GenerateEmailAuthorizationCode(ctx, auth_model.PasswordReset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sendUserMail(u.Language, u, mailAuthResetPassword, code, locale.TrString("mail.reset_password"), "recover account")
|
||||
}
|
||||
|
||||
// SendActivateEmailMail sends confirmation email to confirm new email address
|
||||
func SendActivateEmailMail(u *user_model.User, email string) {
|
||||
func SendActivateEmailMail(ctx context.Context, u *user_model.User, email string) error {
|
||||
if setting.MailService == nil {
|
||||
// No mail service configured
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
locale := translation.NewLocale(u.Language)
|
||||
code, err := u.GenerateEmailAuthorizationCode(ctx, auth_model.EmailActivation(email))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := map[string]any{
|
||||
"locale": locale,
|
||||
"DisplayName": u.DisplayName(),
|
||||
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale),
|
||||
"Code": u.GenerateEmailActivateCode(email),
|
||||
"Code": code,
|
||||
"Email": email,
|
||||
"Language": locale.Language(),
|
||||
}
|
||||
|
@ -132,14 +151,14 @@ func SendActivateEmailMail(u *user_model.User, email string) {
|
|||
var content bytes.Buffer
|
||||
|
||||
if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthActivateEmail), data); err != nil {
|
||||
log.Error("Template: %v", err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
msg := NewMessage(email, locale.TrString("mail.activate_email"), content.String())
|
||||
msg.Info = fmt.Sprintf("UID: %d, activate email", u.ID)
|
||||
|
||||
SendAsync(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendRegisterNotifyMail triggers a notify e-mail by admin created a account.
|
||||
|
|
|
@ -22,9 +22,16 @@ import (
|
|||
//
|
||||
// The payload is verifiable by the generated HMAC using the user secret. It contains:
|
||||
// | Timestamp | Action/Handler Type | Action/Handler Data |
|
||||
//
|
||||
//
|
||||
// Version changelog
|
||||
//
|
||||
// v1 -> v2:
|
||||
// Use 128 instead of 80 bits of the HMAC-SHA256 output.
|
||||
|
||||
const (
|
||||
tokenVersion1 byte = 1
|
||||
tokenVersion2 byte = 2
|
||||
tokenLifetimeInYears int = 1
|
||||
)
|
||||
|
||||
|
@ -70,7 +77,7 @@ func CreateToken(ht HandlerType, user *user_model.User, data []byte) (string, er
|
|||
return "", err
|
||||
}
|
||||
|
||||
return encodingWithoutPadding.EncodeToString(append([]byte{tokenVersion1}, packagedData...)), nil
|
||||
return encodingWithoutPadding.EncodeToString(append([]byte{tokenVersion2}, packagedData...)), nil
|
||||
}
|
||||
|
||||
// ExtractToken extracts the action/user tuple from the token and verifies the content
|
||||
|
@ -84,7 +91,7 @@ func ExtractToken(ctx context.Context, token string) (HandlerType, *user_model.U
|
|||
return UnknownHandlerType, nil, nil, &ErrToken{"no data"}
|
||||
}
|
||||
|
||||
if data[0] != tokenVersion1 {
|
||||
if data[0] != tokenVersion2 {
|
||||
return UnknownHandlerType, nil, nil, &ErrToken{fmt.Sprintf("unsupported token version: %v", data[0])}
|
||||
}
|
||||
|
||||
|
@ -124,5 +131,8 @@ func generateHmac(secret, payload []byte) []byte {
|
|||
mac.Write(payload)
|
||||
hmac := mac.Sum(nil)
|
||||
|
||||
return hmac[:10] // RFC2104 recommends not using less then 80 bits
|
||||
// RFC2104 section 5 recommends that if you do HMAC truncation, you should use
|
||||
// the max(80, hash_len/2) of the leftmost bits.
|
||||
// For SHA256 this works out to using 128 of the leftmost bits.
|
||||
return hmac[:16]
|
||||
}
|
||||
|
|
|
@ -307,9 +307,10 @@ 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,
|
||||
IncludeTags: true,
|
||||
RepoID: repo.ID,
|
||||
TagNames: tags,
|
||||
IncludeDrafts: true,
|
||||
IncludeTags: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("db.Find[repo_model.Release]: %w", err)
|
||||
|
@ -394,13 +395,17 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
|||
}
|
||||
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
|
||||
if rel.IsTag && author != nil {
|
||||
rel.PublisherID = author.ID
|
||||
if rel.IsTag {
|
||||
rel.Title = parts[0]
|
||||
rel.Note = note
|
||||
if author != nil {
|
||||
rel.PublisherID = author.ID
|
||||
}
|
||||
} else {
|
||||
rel.IsDraft = false
|
||||
}
|
||||
if err = repo_model.UpdateRelease(ctx, rel); err != nil {
|
||||
return fmt.Errorf("Update: %w", err)
|
||||
|
|
|
@ -96,6 +96,7 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error)
|
|||
&user_model.BlockedUser{BlockID: u.ID},
|
||||
&user_model.BlockedUser{UserID: u.ID},
|
||||
&actions_model.ActionRunnerToken{OwnerID: u.ID},
|
||||
&auth_model.AuthorizationToken{UID: u.ID},
|
||||
); err != nil {
|
||||
return fmt.Errorf("deleteBeans: %w", err)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
@ -202,6 +203,9 @@ func (d discordConvertor) Push(p *api.PushPayload) (DiscordPayload, error) {
|
|||
// limit the commit message display to just the summary, otherwise it would be hard to read
|
||||
message := strings.TrimRight(strings.SplitN(commit.Message, "\n", 1)[0], "\r")
|
||||
|
||||
// Escaping markdown character
|
||||
message = escapeMarkdown(message)
|
||||
|
||||
// a limit of 50 is set because GitHub does the same
|
||||
if utf8.RuneCountInString(message) > 50 {
|
||||
message = fmt.Sprintf("%.47s...", message)
|
||||
|
@ -365,3 +369,40 @@ func (d discordConvertor) createPayload(s *api.User, title, text, url string, co
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
var orderedListPattern = regexp.MustCompile(`(\d+)\.`)
|
||||
|
||||
var markdownPatterns = map[string]*regexp.Regexp{
|
||||
"~": regexp.MustCompile(`\~(.*?)\~`),
|
||||
"*": regexp.MustCompile(`\*(.*?)\*`),
|
||||
"_": regexp.MustCompile(`\_(.*?)\_`),
|
||||
}
|
||||
|
||||
var markdownToEscape = strings.NewReplacer(
|
||||
"* ", "\\* ",
|
||||
"`", "\\`",
|
||||
"[", "\\[",
|
||||
"]", "\\]",
|
||||
"(", "\\(",
|
||||
")", "\\)",
|
||||
"#", "\\#",
|
||||
"+ ", "\\+ ",
|
||||
"- ", "\\- ",
|
||||
"---", "\\---",
|
||||
"!", "\\!",
|
||||
"|", "\\|",
|
||||
"<", "\\<",
|
||||
">", "\\>",
|
||||
)
|
||||
|
||||
// Escape Markdown characters
|
||||
func escapeMarkdown(input string) string {
|
||||
// Escaping ordered list
|
||||
output := orderedListPattern.ReplaceAllString(input, "$1\\.")
|
||||
|
||||
for char, pattern := range markdownPatterns {
|
||||
output = pattern.ReplaceAllString(output, fmt.Sprintf(`\%s$1\%s`, char, char))
|
||||
}
|
||||
|
||||
return markdownToEscape.Replace(output)
|
||||
}
|
||||
|
|
|
@ -94,6 +94,20 @@ func TestDiscordPayload(t *testing.T) {
|
|||
assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
|
||||
})
|
||||
|
||||
t.Run("PushWithMarkdownCharactersInCommitMessage", func(t *testing.T) {
|
||||
p := pushTestEscapeCommitMessagePayload()
|
||||
|
||||
pl, err := dc.Push(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, pl.Embeds, 1)
|
||||
assert.Equal(t, "[test/repo:test] 2 new commits", pl.Embeds[0].Title)
|
||||
assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) \\# conflicts\n\\# \\- some/conflicting/file.txt - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) \\# conflicts\n\\# \\- some/conflicting/file.txt - user1", pl.Embeds[0].Description)
|
||||
assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
|
||||
assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
|
||||
assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
|
||||
})
|
||||
|
||||
t.Run("Issue", func(t *testing.T) {
|
||||
p := issueTestPayload()
|
||||
|
||||
|
@ -346,3 +360,89 @@ func TestDiscordJSONPayload(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", body.Embeds[0].Description)
|
||||
}
|
||||
|
||||
var escapedMarkdownTests = map[string]struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
"Escape heading level 1": {
|
||||
input: "# Heading level 1",
|
||||
expected: "\\# Heading level 1",
|
||||
},
|
||||
"Escape heading level 2": {
|
||||
input: "## Heading level 2",
|
||||
expected: "\\#\\# Heading level 2",
|
||||
},
|
||||
"Escape heading level 3": {
|
||||
input: "### Heading level 3",
|
||||
expected: "\\#\\#\\# Heading level 3",
|
||||
},
|
||||
"Escape bold text": {
|
||||
input: "**bold text**",
|
||||
expected: "\\*\\*bold text\\*\\*",
|
||||
},
|
||||
"Escape italic text": {
|
||||
input: "*italic text*",
|
||||
expected: "\\*italic text\\*",
|
||||
},
|
||||
"Escape italic text underline": {
|
||||
input: "_italic text_",
|
||||
expected: "\\_italic text\\_",
|
||||
},
|
||||
"Escape strikethrough": {
|
||||
input: "~~strikethrough~~",
|
||||
expected: "\\~\\~strikethrough\\~\\~",
|
||||
},
|
||||
"Escape Ordered list item": {
|
||||
input: "1. Ordered list item\n2. Second ordered list item\n999999999999. 999999999999 ordered list item",
|
||||
expected: "1\\. Ordered list item\n2\\. Second ordered list item\n999999999999\\. 999999999999 ordered list item",
|
||||
},
|
||||
"Escape Unordered list item": {
|
||||
input: "- Unordered list\n + using plus",
|
||||
expected: "\\- Unordered list\n \\+ using plus",
|
||||
},
|
||||
"Escape bullet list item": {
|
||||
input: "* Bullet list item",
|
||||
expected: "\\* Bullet list item",
|
||||
},
|
||||
"Escape table": {
|
||||
input: "| Table | Example |\n|-|-|\n| Lorem | Ipsum |",
|
||||
expected: "\\| Table \\| Example \\|\n\\|-\\|-\\|\n\\| Lorem \\| Ipsum \\|",
|
||||
},
|
||||
"Escape link": {
|
||||
input: "[Link to Forgejo](https://forgejo.org/)",
|
||||
expected: "\\[Link to Forgejo\\]\\(https://forgejo.org/\\)",
|
||||
},
|
||||
"Escape Alt text for an image": {
|
||||
input: "![Alt text for an image](https://forgejo.org/_astro/mascot-dark.1omhhgvT_Zm0N2n.webp)",
|
||||
expected: "\\!\\[Alt text for an image\\]\\(https://forgejo.org/\\_astro/mascot-dark.1omhhgvT\\_Zm0N2n.webp\\)",
|
||||
},
|
||||
"Escape URL if it has markdown character": {
|
||||
input: "https://forgejo.org/_astro/mascot-dark.1omhhgvT_Zm0N2n.webp",
|
||||
expected: "https://forgejo.org/\\_astro/mascot-dark.1omhhgvT\\_Zm0N2n.webp",
|
||||
},
|
||||
"Escape blockquote text": {
|
||||
input: "> Blockquote text.",
|
||||
expected: "\\> Blockquote text.",
|
||||
},
|
||||
"Escape inline code": {
|
||||
input: "`Inline code`",
|
||||
expected: "\\`Inline code\\`",
|
||||
},
|
||||
"Escape multiple code": {
|
||||
input: "```\nCode block\nwith multiple lines\n```\n",
|
||||
expected: "\\`\\`\\`\nCode block\nwith multiple lines\n\\`\\`\\`\n",
|
||||
},
|
||||
"Escape horizontal rule": {
|
||||
input: "---",
|
||||
expected: "\\---",
|
||||
},
|
||||
}
|
||||
|
||||
func TestEscapeMarkdownChar(t *testing.T) {
|
||||
for name, test := range escapedMarkdownTests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, escapeMarkdown(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,6 +72,10 @@ func pushTestMultilineCommitMessagePayload() *api.PushPayload {
|
|||
return pushTestPayloadWithCommitMessage("This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好 ⚠️⚠️️\n\nThis is the message body.")
|
||||
}
|
||||
|
||||
func pushTestEscapeCommitMessagePayload() *api.PushPayload {
|
||||
return pushTestPayloadWithCommitMessage("# conflicts\n# - some/conflicting/file.txt")
|
||||
}
|
||||
|
||||
func pushTestPayloadWithCommitMessage(message string) *api.PushPayload {
|
||||
commit := &api.PayloadCommit{
|
||||
ID: "2020558fe2e34debb818a514715839cabd25e778",
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
.ui.secondary.menu .dropdown.item > .menu { margin-top: 0; }
|
||||
</style>
|
||||
</noscript>
|
||||
{{template "shared/user/mention_highlight" .}}
|
||||
{{template "base/head_opengraph" .}}
|
||||
{{template "base/head_style" .}}
|
||||
{{template "custom/header" .}}
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
{{svg "octicon-no-entry" 48}}
|
||||
<h2>{{ctx.Locale.Tr "actions.runs.no_workflows"}}</h2>
|
||||
{{if and .CanWriteCode .CanWriteActions}}
|
||||
<p>{{ctx.Locale.Tr "actions.runs.no_workflows.quick_start" "https://forgejo.org/docs/latest/admin/actions/"}}</p>
|
||||
<p>{{ctx.Locale.Tr "actions.runs.no_workflows.help_write_access" "https://forgejo.org/docs/latest/user/actions/#quick-start" "https://forgejo.org/docs/latest/admin/runner-installation/"}}</p>
|
||||
{{else}}
|
||||
<p>{{ctx.Locale.Tr "actions.runs.no_workflows.help_no_write_access" "https://forgejo.org/docs/latest/user/actions/"}}</p>
|
||||
{{end}}
|
||||
<p>{{ctx.Locale.Tr "actions.runs.no_workflows.documentation" "https://forgejo.org/docs/latest/admin/actions/"}}</p>
|
||||
</div>
|
||||
|
|
|
@ -128,6 +128,9 @@
|
|||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="commit-notes-add-button" class="item">
|
||||
{{ctx.Locale.Tr "repo.diff.git-notes.add"}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
@ -275,10 +278,60 @@
|
|||
<strong>{{.NoteCommit.Author.Name}}</strong>
|
||||
{{end}}
|
||||
<span class="text grey" id="note-authored-time">{{DateUtils.TimeSince .NoteCommit.Author.When}}</span>
|
||||
{{if or ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}}
|
||||
<div class="ui right">
|
||||
<button id="commit-notes-edit-button" class="ui tiny primary button" data-modal="#delete-note-modal">{{ctx.Locale.Tr "edit"}}</button>
|
||||
<button class="ui tiny button red show-modal" data-modal="#delete-note-modal">{{ctx.Locale.Tr "remove"}}</button>
|
||||
</div>
|
||||
<div class="ui small modal" id="delete-note-modal">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.diff.git-notes.remove-header"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "repo.diff.git-notes.remove-body"}}</p>
|
||||
<div class="text right actions">
|
||||
<form action="{{.Link}}/notes/remove" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<button type="button" class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
|
||||
<button type="submit" class="ui red button" href="{{.Link}}/notes/remove">{{ctx.Locale.Tr "remove"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="ui bottom attached info segment git-notes">
|
||||
<div id="commit-notes-display-area" class="ui bottom attached info segment git-notes">
|
||||
<pre class="commit-body">{{.NoteRendered | SanitizeHTML}}</pre>
|
||||
</div>
|
||||
{{if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}}
|
||||
<div id="commit-notes-edit-area" class="ui bottom attached info segment git-notes tw-hidden">
|
||||
<form class="ui form" action="{{.Link}}/notes" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
|
||||
<div class="field">
|
||||
<textarea name="notes">{{.NoteRendered | SanitizeHTML}}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<button id="notes-save-button" class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}}
|
||||
<div id="commit-notes-add-area" class="ui tw-mt-3 segment tw-hidden">
|
||||
<form class="ui form" action="{{.Link}}/notes" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
|
||||
<div class="field">
|
||||
<textarea name="notes"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "add"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
{{template "repo/diff/box" .}}
|
||||
</div>
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<div class="field">
|
||||
<div class="ui top attached tabular menu" data-write="write" data-preview="preview" data-diff="diff">
|
||||
<a class="active item" data-tab="write">{{svg "octicon-code"}} {{if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.new_file"}}{{else}}{{ctx.Locale.Tr "repo.editor.edit_file"}}{{end}}</a>
|
||||
<a class="item" data-tab="preview" data-url="{{.Repository.Link}}/markup" data-context="{{.RepoLink}}/src/{{.BranchNameSubURL}}" data-markup-mode="file">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a>
|
||||
<a class="item" data-tab="preview" data-url="{{.Repository.Link}}/markup" data-context="{{.RepoLink}}" data-branch-path="{{.BranchNameSubURL}}" data-markup-mode="file">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a>
|
||||
{{if not .IsNewFile}}
|
||||
<a class="item" data-tab="diff" hx-params="context,content" hx-vals='{"context":"{{.BranchLink}}"}' hx-include="#edit_area" hx-swap="innerHTML" hx-target=".tab[data-tab='diff']" hx-indicator=".tab[data-tab='diff']" hx-post="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">{{svg "octicon-diff"}} {{ctx.Locale.Tr "repo.editor.preview_changes"}}</a>
|
||||
{{end}}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue