mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-08 18:29:48 +00:00
Compare commits
51 Commits
v1.28.0-de
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49ef93940a | ||
|
|
308a6f12ae | ||
|
|
97078b96cf | ||
|
|
6507f1fd94 | ||
|
|
0964899799 | ||
|
|
550efdcdfd | ||
|
|
b96bd22372 | ||
|
|
a74f618ade | ||
|
|
2b89e2ac97 | ||
|
|
26bff7f47e | ||
|
|
582217a0da | ||
|
|
a46e331637 | ||
|
|
580cc26d63 | ||
|
|
e3d83bcf9c | ||
|
|
44f927eacf | ||
|
|
35413d5b65 | ||
|
|
e797a27d4e | ||
|
|
4b15260277 | ||
|
|
18fdc77130 | ||
|
|
2c2691b969 | ||
|
|
08d4abbb46 | ||
|
|
e4ef995f2a | ||
|
|
7fdfb8d642 | ||
|
|
d4c4142123 | ||
|
|
f7fd510224 | ||
|
|
38a5824753 | ||
|
|
e8d2c493bb | ||
|
|
b09920a537 | ||
|
|
c6184ed184 | ||
|
|
8909958055 | ||
|
|
638e4bce09 | ||
|
|
a031454586 | ||
|
|
c52a07dcfe | ||
|
|
b6ef881a9f | ||
|
|
6240d8bf89 | ||
|
|
9cb2719fab | ||
|
|
e8654c7e06 | ||
|
|
67a6bd7fc0 | ||
|
|
77e221ffaf | ||
|
|
458c11bd68 | ||
|
|
3d2bbd25ec | ||
|
|
7745720292 | ||
|
|
d46d0540d0 | ||
|
|
e449018730 | ||
|
|
e1cdb71845 | ||
|
|
a64131e22d | ||
|
|
0f0a38c1b9 | ||
|
|
535f791166 | ||
|
|
b34a09be38 | ||
|
|
6f2e328c85 | ||
|
|
55983320ed |
8
.github/actions/docker-dryrun/action.yml
vendored
8
.github/actions/docker-dryrun/action.yml
vendored
@@ -9,10 +9,10 @@ inputs:
|
|||||||
runs:
|
runs:
|
||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
- uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
||||||
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||||
- name: Build regular image
|
- name: Build regular image
|
||||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: ${{ inputs.platform }}
|
platforms: ${{ inputs.platform }}
|
||||||
@@ -20,7 +20,7 @@ runs:
|
|||||||
file: Dockerfile
|
file: Dockerfile
|
||||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
|
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
|
||||||
- name: Build rootless image
|
- name: Build rootless image
|
||||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: ${{ inputs.platform }}
|
platforms: ${{ inputs.platform }}
|
||||||
|
|||||||
12
.github/actions/go-cache/action.yml
vendored
12
.github/actions/go-cache/action.yml
vendored
@@ -16,34 +16,34 @@ runs:
|
|||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- if: ${{ github.workflow == 'cache-seeder' }}
|
- if: ${{ github.workflow == 'cache-seeder' }}
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
path: ~/go/pkg/mod
|
||||||
key: gomod-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
key: gomod-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
||||||
- if: ${{ github.workflow != 'cache-seeder' }}
|
- if: ${{ github.workflow != 'cache-seeder' }}
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
path: ~/go/pkg/mod
|
||||||
key: gomod-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
key: gomod-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
||||||
restore-keys: gomod-${{ runner.os }}-${{ runner.arch }}
|
restore-keys: gomod-${{ runner.os }}-${{ runner.arch }}
|
||||||
- if: ${{ github.workflow == 'cache-seeder' && inputs.lint-cache != 'true' }}
|
- if: ${{ github.workflow == 'cache-seeder' && inputs.lint-cache != 'true' }}
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/go-build
|
path: ~/.cache/go-build
|
||||||
key: gobuild-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
key: gobuild-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
||||||
- if: ${{ github.workflow != 'cache-seeder' || inputs.lint-cache == 'true' }}
|
- if: ${{ github.workflow != 'cache-seeder' || inputs.lint-cache == 'true' }}
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/go-build
|
path: ~/.cache/go-build
|
||||||
key: gobuild-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
key: gobuild-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
||||||
restore-keys: gobuild-${{ runner.os }}-${{ runner.arch }}
|
restore-keys: gobuild-${{ runner.os }}-${{ runner.arch }}
|
||||||
- if: ${{ inputs.lint-cache == 'true' && github.workflow == 'cache-seeder' }}
|
- if: ${{ inputs.lint-cache == 'true' && github.workflow == 'cache-seeder' }}
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/golangci-lint
|
path: ~/.cache/golangci-lint
|
||||||
key: golint-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum', '.golangci.yml') }}
|
key: golint-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum', '.golangci.yml') }}
|
||||||
- if: ${{ inputs.lint-cache == 'true' && github.workflow != 'cache-seeder' }}
|
- if: ${{ inputs.lint-cache == 'true' && github.workflow != 'cache-seeder' }}
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/golangci-lint
|
path: ~/.cache/golangci-lint
|
||||||
key: golint-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum', '.golangci.yml') }}
|
key: golint-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum', '.golangci.yml') }}
|
||||||
|
|||||||
4
.github/actions/node-setup/action.yml
vendored
4
.github/actions/node-setup/action.yml
vendored
@@ -13,10 +13,10 @@ runs:
|
|||||||
- if: ${{ inputs.cache == 'true' }}
|
- if: ${{ inputs.cache == 'true' }}
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 26
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
cache-dependency-path: pnpm-lock.yaml
|
||||||
- if: ${{ inputs.cache != 'true' }}
|
- if: ${{ inputs.cache != 'true' }}
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 26
|
||||||
|
|||||||
4
.github/workflows/cron-renovate.yml
vendored
4
.github/workflows/cron-renovate.yml
vendored
@@ -21,12 +21,12 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||||
- uses: renovatebot/github-action@6d859fc95779be83a0335ca704879b47e5d79641 # v46.1.16
|
- uses: renovatebot/github-action@dd5302ec17783b2fc721b19ae7209b57b1587765 # v46.1.17
|
||||||
with:
|
with:
|
||||||
renovate-version: ${{ env.RENOVATE_VERSION }}
|
renovate-version: ${{ env.RENOVATE_VERSION }}
|
||||||
configurationFile: renovate.json5
|
configurationFile: renovate.json5
|
||||||
token: ${{ secrets.RENOVATE_TOKEN }}
|
token: ${{ secrets.RENOVATE_TOKEN }}
|
||||||
env:
|
env:
|
||||||
RENOVATE_BINARY_SOURCE: install # auto-install go/node toolchains needed by post-upgrade tasks.
|
RENOVATE_BINARY_SOURCE: install # auto-install go/node toolchains needed by post-upgrade tasks.
|
||||||
RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^make (tidy|svg)$"]'
|
RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^make (tidy|svg|generate-codemirror-languages)$"]'
|
||||||
RENOVATE_REPOSITORIES: '["go-gitea/gitea"]'
|
RENOVATE_REPOSITORIES: '["go-gitea/gitea"]'
|
||||||
|
|||||||
2
.github/workflows/pull-labeler.yml
vendored
2
.github/workflows/pull-labeler.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
ref: ${{ github.event.pull_request.base.sha }}
|
ref: ${{ github.event.pull_request.base.sha }}
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 26
|
||||||
# Labels are only synced after the title lints, so an invalid title never reaches the label diff.
|
# Labels are only synced after the title lints, so an invalid title never reaches the label diff.
|
||||||
- run: node ./tools/ci-tools.ts lint-pr-title
|
- run: node ./tools/ci-tools.ts lint-pr-title
|
||||||
env:
|
env:
|
||||||
|
|||||||
48
.github/workflows/release-nightly-snapcraft.yml
vendored
48
.github/workflows/release-nightly-snapcraft.yml
vendored
@@ -6,40 +6,30 @@ on:
|
|||||||
- main
|
- main
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-publish:
|
build-and-publish:
|
||||||
runs-on: ubuntu-latest
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
env:
|
matrix:
|
||||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
runner: [ubuntu-24.04, ubuntu-24.04-arm]
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||||
|
with:
|
||||||
- name: Install snapcraft
|
fetch-depth: 0
|
||||||
run: sudo snap install snapcraft --classic
|
- uses: snapcore/action-build@3bdaa03e1ba6bf59a65f84a751d943d549a54e79 # v1.3.0
|
||||||
|
id: build
|
||||||
- name: Authenticate snapcraft
|
- uses: snapcore/action-publish@214b86e5ca036ead1668c79afb81e550e6c54d40 # v1.2.0
|
||||||
shell: bash
|
env:
|
||||||
run: snapcraft login --with <(printf '%s' "$SNAPCRAFT_STORE_CREDENTIALS")
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
||||||
|
with:
|
||||||
- name: Remote build
|
snap: ${{ steps.build.outputs.snap }}
|
||||||
run: |
|
release: latest/edge
|
||||||
snapcraft remote-build \
|
|
||||||
--launchpad-accept-public-upload \
|
|
||||||
--build-for=amd64,arm64,armhf
|
|
||||||
|
|
||||||
- name: List built snaps
|
|
||||||
run: find . -maxdepth 1 -type f -name '*.snap' -print
|
|
||||||
|
|
||||||
- name: Upload and release snapcraft nightly build
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
for snap in ./*.snap; do
|
|
||||||
echo "Uploading $snap to edge"
|
|
||||||
snapcraft upload --release="latest/edge" "$snap"
|
|
||||||
done
|
|
||||||
|
|||||||
25
.github/workflows/release-nightly.yml
vendored
25
.github/workflows/release-nightly.yml
vendored
@@ -23,12 +23,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
- uses: ./.github/actions/node-setup
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
|
||||||
- run: make deps-frontend deps-backend
|
- run: make deps-frontend deps-backend
|
||||||
# xgo build
|
# xgo build
|
||||||
- run: make release
|
- run: make release
|
||||||
@@ -61,7 +56,7 @@ jobs:
|
|||||||
echo "Cleaned name is ${REF_NAME}"
|
echo "Cleaned name is ${REF_NAME}"
|
||||||
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||||
- name: configure aws
|
- name: configure aws
|
||||||
uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0
|
uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1
|
||||||
with:
|
with:
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
@@ -83,8 +78,8 @@ jobs:
|
|||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
- run: git fetch --unshallow --quiet --tags --force
|
||||||
- uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
- uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
||||||
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||||
- name: Get cleaned branch name
|
- name: Get cleaned branch name
|
||||||
id: clean_name
|
id: clean_name
|
||||||
env:
|
env:
|
||||||
@@ -92,7 +87,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
REF_NAME=$(echo "$REF" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
REF_NAME=$(echo "$REF" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
||||||
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||||
- uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
- uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
@@ -102,7 +97,7 @@ jobs:
|
|||||||
type=raw,value=${{ steps.clean_name.outputs.branch }}
|
type=raw,value=${{ steps.clean_name.outputs.branch }}
|
||||||
annotations: |
|
annotations: |
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||||
- uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
- uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||||
id: meta_rootless
|
id: meta_rootless
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
@@ -116,18 +111,18 @@ jobs:
|
|||||||
annotations: |
|
annotations: |
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Login to GHCR using PAT
|
- name: Login to GHCR using PAT
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build regular docker image
|
- name: build regular docker image
|
||||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
@@ -137,7 +132,7 @@ jobs:
|
|||||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
|
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
|
||||||
cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful,mode=max
|
cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful,mode=max
|
||||||
- name: build rootless docker image
|
- name: build rootless docker image
|
||||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
|
|||||||
25
.github/workflows/release-tag-rc.yml
vendored
25
.github/workflows/release-tag-rc.yml
vendored
@@ -24,12 +24,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
- uses: ./.github/actions/node-setup
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
|
||||||
- run: make deps-frontend deps-backend
|
- run: make deps-frontend deps-backend
|
||||||
# xgo build
|
# xgo build
|
||||||
- run: make release
|
- run: make release
|
||||||
@@ -62,7 +57,7 @@ jobs:
|
|||||||
echo "Cleaned name is ${REF_NAME}"
|
echo "Cleaned name is ${REF_NAME}"
|
||||||
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
- name: configure aws
|
- name: configure aws
|
||||||
uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0
|
uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1
|
||||||
with:
|
with:
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
@@ -94,9 +89,9 @@ jobs:
|
|||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
- run: git fetch --unshallow --quiet --tags --force
|
||||||
- uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
- uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
||||||
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||||
- uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
- uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
@@ -109,7 +104,7 @@ jobs:
|
|||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
annotations: |
|
annotations: |
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||||
- uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
- uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||||
id: meta_rootless
|
id: meta_rootless
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
@@ -125,18 +120,18 @@ jobs:
|
|||||||
annotations: |
|
annotations: |
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Login to GHCR using PAT
|
- name: Login to GHCR using PAT
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build regular container image
|
- name: build regular container image
|
||||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
@@ -144,7 +139,7 @@ jobs:
|
|||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
annotations: ${{ steps.meta.outputs.annotations }}
|
||||||
- name: build rootless container image
|
- name: build rootless container image
|
||||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
|
|||||||
25
.github/workflows/release-tag-version.yml
vendored
25
.github/workflows/release-tag-version.yml
vendored
@@ -27,12 +27,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
- uses: ./.github/actions/node-setup
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
|
||||||
- run: make deps-frontend deps-backend
|
- run: make deps-frontend deps-backend
|
||||||
# xgo build
|
# xgo build
|
||||||
- run: make release
|
- run: make release
|
||||||
@@ -65,7 +60,7 @@ jobs:
|
|||||||
echo "Cleaned name is ${REF_NAME}"
|
echo "Cleaned name is ${REF_NAME}"
|
||||||
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
- name: configure aws
|
- name: configure aws
|
||||||
uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0
|
uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1
|
||||||
with:
|
with:
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
@@ -97,9 +92,9 @@ jobs:
|
|||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
- run: git fetch --unshallow --quiet --tags --force
|
||||||
- uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
- uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
||||||
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||||
- uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
- uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
@@ -116,7 +111,7 @@ jobs:
|
|||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
annotations: |
|
annotations: |
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||||
- uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
- uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||||
id: meta_rootless
|
id: meta_rootless
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
@@ -137,18 +132,18 @@ jobs:
|
|||||||
annotations: |
|
annotations: |
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Login to GHCR using PAT
|
- name: Login to GHCR using PAT
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build regular container image
|
- name: build regular container image
|
||||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
@@ -156,7 +151,7 @@ jobs:
|
|||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
annotations: ${{ steps.meta.outputs.annotations }}
|
||||||
- name: build rootless container image
|
- name: build rootless container image
|
||||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
|
|||||||
18
Makefile
18
Makefile
@@ -12,13 +12,13 @@ COMMA := ,
|
|||||||
XGO_VERSION := go-1.26.x
|
XGO_VERSION := go-1.26.x
|
||||||
|
|
||||||
AIR_PACKAGE ?= github.com/air-verse/air@v1.65.3 # renovate: datasource=go
|
AIR_PACKAGE ?= github.com/air-verse/air@v1.65.3 # renovate: datasource=go
|
||||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.7.0 # renovate: datasource=go
|
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.8.0 # renovate: datasource=go
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 # renovate: datasource=go
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 # renovate: datasource=go
|
||||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go
|
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go
|
||||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 # renovate: datasource=go
|
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 # renovate: datasource=go
|
||||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.34.1 # renovate: datasource=go
|
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.35.0 # renovate: datasource=go
|
||||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@v1.9.0 # renovate: datasource=go
|
XGO_PACKAGE ?= src.techknowlogick.com/xgo@v1.9.0 # renovate: datasource=go
|
||||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.4.0 # renovate: datasource=go
|
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.5.0 # renovate: datasource=go
|
||||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 # renovate: datasource=go
|
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 # renovate: datasource=go
|
||||||
SHELLCHECK_IMAGE ?= docker.io/koalaman/shellcheck:v0.11.0@sha256:61862eba1fcf09a484ebcc6feea46f1782532571a34ed51fedf90dd25f925a8d # renovate: datasource=docker
|
SHELLCHECK_IMAGE ?= docker.io/koalaman/shellcheck:v0.11.0@sha256:61862eba1fcf09a484ebcc6feea46f1782532571a34ed51fedf90dd25f925a8d # renovate: datasource=docker
|
||||||
|
|
||||||
@@ -231,7 +231,9 @@ endif
|
|||||||
generate-swagger: $(SWAGGER_SPEC) $(OPENAPI3_SPEC) ## generate the swagger spec from code comments
|
generate-swagger: $(SWAGGER_SPEC) $(OPENAPI3_SPEC) ## generate the swagger spec from code comments
|
||||||
|
|
||||||
$(SWAGGER_SPEC): $(GO_SOURCES) $(SWAGGER_SPEC_INPUT)
|
$(SWAGGER_SPEC): $(GO_SOURCES) $(SWAGGER_SPEC_INPUT)
|
||||||
$(GO) run $(SWAGGER_PACKAGE) generate spec --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)'
|
@output="$$($(GO) run $(SWAGGER_PACKAGE) generate spec --enable-allof-compounding --skip-enum-desc --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)' 2>&1)" || { printf '%s\n' "$$output" >&2; exit 1; }; \
|
||||||
|
warnings="$$(printf '%s\n' "$$output" | grep -v '^go: ')"; \
|
||||||
|
if [ -n "$$warnings" ]; then printf '%s\n' "$$warnings" >&2; exit 1; fi
|
||||||
|
|
||||||
.PHONY: swagger-check
|
.PHONY: swagger-check
|
||||||
swagger-check: generate-swagger
|
swagger-check: generate-swagger
|
||||||
@@ -246,9 +248,11 @@ swagger-check: generate-swagger
|
|||||||
swagger-validate: ## check if the swagger spec is valid
|
swagger-validate: ## check if the swagger spec is valid
|
||||||
@# swagger "validate" requires that the "basePath" must start with a slash, but we are using Golang template "{{...}}"
|
@# swagger "validate" requires that the "basePath" must start with a slash, but we are using Golang template "{{...}}"
|
||||||
@$(SED_INPLACE) -E -e 's|"basePath":( *)"(.*)"|"basePath":\1"/\2"|g' './$(SWAGGER_SPEC)' # add a prefix slash to basePath
|
@$(SED_INPLACE) -E -e 's|"basePath":( *)"(.*)"|"basePath":\1"/\2"|g' './$(SWAGGER_SPEC)' # add a prefix slash to basePath
|
||||||
@# FIXME: there are some warnings
|
@output="$$($(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)' 2>&1)"; status=$$?; \
|
||||||
$(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)'
|
$(SED_INPLACE) -E -e 's|"basePath":( *)"/(.*)"|"basePath":\1"\2"|g' './$(SWAGGER_SPEC)'; \
|
||||||
@$(SED_INPLACE) -E -e 's|"basePath":( *)"/(.*)"|"basePath":\1"\2"|g' './$(SWAGGER_SPEC)' # remove the prefix slash from basePath
|
printf '%s\n' "$$output" | grep -v '^go: '; \
|
||||||
|
[ $$status -eq 0 ] || exit $$status; \
|
||||||
|
case "$$output" in *WARNING:*) exit 1;; esac
|
||||||
|
|
||||||
.PHONY: generate-openapi3
|
.PHONY: generate-openapi3
|
||||||
generate-openapi3: $(OPENAPI3_SPEC) ## generate the OpenAPI 3.0 spec from the Swagger 2.0 spec
|
generate-openapi3: $(OPENAPI3_SPEC) ## generate the OpenAPI 3.0 spec from the Swagger 2.0 spec
|
||||||
|
|||||||
8
assets/codemirror-languages.json
generated
8
assets/codemirror-languages.json
generated
@@ -436,6 +436,7 @@
|
|||||||
"jsonl",
|
"jsonl",
|
||||||
"mcmeta",
|
"mcmeta",
|
||||||
"sarif",
|
"sarif",
|
||||||
|
"slnlaunch",
|
||||||
"tact",
|
"tact",
|
||||||
"tfstate",
|
"tfstate",
|
||||||
"topojson",
|
"topojson",
|
||||||
@@ -691,10 +692,17 @@
|
|||||||
"extensions": [
|
"extensions": [
|
||||||
"ini",
|
"ini",
|
||||||
"cnf",
|
"cnf",
|
||||||
|
"container",
|
||||||
"dof",
|
"dof",
|
||||||
"lektorproject",
|
"lektorproject",
|
||||||
|
"mount",
|
||||||
|
"network",
|
||||||
"prefs",
|
"prefs",
|
||||||
"properties",
|
"properties",
|
||||||
|
"service",
|
||||||
|
"socket",
|
||||||
|
"target",
|
||||||
|
"timer",
|
||||||
"url",
|
"url",
|
||||||
"conf"
|
"conf"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ func newUserCommand() *cli.Command {
|
|||||||
microcmdUserDelete(),
|
microcmdUserDelete(),
|
||||||
newUserGenerateAccessTokenCommand(),
|
newUserGenerateAccessTokenCommand(),
|
||||||
microcmdUserMustChangePassword(),
|
microcmdUserMustChangePassword(),
|
||||||
|
microcmdUserDisableTwoFactor(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
72
cmd/admin_user_disable_2fa.go
Normal file
72
cmd/admin_user_disable_2fa.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
auth_model "gitea.dev/models/auth"
|
||||||
|
user_model "gitea.dev/models/user"
|
||||||
|
"gitea.dev/modules/setting"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func microcmdUserDisableTwoFactor() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "disable-2fa",
|
||||||
|
Usage: "Disable two-factor authentication for a user",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "username",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Usage: "Username of the user to disable 2FA for",
|
||||||
|
},
|
||||||
|
&cli.Int64Flag{
|
||||||
|
Name: "id",
|
||||||
|
Usage: "ID of the user to disable 2FA for",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: runDisableTwoFactor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDisableTwoFactor(ctx context.Context, c *cli.Command) error {
|
||||||
|
if !c.IsSet("id") && !c.IsSet("username") {
|
||||||
|
return errors.New("either --id or --username must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !setting.IsInTesting {
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var user *user_model.User
|
||||||
|
var err error
|
||||||
|
if c.IsSet("id") {
|
||||||
|
user, err = user_model.GetUserByID(ctx, c.Int64("id"))
|
||||||
|
} else {
|
||||||
|
user, err = user_model.GetUserByName(ctx, c.String("username"))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// When both selectors are given, make sure they refer to the same user.
|
||||||
|
if c.IsSet("id") && c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) {
|
||||||
|
return fmt.Errorf("the user with id %d is %q, which does not match the provided username %q", user.ID, user.Name, c.String("username"))
|
||||||
|
}
|
||||||
|
|
||||||
|
totp, webAuthn, err := auth_model.DisableTwoFactor(ctx, user.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Disabled 2FA for user %q (removed %d TOTP and %d WebAuthn credential(s))\n", user.Name, totp, webAuthn)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
119
cmd/admin_user_disable_2fa_test.go
Normal file
119
cmd/admin_user_disable_2fa_test.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "gitea.dev/models/auth"
|
||||||
|
"gitea.dev/models/db"
|
||||||
|
"gitea.dev/models/unittest"
|
||||||
|
user_model "gitea.dev/models/user"
|
||||||
|
|
||||||
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDisableTwoFactorCommand(t *testing.T) {
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}, &auth_model.TwoFactor{}, &auth_model.WebAuthnCredential{}))
|
||||||
|
}()
|
||||||
|
|
||||||
|
t.Run("disable TOTP and WebAuthn", func(t *testing.T) {
|
||||||
|
require.NoError(t, microcmdUserCreate().Run(ctx, []string{"create", "--username", "tfuser", "--email", "tfuser@gitea.local", "--random-password"}))
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "tfuser"})
|
||||||
|
|
||||||
|
// Enroll TOTP.
|
||||||
|
tf := &auth_model.TwoFactor{UID: user.ID}
|
||||||
|
require.NoError(t, tf.SetSecret("test-secret"))
|
||||||
|
_, err := tf.GenerateScratchToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, auth_model.NewTwoFactor(ctx, tf))
|
||||||
|
|
||||||
|
// Register a WebAuthn credential.
|
||||||
|
_, err = auth_model.CreateCredential(ctx, user.ID, "test-key", &webauthn.Credential{ID: []byte("test-cred-id")})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
has, err := auth_model.HasTwoFactorOrWebAuthn(ctx, user.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, has)
|
||||||
|
|
||||||
|
require.NoError(t, microcmdUserDisableTwoFactor().Run(ctx, []string{"disable-2fa", "--username", "tfuser"}))
|
||||||
|
|
||||||
|
// Both factors must be gone afterwards.
|
||||||
|
has, err = auth_model.HasTwoFactorOrWebAuthn(ctx, user.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, has)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("disable by id", func(t *testing.T) {
|
||||||
|
require.NoError(t, microcmdUserCreate().Run(ctx, []string{"create", "--username", "iduser", "--email", "iduser@gitea.local", "--random-password"}))
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "iduser"})
|
||||||
|
|
||||||
|
tf := &auth_model.TwoFactor{UID: user.ID}
|
||||||
|
require.NoError(t, tf.SetSecret("test-secret"))
|
||||||
|
require.NoError(t, auth_model.NewTwoFactor(ctx, tf))
|
||||||
|
|
||||||
|
require.NoError(t, microcmdUserDisableTwoFactor().Run(ctx, []string{"disable-2fa", "--id", strconv.FormatInt(user.ID, 10)}))
|
||||||
|
|
||||||
|
has, err := auth_model.HasTwoFactorOrWebAuthn(ctx, user.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, has)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no enrollment is a no-op", func(t *testing.T) {
|
||||||
|
require.NoError(t, microcmdUserCreate().Run(ctx, []string{"create", "--username", "plainuser", "--email", "plainuser@gitea.local", "--random-password"}))
|
||||||
|
require.NoError(t, microcmdUserDisableTwoFactor().Run(ctx, []string{"disable-2fa", "--username", "plainuser"}))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("id and username must match when both given", func(t *testing.T) {
|
||||||
|
require.NoError(t, microcmdUserCreate().Run(ctx, []string{"create", "--username", "matchuser", "--email", "matchuser@gitea.local", "--random-password"}))
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "matchuser"})
|
||||||
|
id := strconv.FormatInt(user.ID, 10)
|
||||||
|
|
||||||
|
// Matching id + username is accepted.
|
||||||
|
require.NoError(t, microcmdUserDisableTwoFactor().Run(ctx, []string{"disable-2fa", "--id", id, "--username", "matchuser"}))
|
||||||
|
|
||||||
|
// Mismatched id + username is rejected.
|
||||||
|
cmd := microcmdUserDisableTwoFactor()
|
||||||
|
cmd.Writer, cmd.ErrWriter = io.Discard, io.Discard
|
||||||
|
err := cmd.Run(ctx, []string{"disable-2fa", "--id", id, "--username", "someotheruser"})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "does not match the provided username")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("failure cases", func(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "user does not exist",
|
||||||
|
args: []string{"disable-2fa", "--username", "nonexistentuser"},
|
||||||
|
expectedErr: "user does not exist",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "neither id nor username",
|
||||||
|
args: []string{"disable-2fa"},
|
||||||
|
expectedErr: "either --id or --username must be provided",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
cmd := microcmdUserDisableTwoFactor()
|
||||||
|
cmd.Writer, cmd.ErrWriter = io.Discard, io.Discard
|
||||||
|
err := cmd.Run(ctx, tc.args)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), tc.expectedErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1191,6 +1191,7 @@ LEVEL = Info
|
|||||||
;; Default source for the pull request title when opening a new PR.
|
;; Default source for the pull request title when opening a new PR.
|
||||||
;; "first-commit" uses the oldest commit's summary.
|
;; "first-commit" uses the oldest commit's summary.
|
||||||
;; "auto" uses commit's summary if the PR only has one commit, normalizes the branch name if multiple commits.
|
;; "auto" uses commit's summary if the PR only has one commit, normalizes the branch name if multiple commits.
|
||||||
|
;; "branch-name" always uses the PR's branch name.
|
||||||
;DEFAULT_TITLE_SOURCE = auto
|
;DEFAULT_TITLE_SOURCE = auto
|
||||||
;;
|
;;
|
||||||
;; Delay mergeable check until page view or API access, for pull requests that have not been updated in the specified days when their base branches get updated.
|
;; Delay mergeable check until page view or API access, for pull requests that have not been updated in the specified days when their base branches get updated.
|
||||||
@@ -3008,6 +3009,10 @@ LEVEL = Info
|
|||||||
;SCOPED_WORKFLOW_DIRS = .gitea/scoped_workflows
|
;SCOPED_WORKFLOW_DIRS = .gitea/scoped_workflows
|
||||||
;; Maximum number of attempts a single workflow run can have. Default value is 50.
|
;; Maximum number of attempts a single workflow run can have. Default value is 50.
|
||||||
;MAX_RERUN_ATTEMPTS = 50
|
;MAX_RERUN_ATTEMPTS = 50
|
||||||
|
;; Maximum number of runners that may run the task-assignment query concurrently, per Gitea instance.
|
||||||
|
;; Caps this instance's DB load when many runners poll at once; excess runners retry on their next poll.
|
||||||
|
;; In a multi-instance deployment the cluster-wide limit is this value times the number of instances. Default value is 16.
|
||||||
|
;MAX_CONCURRENT_TASK_PICKS = 16
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|||||||
@@ -95,8 +95,15 @@ However, if there are no objections from maintainers, the PR can be merged with
|
|||||||
### Commit messages
|
### Commit messages
|
||||||
|
|
||||||
Mergers are required to rewrite the PR title and the first comment (the summary) when necessary so the squash commit message is clear.
|
Mergers are required to rewrite the PR title and the first comment (the summary) when necessary so the squash commit message is clear.
|
||||||
|
Usually the Pull Request description and commit message body should not be empty, unless the title is already clear enough or the description would be a copy of the comments in code.
|
||||||
|
|
||||||
The final commit message should not hedge: replace phrases like `hopefully, <x> won't happen anymore` with definite wording.
|
The final commit message:
|
||||||
|
|
||||||
|
- should match the code changes.
|
||||||
|
- should only keep true co-authors, false-positive co-authors should be removed.
|
||||||
|
- should not hedge: replace phrases like `hopefully, <x> won't happen anymore` with definite wording.
|
||||||
|
- should not contain hidden information like `<!-- -->` or extra information after the description's divider `----`.
|
||||||
|
- should not contain unrelated contents (e.g.: Release Notes, Configuration, etc.) from a Renovate update PR.
|
||||||
|
|
||||||
#### PR Co-authors
|
#### PR Co-authors
|
||||||
|
|
||||||
@@ -158,9 +165,16 @@ Any account with write access (including bots and TOC members) **must** use [2FA
|
|||||||
Mergers are the maintainers who carry out the final merge of approved PRs. Their responsibilities, described throughout this guide, are:
|
Mergers are the maintainers who carry out the final merge of approved PRs. Their responsibilities, described throughout this guide, are:
|
||||||
|
|
||||||
- Merging PRs from the [merge queue](#getting-prs-merged) in order, once a PR has `lgtm/done`, no open discussions, and no merge conflicts.
|
- Merging PRs from the [merge queue](#getting-prs-merged) in order, once a PR has `lgtm/done`, no open discussions, and no merge conflicts.
|
||||||
- Rewriting the PR title and summary so the squash [commit message](#commit-messages) is clear, removing false-positive co-authors while keeping every true co-author.
|
- Rewriting the PR title and description prior to the merge, making the [commit message](#commit-messages) clear.\
|
||||||
|
In particular, mergers should edit the PR description.\
|
||||||
|
Mergers should **not** edit the actual commit message except to remove unnecessary information. Because of that, even if users are looking at the PR, they can understand what changed.
|
||||||
- Assigning the correct labels (including `type/…`) needed for changelog and backport decisions.
|
- Assigning the correct labels (including `type/…`) needed for changelog and backport decisions.
|
||||||
- Agreeing, together with the owners, on when a release is ready (see [release management](release-management.md)).
|
- Agreeing, together with the owners, on when a release is ready (see [release management](release-management.md)).
|
||||||
|
- Merging a PR also means the PR looks good to the merger and is approved by the merger.
|
||||||
|
|
||||||
|
If a merger violates these merge guides more than 3 times in the past 365 days
|
||||||
|
(e.g.: merge with unresolved reviews without TOC decision to ignore the review, merge with garbage commit messages),
|
||||||
|
they may lose their merging privileges for at least three months.
|
||||||
|
|
||||||
#### Becoming a merger
|
#### Becoming a merger
|
||||||
|
|
||||||
@@ -200,20 +214,20 @@ random.seed("Gitea TOC <YEAR> Election")
|
|||||||
random.choice([<CANDIDATE_1>, <CANDIDATE_2>, ...])
|
random.choice([<CANDIDATE_1>, <CANDIDATE_2>, ...])
|
||||||
```
|
```
|
||||||
|
|
||||||
The result of this script needs then to be published in the TOC election issue to ensure transparency of the process.
|
The result of this script needs then to be published in the TOC election issue to ensure transparency of the process.
|
||||||
|
|
||||||
### Current TOC members
|
### Current TOC members
|
||||||
|
|
||||||
- 2026-06-14 ~ 2026-12-31
|
- 2026-06-14 ~ 2026-12-31
|
||||||
- Company
|
- Company
|
||||||
- [Jason Song](https://gitea.com/wolfogre) <i@wolfogre.com>
|
- [Yu Tian](https://gitea.com/Zettat123) <zettat123@gmail.com>
|
||||||
- [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
|
- [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
|
||||||
- [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.com>
|
- [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.com>
|
||||||
- Community
|
- Community
|
||||||
- [bircni](https://gitea.com/bircni) <bircni@icloud.com>
|
- [bircni](https://gitea.com/bircni) <bircni@icloud.com>
|
||||||
- [delvh](https://gitea.com/delvh) <dev.lh@web.de>
|
- [delvh](https://gitea.com/delvh) <dev.lh@web.de>
|
||||||
- [TheFox0x7](https://gitea.com/TheFox0x7) <thefox0x7@gmail.com>
|
- [TheFox0x7](https://gitea.com/TheFox0x7) <thefox0x7@gmail.com>
|
||||||
|
|
||||||
|
|
||||||
### Previous TOC/owners members
|
### Previous TOC/owners members
|
||||||
|
|
||||||
@@ -227,7 +241,7 @@ Here's the history of the owners and the time they served:
|
|||||||
- [Andrew Thornton](https://gitea.com/zeripath) - [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
|
- [Andrew Thornton](https://gitea.com/zeripath) - [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
|
||||||
- [6543](https://gitea.com/6543) - 2023, 2025
|
- [6543](https://gitea.com/6543) - 2023, 2025
|
||||||
- [John Olheiser](https://gitea.com/jolheiser) - 2023, 2024
|
- [John Olheiser](https://gitea.com/jolheiser) - 2023, 2024
|
||||||
- [Jason Song](https://gitea.com/wolfogre) - 2023
|
- [Jason Song](https://gitea.com/wolfogre) - 2023, 2025
|
||||||
|
|
||||||
## Governance Compensation
|
## Governance Compensation
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import arrayFunc from 'eslint-plugin-array-func';
|
|
||||||
import comments from '@eslint-community/eslint-plugin-eslint-comments';
|
import comments from '@eslint-community/eslint-plugin-eslint-comments';
|
||||||
import deMorgan from 'eslint-plugin-de-morgan';
|
|
||||||
import globals from 'globals';
|
import globals from 'globals';
|
||||||
import importPlugin from 'eslint-plugin-import-x';
|
import importPlugin from 'eslint-plugin-import-x';
|
||||||
import playwright from 'eslint-plugin-playwright';
|
import playwright from 'eslint-plugin-playwright';
|
||||||
@@ -15,7 +13,6 @@ import vue from 'eslint-plugin-vue';
|
|||||||
import vueScopedCss from 'eslint-plugin-vue-scoped-css';
|
import vueScopedCss from 'eslint-plugin-vue-scoped-css';
|
||||||
import wc from 'eslint-plugin-wc';
|
import wc from 'eslint-plugin-wc';
|
||||||
import {defineConfig, globalIgnores} from 'eslint/config';
|
import {defineConfig, globalIgnores} from 'eslint/config';
|
||||||
import type {ESLint} from 'eslint';
|
|
||||||
|
|
||||||
import unescapedHtmlLiteral from './tools/eslint-rules/unescaped-html-literal.ts';
|
import unescapedHtmlLiteral from './tools/eslint-rules/unescaped-html-literal.ts';
|
||||||
|
|
||||||
@@ -64,10 +61,8 @@ export default defineConfig([
|
|||||||
'@eslint-community/eslint-comments': comments,
|
'@eslint-community/eslint-comments': comments,
|
||||||
'@stylistic': stylistic,
|
'@stylistic': stylistic,
|
||||||
'@typescript-eslint': typescriptPlugin.plugin,
|
'@typescript-eslint': typescriptPlugin.plugin,
|
||||||
'array-func': arrayFunc,
|
|
||||||
'de-morgan': deMorgan,
|
|
||||||
'gitea': {rules: {'unescaped-html-literal': unescapedHtmlLiteral}},
|
'gitea': {rules: {'unescaped-html-literal': unescapedHtmlLiteral}},
|
||||||
'import-x': importPlugin as unknown as ESLint.Plugin, // https://github.com/un-ts/eslint-plugin-import-x/issues/203
|
'import-x': importPlugin,
|
||||||
regexp,
|
regexp,
|
||||||
sonarjs,
|
sonarjs,
|
||||||
unicorn,
|
unicorn,
|
||||||
@@ -278,12 +273,6 @@ export default defineConfig([
|
|||||||
'@typescript-eslint/unified-signatures': [2],
|
'@typescript-eslint/unified-signatures': [2],
|
||||||
'accessor-pairs': [2],
|
'accessor-pairs': [2],
|
||||||
'array-callback-return': [2, {checkForEach: true}],
|
'array-callback-return': [2, {checkForEach: true}],
|
||||||
'array-func/avoid-reverse': [2],
|
|
||||||
'array-func/from-map': [2],
|
|
||||||
'array-func/no-unnecessary-this-arg': [2],
|
|
||||||
'array-func/prefer-array-from': [2],
|
|
||||||
'array-func/prefer-flat-map': [0], // handled by unicorn/prefer-array-flat-map
|
|
||||||
'array-func/prefer-flat': [0], // handled by unicorn/prefer-array-flat
|
|
||||||
'arrow-body-style': [0],
|
'arrow-body-style': [0],
|
||||||
'block-scoped-var': [2],
|
'block-scoped-var': [2],
|
||||||
'camelcase': [0],
|
'camelcase': [0],
|
||||||
@@ -294,8 +283,6 @@ export default defineConfig([
|
|||||||
'consistent-this': [0],
|
'consistent-this': [0],
|
||||||
'constructor-super': [2],
|
'constructor-super': [2],
|
||||||
'curly': [0],
|
'curly': [0],
|
||||||
'de-morgan/no-negated-conjunction': [2],
|
|
||||||
'de-morgan/no-negated-disjunction': [2],
|
|
||||||
'default-case-last': [2],
|
'default-case-last': [2],
|
||||||
'default-case': [0],
|
'default-case': [0],
|
||||||
'default-param-last': [0],
|
'default-param-last': [0],
|
||||||
@@ -745,6 +732,7 @@ export default defineConfig([
|
|||||||
'unicorn/consistent-json-file-read': [2],
|
'unicorn/consistent-json-file-read': [2],
|
||||||
'unicorn/consistent-optional-chaining': [2],
|
'unicorn/consistent-optional-chaining': [2],
|
||||||
'unicorn/consistent-template-literal-escape': [2],
|
'unicorn/consistent-template-literal-escape': [2],
|
||||||
|
'unicorn/consistent-tuple-labels': [2],
|
||||||
'unicorn/custom-error-definition': [0],
|
'unicorn/custom-error-definition': [0],
|
||||||
'unicorn/default-export-style': [2],
|
'unicorn/default-export-style': [2],
|
||||||
'unicorn/dom-node-dataset': [2, {preferAttributes: true}],
|
'unicorn/dom-node-dataset': [2, {preferAttributes: true}],
|
||||||
@@ -778,6 +766,7 @@ export default defineConfig([
|
|||||||
'unicorn/no-array-sort-for-min-max': [2],
|
'unicorn/no-array-sort-for-min-max': [2],
|
||||||
'unicorn/no-array-splice': [0],
|
'unicorn/no-array-splice': [0],
|
||||||
'unicorn/no-asterisk-prefix-in-documentation-comments': [0],
|
'unicorn/no-asterisk-prefix-in-documentation-comments': [0],
|
||||||
|
'unicorn/no-async-promise-finally': [2],
|
||||||
'unicorn/no-await-expression-member': [0],
|
'unicorn/no-await-expression-member': [0],
|
||||||
'unicorn/no-await-in-promise-methods': [2],
|
'unicorn/no-await-in-promise-methods': [2],
|
||||||
'unicorn/no-blob-to-file': [2],
|
'unicorn/no-blob-to-file': [2],
|
||||||
@@ -814,8 +803,10 @@ export default defineConfig([
|
|||||||
'unicorn/no-invalid-fetch-options': [2],
|
'unicorn/no-invalid-fetch-options': [2],
|
||||||
'unicorn/no-invalid-file-input-accept': [2],
|
'unicorn/no-invalid-file-input-accept': [2],
|
||||||
'unicorn/no-invalid-remove-event-listener': [2],
|
'unicorn/no-invalid-remove-event-listener': [2],
|
||||||
|
'unicorn/no-invalid-well-known-symbol-methods': [2],
|
||||||
'unicorn/no-keyword-prefix': [0],
|
'unicorn/no-keyword-prefix': [0],
|
||||||
'unicorn/no-late-current-target-access': [2],
|
'unicorn/no-late-current-target-access': [2],
|
||||||
|
'unicorn/no-late-event-control': [2],
|
||||||
'unicorn/no-lonely-if': [2],
|
'unicorn/no-lonely-if': [2],
|
||||||
'unicorn/no-loop-iterable-mutation': [2],
|
'unicorn/no-loop-iterable-mutation': [2],
|
||||||
'unicorn/no-magic-array-flat-depth': [0],
|
'unicorn/no-magic-array-flat-depth': [0],
|
||||||
@@ -852,9 +843,11 @@ export default defineConfig([
|
|||||||
'unicorn/no-uncalled-method': [2],
|
'unicorn/no-uncalled-method': [2],
|
||||||
'unicorn/no-undeclared-class-members': [2],
|
'unicorn/no-undeclared-class-members': [2],
|
||||||
'unicorn/no-unnecessary-array-flat-depth': [2],
|
'unicorn/no-unnecessary-array-flat-depth': [2],
|
||||||
|
'unicorn/no-unnecessary-array-flat-map': [2],
|
||||||
'unicorn/no-unnecessary-array-splice-count': [2],
|
'unicorn/no-unnecessary-array-splice-count': [2],
|
||||||
'unicorn/no-unnecessary-await': [2],
|
'unicorn/no-unnecessary-await': [2],
|
||||||
'unicorn/no-unnecessary-boolean-comparison': [2],
|
'unicorn/no-unnecessary-boolean-comparison': [2],
|
||||||
|
'unicorn/no-unnecessary-fetch-options': [0],
|
||||||
'unicorn/no-unnecessary-global-this': [0],
|
'unicorn/no-unnecessary-global-this': [0],
|
||||||
'unicorn/no-unnecessary-nested-ternary': [2],
|
'unicorn/no-unnecessary-nested-ternary': [2],
|
||||||
'unicorn/no-unnecessary-polyfills': [2],
|
'unicorn/no-unnecessary-polyfills': [2],
|
||||||
@@ -867,6 +860,7 @@ export default defineConfig([
|
|||||||
'unicorn/no-unreadable-object-destructuring': [0],
|
'unicorn/no-unreadable-object-destructuring': [0],
|
||||||
'unicorn/no-unsafe-buffer-conversion': [2],
|
'unicorn/no-unsafe-buffer-conversion': [2],
|
||||||
'unicorn/no-unsafe-dom-html': [0],
|
'unicorn/no-unsafe-dom-html': [0],
|
||||||
|
'unicorn/no-unsafe-promise-all-settled-values': [2],
|
||||||
'unicorn/no-unsafe-property-key': [0],
|
'unicorn/no-unsafe-property-key': [0],
|
||||||
'unicorn/no-unsafe-string-replacement': [0],
|
'unicorn/no-unsafe-string-replacement': [0],
|
||||||
'unicorn/no-unused-array-method-return': [2],
|
'unicorn/no-unused-array-method-return': [2],
|
||||||
@@ -896,13 +890,17 @@ export default defineConfig([
|
|||||||
'unicorn/number-literal-case': [0],
|
'unicorn/number-literal-case': [0],
|
||||||
'unicorn/numeric-separators-style': [0],
|
'unicorn/numeric-separators-style': [0],
|
||||||
'unicorn/operator-assignment': [2],
|
'unicorn/operator-assignment': [2],
|
||||||
|
'unicorn/prefer-abort-signal-any': [2],
|
||||||
|
'unicorn/prefer-abort-signal-timeout': [2],
|
||||||
'unicorn/prefer-add-event-listener': [2],
|
'unicorn/prefer-add-event-listener': [2],
|
||||||
'unicorn/prefer-add-event-listener-options': [2],
|
'unicorn/prefer-add-event-listener-options': [2],
|
||||||
|
'unicorn/prefer-aggregate-error': [2],
|
||||||
'unicorn/prefer-array-find': [0], // handled by @typescript-eslint/prefer-find
|
'unicorn/prefer-array-find': [0], // handled by @typescript-eslint/prefer-find
|
||||||
'unicorn/prefer-array-flat': [2],
|
'unicorn/prefer-array-flat': [2],
|
||||||
'unicorn/prefer-array-flat-map': [2],
|
'unicorn/prefer-array-flat-map': [2],
|
||||||
'unicorn/prefer-array-from-async': [2],
|
'unicorn/prefer-array-from-async': [2],
|
||||||
'unicorn/prefer-array-from-map': [2],
|
'unicorn/prefer-array-from-map': [2],
|
||||||
|
'unicorn/prefer-array-from-range': [2],
|
||||||
'unicorn/prefer-array-index-of': [2],
|
'unicorn/prefer-array-index-of': [2],
|
||||||
'unicorn/prefer-array-iterable-methods': [2],
|
'unicorn/prefer-array-iterable-methods': [2],
|
||||||
'unicorn/prefer-array-last-methods': [2],
|
'unicorn/prefer-array-last-methods': [2],
|
||||||
@@ -912,6 +910,7 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-await': [2],
|
'unicorn/prefer-await': [2],
|
||||||
'unicorn/prefer-bigint-literals': [2],
|
'unicorn/prefer-bigint-literals': [2],
|
||||||
'unicorn/prefer-blob-reading-methods': [2],
|
'unicorn/prefer-blob-reading-methods': [2],
|
||||||
|
'unicorn/prefer-block-statement-over-iife': [2],
|
||||||
'unicorn/prefer-boolean-return': [2],
|
'unicorn/prefer-boolean-return': [2],
|
||||||
'unicorn/prefer-class-fields': [2],
|
'unicorn/prefer-class-fields': [2],
|
||||||
'unicorn/prefer-classlist-toggle': [2],
|
'unicorn/prefer-classlist-toggle': [2],
|
||||||
@@ -924,15 +923,18 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-dom-node-append': [2],
|
'unicorn/prefer-dom-node-append': [2],
|
||||||
'unicorn/prefer-dom-node-html-methods': [0],
|
'unicorn/prefer-dom-node-html-methods': [0],
|
||||||
'unicorn/prefer-dom-node-remove': [2],
|
'unicorn/prefer-dom-node-remove': [2],
|
||||||
|
'unicorn/prefer-dom-node-replace-children': [2],
|
||||||
'unicorn/prefer-dom-node-text-content': [2],
|
'unicorn/prefer-dom-node-text-content': [2],
|
||||||
'unicorn/prefer-early-return': [0],
|
'unicorn/prefer-early-return': [0],
|
||||||
'unicorn/prefer-else-if': [2],
|
'unicorn/prefer-else-if': [2],
|
||||||
|
'unicorn/prefer-error-is-error': [0],
|
||||||
'unicorn/prefer-event-target': [2],
|
'unicorn/prefer-event-target': [2],
|
||||||
'unicorn/prefer-export-from': [0],
|
'unicorn/prefer-export-from': [0],
|
||||||
'unicorn/prefer-flat-math-min-max': [2],
|
'unicorn/prefer-flat-math-min-max': [2],
|
||||||
'unicorn/prefer-get-or-insert-computed': [2],
|
'unicorn/prefer-get-or-insert-computed': [2],
|
||||||
'unicorn/prefer-global-number-constants': [2],
|
'unicorn/prefer-global-number-constants': [2],
|
||||||
'unicorn/prefer-global-this': [0],
|
'unicorn/prefer-global-this': [0],
|
||||||
|
'unicorn/prefer-group-by': [2],
|
||||||
'unicorn/prefer-has-check': [2],
|
'unicorn/prefer-has-check': [2],
|
||||||
'unicorn/prefer-hoisting-branch-code': [2],
|
'unicorn/prefer-hoisting-branch-code': [2],
|
||||||
'unicorn/prefer-https': [0], // false-positives on namespace and schema URIs
|
'unicorn/prefer-https': [0], // false-positives on namespace and schema URIs
|
||||||
@@ -942,6 +944,7 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-includes-over-repeated-comparisons': [0], // too opinionated
|
'unicorn/prefer-includes-over-repeated-comparisons': [0], // too opinionated
|
||||||
'unicorn/prefer-iterable-in-constructor': [2],
|
'unicorn/prefer-iterable-in-constructor': [2],
|
||||||
'unicorn/prefer-iterator-concat': [0], // too opinionated
|
'unicorn/prefer-iterator-concat': [0], // too opinionated
|
||||||
|
'unicorn/prefer-iterator-helpers': [2],
|
||||||
'unicorn/prefer-iterator-to-array': [2],
|
'unicorn/prefer-iterator-to-array': [2],
|
||||||
'unicorn/prefer-iterator-to-array-at-end': [2],
|
'unicorn/prefer-iterator-to-array-at-end': [2],
|
||||||
'unicorn/prefer-keyboard-event-key': [2],
|
'unicorn/prefer-keyboard-event-key': [2],
|
||||||
@@ -966,9 +969,11 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-object-destructuring-defaults': [2],
|
'unicorn/prefer-object-destructuring-defaults': [2],
|
||||||
'unicorn/prefer-object-from-entries': [2],
|
'unicorn/prefer-object-from-entries': [2],
|
||||||
'unicorn/prefer-object-iterable-methods': [2],
|
'unicorn/prefer-object-iterable-methods': [2],
|
||||||
|
'unicorn/prefer-observer-apis': [2],
|
||||||
'unicorn/prefer-optional-catch-binding': [2],
|
'unicorn/prefer-optional-catch-binding': [2],
|
||||||
'unicorn/prefer-path2d': [2],
|
'unicorn/prefer-path2d': [2],
|
||||||
'unicorn/prefer-private-class-fields': [0],
|
'unicorn/prefer-private-class-fields': [0],
|
||||||
|
'unicorn/prefer-promise-try': [2],
|
||||||
'unicorn/prefer-promise-with-resolvers': [2],
|
'unicorn/prefer-promise-with-resolvers': [2],
|
||||||
'unicorn/prefer-prototype-methods': [0],
|
'unicorn/prefer-prototype-methods': [0],
|
||||||
'unicorn/prefer-query-selector': [2],
|
'unicorn/prefer-query-selector': [2],
|
||||||
@@ -979,10 +984,12 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-response-static-json': [2],
|
'unicorn/prefer-response-static-json': [2],
|
||||||
'unicorn/prefer-scoped-selector': [0],
|
'unicorn/prefer-scoped-selector': [0],
|
||||||
'unicorn/prefer-set-has': [0],
|
'unicorn/prefer-set-has': [0],
|
||||||
|
'unicorn/prefer-set-methods': [0],
|
||||||
'unicorn/prefer-set-size': [2],
|
'unicorn/prefer-set-size': [2],
|
||||||
'unicorn/prefer-short-arrow-method': [2],
|
'unicorn/prefer-short-arrow-method': [2],
|
||||||
'unicorn/prefer-simple-condition-first': [0],
|
'unicorn/prefer-simple-condition-first': [0],
|
||||||
'unicorn/prefer-simple-sort-comparator': [2],
|
'unicorn/prefer-simple-sort-comparator': [2],
|
||||||
|
'unicorn/prefer-simplified-conditions': [2],
|
||||||
'unicorn/prefer-single-array-predicate': [2],
|
'unicorn/prefer-single-array-predicate': [2],
|
||||||
'unicorn/prefer-single-call': [2],
|
'unicorn/prefer-single-call': [2],
|
||||||
'unicorn/prefer-single-object-destructuring': [2],
|
'unicorn/prefer-single-object-destructuring': [2],
|
||||||
@@ -1002,6 +1009,7 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-switch': [0],
|
'unicorn/prefer-switch': [0],
|
||||||
'unicorn/prefer-temporal': [0],
|
'unicorn/prefer-temporal': [0],
|
||||||
'unicorn/prefer-ternary': [0],
|
'unicorn/prefer-ternary': [0],
|
||||||
|
'unicorn/prefer-toggle-attribute': [2],
|
||||||
'unicorn/prefer-top-level-await': [0],
|
'unicorn/prefer-top-level-await': [0],
|
||||||
'unicorn/prefer-type-error': [0],
|
'unicorn/prefer-type-error': [0],
|
||||||
'unicorn/prefer-type-literal-last': [0],
|
'unicorn/prefer-type-literal-last': [0],
|
||||||
@@ -1010,6 +1018,7 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-unicode-code-point-escapes': [0],
|
'unicorn/prefer-unicode-code-point-escapes': [0],
|
||||||
'unicorn/prefer-url-can-parse': [2],
|
'unicorn/prefer-url-can-parse': [2],
|
||||||
'unicorn/prefer-url-href': [2],
|
'unicorn/prefer-url-href': [2],
|
||||||
|
'unicorn/prefer-url-search-parameters': [2],
|
||||||
'unicorn/prefer-while-loop-condition': [2],
|
'unicorn/prefer-while-loop-condition': [2],
|
||||||
'unicorn/prevent-abbreviations': [0],
|
'unicorn/prevent-abbreviations': [0],
|
||||||
'unicorn/relative-url-style': [2],
|
'unicorn/relative-url-style': [2],
|
||||||
|
|||||||
6
flake.lock
generated
6
flake.lock
generated
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776877367,
|
"lastModified": 1782723713,
|
||||||
"narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=",
|
"narHash": "sha256-oPXCU/SSUokcGaJREHibG1CBX3+s/W7orDWQOZDsEeQ=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "0726a0ecb6d4e08f6adced58726b95db924cef57",
|
"rev": "b5aa0fbd538984f6e3d201be0005b4463d8b09f8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
# only bump toolchain versions here
|
# only bump toolchain versions here
|
||||||
go = pkgs.go_1_26;
|
go = pkgs.go_1_26;
|
||||||
nodejs = pkgs.nodejs_24;
|
nodejs = pkgs.nodejs_26;
|
||||||
python3 = pkgs.python314;
|
python3 = pkgs.python314;
|
||||||
pnpm = pkgs.pnpm_10;
|
pnpm = pkgs.pnpm_10;
|
||||||
|
|
||||||
|
|||||||
20
go.mod
20
go.mod
@@ -13,7 +13,7 @@ require (
|
|||||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
||||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
||||||
gitea.dev/actions-proto-go v0.6.0
|
gitea.dev/actions-proto-go v0.6.0
|
||||||
gitea.dev/sdk v1.1.0
|
gitea.dev/sdk v1.2.0
|
||||||
github.com/42wim/httpsig v1.2.4
|
github.com/42wim/httpsig v1.2.4
|
||||||
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
|
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0
|
||||||
@@ -24,8 +24,8 @@ require (
|
|||||||
github.com/PuerkitoBio/goquery v1.12.0
|
github.com/PuerkitoBio/goquery v1.12.0
|
||||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0
|
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0
|
||||||
github.com/alecthomas/chroma/v2 v2.27.0
|
github.com/alecthomas/chroma/v2 v2.27.0
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.24
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.25
|
||||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.34.4
|
github.com/aws/aws-sdk-go-v2/service/codecommit v1.34.5
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||||
github.com/blevesearch/bleve/v2 v2.6.0
|
github.com/blevesearch/bleve/v2 v2.6.0
|
||||||
github.com/bohde/codel v0.2.0
|
github.com/bohde/codel v0.2.0
|
||||||
@@ -68,8 +68,8 @@ require (
|
|||||||
github.com/huandu/xstrings v1.5.0
|
github.com/huandu/xstrings v1.5.0
|
||||||
github.com/jhillyerd/enmime/v2 v2.4.1
|
github.com/jhillyerd/enmime/v2 v2.4.1
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||||
github.com/klauspost/compress v1.18.6
|
github.com/klauspost/compress v1.19.0
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0
|
github.com/klauspost/cpuid/v2 v2.4.0
|
||||||
github.com/lib/pq v1.12.3
|
github.com/lib/pq v1.12.3
|
||||||
github.com/markbates/goth v1.82.0
|
github.com/markbates/goth v1.82.0
|
||||||
github.com/mattn/go-isatty v0.0.22
|
github.com/mattn/go-isatty v0.0.22
|
||||||
@@ -78,7 +78,7 @@ require (
|
|||||||
github.com/mholt/archives v0.1.5
|
github.com/mholt/archives v0.1.5
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27
|
github.com/microcosm-cc/bluemonday v1.0.27
|
||||||
github.com/microsoft/go-mssqldb v1.10.0
|
github.com/microsoft/go-mssqldb v1.10.0
|
||||||
github.com/minio/minio-go/v7 v7.2.0
|
github.com/minio/minio-go/v7 v7.2.1
|
||||||
github.com/msteinert/pam/v2 v2.1.0
|
github.com/msteinert/pam/v2 v2.1.0
|
||||||
github.com/niklasfasching/go-org v1.9.1
|
github.com/niklasfasching/go-org v1.9.1
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
@@ -96,12 +96,12 @@ require (
|
|||||||
github.com/tstranex/u2f v1.0.0
|
github.com/tstranex/u2f v1.0.0
|
||||||
github.com/ulikunitz/xz v0.5.15
|
github.com/ulikunitz/xz v0.5.15
|
||||||
github.com/urfave/cli-docs/v3 v3.1.0
|
github.com/urfave/cli-docs/v3 v3.1.0
|
||||||
github.com/urfave/cli/v3 v3.10.0
|
github.com/urfave/cli/v3 v3.10.1
|
||||||
github.com/wneessen/go-mail v0.7.3
|
github.com/wneessen/go-mail v0.7.3
|
||||||
github.com/yohcop/openid-go v1.0.1
|
github.com/yohcop/openid-go v1.0.1
|
||||||
github.com/yuin/goldmark v1.8.2
|
github.com/yuin/goldmark v1.8.2
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||||
gitlab.com/gitlab-org/api/client-go/v2 v2.42.0
|
gitlab.com/gitlab-org/api/client-go/v2 v2.44.0
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.5
|
go.yaml.in/yaml/v4 v4.0.0-rc.5
|
||||||
golang.org/x/crypto v0.53.0
|
golang.org/x/crypto v0.53.0
|
||||||
golang.org/x/image v0.43.0
|
golang.org/x/image v0.43.0
|
||||||
@@ -111,14 +111,14 @@ require (
|
|||||||
golang.org/x/sync v0.21.0
|
golang.org/x/sync v0.21.0
|
||||||
golang.org/x/sys v0.46.0
|
golang.org/x/sys v0.46.0
|
||||||
golang.org/x/text v0.38.0
|
golang.org/x/text v0.38.0
|
||||||
google.golang.org/grpc v1.81.1
|
google.golang.org/grpc v1.82.0
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
gopkg.in/ini.v1 v1.67.3
|
gopkg.in/ini.v1 v1.67.3
|
||||||
modernc.org/sqlite v1.53.0
|
modernc.org/sqlite v1.53.0
|
||||||
mvdan.cc/xurls/v2 v2.6.0
|
mvdan.cc/xurls/v2 v2.6.0
|
||||||
strk.kbt.io/projects/go/libravatar v0.0.0-20260301104140-add494e31dab
|
strk.kbt.io/projects/go/libravatar v0.0.0-20260301104140-add494e31dab
|
||||||
xorm.io/builder v0.3.13
|
xorm.io/builder v0.3.13
|
||||||
xorm.io/xorm v1.3.11
|
xorm.io/xorm v1.4.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
42
go.sum
42
go.sum
@@ -28,8 +28,8 @@ gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGq
|
|||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||||
gitea.dev/actions-proto-go v0.6.0 h1:gjllYQ5vmwlkqOeofTQu5qKTZpmf7kWsafoHvoPCSzY=
|
gitea.dev/actions-proto-go v0.6.0 h1:gjllYQ5vmwlkqOeofTQu5qKTZpmf7kWsafoHvoPCSzY=
|
||||||
gitea.dev/actions-proto-go v0.6.0/go.mod h1:p4RX+D9oqiEEzzkPMXscw2CmaGuYFPWFc6xIOmDNDqs=
|
gitea.dev/actions-proto-go v0.6.0/go.mod h1:p4RX+D9oqiEEzzkPMXscw2CmaGuYFPWFc6xIOmDNDqs=
|
||||||
gitea.dev/sdk v1.1.0 h1:wLlz03WkLEiXa2bQpO1JQBTlYf7tQI2neYtZK1kU+TE=
|
gitea.dev/sdk v1.2.0 h1:avRtJl/nKCGispgSalo9czoZM9Rto1awnE0caNAoXGo=
|
||||||
gitea.dev/sdk v1.1.0/go.mod h1:Zfl+EZXdsGGCLkryDfsmvYrQo6GKMl4U3BJA8Beu+cs=
|
gitea.dev/sdk v1.2.0/go.mod h1:rfh5oNdIK24cbCREwIn1tqWKQW+IICXFGWJyebuOAOE=
|
||||||
github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU=
|
github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU=
|
||||||
github.com/42wim/httpsig v1.2.4/go.mod h1:yKsYfSyTBEohkPik224QPFylmzEBtda/kjyIAJjh3ps=
|
github.com/42wim/httpsig v1.2.4/go.mod h1:yKsYfSyTBEohkPik224QPFylmzEBtda/kjyIAJjh3ps=
|
||||||
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432 h1:3Fcz1QzlS7Jv4FT2KI3cHNSZL+KPN3dXxurn9f3YL/Y=
|
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432 h1:3Fcz1QzlS7Jv4FT2KI3cHNSZL+KPN3dXxurn9f3YL/Y=
|
||||||
@@ -94,14 +94,14 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
|
|||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.42.0 h1:XvXMJTkFQtpBKIWZnmr9ZEOc2InWM2yldjXEJ/bymhA=
|
github.com/aws/aws-sdk-go-v2 v1.42.0 h1:XvXMJTkFQtpBKIWZnmr9ZEOc2InWM2yldjXEJ/bymhA=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.42.0/go.mod h1:27+ACypSLljLAEKsCYOmrjKh83vuTRkuAe9Uv/3A4bg=
|
github.com/aws/aws-sdk-go-v2 v1.42.0/go.mod h1:27+ACypSLljLAEKsCYOmrjKh83vuTRkuAe9Uv/3A4bg=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.24 h1:2hQqYCV9yqyePQ9o6dCrZc/zO8U3TwPr9mIKlZnPu/I=
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.25 h1:TzPVjfUZ1hsKafvYE+DIzKXIik2KufQxsPHanlkttbo=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.24/go.mod h1:IDwpACtwqHLISdzfwUUNq4P9DsB/h5BLg4FwJPNfqFY=
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.25/go.mod h1:K4hw0buguVvtC74HnVfTRr0LzQQHAWPqJbBU9QGk2Pg=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 h1:f3vKqSo13fhTYb+JEcXwXefZQE26I1FB5eTSniU67ko=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 h1:f3vKqSo13fhTYb+JEcXwXefZQE26I1FB5eTSniU67ko=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29/go.mod h1:MzoLFUArKGpGD+ukmPiTPG1X5x4o6M2kq4v2dr1FiEc=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29/go.mod h1:MzoLFUArKGpGD+ukmPiTPG1X5x4o6M2kq4v2dr1FiEc=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 h1:RdwIf/CuUsvJX3RgJagbOyotl/cxoLY4xviKuE7p2GY=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 h1:RdwIf/CuUsvJX3RgJagbOyotl/cxoLY4xviKuE7p2GY=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29/go.mod h1:71wt8W2EgswdZy9Mf9KNnzxZ3TiZlv4caKghPktDOkA=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29/go.mod h1:71wt8W2EgswdZy9Mf9KNnzxZ3TiZlv4caKghPktDOkA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.34.4 h1:Uu+wqrOXozYYvaxcNIqjFsMTjoIJIZDN3R0f70ZIjyQ=
|
github.com/aws/aws-sdk-go-v2/service/codecommit v1.34.5 h1:mY0qCJuWfbRxok5sRkGxehMGshSYAVIskDvPE4zIZwM=
|
||||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.34.4/go.mod h1:pYrBdL1tMTZO7PaKRsa1cTUB8HtQh3fFM3zJHGhTQcE=
|
github.com/aws/aws-sdk-go-v2/service/codecommit v1.34.5/go.mod h1:pYrBdL1tMTZO7PaKRsa1cTUB8HtQh3fFM3zJHGhTQcE=
|
||||||
github.com/aws/smithy-go v1.27.2 h1:y9NPmSE6am6LjEFPfqHqG/jJk7AauQvhCJONKh7kpzk=
|
github.com/aws/smithy-go v1.27.2 h1:y9NPmSE6am6LjEFPfqHqG/jJk7AauQvhCJONKh7kpzk=
|
||||||
github.com/aws/smithy-go v1.27.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
github.com/aws/smithy-go v1.27.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
@@ -413,8 +413,6 @@ github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pw
|
|||||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
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/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||||
github.com/graph-gophers/graphql-go v1.10.2 h1:HXu6Wu5klCH4ALn1fQHVI20cjEIa4wftavHIgbLA4Fo=
|
|
||||||
github.com/graph-gophers/graphql-go v1.10.2/go.mod h1:AsADheC4CCFwd8n1/QbkduTlHgYYMsRgtPihYVAlEsk=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@@ -471,12 +469,12 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C
|
|||||||
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
|
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
|
||||||
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ=
|
||||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||||
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
|
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
|
||||||
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
|
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
|
||||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
@@ -534,8 +532,8 @@ github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI
|
|||||||
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
github.com/minio/minio-go/v7 v7.2.0 h1:RCJM0R1XOsRs+A3x3UCaf3ZYbByDaLjFeAi+YCQEPhs=
|
github.com/minio/minio-go/v7 v7.2.1 h1:PfBfwvKB/MmqyN8Vb1G9voWisaM9OrLv+WwOvMwS9Dw=
|
||||||
github.com/minio/minio-go/v7 v7.2.0/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0=
|
github.com/minio/minio-go/v7 v7.2.1/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0=
|
||||||
github.com/minio/minlz v1.1.1 h1:OGmft1V6AnI/Wme332U6bhG54nxEan+VFgkD7lat4KM=
|
github.com/minio/minlz v1.1.1 h1:OGmft1V6AnI/Wme332U6bhG54nxEan+VFgkD7lat4KM=
|
||||||
github.com/minio/minlz v1.1.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
github.com/minio/minlz v1.1.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
@@ -709,8 +707,8 @@ github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
|
|||||||
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
|
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
|
||||||
github.com/urfave/cli-docs/v3 v3.1.0 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7vsKw=
|
github.com/urfave/cli-docs/v3 v3.1.0 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7vsKw=
|
||||||
github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to=
|
github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to=
|
||||||
github.com/urfave/cli/v3 v3.10.0 h1:0aU8yOObVDMkM13Cj4G+zb4P0PdeJMec65f81Ak1ioM=
|
github.com/urfave/cli/v3 v3.10.1 h1:7Kx9H50hrHbRbyxgO1KP6/BcbiGRz0uYh5YyQ30JEEY=
|
||||||
github.com/urfave/cli/v3 v3.10.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
github.com/urfave/cli/v3 v3.10.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/wneessen/go-mail v0.7.3 h1:g3DravXC5SMlVdboFrQA8Jx95A8sOzoBeS5F+vzNRK0=
|
github.com/wneessen/go-mail v0.7.3 h1:g3DravXC5SMlVdboFrQA8Jx95A8sOzoBeS5F+vzNRK0=
|
||||||
github.com/wneessen/go-mail v0.7.3/go.mod h1:QGhBX0yNbc1J+Mkjcu7z2rpj4B4l+BmDY8gYznPC9sk=
|
github.com/wneessen/go-mail v0.7.3/go.mod h1:QGhBX0yNbc1J+Mkjcu7z2rpj4B4l+BmDY8gYznPC9sk=
|
||||||
@@ -740,8 +738,8 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
|||||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||||
gitlab.com/gitlab-org/api/client-go/v2 v2.42.0 h1:Bq5YIYgUJVbt4Hbh7ibBwNR4SNEafsyDVhIXl7dXDdg=
|
gitlab.com/gitlab-org/api/client-go/v2 v2.44.0 h1:Vtv2WKC8p9BAygu5VCZlZUwDhTQ7UMlS3PErXyjUmeY=
|
||||||
gitlab.com/gitlab-org/api/client-go/v2 v2.42.0/go.mod h1:SKUbKSS59KPt6WeGNJoYF8HDaf/rFMUSITlftj/HkLg=
|
gitlab.com/gitlab-org/api/client-go/v2 v2.44.0/go.mod h1:pTbeBowtVA+0/ZExWEZYUGOrpu5qlRN5ZyOUf27BnVY=
|
||||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||||
@@ -898,8 +896,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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-20260610212136-7ab31c22f7ad h1:45WmJvIV6C2+O/jjLkPUH+F3aOj/1miDoU2DD0+NWbg=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad h1:45WmJvIV6C2+O/jjLkPUH+F3aOj/1miDoU2DD0+NWbg=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
google.golang.org/grpc v1.82.0 h1:vguDnZUPjE26w09A63VoxZPnvPjB5Riyc0mkXPFmAIU=
|
||||||
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
google.golang.org/grpc v1.82.0/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@@ -964,5 +962,5 @@ strk.kbt.io/projects/go/libravatar v0.0.0-20260301104140-add494e31dab h1:3IZDVyI
|
|||||||
strk.kbt.io/projects/go/libravatar v0.0.0-20260301104140-add494e31dab/go.mod h1:FJGmPh3vz9jSos1L/F91iAgnC/aejc0wIIrF2ZwJxdY=
|
strk.kbt.io/projects/go/libravatar v0.0.0-20260301104140-add494e31dab/go.mod h1:FJGmPh3vz9jSos1L/F91iAgnC/aejc0wIIrF2ZwJxdY=
|
||||||
xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
|
xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
|
||||||
xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||||
xorm.io/xorm v1.3.11 h1:i4tlVUASogb0ZZFJHA7dZqoRU2pUpUsutnNdaOlFyMI=
|
xorm.io/xorm v1.4.1 h1:m7QlNd0eBGb31IV4Q/ow0Du83rtdC1CiwlvJZGvYde8=
|
||||||
xorm.io/xorm v1.3.11/go.mod h1:cs0ePc8O4a0jD78cNvD+0VFwhqotTvLQZv372QsDw7Q=
|
xorm.io/xorm v1.4.1/go.mod h1:cs0ePc8O4a0jD78cNvD+0VFwhqotTvLQZv372QsDw7Q=
|
||||||
|
|||||||
@@ -135,8 +135,8 @@ type StatusInfo struct {
|
|||||||
|
|
||||||
// GetStatusInfoList returns a slice of StatusInfo
|
// GetStatusInfoList returns a slice of StatusInfo
|
||||||
func GetStatusInfoList(ctx context.Context, lang translation.Locale) []StatusInfo {
|
func GetStatusInfoList(ctx context.Context, lang translation.Locale) []StatusInfo {
|
||||||
// same as those in aggregateJobStatus
|
// same as those in aggregateJobStatus (StatusUnknown excluded; it's the "shouldn't happen" fallback)
|
||||||
allStatus := []Status{StatusSuccess, StatusFailure, StatusWaiting, StatusRunning, StatusCancelling}
|
allStatus := []Status{StatusSuccess, StatusFailure, StatusCancelled, StatusSkipped, StatusWaiting, StatusRunning, StatusBlocked, StatusCancelling}
|
||||||
statusInfoList := make([]StatusInfo, 0, len(allStatus))
|
statusInfoList := make([]StatusInfo, 0, len(allStatus))
|
||||||
for _, s := range allStatus {
|
for _, s := range allStatus {
|
||||||
statusInfoList = append(statusInfoList, StatusInfo{
|
statusInfoList = append(statusInfoList, StatusInfo{
|
||||||
|
|||||||
@@ -73,8 +73,11 @@ func TestGetStatusInfoList(t *testing.T) {
|
|||||||
assert.Equal(t, []StatusInfo{
|
assert.Equal(t, []StatusInfo{
|
||||||
{Status: int(StatusSuccess), StatusName: StatusSuccess.String(), DisplayedStatus: "actions.status.success"},
|
{Status: int(StatusSuccess), StatusName: StatusSuccess.String(), DisplayedStatus: "actions.status.success"},
|
||||||
{Status: int(StatusFailure), StatusName: StatusFailure.String(), DisplayedStatus: "actions.status.failure"},
|
{Status: int(StatusFailure), StatusName: StatusFailure.String(), DisplayedStatus: "actions.status.failure"},
|
||||||
|
{Status: int(StatusCancelled), StatusName: StatusCancelled.String(), DisplayedStatus: "actions.status.cancelled"},
|
||||||
|
{Status: int(StatusSkipped), StatusName: StatusSkipped.String(), DisplayedStatus: "actions.status.skipped"},
|
||||||
{Status: int(StatusWaiting), StatusName: StatusWaiting.String(), DisplayedStatus: "actions.status.waiting"},
|
{Status: int(StatusWaiting), StatusName: StatusWaiting.String(), DisplayedStatus: "actions.status.waiting"},
|
||||||
{Status: int(StatusRunning), StatusName: StatusRunning.String(), DisplayedStatus: "actions.status.running"},
|
{Status: int(StatusRunning), StatusName: StatusRunning.String(), DisplayedStatus: "actions.status.running"},
|
||||||
|
{Status: int(StatusBlocked), StatusName: StatusBlocked.String(), DisplayedStatus: "actions.status.blocked"},
|
||||||
{Status: int(StatusCancelling), StatusName: StatusCancelling.String(), DisplayedStatus: "actions.status.cancelling"},
|
{Status: int(StatusCancelling), StatusName: StatusCancelling.String(), DisplayedStatus: "actions.status.cancelling"},
|
||||||
}, statusInfoList)
|
}, statusInfoList)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,8 +75,28 @@ type ActionRunner struct {
|
|||||||
const (
|
const (
|
||||||
RunnerOfflineTime = time.Minute
|
RunnerOfflineTime = time.Minute
|
||||||
RunnerIdleTime = 10 * time.Second
|
RunnerIdleTime = 10 * time.Second
|
||||||
|
// RunnerHeartbeatInterval is how often last_online is persisted on poll.
|
||||||
|
// Must stay well below RunnerOfflineTime so runners don't flap to offline.
|
||||||
|
RunnerHeartbeatInterval = 30 * time.Second
|
||||||
|
// RunnerActiveInterval is how often last_active is persisted while a runner
|
||||||
|
// streams task updates and logs. Must stay well below RunnerIdleTime so a
|
||||||
|
// busy runner keeps showing ACTIVE without a DB write on every RPC.
|
||||||
|
RunnerActiveInterval = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ShouldPersistLastOnline reports whether last_online is stale enough to be
|
||||||
|
// worth writing back. Avoids a DB write on every runner poll.
|
||||||
|
func ShouldPersistLastOnline(last timeutil.TimeStamp, now time.Time) bool {
|
||||||
|
return now.Sub(last.AsTime()) >= RunnerHeartbeatInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldPersistLastActive reports whether last_active is stale enough to be
|
||||||
|
// worth writing back. Avoids a DB write on every UpdateTask/UpdateLog RPC while
|
||||||
|
// a runner is actively streaming logs.
|
||||||
|
func ShouldPersistLastActive(last timeutil.TimeStamp, now time.Time) bool {
|
||||||
|
return now.Sub(last.AsTime()) >= RunnerActiveInterval
|
||||||
|
}
|
||||||
|
|
||||||
// BelongsToOwnerName before calling, should guarantee that all attributes are loaded
|
// BelongsToOwnerName before calling, should guarantee that all attributes are loaded
|
||||||
func (r *ActionRunner) BelongsToOwnerName() string {
|
func (r *ActionRunner) BelongsToOwnerName() string {
|
||||||
if r.RepoID != 0 {
|
if r.RepoID != 0 {
|
||||||
@@ -251,21 +271,24 @@ func (opts FindRunnerOptions) ToConds() builder.Cond {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (opts FindRunnerOptions) ToOrders() string {
|
func (opts FindRunnerOptions) ToOrders() string {
|
||||||
|
// A unique tiebreaker (id) is appended so that runners sharing the same
|
||||||
|
// last_online or name keep a deterministic order across paginated queries,
|
||||||
|
// otherwise the same runner may appear on more than one page.
|
||||||
switch opts.Sort {
|
switch opts.Sort {
|
||||||
case "online":
|
case "online":
|
||||||
return "last_online DESC"
|
return "last_online DESC, id ASC"
|
||||||
case "offline":
|
case "offline":
|
||||||
return "last_online ASC"
|
return "last_online ASC, id ASC"
|
||||||
case "alphabetically":
|
case "alphabetically":
|
||||||
return "name ASC"
|
return "name ASC, id ASC"
|
||||||
case "reversealphabetically":
|
case "reversealphabetically":
|
||||||
return "name DESC"
|
return "name DESC, id ASC"
|
||||||
case "newest":
|
case "newest":
|
||||||
return "id DESC"
|
return "id DESC"
|
||||||
case "oldest":
|
case "oldest":
|
||||||
return "id ASC"
|
return "id ASC"
|
||||||
}
|
}
|
||||||
return "last_online DESC"
|
return "last_online DESC, id ASC"
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRunnerByUUID returns a runner via uuid
|
// GetRunnerByUUID returns a runner via uuid
|
||||||
|
|||||||
149
models/actions/runner_test.go
Normal file
149
models/actions/runner_test.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.dev/models/db"
|
||||||
|
"gitea.dev/models/unittest"
|
||||||
|
"gitea.dev/modules/timeutil"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShouldPersistLastOnline(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
last timeutil.TimeStamp
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "fresh, skip write",
|
||||||
|
last: timeutil.TimeStamp(now.Add(-5 * time.Second).Unix()),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exactly at interval, write",
|
||||||
|
last: timeutil.TimeStamp(now.Add(-RunnerHeartbeatInterval).Unix()),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stale, write",
|
||||||
|
last: timeutil.TimeStamp(now.Add(-2 * RunnerHeartbeatInterval).Unix()),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero (never seen), write",
|
||||||
|
last: 0,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.want, ShouldPersistLastOnline(tt.last, now))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldPersistLastActive(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
last timeutil.TimeStamp
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "fresh, skip write",
|
||||||
|
last: timeutil.TimeStamp(now.Add(-1 * time.Second).Unix()),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exactly at interval, write",
|
||||||
|
last: timeutil.TimeStamp(now.Add(-RunnerActiveInterval).Unix()),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stale, write",
|
||||||
|
last: timeutil.TimeStamp(now.Add(-2 * RunnerActiveInterval).Unix()),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero (never seen), write",
|
||||||
|
last: 0,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.want, ShouldPersistLastActive(tt.last, now))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindRunnerOptions_ToOrders_StableTiebreaker(t *testing.T) {
|
||||||
|
// Sorts on a non-unique column must end with the unique id tiebreaker so
|
||||||
|
// pagination is deterministic; without it, runners sharing the same
|
||||||
|
// last_online or name can appear on more than one page. Sorts already on
|
||||||
|
// the unique id need no tiebreaker.
|
||||||
|
expected := map[string]string{
|
||||||
|
"": "last_online DESC, id ASC",
|
||||||
|
"online": "last_online DESC, id ASC",
|
||||||
|
"offline": "last_online ASC, id ASC",
|
||||||
|
"alphabetically": "name ASC, id ASC",
|
||||||
|
"reversealphabetically": "name DESC, id ASC",
|
||||||
|
"newest": "id DESC",
|
||||||
|
"oldest": "id ASC",
|
||||||
|
}
|
||||||
|
for sort, want := range expected {
|
||||||
|
assert.Equal(t, want, FindRunnerOptions{Sort: sort}.ToOrders(), "sort %q", sort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindRunners_PaginationNoDuplicates(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
// Create several runners that all share the same last_online value so the
|
||||||
|
// primary sort key (last_online) is tied for all of them.
|
||||||
|
const ownerID = 1000
|
||||||
|
const count = 6
|
||||||
|
for i := range count {
|
||||||
|
runner := &ActionRunner{
|
||||||
|
Name: "paginated-runner",
|
||||||
|
UUID: fmt.Sprintf("PAGINATE-TEST-0000-0000-00000000000%d", i),
|
||||||
|
TokenHash: fmt.Sprintf("paginate-test-token-hash-%d", i),
|
||||||
|
OwnerID: ownerID,
|
||||||
|
RepoID: 0,
|
||||||
|
LastOnline: 42,
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(ctx, runner))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page through the runners and ensure every id is returned exactly once.
|
||||||
|
seen := make(map[int64]int)
|
||||||
|
const pageSize = 2
|
||||||
|
for page := 1; ; page++ {
|
||||||
|
runners, err := db.Find[ActionRunner](ctx, FindRunnerOptions{
|
||||||
|
ListOptions: db.ListOptions{Page: page, PageSize: pageSize},
|
||||||
|
OwnerID: ownerID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
if len(runners) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for _, r := range runners {
|
||||||
|
seen[r.ID]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Len(t, seen, count, "each runner should be returned exactly once across all pages")
|
||||||
|
for id, n := range seen {
|
||||||
|
assert.Equal(t, 1, n, "runner %d appeared on %d pages", id, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -231,6 +231,11 @@ func makeTaskStepDisplayName(step *jobparser.Step, limit int) (name string) {
|
|||||||
// another runner won the optimistic-lock race; it is never returned to callers.
|
// another runner won the optimistic-lock race; it is never returned to callers.
|
||||||
var errJobAlreadyClaimed = errors.New("job already claimed by another runner")
|
var errJobAlreadyClaimed = errors.New("job already claimed by another runner")
|
||||||
|
|
||||||
|
// pickTaskBatchSize bounds how many waiting jobs each CreateTaskForRunner query loads,
|
||||||
|
// so a large backlog is not fetched into memory on every runner poll.
|
||||||
|
// It is a var only so tests can shrink it to exercise pagination cheaply.
|
||||||
|
var pickTaskBatchSize = 100
|
||||||
|
|
||||||
// CreateTaskForRunner finds a waiting job that matches the runner's labels and
|
// CreateTaskForRunner finds a waiting job that matches the runner's labels and
|
||||||
// atomically claims it. It iterates through all matching jobs so that a
|
// atomically claims it. It iterates through all matching jobs so that a
|
||||||
// concurrent claim by another runner (which would lose the optimistic lock on
|
// concurrent claim by another runner (which would lose the optimistic lock on
|
||||||
@@ -249,31 +254,51 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
|
|||||||
Join("INNER", "repo_unit", "`repository`.id = `repo_unit`.repo_id").
|
Join("INNER", "repo_unit", "`repository`.id = `repo_unit`.repo_id").
|
||||||
Where(builder.Eq{"`repository`.owner_id": runner.OwnerID, "`repo_unit`.type": unit.TypeActions}))
|
Where(builder.Eq{"`repository`.owner_id": runner.OwnerID, "`repo_unit`.type": unit.TypeActions}))
|
||||||
}
|
}
|
||||||
if jobCond.IsValid() {
|
baseCond := builder.Eq{"task_id": 0, "status": StatusWaiting, "is_reusable_caller": false}.And(jobCond)
|
||||||
jobCond = builder.In("run_id", builder.Select("id").From("action_run").Where(jobCond))
|
|
||||||
}
|
|
||||||
|
|
||||||
var jobs []*ActionRunJob
|
|
||||||
if err := e.Where("task_id=? AND status=? AND is_reusable_caller=?", 0, StatusWaiting, false).And(jobCond).Asc("updated", "id").Find(&jobs); err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: a more efficient way to filter labels
|
// TODO: a more efficient way to filter labels
|
||||||
log.Trace("runner labels: %v", runner.AgentLabels)
|
log.Trace("runner labels: %v", runner.AgentLabels)
|
||||||
for _, v := range jobs {
|
|
||||||
if !runner.CanMatchLabels(v.RunsOn) {
|
// Page through the waiting jobs oldest-first instead of loading the whole backlog into memory on every poll.
|
||||||
continue
|
// Keyset pagination on (updated, id) is safe under concurrent claims:
|
||||||
|
// updated only moves forward, so the advancing cursor never skips a still-waiting job even as claimed jobs drop out.
|
||||||
|
var cursorUpdated timeutil.TimeStamp
|
||||||
|
var cursorID int64
|
||||||
|
for {
|
||||||
|
cond := baseCond
|
||||||
|
if cursorID > 0 {
|
||||||
|
cond = cond.And(builder.Or(
|
||||||
|
builder.Gt{"updated": cursorUpdated},
|
||||||
|
builder.And(builder.Eq{"updated": cursorUpdated}, builder.Gt{"id": cursorID}),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
task, ok, err := claimJobForRunner(ctx, runner, v)
|
|
||||||
if err != nil {
|
var jobs []*ActionRunJob
|
||||||
|
if err := e.Where(cond).Asc("updated", "id").Limit(pickTaskBatchSize).Find(&jobs); err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
if ok {
|
|
||||||
return task, true, nil
|
for _, v := range jobs {
|
||||||
|
if !runner.CanMatchLabels(v.RunsOn) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
task, ok, err := claimJobForRunner(ctx, runner, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return task, true, nil
|
||||||
|
}
|
||||||
|
// Another runner claimed this job concurrently; try the next one.
|
||||||
}
|
}
|
||||||
// Another runner claimed this job concurrently; try the next one.
|
|
||||||
|
// A short page means no waiting jobs remain beyond it.
|
||||||
|
if len(jobs) < pickTaskBatchSize {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
last := jobs[len(jobs)-1]
|
||||||
|
cursorUpdated, cursorID = last.Updated, last.ID
|
||||||
}
|
}
|
||||||
return nil, false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// claimJobForRunner attempts to atomically claim job for runner inside its own
|
// claimJobForRunner attempts to atomically claim job for runner inside its own
|
||||||
|
|||||||
@@ -371,3 +371,74 @@ func TestReleaseTaskForRunner(t *testing.T) {
|
|||||||
unittest.AssertNotExistsBean(t, &ActionTask{ID: task.ID})
|
unittest.AssertNotExistsBean(t, &ActionTask{ID: task.ID})
|
||||||
unittest.AssertNotExistsBean(t, &ActionTaskStep{TaskID: task.ID})
|
unittest.AssertNotExistsBean(t, &ActionTaskStep{TaskID: task.ID})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCreateTaskForRunnerPagination verifies that a job sitting beyond the first page is still claimed
|
||||||
|
func TestCreateTaskForRunnerPagination(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
defer func(orig int) { pickTaskBatchSize = orig }(pickTaskBatchSize)
|
||||||
|
pickTaskBatchSize = 2
|
||||||
|
|
||||||
|
run := &ActionRun{
|
||||||
|
Title: "pagination-test-run",
|
||||||
|
RepoID: 1,
|
||||||
|
OwnerID: 2,
|
||||||
|
WorkflowID: "test.yaml",
|
||||||
|
Index: 9903,
|
||||||
|
TriggerUserID: 2,
|
||||||
|
Ref: "refs/heads/main",
|
||||||
|
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
|
||||||
|
Event: "push",
|
||||||
|
TriggerEvent: "push",
|
||||||
|
Status: StatusWaiting,
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(t.Context(), run))
|
||||||
|
|
||||||
|
// Five waiting jobs the runner cannot run, then one it can.
|
||||||
|
// With a page size of 2 the matching job only appears on the third page.
|
||||||
|
for i := range 5 {
|
||||||
|
mismatch := &ActionRunJob{
|
||||||
|
RunID: run.ID,
|
||||||
|
RepoID: run.RepoID,
|
||||||
|
OwnerID: run.OwnerID,
|
||||||
|
CommitSHA: run.CommitSHA,
|
||||||
|
Name: "mismatch-" + string(rune('a'+i)),
|
||||||
|
Attempt: 1,
|
||||||
|
JobID: "mismatch-" + string(rune('a'+i)),
|
||||||
|
Status: StatusWaiting,
|
||||||
|
RunsOn: []string{"windows-latest"},
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(t.Context(), mismatch))
|
||||||
|
}
|
||||||
|
|
||||||
|
target := &ActionRunJob{
|
||||||
|
RunID: run.ID,
|
||||||
|
RepoID: run.RepoID,
|
||||||
|
OwnerID: run.OwnerID,
|
||||||
|
CommitSHA: run.CommitSHA,
|
||||||
|
Name: "target-job",
|
||||||
|
Attempt: 1,
|
||||||
|
JobID: "target-job",
|
||||||
|
Status: StatusWaiting,
|
||||||
|
RunsOn: []string{"ubuntu-latest"},
|
||||||
|
WorkflowPayload: []byte("on: push\njobs:\n target-job:\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"),
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(t.Context(), target))
|
||||||
|
|
||||||
|
runner := &ActionRunner{
|
||||||
|
UUID: "pagination-runner-uuid",
|
||||||
|
Name: "pagination-runner",
|
||||||
|
AgentLabels: []string{"ubuntu-latest"},
|
||||||
|
}
|
||||||
|
runner.GenerateAndFillToken()
|
||||||
|
require.NoError(t, db.Insert(t.Context(), runner))
|
||||||
|
|
||||||
|
task, ok, err := CreateTaskForRunner(t.Context(), runner)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.NotNil(t, task)
|
||||||
|
|
||||||
|
claimed := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: target.ID})
|
||||||
|
assert.Equal(t, StatusRunning, claimed.Status)
|
||||||
|
assert.Equal(t, task.ID, claimed.TaskID)
|
||||||
|
}
|
||||||
|
|||||||
@@ -195,3 +195,18 @@ func HasTwoFactorOrWebAuthn(ctx context.Context, id int64) (bool, error) {
|
|||||||
}
|
}
|
||||||
return HasWebAuthnRegistrationsByUID(ctx, id)
|
return HasWebAuthnRegistrationsByUID(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisableTwoFactor removes every two-factor method of the given user atomically,
|
||||||
|
// returning the number of TOTP records and WebAuthn credentials removed.
|
||||||
|
// It is a no-op for a user that has no 2FA enrolled.
|
||||||
|
func DisableTwoFactor(ctx context.Context, uid int64) (totp, webAuthn int64, err error) {
|
||||||
|
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
var e error
|
||||||
|
if totp, e = db.GetEngine(ctx).Where("uid = ?", uid).Delete(&TwoFactor{}); e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
webAuthn, e = db.GetEngine(ctx).Where("user_id = ?", uid).Delete(&WebAuthnCredential{})
|
||||||
|
return e
|
||||||
|
})
|
||||||
|
return totp, webAuthn, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
auth_model "gitea.dev/models/auth"
|
auth_model "gitea.dev/models/auth"
|
||||||
"gitea.dev/models/unittest"
|
"gitea.dev/models/unittest"
|
||||||
|
|
||||||
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
"github.com/pquerna/otp/totp"
|
"github.com/pquerna/otp/totp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -45,3 +46,37 @@ func TestTwoFactorValidateAndConsumeTOTP(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDisableTwoFactor(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
const uid = 1000 // a uid with no user/2FA fixtures
|
||||||
|
|
||||||
|
// Enroll TOTP and register a WebAuthn credential.
|
||||||
|
tfa := &auth_model.TwoFactor{UID: uid}
|
||||||
|
require.NoError(t, tfa.SetSecret("test-secret"))
|
||||||
|
require.NoError(t, auth_model.NewTwoFactor(ctx, tfa))
|
||||||
|
_, err := auth_model.CreateCredential(ctx, uid, "test-key", &webauthn.Credential{ID: []byte("test-cred-id")})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
has, err := auth_model.HasTwoFactorOrWebAuthn(ctx, uid)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, has)
|
||||||
|
|
||||||
|
// Both records are removed and counted separately.
|
||||||
|
totp, webAuthn, err := auth_model.DisableTwoFactor(ctx, uid)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, totp)
|
||||||
|
assert.EqualValues(t, 1, webAuthn)
|
||||||
|
|
||||||
|
has, err = auth_model.HasTwoFactorOrWebAuthn(ctx, uid)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, has)
|
||||||
|
|
||||||
|
// A second call on a user without 2FA is a no-op.
|
||||||
|
totp, webAuthn, err = auth_model.DisableTwoFactor(ctx, uid)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 0, totp)
|
||||||
|
assert.EqualValues(t, 0, webAuthn)
|
||||||
|
}
|
||||||
|
|||||||
@@ -55,29 +55,34 @@ func ParseScopedWorkflows(sourceCommit *git.Commit) ([]*ParsedScopedWorkflow, er
|
|||||||
return parsed, nil
|
return parsed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchScopedWorkflows evaluates already-parsed scoped workflows against one consuming event, returning those whose `on:` matches.
|
// MatchScopedWorkflows evaluates already-parsed scoped workflows against one consuming event.
|
||||||
|
// It returns the workflows whose `on:` matches, and those that matched the event but were excluded by a branch/paths filter (filtered).
|
||||||
func MatchScopedWorkflows(
|
func MatchScopedWorkflows(
|
||||||
parsed []*ParsedScopedWorkflow,
|
parsed []*ParsedScopedWorkflow,
|
||||||
consumerGitRepo *git.Repository,
|
consumerGitRepo *git.Repository,
|
||||||
consumerCommit *git.Commit,
|
consumerCommit *git.Commit,
|
||||||
triggedEvent webhook_module.HookEventType,
|
triggedEvent webhook_module.HookEventType,
|
||||||
payload api.Payloader,
|
payload api.Payloader,
|
||||||
) []*DetectedWorkflow {
|
) (matched, filtered []*DetectedWorkflow) {
|
||||||
workflows := make([]*DetectedWorkflow, 0, len(parsed))
|
|
||||||
for _, p := range parsed {
|
for _, p := range parsed {
|
||||||
for _, evt := range p.Events {
|
for _, evt := range p.Events {
|
||||||
if evt.IsSchedule() {
|
if evt.IsSchedule() {
|
||||||
// schedule is a non-target for scoped workflows
|
// schedule is a non-target for scoped workflows
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if detectMatched(consumerGitRepo, consumerCommit, triggedEvent, payload, evt) {
|
dwf := &DetectedWorkflow{
|
||||||
workflows = append(workflows, &DetectedWorkflow{
|
EntryName: p.EntryName,
|
||||||
EntryName: p.EntryName,
|
TriggerEvent: evt,
|
||||||
TriggerEvent: evt,
|
Content: p.Content,
|
||||||
Content: p.Content,
|
}
|
||||||
})
|
switch detectWorkflowMatch(consumerGitRepo, consumerCommit, triggedEvent, payload, evt) {
|
||||||
|
case detectMatched:
|
||||||
|
matched = append(matched, dwf)
|
||||||
|
case detectFilteredOut:
|
||||||
|
filtered = append(filtered, dwf)
|
||||||
|
case detectNotApplicable:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return workflows
|
return matched, filtered
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,14 @@ type DetectedWorkflow struct {
|
|||||||
Content []byte
|
Content []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type detectResult int
|
||||||
|
|
||||||
|
const (
|
||||||
|
detectMatched detectResult = iota // event matched; run normally
|
||||||
|
detectNotApplicable // event/type doesn't apply; create nothing
|
||||||
|
detectFilteredOut // matched but excluded by a branch/paths filter; emits a skipped commit status
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
model.OnDecodeNodeError = func(node yaml.Node, out any, err error) {
|
model.OnDecodeNodeError = func(node yaml.Node, out any, err error) {
|
||||||
// Log the error instead of panic or fatal.
|
// Log the error instead of panic or fatal.
|
||||||
@@ -172,18 +180,16 @@ func DetectWorkflows(
|
|||||||
triggedEvent webhook_module.HookEventType,
|
triggedEvent webhook_module.HookEventType,
|
||||||
payload api.Payloader,
|
payload api.Payloader,
|
||||||
detectSchedule bool,
|
detectSchedule bool,
|
||||||
) ([]*DetectedWorkflow, []*DetectedWorkflow, error) {
|
) (workflows, schedules, filtered []*DetectedWorkflow, err error) {
|
||||||
_, entries, err := ListWorkflows(commit)
|
_, entries, err := ListWorkflows(commit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
workflows := make([]*DetectedWorkflow, 0, len(entries))
|
|
||||||
schedules := make([]*DetectedWorkflow, 0, len(entries))
|
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
content, err := GetContentFromEntry(entry)
|
content, err := GetContentFromEntry(entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// one workflow may have multiple events
|
// one workflow may have multiple events
|
||||||
@@ -203,18 +209,24 @@ func DetectWorkflows(
|
|||||||
}
|
}
|
||||||
schedules = append(schedules, dwf)
|
schedules = append(schedules, dwf)
|
||||||
}
|
}
|
||||||
} else if detectMatched(gitRepo, commit, triggedEvent, payload, evt) {
|
} else {
|
||||||
dwf := &DetectedWorkflow{
|
dwf := &DetectedWorkflow{
|
||||||
EntryName: entry.Name(),
|
EntryName: entry.Name(),
|
||||||
TriggerEvent: evt,
|
TriggerEvent: evt,
|
||||||
Content: content,
|
Content: content,
|
||||||
}
|
}
|
||||||
workflows = append(workflows, dwf)
|
switch detectWorkflowMatch(gitRepo, commit, triggedEvent, payload, evt) {
|
||||||
|
case detectMatched:
|
||||||
|
workflows = append(workflows, dwf)
|
||||||
|
case detectFilteredOut:
|
||||||
|
filtered = append(filtered, dwf)
|
||||||
|
case detectNotApplicable:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return workflows, schedules, nil
|
return workflows, schedules, filtered, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DetectScheduledWorkflows(gitRepo *git.Repository, commit *git.Commit) ([]*DetectedWorkflow, error) {
|
func DetectScheduledWorkflows(gitRepo *git.Repository, commit *git.Commit) ([]*DetectedWorkflow, error) {
|
||||||
@@ -252,9 +264,9 @@ func DetectScheduledWorkflows(gitRepo *git.Repository, commit *git.Commit) ([]*D
|
|||||||
return wfs, nil
|
return wfs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent webhook_module.HookEventType, payload api.Payloader, evt *jobparser.Event) bool {
|
func detectWorkflowMatch(gitRepo *git.Repository, commit *git.Commit, triggedEvent webhook_module.HookEventType, payload api.Payloader, evt *jobparser.Event) detectResult {
|
||||||
if !canGithubEventMatch(evt.Name, triggedEvent) {
|
if !canGithubEventMatch(evt.Name, triggedEvent) {
|
||||||
return false
|
return detectNotApplicable
|
||||||
}
|
}
|
||||||
|
|
||||||
switch triggedEvent {
|
switch triggedEvent {
|
||||||
@@ -268,7 +280,7 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
|
|||||||
log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts())
|
log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts())
|
||||||
}
|
}
|
||||||
// no special filter parameters for these events, just return true if name matched
|
// no special filter parameters for these events, just return true if name matched
|
||||||
return true
|
return detectMatched
|
||||||
|
|
||||||
case // push
|
case // push
|
||||||
webhook_module.HookEventPush:
|
webhook_module.HookEventPush:
|
||||||
@@ -279,14 +291,20 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
|
|||||||
webhook_module.HookEventIssueAssign,
|
webhook_module.HookEventIssueAssign,
|
||||||
webhook_module.HookEventIssueLabel,
|
webhook_module.HookEventIssueLabel,
|
||||||
webhook_module.HookEventIssueMilestone:
|
webhook_module.HookEventIssueMilestone:
|
||||||
return matchIssuesEvent(payload.(*api.IssuePayload), evt)
|
if matchIssuesEvent(payload.(*api.IssuePayload), evt) {
|
||||||
|
return detectMatched
|
||||||
|
}
|
||||||
|
return detectNotApplicable
|
||||||
|
|
||||||
case // issue_comment
|
case // issue_comment
|
||||||
webhook_module.HookEventIssueComment,
|
webhook_module.HookEventIssueComment,
|
||||||
// `pull_request_comment` is same as `issue_comment`
|
// `pull_request_comment` is same as `issue_comment`
|
||||||
// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment
|
// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment
|
||||||
webhook_module.HookEventPullRequestComment:
|
webhook_module.HookEventPullRequestComment:
|
||||||
return matchIssueCommentEvent(payload.(*api.IssueCommentPayload), evt)
|
if matchIssueCommentEvent(payload.(*api.IssueCommentPayload), evt) {
|
||||||
|
return detectMatched
|
||||||
|
}
|
||||||
|
return detectNotApplicable
|
||||||
|
|
||||||
case // pull_request
|
case // pull_request
|
||||||
webhook_module.HookEventPullRequest,
|
webhook_module.HookEventPullRequest,
|
||||||
@@ -300,34 +318,49 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
|
|||||||
case // pull_request_review
|
case // pull_request_review
|
||||||
webhook_module.HookEventPullRequestReviewApproved,
|
webhook_module.HookEventPullRequestReviewApproved,
|
||||||
webhook_module.HookEventPullRequestReviewRejected:
|
webhook_module.HookEventPullRequestReviewRejected:
|
||||||
return matchPullRequestReviewEvent(payload.(*api.PullRequestPayload), evt)
|
if matchPullRequestReviewEvent(payload.(*api.PullRequestPayload), evt) {
|
||||||
|
return detectMatched
|
||||||
|
}
|
||||||
|
return detectNotApplicable
|
||||||
|
|
||||||
case // pull_request_review_comment
|
case // pull_request_review_comment
|
||||||
webhook_module.HookEventPullRequestReviewComment:
|
webhook_module.HookEventPullRequestReviewComment:
|
||||||
return matchPullRequestReviewCommentEvent(payload.(*api.PullRequestPayload), evt)
|
if matchPullRequestReviewCommentEvent(payload.(*api.PullRequestPayload), evt) {
|
||||||
|
return detectMatched
|
||||||
|
}
|
||||||
|
return detectNotApplicable
|
||||||
|
|
||||||
case // release
|
case // release
|
||||||
webhook_module.HookEventRelease:
|
webhook_module.HookEventRelease:
|
||||||
return matchReleaseEvent(payload.(*api.ReleasePayload), evt)
|
if matchReleaseEvent(payload.(*api.ReleasePayload), evt) {
|
||||||
|
return detectMatched
|
||||||
|
}
|
||||||
|
return detectNotApplicable
|
||||||
|
|
||||||
case // registry_package
|
case // registry_package
|
||||||
webhook_module.HookEventPackage:
|
webhook_module.HookEventPackage:
|
||||||
return matchPackageEvent(payload.(*api.PackagePayload), evt)
|
if matchPackageEvent(payload.(*api.PackagePayload), evt) {
|
||||||
|
return detectMatched
|
||||||
|
}
|
||||||
|
return detectNotApplicable
|
||||||
|
|
||||||
case // workflow_run
|
case // workflow_run
|
||||||
webhook_module.HookEventWorkflowRun:
|
webhook_module.HookEventWorkflowRun:
|
||||||
return matchWorkflowRunEvent(payload.(*api.WorkflowRunPayload), evt)
|
if matchWorkflowRunEvent(payload.(*api.WorkflowRunPayload), evt) {
|
||||||
|
return detectMatched
|
||||||
|
}
|
||||||
|
return detectNotApplicable
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Warn("unsupported event %q", triggedEvent)
|
log.Warn("unsupported event %q", triggedEvent)
|
||||||
return false
|
return detectNotApplicable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobparser.Event) bool {
|
func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobparser.Event) detectResult {
|
||||||
// with no special filter parameters
|
// with no special filter parameters
|
||||||
if len(evt.Acts()) == 0 {
|
if len(evt.Acts()) == 0 {
|
||||||
return true
|
return detectMatched
|
||||||
}
|
}
|
||||||
|
|
||||||
matchTimes := 0
|
matchTimes := 0
|
||||||
@@ -393,14 +426,14 @@ func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobpa
|
|||||||
filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before)
|
filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err)
|
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err)
|
||||||
} else {
|
return detectNotApplicable
|
||||||
patterns, err := workflowpattern.CompilePatterns(vals...)
|
}
|
||||||
if err != nil {
|
patterns, err := workflowpattern.CompilePatterns(vals...)
|
||||||
break
|
if err != nil {
|
||||||
}
|
break
|
||||||
if !workflowpattern.Skip(patterns, filesChanged) {
|
}
|
||||||
matchTimes++
|
if !workflowpattern.Skip(patterns, filesChanged) {
|
||||||
}
|
matchTimes++
|
||||||
}
|
}
|
||||||
case "paths-ignore":
|
case "paths-ignore":
|
||||||
if refName.IsTag() {
|
if refName.IsTag() {
|
||||||
@@ -410,14 +443,14 @@ func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobpa
|
|||||||
filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before)
|
filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err)
|
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err)
|
||||||
} else {
|
return detectNotApplicable
|
||||||
patterns, err := workflowpattern.CompilePatterns(vals...)
|
}
|
||||||
if err != nil {
|
patterns, err := workflowpattern.CompilePatterns(vals...)
|
||||||
break
|
if err != nil {
|
||||||
}
|
break
|
||||||
if !workflowpattern.Filter(patterns, filesChanged) {
|
}
|
||||||
matchTimes++
|
if !workflowpattern.Filter(patterns, filesChanged) {
|
||||||
}
|
matchTimes++
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Warn("push event unsupported condition %q", cond)
|
log.Warn("push event unsupported condition %q", cond)
|
||||||
@@ -427,7 +460,10 @@ func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobpa
|
|||||||
if hasBranchFilter && hasTagFilter {
|
if hasBranchFilter && hasTagFilter {
|
||||||
matchTimes++
|
matchTimes++
|
||||||
}
|
}
|
||||||
return matchTimes == len(evt.Acts())
|
if matchTimes == len(evt.Acts()) {
|
||||||
|
return detectMatched
|
||||||
|
}
|
||||||
|
return detectFilteredOut
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchIssuesEvent(issuePayload *api.IssuePayload, evt *jobparser.Event) bool {
|
func matchIssuesEvent(issuePayload *api.IssuePayload, evt *jobparser.Event) bool {
|
||||||
@@ -478,7 +514,7 @@ func matchIssuesEvent(issuePayload *api.IssuePayload, evt *jobparser.Event) bool
|
|||||||
return matchTimes == len(evt.Acts())
|
return matchTimes == len(evt.Acts())
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayload *api.PullRequestPayload, evt *jobparser.Event) bool {
|
func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayload *api.PullRequestPayload, evt *jobparser.Event) detectResult {
|
||||||
acts := evt.Acts()
|
acts := evt.Acts()
|
||||||
activityTypeMatched := false
|
activityTypeMatched := false
|
||||||
matchTimes := 0
|
matchTimes := 0
|
||||||
@@ -525,7 +561,7 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
|
|||||||
headCommit, err = gitRepo.GetCommit(prPayload.PullRequest.Head.Sha)
|
headCommit, err = gitRepo.GetCommit(prPayload.PullRequest.Head.Sha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetCommit [ref: %s]: %v", prPayload.PullRequest.Head.Sha, err)
|
log.Error("GetCommit [ref: %s]: %v", prPayload.PullRequest.Head.Sha, err)
|
||||||
return false
|
return detectNotApplicable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,33 +593,39 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
|
|||||||
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.MergeBase)
|
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.MergeBase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
|
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
|
||||||
} else {
|
return detectNotApplicable
|
||||||
patterns, err := workflowpattern.CompilePatterns(vals...)
|
}
|
||||||
if err != nil {
|
patterns, err := workflowpattern.CompilePatterns(vals...)
|
||||||
break
|
if err != nil {
|
||||||
}
|
break
|
||||||
if !workflowpattern.Skip(patterns, filesChanged) {
|
}
|
||||||
matchTimes++
|
if !workflowpattern.Skip(patterns, filesChanged) {
|
||||||
}
|
matchTimes++
|
||||||
}
|
}
|
||||||
case "paths-ignore":
|
case "paths-ignore":
|
||||||
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.MergeBase)
|
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.MergeBase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
|
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
|
||||||
} else {
|
return detectNotApplicable
|
||||||
patterns, err := workflowpattern.CompilePatterns(vals...)
|
}
|
||||||
if err != nil {
|
patterns, err := workflowpattern.CompilePatterns(vals...)
|
||||||
break
|
if err != nil {
|
||||||
}
|
break
|
||||||
if !workflowpattern.Filter(patterns, filesChanged) {
|
}
|
||||||
matchTimes++
|
if !workflowpattern.Filter(patterns, filesChanged) {
|
||||||
}
|
matchTimes++
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Warn("pull request event unsupported condition %q", cond)
|
log.Warn("pull request event unsupported condition %q", cond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return activityTypeMatched && matchTimes == len(evt.Acts())
|
if !activityTypeMatched {
|
||||||
|
return detectNotApplicable
|
||||||
|
}
|
||||||
|
if matchTimes != len(evt.Acts()) {
|
||||||
|
return detectFilteredOut
|
||||||
|
}
|
||||||
|
return detectMatched
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchIssueCommentEvent(issueCommentPayload *api.IssueCommentPayload, evt *jobparser.Event) bool {
|
func matchIssueCommentEvent(issueCommentPayload *api.IssueCommentPayload, evt *jobparser.Event) bool {
|
||||||
|
|||||||
@@ -101,49 +101,49 @@ func TestDetectMatched(t *testing.T) {
|
|||||||
triggedEvent webhook_module.HookEventType
|
triggedEvent webhook_module.HookEventType
|
||||||
payload api.Payloader
|
payload api.Payloader
|
||||||
yamlOn string
|
yamlOn string
|
||||||
expected bool
|
expected detectResult
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "HookEventCreate(create) matches GithubEventCreate(create)",
|
desc: "HookEventCreate(create) matches GithubEventCreate(create)",
|
||||||
triggedEvent: webhook_module.HookEventCreate,
|
triggedEvent: webhook_module.HookEventCreate,
|
||||||
payload: nil,
|
payload: nil,
|
||||||
yamlOn: "on: create",
|
yamlOn: "on: create",
|
||||||
expected: true,
|
expected: detectMatched,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HookEventIssues(issues) `opened` action matches GithubEventIssues(issues)",
|
desc: "HookEventIssues(issues) `opened` action matches GithubEventIssues(issues)",
|
||||||
triggedEvent: webhook_module.HookEventIssues,
|
triggedEvent: webhook_module.HookEventIssues,
|
||||||
payload: &api.IssuePayload{Action: api.HookIssueOpened},
|
payload: &api.IssuePayload{Action: api.HookIssueOpened},
|
||||||
yamlOn: "on: issues",
|
yamlOn: "on: issues",
|
||||||
expected: true,
|
expected: detectMatched,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HookEventIssues(issues) `milestoned` action matches GithubEventIssues(issues)",
|
desc: "HookEventIssues(issues) `milestoned` action matches GithubEventIssues(issues)",
|
||||||
triggedEvent: webhook_module.HookEventIssues,
|
triggedEvent: webhook_module.HookEventIssues,
|
||||||
payload: &api.IssuePayload{Action: api.HookIssueMilestoned},
|
payload: &api.IssuePayload{Action: api.HookIssueMilestoned},
|
||||||
yamlOn: "on: issues",
|
yamlOn: "on: issues",
|
||||||
expected: true,
|
expected: detectMatched,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HookEventPullRequestSync(pull_request_sync) matches GithubEventPullRequest(pull_request)",
|
desc: "HookEventPullRequestSync(pull_request_sync) matches GithubEventPullRequest(pull_request)",
|
||||||
triggedEvent: webhook_module.HookEventPullRequestSync,
|
triggedEvent: webhook_module.HookEventPullRequestSync,
|
||||||
payload: &api.PullRequestPayload{Action: api.HookIssueSynchronized},
|
payload: &api.PullRequestPayload{Action: api.HookIssueSynchronized},
|
||||||
yamlOn: "on: pull_request",
|
yamlOn: "on: pull_request",
|
||||||
expected: true,
|
expected: detectMatched,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HookEventPullRequest(pull_request) `label_updated` action doesn't match GithubEventPullRequest(pull_request) with no activity type",
|
desc: "HookEventPullRequest(pull_request) `label_updated` action doesn't match GithubEventPullRequest(pull_request) with no activity type",
|
||||||
triggedEvent: webhook_module.HookEventPullRequest,
|
triggedEvent: webhook_module.HookEventPullRequest,
|
||||||
payload: &api.PullRequestPayload{Action: api.HookIssueLabelUpdated},
|
payload: &api.PullRequestPayload{Action: api.HookIssueLabelUpdated},
|
||||||
yamlOn: "on: pull_request",
|
yamlOn: "on: pull_request",
|
||||||
expected: false,
|
expected: detectNotApplicable,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HookEventPullRequest(pull_request) `closed` action doesn't match GithubEventPullRequest(pull_request) with no activity type",
|
desc: "HookEventPullRequest(pull_request) `closed` action doesn't match GithubEventPullRequest(pull_request) with no activity type",
|
||||||
triggedEvent: webhook_module.HookEventPullRequest,
|
triggedEvent: webhook_module.HookEventPullRequest,
|
||||||
payload: &api.PullRequestPayload{Action: api.HookIssueClosed},
|
payload: &api.PullRequestPayload{Action: api.HookIssueClosed},
|
||||||
yamlOn: "on: pull_request",
|
yamlOn: "on: pull_request",
|
||||||
expected: false,
|
expected: detectNotApplicable,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HookEventPullRequest(pull_request) `closed` action doesn't match GithubEventPullRequest(pull_request) with branches",
|
desc: "HookEventPullRequest(pull_request) `closed` action doesn't match GithubEventPullRequest(pull_request) with branches",
|
||||||
@@ -155,56 +155,56 @@ func TestDetectMatched(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
yamlOn: "on:\n pull_request:\n branches: [main]",
|
yamlOn: "on:\n pull_request:\n branches: [main]",
|
||||||
expected: false,
|
expected: detectNotApplicable,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HookEventPullRequest(pull_request) `label_updated` action matches GithubEventPullRequest(pull_request) with `label` activity type",
|
desc: "HookEventPullRequest(pull_request) `label_updated` action matches GithubEventPullRequest(pull_request) with `label` activity type",
|
||||||
triggedEvent: webhook_module.HookEventPullRequest,
|
triggedEvent: webhook_module.HookEventPullRequest,
|
||||||
payload: &api.PullRequestPayload{Action: api.HookIssueLabelUpdated},
|
payload: &api.PullRequestPayload{Action: api.HookIssueLabelUpdated},
|
||||||
yamlOn: "on:\n pull_request:\n types: [labeled]",
|
yamlOn: "on:\n pull_request:\n types: [labeled]",
|
||||||
expected: true,
|
expected: detectMatched,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HookEventPullRequestReviewComment(pull_request_review_comment) matches GithubEventPullRequestReviewComment(pull_request_review_comment)",
|
desc: "HookEventPullRequestReviewComment(pull_request_review_comment) matches GithubEventPullRequestReviewComment(pull_request_review_comment)",
|
||||||
triggedEvent: webhook_module.HookEventPullRequestReviewComment,
|
triggedEvent: webhook_module.HookEventPullRequestReviewComment,
|
||||||
payload: &api.PullRequestPayload{Action: api.HookIssueReviewed},
|
payload: &api.PullRequestPayload{Action: api.HookIssueReviewed},
|
||||||
yamlOn: "on:\n pull_request_review_comment:\n types: [created]",
|
yamlOn: "on:\n pull_request_review_comment:\n types: [created]",
|
||||||
expected: true,
|
expected: detectMatched,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HookEventPullRequestReviewRejected(pull_request_review_rejected) doesn't match GithubEventPullRequestReview(pull_request_review) with `dismissed` activity type (we don't support `dismissed` at present)",
|
desc: "HookEventPullRequestReviewRejected(pull_request_review_rejected) doesn't match GithubEventPullRequestReview(pull_request_review) with `dismissed` activity type (we don't support `dismissed` at present)",
|
||||||
triggedEvent: webhook_module.HookEventPullRequestReviewRejected,
|
triggedEvent: webhook_module.HookEventPullRequestReviewRejected,
|
||||||
payload: &api.PullRequestPayload{Action: api.HookIssueReviewed},
|
payload: &api.PullRequestPayload{Action: api.HookIssueReviewed},
|
||||||
yamlOn: "on:\n pull_request_review:\n types: [dismissed]",
|
yamlOn: "on:\n pull_request_review:\n types: [dismissed]",
|
||||||
expected: false,
|
expected: detectNotApplicable,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HookEventRelease(release) `published` action matches GithubEventRelease(release) with `published` activity type",
|
desc: "HookEventRelease(release) `published` action matches GithubEventRelease(release) with `published` activity type",
|
||||||
triggedEvent: webhook_module.HookEventRelease,
|
triggedEvent: webhook_module.HookEventRelease,
|
||||||
payload: &api.ReleasePayload{Action: api.HookReleasePublished},
|
payload: &api.ReleasePayload{Action: api.HookReleasePublished},
|
||||||
yamlOn: "on:\n release:\n types: [published]",
|
yamlOn: "on:\n release:\n types: [published]",
|
||||||
expected: true,
|
expected: detectMatched,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HookEventPackage(package) `created` action doesn't match GithubEventRegistryPackage(registry_package) with `updated` activity type",
|
desc: "HookEventPackage(package) `created` action doesn't match GithubEventRegistryPackage(registry_package) with `updated` activity type",
|
||||||
triggedEvent: webhook_module.HookEventPackage,
|
triggedEvent: webhook_module.HookEventPackage,
|
||||||
payload: &api.PackagePayload{Action: api.HookPackageCreated},
|
payload: &api.PackagePayload{Action: api.HookPackageCreated},
|
||||||
yamlOn: "on:\n registry_package:\n types: [updated]",
|
yamlOn: "on:\n registry_package:\n types: [updated]",
|
||||||
expected: false,
|
expected: detectNotApplicable,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HookEventWiki(wiki) matches GithubEventGollum(gollum)",
|
desc: "HookEventWiki(wiki) matches GithubEventGollum(gollum)",
|
||||||
triggedEvent: webhook_module.HookEventWiki,
|
triggedEvent: webhook_module.HookEventWiki,
|
||||||
payload: nil,
|
payload: nil,
|
||||||
yamlOn: "on: gollum",
|
yamlOn: "on: gollum",
|
||||||
expected: true,
|
expected: detectMatched,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HookEventSchedule(schedule) matches GithubEventSchedule(schedule)",
|
desc: "HookEventSchedule(schedule) matches GithubEventSchedule(schedule)",
|
||||||
triggedEvent: webhook_module.HookEventSchedule,
|
triggedEvent: webhook_module.HookEventSchedule,
|
||||||
payload: nil,
|
payload: nil,
|
||||||
yamlOn: "on: schedule",
|
yamlOn: "on: schedule",
|
||||||
expected: true,
|
expected: detectMatched,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "push to tag matches workflow with paths condition (should skip paths check)",
|
desc: "push to tag matches workflow with paths condition (should skip paths check)",
|
||||||
@@ -222,7 +222,19 @@ func TestDetectMatched(t *testing.T) {
|
|||||||
},
|
},
|
||||||
commit: nil,
|
commit: nil,
|
||||||
yamlOn: "on:\n push:\n paths:\n - src/**",
|
yamlOn: "on:\n push:\n paths:\n - src/**",
|
||||||
expected: true,
|
expected: detectMatched,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "push branch filter excludes -> filtered out",
|
||||||
|
triggedEvent: webhook_module.HookEventPush,
|
||||||
|
payload: &api.PushPayload{
|
||||||
|
Ref: "refs/heads/feature/x",
|
||||||
|
Before: "0000000",
|
||||||
|
Commits: []*api.PayloadCommit{{ID: "abc", Added: []string{"a.go"}, Message: "x"}},
|
||||||
|
},
|
||||||
|
commit: nil,
|
||||||
|
yamlOn: "on:\n push:\n branches: [main]",
|
||||||
|
expected: detectFilteredOut,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +243,7 @@ func TestDetectMatched(t *testing.T) {
|
|||||||
evts, err := GetEventsFromContent(fullWorkflowContent(tc.yamlOn))
|
evts, err := GetEventsFromContent(fullWorkflowContent(tc.yamlOn))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, evts, 1)
|
assert.Len(t, evts, 1)
|
||||||
assert.Equal(t, tc.expected, detectMatched(nil, tc.commit, tc.triggedEvent, tc.payload, evts[0]))
|
assert.Equal(t, tc.expected, detectWorkflowMatch(nil, tc.commit, tc.triggedEvent, tc.payload, evts[0]))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,19 +21,6 @@ func TestCommitsCount(t *testing.T) {
|
|||||||
assert.Equal(t, int64(3), commitsCount)
|
assert.Equal(t, int64(3), commitsCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCommitsCountWithoutBase(t *testing.T) {
|
|
||||||
bareRepo1 := &mockRepository{path: "repo1_bare"}
|
|
||||||
|
|
||||||
commitsCount, err := CommitsCount(t.Context(), bareRepo1,
|
|
||||||
CommitsCountOptions{
|
|
||||||
Not: "master",
|
|
||||||
Revision: []string{"branch1"},
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int64(2), commitsCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCommitsCountWithSinceUntil(t *testing.T) {
|
func TestCommitsCountWithSinceUntil(t *testing.T) {
|
||||||
bareRepo1 := &mockRepository{path: "repo1_bare"}
|
bareRepo1 := &mockRepository{path: "repo1_bare"}
|
||||||
revision := []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"}
|
revision := []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"}
|
||||||
@@ -65,6 +52,19 @@ func TestCommitsCountWithSinceUntil(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommitsCountWithoutBase(t *testing.T) {
|
||||||
|
bareRepo1 := &mockRepository{path: "repo1_bare"}
|
||||||
|
|
||||||
|
commitsCount, err := CommitsCount(t.Context(), bareRepo1,
|
||||||
|
CommitsCountOptions{
|
||||||
|
Not: "master",
|
||||||
|
Revision: []string{"branch1"},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(2), commitsCount)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetLatestCommitTime(t *testing.T) {
|
func TestGetLatestCommitTime(t *testing.T) {
|
||||||
bareRepo1 := &mockRepository{path: "repo1_bare"}
|
bareRepo1 := &mockRepository{path: "repo1_bare"}
|
||||||
lct, err := GetLatestCommitTime(t.Context(), bareRepo1)
|
lct, err := GetLatestCommitTime(t.Context(), bareRepo1)
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy {
|
|||||||
// Allow 'color' and 'background-color' properties for the style attribute on text elements.
|
// Allow 'color' and 'background-color' properties for the style attribute on text elements.
|
||||||
policy.AllowStyles("color", "background-color").OnElements("div", "span", "p", "tr", "th", "td")
|
policy.AllowStyles("color", "background-color").OnElements("div", "span", "p", "tr", "th", "td")
|
||||||
|
|
||||||
policy.AllowAttrs("src", "autoplay", "controls").OnElements("video")
|
policy.AllowAttrs("src", "autoplay", "controls", "muted", "loop", "playsinline").OnElements("video")
|
||||||
|
|
||||||
// Native support of "<picture><source media=... srcset=...><img src=...></picture>"
|
// Native support of "<picture><source media=... srcset=...><img src=...></picture>"
|
||||||
// ATTENTION: it only works with "auto" theme, because "media" query doesn't work with the theme chosen by end user manually.
|
// ATTENTION: it only works with "auto" theme, because "media" query doesn't work with the theme chosen by end user manually.
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import (
|
|||||||
|
|
||||||
const defaultMaxRerunAttempts = 50
|
const defaultMaxRerunAttempts = 50
|
||||||
|
|
||||||
|
const defaultMaxConcurrentTaskPicks = 16
|
||||||
|
|
||||||
// Actions settings
|
// Actions settings
|
||||||
var (
|
var (
|
||||||
Actions = struct {
|
Actions = struct {
|
||||||
@@ -31,13 +33,18 @@ var (
|
|||||||
WorkflowDirs []string `ini:"WORKFLOW_DIRS"`
|
WorkflowDirs []string `ini:"WORKFLOW_DIRS"`
|
||||||
ScopedWorkflowDirs []string `ini:"SCOPED_WORKFLOW_DIRS"`
|
ScopedWorkflowDirs []string `ini:"SCOPED_WORKFLOW_DIRS"`
|
||||||
MaxRerunAttempts int64 `ini:"MAX_RERUN_ATTEMPTS"`
|
MaxRerunAttempts int64 `ini:"MAX_RERUN_ATTEMPTS"`
|
||||||
|
// MaxConcurrentTaskPicks bounds how many runners may run the task-assignment
|
||||||
|
// transaction at once per Gitea instance, to avoid a thundering herd when many
|
||||||
|
// runners poll together. It is a per-process limit, not a cluster-wide one.
|
||||||
|
MaxConcurrentTaskPicks int `ini:"MAX_CONCURRENT_TASK_PICKS"`
|
||||||
}{
|
}{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
DefaultActionsURL: defaultActionsURLGitHub,
|
DefaultActionsURL: defaultActionsURLGitHub,
|
||||||
SkipWorkflowStrings: []string{"[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"},
|
SkipWorkflowStrings: []string{"[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"},
|
||||||
WorkflowDirs: []string{".gitea/workflows", ".github/workflows"},
|
WorkflowDirs: []string{".gitea/workflows", ".github/workflows"},
|
||||||
ScopedWorkflowDirs: []string{".gitea/scoped_workflows"},
|
ScopedWorkflowDirs: []string{".gitea/scoped_workflows"},
|
||||||
MaxRerunAttempts: defaultMaxRerunAttempts,
|
MaxRerunAttempts: defaultMaxRerunAttempts,
|
||||||
|
MaxConcurrentTaskPicks: defaultMaxConcurrentTaskPicks,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -128,6 +135,10 @@ func loadActionsFrom(rootCfg ConfigProvider) error {
|
|||||||
Actions.MaxRerunAttempts = defaultMaxRerunAttempts
|
Actions.MaxRerunAttempts = defaultMaxRerunAttempts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Actions.MaxConcurrentTaskPicks <= 0 {
|
||||||
|
Actions.MaxConcurrentTaskPicks = defaultMaxConcurrentTaskPicks
|
||||||
|
}
|
||||||
|
|
||||||
if !Actions.LogCompression.IsValid() {
|
if !Actions.LogCompression.IsValid() {
|
||||||
return fmt.Errorf("invalid [actions] LOG_COMPRESSION: %q", Actions.LogCompression)
|
return fmt.Errorf("invalid [actions] LOG_COMPRESSION: %q", Actions.LogCompression)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const (
|
|||||||
const (
|
const (
|
||||||
RepoPRTitleSourceFirstCommit = "first-commit"
|
RepoPRTitleSourceFirstCommit = "first-commit"
|
||||||
RepoPRTitleSourceAuto = "auto"
|
RepoPRTitleSourceAuto = "auto"
|
||||||
|
RepoPRTitleSourceBranchName = "branch-name"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ItemsPerPage maximum items per page in forks, watchers and stars of a repo
|
// ItemsPerPage maximum items per page in forks, watchers and stars of a repo
|
||||||
|
|||||||
@@ -73,17 +73,18 @@ func TestInitKeys(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, keyFiles, len(keyTypes))
|
assert.Len(t, keyFiles, len(keyTypes))
|
||||||
|
|
||||||
metadata := map[string]os.FileInfo{}
|
// Record file contents so regeneration can be detected
|
||||||
|
content := map[string][]byte{}
|
||||||
for _, keyType := range keyTypes {
|
for _, keyType := range keyTypes {
|
||||||
privKeyPath := filepath.Join(tempDir, "gitea."+keyType)
|
privKeyPath := filepath.Join(tempDir, "gitea."+keyType)
|
||||||
pubKeyPath := filepath.Join(tempDir, "gitea."+keyType+".pub")
|
pubKeyPath := filepath.Join(tempDir, "gitea."+keyType+".pub")
|
||||||
info, err := os.Stat(privKeyPath)
|
data, err := os.ReadFile(privKeyPath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
metadata[privKeyPath] = info
|
content[privKeyPath] = data
|
||||||
|
|
||||||
info, err = os.Stat(pubKeyPath)
|
data, err = os.ReadFile(pubKeyPath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
metadata[pubKeyPath] = info
|
content[pubKeyPath] = data
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test recreation on missing private key and noop for missing pub key
|
// Test recreation on missing private key and noop for missing pub key
|
||||||
@@ -98,26 +99,26 @@ func TestInitKeys(t *testing.T) {
|
|||||||
privKeyPath := filepath.Join(tempDir, "gitea."+keyType)
|
privKeyPath := filepath.Join(tempDir, "gitea."+keyType)
|
||||||
pubKeyPath := filepath.Join(tempDir, "gitea."+keyType+".pub")
|
pubKeyPath := filepath.Join(tempDir, "gitea."+keyType+".pub")
|
||||||
|
|
||||||
infoPriv, err := os.Stat(privKeyPath)
|
dataPriv, err := os.ReadFile(privKeyPath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
switch keyType {
|
switch keyType {
|
||||||
case "rsa":
|
case "rsa":
|
||||||
// No modification to RSA key
|
// No modification to RSA key
|
||||||
infoPub, err := os.Stat(pubKeyPath)
|
dataPub, err := os.ReadFile(pubKeyPath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, metadata[privKeyPath], infoPriv)
|
assert.Equal(t, content[privKeyPath], dataPriv)
|
||||||
assert.Equal(t, metadata[pubKeyPath], infoPub)
|
assert.Equal(t, content[pubKeyPath], dataPub)
|
||||||
case "ecdsa":
|
case "ecdsa":
|
||||||
// ECDSA public key should be missing, private unchanged
|
// ECDSA public key should be missing, private unchanged
|
||||||
assert.Equal(t, metadata[privKeyPath], infoPriv)
|
assert.Equal(t, content[privKeyPath], dataPriv)
|
||||||
assert.NoFileExists(t, pubKeyPath)
|
assert.NoFileExists(t, pubKeyPath)
|
||||||
case "ed25519":
|
case "ed25519":
|
||||||
// ed25519 private key was removed, so both keys regenerated
|
// ed25519 private key was removed, so both keys regenerated
|
||||||
infoPub, err := os.Stat(pubKeyPath)
|
dataPub, err := os.ReadFile(pubKeyPath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotEqual(t, metadata[privKeyPath], infoPriv)
|
assert.NotEqual(t, content[privKeyPath], dataPriv)
|
||||||
assert.NotEqual(t, metadata[pubKeyPath], infoPub)
|
assert.NotEqual(t, content[pubKeyPath], dataPub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package storage
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -47,40 +48,42 @@ type MinioStorage struct {
|
|||||||
basePath string
|
basePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertMinioErr(err error) error {
|
func convertMinioErr(err error, optMsg ...string) error {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
errResp, ok := err.(minio.ErrorResponse)
|
|
||||||
|
wrapErr := func(err error) error {
|
||||||
|
if len(optMsg) == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s: %w", optMsg[0], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
errResp, ok := errors.AsType[minio.ErrorResponse](err)
|
||||||
if !ok {
|
if !ok {
|
||||||
return err
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert two responses to standard analogues
|
// Convert two responses to standard analogues
|
||||||
switch errResp.Code {
|
switch errResp.Code {
|
||||||
case "NoSuchKey":
|
case "NoSuchKey":
|
||||||
return os.ErrNotExist
|
return wrapErr(os.ErrNotExist)
|
||||||
case "AccessDenied":
|
case "AccessDenied":
|
||||||
return os.ErrPermission
|
return wrapErr(os.ErrPermission)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return wrapErr(err)
|
||||||
}
|
|
||||||
|
|
||||||
var getBucketVersioning = func(ctx context.Context, minioClient *minio.Client, bucket string) error {
|
|
||||||
_, err := minioClient.GetBucketVersioning(ctx, bucket)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMinioStorage returns a minio storage
|
// NewMinioStorage returns a minio storage
|
||||||
func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage, error) {
|
func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage, error) {
|
||||||
config := cfg.MinioConfig
|
config := cfg.MinioConfig
|
||||||
|
log.Info("Creating minio storage at %s:%s with base path %s", config.Endpoint, config.Bucket, config.BasePath)
|
||||||
if config.ChecksumAlgorithm != "" && config.ChecksumAlgorithm != "default" && config.ChecksumAlgorithm != "md5" {
|
if config.ChecksumAlgorithm != "" && config.ChecksumAlgorithm != "default" && config.ChecksumAlgorithm != "md5" {
|
||||||
return nil, fmt.Errorf("invalid minio checksum algorithm: %s", config.ChecksumAlgorithm)
|
return nil, fmt.Errorf("invalid minio checksum algorithm: %s", config.ChecksumAlgorithm)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Creating Minio storage at %s:%s with base path %s", config.Endpoint, config.Bucket, config.BasePath)
|
|
||||||
|
|
||||||
var lookup minio.BucketLookupType
|
var lookup minio.BucketLookupType
|
||||||
switch config.BucketLookUpType {
|
switch config.BucketLookUpType {
|
||||||
case "auto", "":
|
case "auto", "":
|
||||||
@@ -93,6 +96,13 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage,
|
|||||||
return nil, fmt.Errorf("invalid minio bucket lookup type: %s", config.BucketLookUpType)
|
return nil, fmt.Errorf("invalid minio bucket lookup type: %s", config.BucketLookUpType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The request error message is something like:
|
||||||
|
// * "The request signature we calculated does not match the signature you provided. Check your key and signing method."
|
||||||
|
// It doesn't contain useful information to site admin, so here we wrap the error with our error message
|
||||||
|
// to tell the site admin what is the problem.
|
||||||
|
makeErrMsg := func(hint string) string {
|
||||||
|
return fmt.Sprintf("ObjectStorage.%s: endpoint=%s, location=%s, bucket=%s", hint, config.Endpoint, config.Location, config.Bucket)
|
||||||
|
}
|
||||||
minioClient, err := minio.New(config.Endpoint, &minio.Options{
|
minioClient, err := minio.New(config.Endpoint, &minio.Options{
|
||||||
Creds: buildMinioCredentials(config),
|
Creds: buildMinioCredentials(config),
|
||||||
Secure: config.UseSSL,
|
Secure: config.UseSSL,
|
||||||
@@ -101,37 +111,21 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage,
|
|||||||
BucketLookup: lookup,
|
BucketLookup: lookup,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, convertMinioErr(err)
|
return nil, convertMinioErr(err, makeErrMsg("NewClient"))
|
||||||
}
|
|
||||||
|
|
||||||
// The GetBucketVersioning is only used for checking whether the Object Storage parameters are generally good. It doesn't need to succeed.
|
|
||||||
// The assumption is that if the API returns the HTTP code 400, then the parameters could be incorrect.
|
|
||||||
// Otherwise even if the request itself fails (403, 404, etc), the code should still continue because the parameters seem "good" enough.
|
|
||||||
// Keep in mind that GetBucketVersioning requires "owner" to really succeed, so it can't be used to check the existence.
|
|
||||||
// Not using "BucketExists (HeadBucket)" because it doesn't include detailed failure reasons.
|
|
||||||
err = getBucketVersioning(ctx, minioClient, config.Bucket)
|
|
||||||
if err != nil {
|
|
||||||
errResp, ok := err.(minio.ErrorResponse)
|
|
||||||
if !ok {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if errResp.StatusCode == http.StatusBadRequest {
|
|
||||||
log.Error("S3 storage connection failure at %s:%s with base path %s and region: %s", config.Endpoint, config.Bucket, config.Location, errResp.Message)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if we already own this bucket
|
// Check to see if we already own this bucket
|
||||||
exists, err := minioClient.BucketExists(ctx, config.Bucket)
|
exists, err := minioClient.BucketExists(ctx, config.Bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, convertMinioErr(err)
|
return nil, convertMinioErr(err, makeErrMsg("BucketExists"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the bucket doesn't exist, try to create one
|
||||||
if !exists {
|
if !exists {
|
||||||
if err := minioClient.MakeBucket(ctx, config.Bucket, minio.MakeBucketOptions{
|
if err := minioClient.MakeBucket(ctx, config.Bucket, minio.MakeBucketOptions{
|
||||||
Region: config.Location,
|
Region: config.Location,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, convertMinioErr(err)
|
return nil, convertMinioErr(err, makeErrMsg("MakeBucket"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,16 +4,13 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
"gitea.dev/modules/test"
|
"gitea.dev/modules/test"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -84,31 +81,18 @@ func TestMinioStoragePath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestS3StorageBadRequest(t *testing.T) {
|
func TestS3StorageBadRequest(t *testing.T) {
|
||||||
if os.Getenv("CI") == "" {
|
endpoint := test.ExternalServiceHTTP(t, "TEST_MINIO_ENDPOINT", "minio:9000")
|
||||||
t.Skip("S3Storage not present outside of CI")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cfg := &setting.Storage{
|
cfg := &setting.Storage{
|
||||||
MinioConfig: setting.MinioStorageConfig{
|
MinioConfig: setting.MinioStorageConfig{
|
||||||
Endpoint: "minio:9000",
|
Endpoint: endpoint,
|
||||||
AccessKeyID: "123456",
|
AccessKeyID: "123456",
|
||||||
SecretAccessKey: "12345678",
|
SecretAccessKey: "invalid-secret",
|
||||||
Bucket: "bucket",
|
Bucket: "bucket",
|
||||||
Location: "us-east-1",
|
Location: "us-east-1",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
message := "ERROR"
|
|
||||||
old := getBucketVersioning
|
|
||||||
defer func() { getBucketVersioning = old }()
|
|
||||||
getBucketVersioning = func(ctx context.Context, minioClient *minio.Client, bucket string) error {
|
|
||||||
return minio.ErrorResponse{
|
|
||||||
StatusCode: http.StatusBadRequest,
|
|
||||||
Code: "FixtureError",
|
|
||||||
Message: message,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err := NewStorage(setting.MinioStorageType, cfg)
|
_, err := NewStorage(setting.MinioStorageType, cfg)
|
||||||
assert.ErrorContains(t, err, message)
|
assert.ErrorContains(t, err, "ObjectStorage.BucketExists: endpoint="+endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMinioCredentials(t *testing.T) {
|
func TestMinioCredentials(t *testing.T) {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ type CreateUserOption struct {
|
|||||||
// The full display name of the user
|
// The full display name of the user
|
||||||
FullName string `json:"full_name" binding:"MaxSize(100)"`
|
FullName string `json:"full_name" binding:"MaxSize(100)"`
|
||||||
// required: true
|
// required: true
|
||||||
// swagger:strfmt email
|
|
||||||
Email string `json:"email" binding:"Required;Email;MaxSize(254)"`
|
Email string `json:"email" binding:"Required;Email;MaxSize(254)"`
|
||||||
// The plain text password for the user
|
// The plain text password for the user
|
||||||
Password string `json:"password" binding:"MaxSize(255)"`
|
Password string `json:"password" binding:"MaxSize(255)"`
|
||||||
|
|||||||
@@ -133,7 +133,6 @@ type IssueAssigneesOption struct {
|
|||||||
// EditDeadlineOption options for creating a deadline
|
// EditDeadlineOption options for creating a deadline
|
||||||
type EditDeadlineOption struct {
|
type EditDeadlineOption struct {
|
||||||
// required:true
|
// required:true
|
||||||
// swagger:strfmt date-time
|
|
||||||
Deadline *time.Time `json:"due_date"`
|
Deadline *time.Time `json:"due_date"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,10 +54,10 @@ type CreateTeamOption struct {
|
|||||||
// Whether the team has access to all repositories in the organization
|
// Whether the team has access to all repositories in the organization
|
||||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||||
Permission RepoWritePermission `json:"permission"`
|
Permission RepoWritePermission `json:"permission"`
|
||||||
// example: ["repo.actions","repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.ext_wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
|
// example: ["repo.actions","repo.packages","repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
|
||||||
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
|
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
|
||||||
Units []string `json:"units"`
|
Units []string `json:"units"`
|
||||||
// example: {"repo.actions","repo.packages","repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"}
|
// example: {"repo.actions":"read","repo.packages":"read","repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"}
|
||||||
UnitsMap map[string]string `json:"units_map"`
|
UnitsMap map[string]string `json:"units_map"`
|
||||||
// Whether the team can create repositories in the organization
|
// Whether the team can create repositories in the organization
|
||||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||||
|
|||||||
@@ -92,9 +92,12 @@ func TestTemplateEscape(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run("Golang URL Escape", func(t *testing.T) {
|
t.Run("Golang URL Escape", func(t *testing.T) {
|
||||||
// Golang template considers "href", "*src*", "*uri*", "*url*" (and more) ... attributes as contentTypeURL and does auto-escaping
|
// HINT: GOLANG-HTML-TEMPLATE-URL-ESCAPING: demo cases (html/template/attr.go):
|
||||||
|
// Golang template considers "href", "data-href", "*src*", "*uri*", "*url*" (and more) ... attributes as contentTypeURL and does auto-escaping
|
||||||
actual := execTmpl(`<a href="?a={{"%"}}"></a>`)
|
actual := execTmpl(`<a href="?a={{"%"}}"></a>`)
|
||||||
assert.Equal(t, `<a href="?a=%25"></a>`, actual)
|
assert.Equal(t, `<a href="?a=%25"></a>`, actual)
|
||||||
|
actual = execTmpl(`<a data-href="?a={{"%"}}"></a>`)
|
||||||
|
assert.Equal(t, `<a data-href="?a=%25"></a>`, actual)
|
||||||
actual = execTmpl(`<a data-xxx-url="?a={{"%"}}"></a>`)
|
actual = execTmpl(`<a data-xxx-url="?a={{"%"}}"></a>`)
|
||||||
assert.Equal(t, `<a data-xxx-url="?a=%25"></a>`, actual)
|
assert.Equal(t, `<a data-xxx-url="?a=%25"></a>`, actual)
|
||||||
})
|
})
|
||||||
@@ -102,6 +105,10 @@ func TestTemplateEscape(t *testing.T) {
|
|||||||
// non-URL content isn't auto-escaped
|
// non-URL content isn't auto-escaped
|
||||||
actual := execTmpl(`<a data-link="?a={{"%"}}"></a>`)
|
actual := execTmpl(`<a data-link="?a={{"%"}}"></a>`)
|
||||||
assert.Equal(t, `<a data-link="?a=%"></a>`, actual)
|
assert.Equal(t, `<a data-link="?a=%"></a>`, actual)
|
||||||
|
// the attr names like "data-href" and "data-action" are treated as URL (as the "data-" prefix is stripped)
|
||||||
|
// but "data-xxx-href" and "data-xxx-action" are not, so no escaping.
|
||||||
|
actual = execTmpl(`<a data-xxx-href="?a={{"%"}}"></a>`)
|
||||||
|
assert.Equal(t, `<a data-xxx-href="?a=%"></a>`, actual)
|
||||||
})
|
})
|
||||||
t.Run("QueryBuild", func(t *testing.T) {
|
t.Run("QueryBuild", func(t *testing.T) {
|
||||||
actual := execTmpl(`<a href="{{QueryBuild "?" "a" "%"}}"></a>`)
|
actual := execTmpl(`<a href="{{QueryBuild "?" "a" "%"}}"></a>`)
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
"forks": "Forks",
|
"forks": "Forks",
|
||||||
"activities": "Aktivitäten",
|
"activities": "Aktivitäten",
|
||||||
"pull_requests": "Pull-Requests",
|
"pull_requests": "Pull-Requests",
|
||||||
"issues": "Probleme",
|
"issues": "Issues",
|
||||||
"milestones": "Meilensteine",
|
"milestones": "Meilensteine",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
@@ -1101,7 +1101,7 @@
|
|||||||
"repo.migrate_items_wiki": "Wiki",
|
"repo.migrate_items_wiki": "Wiki",
|
||||||
"repo.migrate_items_milestones": "Meilensteine",
|
"repo.migrate_items_milestones": "Meilensteine",
|
||||||
"repo.migrate_items_labels": "Labels",
|
"repo.migrate_items_labels": "Labels",
|
||||||
"repo.migrate_items_issues": "Probleme",
|
"repo.migrate_items_issues": "Issues",
|
||||||
"repo.migrate_items_pullrequests": "Pull-Requests",
|
"repo.migrate_items_pullrequests": "Pull-Requests",
|
||||||
"repo.migrate_items_merge_requests": "Merge-Requests",
|
"repo.migrate_items_merge_requests": "Merge-Requests",
|
||||||
"repo.migrate_items_releases": "Veröffentlichungen",
|
"repo.migrate_items_releases": "Veröffentlichungen",
|
||||||
@@ -1178,7 +1178,7 @@
|
|||||||
"repo.find_tag": "Tag finden",
|
"repo.find_tag": "Tag finden",
|
||||||
"repo.branches": "Branches",
|
"repo.branches": "Branches",
|
||||||
"repo.tags": "Tags",
|
"repo.tags": "Tags",
|
||||||
"repo.issues": "Probleme",
|
"repo.issues": "Issues",
|
||||||
"repo.pulls": "Pull-Requests",
|
"repo.pulls": "Pull-Requests",
|
||||||
"repo.projects": "Projekte",
|
"repo.projects": "Projekte",
|
||||||
"repo.packages": "Pakete",
|
"repo.packages": "Pakete",
|
||||||
@@ -2300,7 +2300,7 @@
|
|||||||
"repo.settings.event_repository": "Repository",
|
"repo.settings.event_repository": "Repository",
|
||||||
"repo.settings.event_repository_desc": "Repository erstellt oder gelöscht.",
|
"repo.settings.event_repository_desc": "Repository erstellt oder gelöscht.",
|
||||||
"repo.settings.event_header_issue": "Issue Ereignisse",
|
"repo.settings.event_header_issue": "Issue Ereignisse",
|
||||||
"repo.settings.event_issues": "Probleme",
|
"repo.settings.event_issues": "Issues",
|
||||||
"repo.settings.event_issues_desc": "Issue geöffnet, geschlossen, wieder geöffnet, bearbeitet oder gelöscht.",
|
"repo.settings.event_issues_desc": "Issue geöffnet, geschlossen, wieder geöffnet, bearbeitet oder gelöscht.",
|
||||||
"repo.settings.event_issue_assign": "Issue zugewiesen",
|
"repo.settings.event_issue_assign": "Issue zugewiesen",
|
||||||
"repo.settings.event_issue_assign_desc": "Issue zugewiesen oder Zuweisung entfernt.",
|
"repo.settings.event_issue_assign_desc": "Issue zugewiesen oder Zuweisung entfernt.",
|
||||||
@@ -3104,7 +3104,7 @@
|
|||||||
"admin.repos.owner": "Besitzer",
|
"admin.repos.owner": "Besitzer",
|
||||||
"admin.repos.name": "Name",
|
"admin.repos.name": "Name",
|
||||||
"admin.repos.private": "Privat",
|
"admin.repos.private": "Privat",
|
||||||
"admin.repos.issues": "Probleme",
|
"admin.repos.issues": "Issues",
|
||||||
"admin.repos.size": "Größe",
|
"admin.repos.size": "Größe",
|
||||||
"admin.repos.lfs_size": "LFS-Größe",
|
"admin.repos.lfs_size": "LFS-Größe",
|
||||||
"admin.packages.package_manage_panel": "Paketverwaltung",
|
"admin.packages.package_manage_panel": "Paketverwaltung",
|
||||||
@@ -3779,6 +3779,7 @@
|
|||||||
"actions.runs.commit": "Commit",
|
"actions.runs.commit": "Commit",
|
||||||
"actions.runs.run_details": "Run Details",
|
"actions.runs.run_details": "Run Details",
|
||||||
"actions.runs.workflow_file": "Workflow-Datei",
|
"actions.runs.workflow_file": "Workflow-Datei",
|
||||||
|
"actions.runs.workflow_file_no_permission": "Keine Berechtigung zum Anzeigen der Workflow-Datei",
|
||||||
"actions.runs.scheduled": "Geplant",
|
"actions.runs.scheduled": "Geplant",
|
||||||
"actions.runs.pushed_by": "gepusht von",
|
"actions.runs.pushed_by": "gepusht von",
|
||||||
"actions.runs.invalid_workflow_helper": "Die Workflow-Konfigurationsdatei ist ungültig. Bitte überprüfe Deine Konfigurationsdatei: %s",
|
"actions.runs.invalid_workflow_helper": "Die Workflow-Konfigurationsdatei ist ungültig. Bitte überprüfe Deine Konfigurationsdatei: %s",
|
||||||
@@ -3835,7 +3836,33 @@
|
|||||||
"actions.workflow.scope_owner": "Besitzer",
|
"actions.workflow.scope_owner": "Besitzer",
|
||||||
"actions.workflow.scope_global": "Global",
|
"actions.workflow.scope_global": "Global",
|
||||||
"actions.workflow.required": "Erforderlich",
|
"actions.workflow.required": "Erforderlich",
|
||||||
|
"actions.workflow.scoped_required_cannot_disable": "Dieser Scoped Workflow ist erforderlich und kann nicht deaktiviert werden.",
|
||||||
|
"actions.scoped_workflows": "Scoped Workflows",
|
||||||
|
"actions.scoped_workflows.desc_org": "Repositories als Scoped Workflow Quellen registrieren. Workflow-Dateien unter den Workflow-Verzeichnissen eines Quell-Repositorys laufen in jedem Projektarchiv dieser Organisation, im eigenen Kontext des Projektarchivs.",
|
||||||
|
"actions.scoped_workflows.desc_user": "Repositories als Scoped Workflow Quellen registrieren. Workflow-Dateien unter den Workflow-Verzeichnissen eines Quell-Repositorys laufen in jedem Projektarchiv dieser Organisation, im eigenen Kontext des Projektarchivs.",
|
||||||
|
"actions.scoped_workflows.desc_global": "Repositories als Scoped Workflow-Quellen registrieren. Workflow-Dateien unter den Workflow-Verzeichnissen eines Quellcode-Repositorys laufen auf jedem Projektarchiv in dieser Instanz im eigenen Kontext des Projektarchivs. Da Quellen auf Instanzenebene auf den Ereignissen jedes Projektarchivs ausgewertet werden, kann die Registrierung bei großen Instanzen Overhead hinzufügen.",
|
||||||
|
"actions.scoped_workflows.add_help": "Um Scoped Workflows aus einem Repository zu erstellen, übertrage die Workflow-Dateien unter <code>%s</code> in seinem Standard Branch, dann registrieren Sie das Projektarchiv als Quelle unten.",
|
||||||
|
"actions.scoped_workflows.security_note": "Der Workflow-Inhalt eines Quell-Repositorys wird in jedem Repository ausgeführt, für das er gilt. Die Skripte der einzelnen Schritte sowie deren Ausgabe werden in den Actions-Logs des jeweiligen Repositorys gespeichert und sind für alle sichtbar, die die Actions des konsumierenden Repositorys einsehen können. Das Registrieren eines privaten Repositorys als Quelle legt daher dessen Workflow-Logik über diese Logs offen. Registriere nur Repositorys, deren Workflow-Inhalte mit allen konsumierenden Repositorys geteilt werden dürfen. Wenn ein scoped Workflow einen wiederverwendbaren Workflow aus einem privaten Repository referenziert, stelle sicher, dass jedes konsumierende Repository darauf Lesezugriff hat – andernfalls schlägt der Workflow dort fehl.",
|
||||||
|
"actions.scoped_workflows.source.add": "Quell-Repository hinzufügen",
|
||||||
|
"actions.scoped_workflows.source.add_success": "Quell-Repository hinzugefügt.",
|
||||||
|
"actions.scoped_workflows.source.remove_success": "Quell-Repository entfernt.",
|
||||||
|
"actions.scoped_workflows.source.not_found": "Repository nicht gefunden.",
|
||||||
|
"actions.scoped_workflows.required.update_success": "Erforderliche Workflows aktualisiert.",
|
||||||
|
"actions.scoped_workflows.required.label": "Workflows als erforderlich markieren (ein erforderlicher Workflow kann nicht durch Repositories deaktiviert werden):",
|
||||||
|
"actions.scoped_workflows.required.patterns": "Erforderliche Statusüberprüfungsmuster",
|
||||||
|
"actions.scoped_workflows.required.patterns_aria": "Erforderliche Statusüberprüfungsmuster für %s",
|
||||||
|
"actions.scoped_workflows.required.patterns_note": "nur erzwungen während der Workflow benötigt wird",
|
||||||
|
"actions.scoped_workflows.required.patterns_hint": "Markieren Sie den Workflow als erforderlich, um seine Statusüberprüfungsmuster zu konfigurieren.",
|
||||||
|
"actions.scoped_workflows.required.patterns_help": "Ein Statusüberprüfungsmuster (glob) pro Zeile. Ein Pull-Request kann erst zusammengeführt werden, wenn ein Status mit jedem Muster übereinstimmt. Dies wird für jeden Zielzweig erzwungen, der eine Schutzregel hat, auch für einen mit einer eigenen Statusüberprüfung; ein Zielzweig ohne Schutzregel ist nicht ausgeschaltet.",
|
||||||
|
"actions.scoped_workflows.required.patterns_empty": "Jeder benötigte Workflow benötigt mindestens ein Statusüberprüfungsmuster.",
|
||||||
|
"actions.scoped_workflows.required.missing_file": "nicht mehr im Quelltext",
|
||||||
|
"actions.scoped_workflows.required.expected_contexts": "Erwartete Statusüberprüfung (eine Prüfung, die zu einem Muster passt)",
|
||||||
|
"actions.scoped_workflows.required.no_status_contexts": "Dieser Workflow postet keine Status Checks, ihn als Anforderung zu markieren würde jede Pull Request blockieren. Deaktiviere die Anforderung.",
|
||||||
|
"actions.scoped_workflows.no_files": "Im Standard-Branch wurden keine Scoped Workflow-Dateien gefunden.",
|
||||||
"actions.workflow.run": "Workflow ausführen",
|
"actions.workflow.run": "Workflow ausführen",
|
||||||
|
"actions.workflow.create_status_badge": "Status Badge erstellen",
|
||||||
|
"actions.workflow.status_badge": "Status Badge",
|
||||||
|
"actions.workflow.status_badge_url": "Badge-URL",
|
||||||
"actions.workflow.not_found": "Workflow '%s' wurde nicht gefunden.",
|
"actions.workflow.not_found": "Workflow '%s' wurde nicht gefunden.",
|
||||||
"actions.workflow.run_success": "Workflow '%s' erfolgreich ausgeführt.",
|
"actions.workflow.run_success": "Workflow '%s' erfolgreich ausgeführt.",
|
||||||
"actions.workflow.from_ref": "Nutze Workflow von",
|
"actions.workflow.from_ref": "Nutze Workflow von",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"sign_out": "Déconnexion",
|
"sign_out": "Déconnexion",
|
||||||
"sign_up": "S'inscrire",
|
"sign_up": "S'inscrire",
|
||||||
"link_account": "Lier un Compte",
|
"link_account": "Lier un Compte",
|
||||||
"register": "S'inscrire",
|
"register": "S’inscrire",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"powered_by": "Propulsé par %s",
|
"powered_by": "Propulsé par %s",
|
||||||
"page": "Page",
|
"page": "Page",
|
||||||
@@ -80,8 +80,8 @@
|
|||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"retry": "Réessayez",
|
"retry": "Réessayez",
|
||||||
"rerun": "Relancer",
|
"rerun": "Relancer",
|
||||||
"rerun_all": "Relancer toutes les tâches",
|
"rerun_all": "Relancer toutes les missions.",
|
||||||
"rerun_failed": "Relancer les tâches échouées",
|
"rerun_failed": "Relancer les missions échouées.",
|
||||||
"save": "Enregistrer",
|
"save": "Enregistrer",
|
||||||
"add": "Ajouter",
|
"add": "Ajouter",
|
||||||
"add_all": "Tout Ajouter",
|
"add_all": "Tout Ajouter",
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
"search.fuzzy_tooltip": "Inclure également les résultats proches de la recherche",
|
"search.fuzzy_tooltip": "Inclure également les résultats proches de la recherche",
|
||||||
"search.words": "Mots",
|
"search.words": "Mots",
|
||||||
"search.words_tooltip": "Inclure uniquement les résultats qui correspondent exactement aux mots recherchés",
|
"search.words_tooltip": "Inclure uniquement les résultats qui correspondent exactement aux mots recherchés",
|
||||||
"search.regexp": "Regexp",
|
"search.regexp": "Expression régulière",
|
||||||
"search.regexp_tooltip": "Inclure uniquement les résultats qui correspondent à l’expression régulière recherchée",
|
"search.regexp_tooltip": "Inclure uniquement les résultats qui correspondent à l’expression régulière recherchée",
|
||||||
"search.exact": "Exact",
|
"search.exact": "Exact",
|
||||||
"search.exact_tooltip": "Inclure uniquement les résultats qui correspondent exactement au terme de recherche",
|
"search.exact_tooltip": "Inclure uniquement les résultats qui correspondent exactement au terme de recherche",
|
||||||
@@ -185,7 +185,7 @@
|
|||||||
"search.tag_kind": "Chercher des étiquettes…",
|
"search.tag_kind": "Chercher des étiquettes…",
|
||||||
"search.tag_tooltip": "Cherchez des étiquettes correspondantes. Utilisez « % » pour rechercher n’importe quelle suite de nombres.",
|
"search.tag_tooltip": "Cherchez des étiquettes correspondantes. Utilisez « % » pour rechercher n’importe quelle suite de nombres.",
|
||||||
"search.commit_kind": "Chercher des révisions…",
|
"search.commit_kind": "Chercher des révisions…",
|
||||||
"search.runner_kind": "Chercher des exécuteurs…",
|
"search.runner_kind": "Chercher des opérateurs…",
|
||||||
"search.no_results": "Aucun résultat correspondant trouvé.",
|
"search.no_results": "Aucun résultat correspondant trouvé.",
|
||||||
"search.issue_kind": "Recherche de tickets…",
|
"search.issue_kind": "Recherche de tickets…",
|
||||||
"search.pull_kind": "Recherche de demandes d’ajouts…",
|
"search.pull_kind": "Recherche de demandes d’ajouts…",
|
||||||
@@ -289,7 +289,7 @@
|
|||||||
"install.smtp_port": "Port SMTP",
|
"install.smtp_port": "Port SMTP",
|
||||||
"install.smtp_from": "Envoyer les courriels en tant que",
|
"install.smtp_from": "Envoyer les courriels en tant que",
|
||||||
"install.smtp_from_invalid": "L’adresse « Envoyer le courriel sous » est invalide",
|
"install.smtp_from_invalid": "L’adresse « Envoyer le courriel sous » est invalide",
|
||||||
"install.smtp_from_helper": "Adresse courriel utilisée par Gitea. Utilisez directement votre adresse ou la forme « Nom <email@example.com> ».",
|
"install.smtp_from_helper": "Adresse courriel utilisée par Gitea. Utilisez directement votre adresse ou la forme « Nom <courriel@exemple.com> ».",
|
||||||
"install.mailer_user": "Utilisateur SMTP",
|
"install.mailer_user": "Utilisateur SMTP",
|
||||||
"install.mailer_password": "Mot de passe SMTP",
|
"install.mailer_password": "Mot de passe SMTP",
|
||||||
"install.register_confirm": "Exiger la confirmation du courriel lors de l’inscription",
|
"install.register_confirm": "Exiger la confirmation du courriel lors de l’inscription",
|
||||||
@@ -308,7 +308,7 @@
|
|||||||
"install.require_sign_in_view_popup": "Limiter l’accès aux pages aux utilisateurs connectés. Les visiteurs ne verront que les pages de connexion et d’inscription.",
|
"install.require_sign_in_view_popup": "Limiter l’accès aux pages aux utilisateurs connectés. Les visiteurs ne verront que les pages de connexion et d’inscription.",
|
||||||
"install.admin_setting_desc": "La création d'un compte administrateur est facultative. Le premier utilisateur enregistré deviendra automatiquement un administrateur le cas échéant.",
|
"install.admin_setting_desc": "La création d'un compte administrateur est facultative. Le premier utilisateur enregistré deviendra automatiquement un administrateur le cas échéant.",
|
||||||
"install.admin_title": "Paramètres de compte administrateur",
|
"install.admin_title": "Paramètres de compte administrateur",
|
||||||
"install.admin_name": "Nom d’utilisateur administrateur",
|
"install.admin_name": "Nom de l’administrateur",
|
||||||
"install.admin_password": "Mot de passe",
|
"install.admin_password": "Mot de passe",
|
||||||
"install.confirm_password": "Confirmez le mot de passe",
|
"install.confirm_password": "Confirmez le mot de passe",
|
||||||
"install.admin_email": "Courriel",
|
"install.admin_email": "Courriel",
|
||||||
@@ -505,10 +505,10 @@
|
|||||||
"mail.repo.actions.run.failed": "L’exécution a échoué",
|
"mail.repo.actions.run.failed": "L’exécution a échoué",
|
||||||
"mail.repo.actions.run.succeeded": "L’exécution a réussi",
|
"mail.repo.actions.run.succeeded": "L’exécution a réussi",
|
||||||
"mail.repo.actions.run.cancelled": "L’exécution a été annulée",
|
"mail.repo.actions.run.cancelled": "L’exécution a été annulée",
|
||||||
"mail.repo.actions.jobs.all_succeeded": "Tous les tâches ont réussi.",
|
"mail.repo.actions.jobs.all_succeeded": "Tous les missions ont réussi.",
|
||||||
"mail.repo.actions.jobs.all_failed": "Toutes les tâches ont échoué.",
|
"mail.repo.actions.jobs.all_failed": "Toutes les missions ont échoué.",
|
||||||
"mail.repo.actions.jobs.some_not_successful": "Certaines tâches n’ont pas réussi.",
|
"mail.repo.actions.jobs.some_not_successful": "Certaines missions n’ont pas réussi.",
|
||||||
"mail.repo.actions.jobs.all_cancelled": "Toutes les tâches ont bien été annulés.",
|
"mail.repo.actions.jobs.all_cancelled": "Toutes les missions ont bien été annulées.",
|
||||||
"mail.team_invite.subject": "%[1]s vous a invité à rejoindre l’organisation %[2]s",
|
"mail.team_invite.subject": "%[1]s vous a invité à rejoindre l’organisation %[2]s",
|
||||||
"mail.team_invite.text_1": "%[1]s vous a invité à rejoindre l’équipe %[2]s dans l’organisation %[3]s.",
|
"mail.team_invite.text_1": "%[1]s vous a invité à rejoindre l’équipe %[2]s dans l’organisation %[3]s.",
|
||||||
"mail.team_invite.text_2": "Veuillez cliquer sur le lien suivant pour rejoindre l'équipe :",
|
"mail.team_invite.text_2": "Veuillez cliquer sur le lien suivant pour rejoindre l'équipe :",
|
||||||
@@ -916,7 +916,7 @@
|
|||||||
"settings.passcode_invalid": "Le mot de passe est invalide. Réessayez.",
|
"settings.passcode_invalid": "Le mot de passe est invalide. Réessayez.",
|
||||||
"settings.twofa_enrolled": "L’authentification à deux facteurs a été activée pour votre compte. Gardez votre clé de secours (%s) en lieu sûr, car il ne vous sera montré qu'une seule fois.",
|
"settings.twofa_enrolled": "L’authentification à deux facteurs a été activée pour votre compte. Gardez votre clé de secours (%s) en lieu sûr, car il ne vous sera montré qu'une seule fois.",
|
||||||
"settings.twofa_failed_get_secret": "Impossible d'obtenir le secret.",
|
"settings.twofa_failed_get_secret": "Impossible d'obtenir le secret.",
|
||||||
"settings.webauthn_desc": "Les clés de sécurité sont des dispositifs matériels contenant des clés cryptographiques. Elles peuvent être utilisées pour l’authentification à deux facteurs. La clé de sécurité doit supporter le standard <a rel=\"noreferrer\" target=\"_blank\" href=\"%s\">WebAuthn Authenticator</a>.",
|
"settings.webauthn_desc": "Les clés de sécurité sont des dispositifs matériels contenant des clés cryptographiques. Elles peuvent être utilisées pour l’authentification à deux facteurs. Gitea requière le support de l’<a rel=\"noreferrer\" target=\"_blank\" href=\"%s\">API Web Authentication</a>.",
|
||||||
"settings.webauthn_register_key": "Ajouter une clé de sécurité",
|
"settings.webauthn_register_key": "Ajouter une clé de sécurité",
|
||||||
"settings.webauthn_nickname": "Pseudonyme",
|
"settings.webauthn_nickname": "Pseudonyme",
|
||||||
"settings.webauthn_delete_key": "Retirer la clé de sécurité",
|
"settings.webauthn_delete_key": "Retirer la clé de sécurité",
|
||||||
@@ -944,8 +944,8 @@
|
|||||||
"settings.email_notifications.disable": "Ne pas notifier",
|
"settings.email_notifications.disable": "Ne pas notifier",
|
||||||
"settings.email_notifications.submit": "Définir les préférences de courriel",
|
"settings.email_notifications.submit": "Définir les préférences de courriel",
|
||||||
"settings.email_notifications.andyourown": "Inclure vos propres notifications",
|
"settings.email_notifications.andyourown": "Inclure vos propres notifications",
|
||||||
"settings.email_notifications.actions.desc": "Notification pour les executions de workflows sur les dépôts configurés avec les <a target=\"_blank\" href=\"%s\">Actions Gitea</a>.",
|
"settings.email_notifications.actions.desc": "Notifier les procédures des dépôts configurés avec les <a target=\"_blank\" href=\"%s\">Actions Gitea</a>.",
|
||||||
"settings.email_notifications.actions.failure_only": "Ne notifier que pour les exécutions échouées",
|
"settings.email_notifications.actions.failure_only": "Ne notifier que procédures échouées",
|
||||||
"settings.visibility": "Visibilité de l'utilisateur",
|
"settings.visibility": "Visibilité de l'utilisateur",
|
||||||
"settings.visibility.public": "Publique",
|
"settings.visibility.public": "Publique",
|
||||||
"settings.visibility.public_tooltip": "Visible par tout le monde",
|
"settings.visibility.public_tooltip": "Visible par tout le monde",
|
||||||
@@ -992,7 +992,7 @@
|
|||||||
"repo.repo_desc": "Description",
|
"repo.repo_desc": "Description",
|
||||||
"repo.repo_desc_helper": "Décrire brièvement votre dépôt",
|
"repo.repo_desc_helper": "Décrire brièvement votre dépôt",
|
||||||
"repo.repo_no_desc": "Aucune description fournie",
|
"repo.repo_no_desc": "Aucune description fournie",
|
||||||
"repo.repo_lang": "Langue",
|
"repo.repo_lang": "Langues",
|
||||||
"repo.repo_gitignore_helper": "Sélectionner quelques .gitignore prédéfinies",
|
"repo.repo_gitignore_helper": "Sélectionner quelques .gitignore prédéfinies",
|
||||||
"repo.repo_gitignore_helper_desc": "De nombreux outils et compilateurs génèrent des fichiers résiduels qui n'ont pas besoin d'être supervisés par git. Composez un .gitignore à l’aide de cette liste des languages de programmation courants.",
|
"repo.repo_gitignore_helper_desc": "De nombreux outils et compilateurs génèrent des fichiers résiduels qui n'ont pas besoin d'être supervisés par git. Composez un .gitignore à l’aide de cette liste des languages de programmation courants.",
|
||||||
"repo.issue_labels": "Jeu de labels pour les tickets",
|
"repo.issue_labels": "Jeu de labels pour les tickets",
|
||||||
@@ -1041,7 +1041,7 @@
|
|||||||
"repo.stars": "Favoris",
|
"repo.stars": "Favoris",
|
||||||
"repo.reactions_more": "et %d de plus",
|
"repo.reactions_more": "et %d de plus",
|
||||||
"repo.reactions": "Réactions",
|
"repo.reactions": "Réactions",
|
||||||
"repo.unit_disabled": "L'administrateur du site a désactivé cette section du dépôt.",
|
"repo.unit_disabled": "L’administrateur du site a désactivé cette section du dépôt.",
|
||||||
"repo.language_other": "Autre",
|
"repo.language_other": "Autre",
|
||||||
"repo.adopt_search": "Entrez un nom d’utilisateur pour rechercher les dépôts dépossédés… (laissez vide pour tous trouver)",
|
"repo.adopt_search": "Entrez un nom d’utilisateur pour rechercher les dépôts dépossédés… (laissez vide pour tous trouver)",
|
||||||
"repo.adopt_preexisting_label": "Adopter les fichiers",
|
"repo.adopt_preexisting_label": "Adopter les fichiers",
|
||||||
@@ -1106,9 +1106,9 @@
|
|||||||
"repo.migrate_items_merge_requests": "Demandes de fusion",
|
"repo.migrate_items_merge_requests": "Demandes de fusion",
|
||||||
"repo.migrate_items_releases": "Publications",
|
"repo.migrate_items_releases": "Publications",
|
||||||
"repo.migrate_repo": "Migrer le dépôt",
|
"repo.migrate_repo": "Migrer le dépôt",
|
||||||
"repo.migrate.clone_address": "Migrer/Cloner depuis une URL",
|
"repo.migrate.clone_address": "Migrer depuis une URL",
|
||||||
"repo.migrate.clone_address_desc": "L’URL ou le lien « Git clone » d’un dépôt existant",
|
"repo.migrate.clone_address_desc": "L’URL ou le lien « Git clone » d’un dépôt existant",
|
||||||
"repo.migrate.github_token_desc": "Vous pouvez mettre un ou plusieurs jetons séparés par des virgules ici pour rendre la migration plus rapide et contourner la limite de débit de l’API GitHub. ATTENTION : abuser de cette fonctionnalité peut enfreindre la politique du fournisseur de service et entraîner un blocage de votre compte.",
|
"repo.migrate.github_token_desc": "Vous pouvez mettre un ou plusieurs jetons séparés par des virgules ici pour rendre la migration plus rapide et contourner la limite de débit de l’API GitHub. Attention : abuser de cette fonctionnalité peut enfreindre la politique du fournisseur de service et entraîner un blocage de votre compte.",
|
||||||
"repo.migrate.clone_local_path": "ou un chemin serveur local",
|
"repo.migrate.clone_local_path": "ou un chemin serveur local",
|
||||||
"repo.migrate.permission_denied": "Vous n'êtes pas autorisé à importer des dépôts locaux.",
|
"repo.migrate.permission_denied": "Vous n'êtes pas autorisé à importer des dépôts locaux.",
|
||||||
"repo.migrate.permission_denied_blocked": "Vous ne pouvez pas importer depuis des domaines bannis, veuillez demander à votre administrateur de vérifier les paramètres ALLOWED_DOMAINS, ALLOW_LOCALNETWORKS ou BLOCKED_DOMAINS.",
|
"repo.migrate.permission_denied_blocked": "Vous ne pouvez pas importer depuis des domaines bannis, veuillez demander à votre administrateur de vérifier les paramètres ALLOWED_DOMAINS, ALLOW_LOCALNETWORKS ou BLOCKED_DOMAINS.",
|
||||||
@@ -1165,7 +1165,7 @@
|
|||||||
"repo.clone_this_repo": "Cloner ce dépôt",
|
"repo.clone_this_repo": "Cloner ce dépôt",
|
||||||
"repo.cite_this_repo": "Citer ce dépôt",
|
"repo.cite_this_repo": "Citer ce dépôt",
|
||||||
"repo.create_new_repo_command": "Création d'un nouveau dépôt en ligne de commande",
|
"repo.create_new_repo_command": "Création d'un nouveau dépôt en ligne de commande",
|
||||||
"repo.push_exist_repo": "Soumission d'un dépôt existant par ligne de commande",
|
"repo.push_exist_repo": "Soumission d’un dépôt existant par ligne de commande",
|
||||||
"repo.empty_message": "Ce dépôt n’a pas de contenu.",
|
"repo.empty_message": "Ce dépôt n’a pas de contenu.",
|
||||||
"repo.broken_message": "Les données git de ce dépôt ne peuvent pas être lues. Contactez l'administrateur de cette instance ou supprimez ce dépôt.",
|
"repo.broken_message": "Les données git de ce dépôt ne peuvent pas être lues. Contactez l'administrateur de cette instance ou supprimez ce dépôt.",
|
||||||
"repo.no_branch": "Ce dépôt n’a aucune branche.",
|
"repo.no_branch": "Ce dépôt n’a aucune branche.",
|
||||||
@@ -1294,7 +1294,7 @@
|
|||||||
"repo.editor.file_changed_while_editing": "Le contenu du fichier a changé depuis que vous avez commencé à éditer. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">Cliquez ici</a> pour voir les changements ou <strong>soumettez de nouveau</strong> pour les écraser.",
|
"repo.editor.file_changed_while_editing": "Le contenu du fichier a changé depuis que vous avez commencé à éditer. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">Cliquez ici</a> pour voir les changements ou <strong>soumettez de nouveau</strong> pour les écraser.",
|
||||||
"repo.editor.file_already_exists": "Un fichier nommé \"%s\" existe déjà dans ce dépôt.",
|
"repo.editor.file_already_exists": "Un fichier nommé \"%s\" existe déjà dans ce dépôt.",
|
||||||
"repo.editor.commit_id_not_matching": "L’ID de la révision ne correspond pas à l’ID lorsque vous avez commencé à éditer. Faites une révision dans une branche de correctif puis fusionnez.",
|
"repo.editor.commit_id_not_matching": "L’ID de la révision ne correspond pas à l’ID lorsque vous avez commencé à éditer. Faites une révision dans une branche de correctif puis fusionnez.",
|
||||||
"repo.editor.push_out_of_date": "Cet envoi semble être obsolète.",
|
"repo.editor.push_out_of_date": "Cette soumission semble être obsolète.",
|
||||||
"repo.editor.commit_empty_file_header": "Réviser un fichier vide",
|
"repo.editor.commit_empty_file_header": "Réviser un fichier vide",
|
||||||
"repo.editor.commit_empty_file_text": "Le fichier que vous allez réviser est vide. Continuer ?",
|
"repo.editor.commit_empty_file_text": "Le fichier que vous allez réviser est vide. Continuer ?",
|
||||||
"repo.editor.no_changes_to_show": "Il n’y a aucune modification à afficher.",
|
"repo.editor.no_changes_to_show": "Il n’y a aucune modification à afficher.",
|
||||||
@@ -1307,7 +1307,7 @@
|
|||||||
"repo.editor.upload_files_to_dir": "Téléverser les fichiers vers \"%s\"",
|
"repo.editor.upload_files_to_dir": "Téléverser les fichiers vers \"%s\"",
|
||||||
"repo.editor.cannot_commit_to_protected_branch": "Impossible de créer une révision sur la branche protégée \"%s\".",
|
"repo.editor.cannot_commit_to_protected_branch": "Impossible de créer une révision sur la branche protégée \"%s\".",
|
||||||
"repo.editor.no_commit_to_branch": "Impossible de réviser cette branche car :",
|
"repo.editor.no_commit_to_branch": "Impossible de réviser cette branche car :",
|
||||||
"repo.editor.user_no_push_to_branch": "L'utilisateur ne peut pas pousser vers la branche",
|
"repo.editor.user_no_push_to_branch": "L’utilisateur ne peut pas soumettre sur la branche",
|
||||||
"repo.editor.require_signed_commit": "Cette branche nécessite une révision signée",
|
"repo.editor.require_signed_commit": "Cette branche nécessite une révision signée",
|
||||||
"repo.editor.cherry_pick": "Picorer %s vers:",
|
"repo.editor.cherry_pick": "Picorer %s vers:",
|
||||||
"repo.editor.revert": "Rétablir %s sur:",
|
"repo.editor.revert": "Rétablir %s sur:",
|
||||||
@@ -1317,10 +1317,11 @@
|
|||||||
"repo.editor.fork_create_description": "Vous ne pouvez pas modifier ce dépôt directement. Cependant, vous pouvez bifurquer ce dépôt, et créer une demande d’ajout avec vos contributions.",
|
"repo.editor.fork_create_description": "Vous ne pouvez pas modifier ce dépôt directement. Cependant, vous pouvez bifurquer ce dépôt, et créer une demande d’ajout avec vos contributions.",
|
||||||
"repo.editor.fork_edit_description": "Vous ne pouvez pas modifier ce dépôt directement. Les modifications seront écrites sur une bifurcation <b>%s</b>, vous permettant de faire une demande d’ajout.",
|
"repo.editor.fork_edit_description": "Vous ne pouvez pas modifier ce dépôt directement. Les modifications seront écrites sur une bifurcation <b>%s</b>, vous permettant de faire une demande d’ajout.",
|
||||||
"repo.editor.fork_not_editable": "Vous avez bifurqué ce dépôt mais votre copie n’est pas modifiable.",
|
"repo.editor.fork_not_editable": "Vous avez bifurqué ce dépôt mais votre copie n’est pas modifiable.",
|
||||||
"repo.editor.fork_failed_to_push_branch": "Impossible de pousser la branche %s vers votre dépôt.",
|
"repo.editor.fork_failed_to_push_branch": "Impossible de soumettre la branche %s vers votre dépôt.",
|
||||||
"repo.editor.fork_branch_exists": "La branche « %s » existe déjà dans votre bifurcation, veuillez choisir un nouveau nom.",
|
"repo.editor.fork_branch_exists": "La branche « %s » existe déjà dans votre bifurcation, veuillez choisir un nouveau nom.",
|
||||||
"repo.commits.desc": "Naviguer dans l'historique des modifications.",
|
"repo.commits.desc": "Naviguer dans l'historique des modifications.",
|
||||||
"repo.commits.commits": "Révisions",
|
"repo.commits.commits": "Révisions",
|
||||||
|
"repo.commits.history_enable_follow_renames": "Inclure les renommages",
|
||||||
"repo.commits.no_commits": "Pas de révisions en commun. \"%s\" et \"%s\" ont des historiques entièrement différents.",
|
"repo.commits.no_commits": "Pas de révisions en commun. \"%s\" et \"%s\" ont des historiques entièrement différents.",
|
||||||
"repo.commits.nothing_to_compare": "Ces révisions sont équivalentes.",
|
"repo.commits.nothing_to_compare": "Ces révisions sont équivalentes.",
|
||||||
"repo.commits.search.tooltip": "Vous pouvez utiliser les mots-clés \"author:\", \"committer:\", \"after:\", ou \"before:\" pour filtrer votre recherche, ex.: \"revert author:Alice before:2019-01-13\".",
|
"repo.commits.search.tooltip": "Vous pouvez utiliser les mots-clés \"author:\", \"committer:\", \"after:\", ou \"before:\" pour filtrer votre recherche, ex.: \"revert author:Alice before:2019-01-13\".",
|
||||||
@@ -1332,8 +1333,8 @@
|
|||||||
"repo.commits.older": "Précédemment",
|
"repo.commits.older": "Précédemment",
|
||||||
"repo.commits.newer": "Récemment",
|
"repo.commits.newer": "Récemment",
|
||||||
"repo.commits.signed_by": "Signé par",
|
"repo.commits.signed_by": "Signé par",
|
||||||
"repo.commits.signed_by_untrusted_user": "Signature provenant d'un utilisateur dilletant",
|
"repo.commits.signed_by_untrusted_user": "Signé en dilettante par",
|
||||||
"repo.commits.signed_by_untrusted_user_unmatched": "Signature discordante de l'auteur de la révision et provenant d'un utilisateur dilletant",
|
"repo.commits.signed_by_untrusted_user_unmatched": "Signé, sans en être l’auteur, par",
|
||||||
"repo.commits.gpg_key_id": "ID de la clé GPG",
|
"repo.commits.gpg_key_id": "ID de la clé GPG",
|
||||||
"repo.commits.ssh_key_fingerprint": "Empreinte numérique de la clé SSH",
|
"repo.commits.ssh_key_fingerprint": "Empreinte numérique de la clé SSH",
|
||||||
"repo.commits.view_path": "Voir à ce point de l'historique",
|
"repo.commits.view_path": "Voir à ce point de l'historique",
|
||||||
@@ -1621,7 +1622,7 @@
|
|||||||
"repo.issues.lock.notice_2": "- Vous et les autres collaborateurs ayant accès à ce dépôt peuvent toujours laisser des commentaires que d’autres peuvent voir.",
|
"repo.issues.lock.notice_2": "- Vous et les autres collaborateurs ayant accès à ce dépôt peuvent toujours laisser des commentaires que d’autres peuvent voir.",
|
||||||
"repo.issues.lock.notice_3": "- Vous pouvez toujours déverrouiller ce ticket à l'avenir.",
|
"repo.issues.lock.notice_3": "- Vous pouvez toujours déverrouiller ce ticket à l'avenir.",
|
||||||
"repo.issues.unlock.notice_1": "- Tout le monde sera de nouveau en mesure de commenter ce ticket.",
|
"repo.issues.unlock.notice_1": "- Tout le monde sera de nouveau en mesure de commenter ce ticket.",
|
||||||
"repo.issues.unlock.notice_2": "- Vous pouvez toujours verrouiller ce ticket à l'avenir.",
|
"repo.issues.unlock.notice_2": "- Vous pouvez toujours verrouiller ce ticket à l’avenir.",
|
||||||
"repo.issues.lock.reason": "Motif de verrouillage",
|
"repo.issues.lock.reason": "Motif de verrouillage",
|
||||||
"repo.issues.lock.title": "Verrouiller la conversation sur ce ticket.",
|
"repo.issues.lock.title": "Verrouiller la conversation sur ce ticket.",
|
||||||
"repo.issues.unlock.title": "Déverrouiller la conversation sur ce ticket.",
|
"repo.issues.unlock.title": "Déverrouiller la conversation sur ce ticket.",
|
||||||
@@ -1817,9 +1818,9 @@
|
|||||||
"repo.pulls.is_checking": "Recherche de conflits de fusion…",
|
"repo.pulls.is_checking": "Recherche de conflits de fusion…",
|
||||||
"repo.pulls.is_ancestor": "Cette branche est déjà présente dans la branche ciblée. Il n'y a rien à fusionner.",
|
"repo.pulls.is_ancestor": "Cette branche est déjà présente dans la branche ciblée. Il n'y a rien à fusionner.",
|
||||||
"repo.pulls.is_empty": "Les changements sur cette branche sont déjà sur la branche cible. Cette révision sera vide.",
|
"repo.pulls.is_empty": "Les changements sur cette branche sont déjà sur la branche cible. Cette révision sera vide.",
|
||||||
"repo.pulls.required_status_check_failed": "Certains contrôles requis n'ont pas réussi.",
|
"repo.pulls.required_status_check_failed": "Certains signaux requis n'ont pas réussi.",
|
||||||
"repo.pulls.required_status_check_missing": "Certains contrôles requis sont manquants.",
|
"repo.pulls.required_status_check_missing": "Certains signaux requis sont manquants.",
|
||||||
"repo.pulls.required_status_check_administrator": "En tant qu'administrateur, vous pouvez toujours fusionner cette requête de pull.",
|
"repo.pulls.required_status_check_administrator": "En tant qu’administrateur, vous pouvez fusionner cette demande d’ajout.",
|
||||||
"repo.pulls.required_status_check_bypass_allowlist": "Vous êtes autorisé à contourner les règles de protection pour cette fusion.",
|
"repo.pulls.required_status_check_bypass_allowlist": "Vous êtes autorisé à contourner les règles de protection pour cette fusion.",
|
||||||
"repo.pulls.blocked_by_approvals": "Cette demande d’ajout n’est pas suffisamment approuvée. %d approbations obtenues sur %d.",
|
"repo.pulls.blocked_by_approvals": "Cette demande d’ajout n’est pas suffisamment approuvée. %d approbations obtenues sur %d.",
|
||||||
"repo.pulls.blocked_by_approvals_whitelisted": "Cette demande d’ajout n’a pas encore assez d’approbations. %d sur %d approbations de la part des utilisateurs ou équipes sur la liste autorisée.",
|
"repo.pulls.blocked_by_approvals_whitelisted": "Cette demande d’ajout n’a pas encore assez d’approbations. %d sur %d approbations de la part des utilisateurs ou équipes sur la liste autorisée.",
|
||||||
@@ -1843,7 +1844,7 @@
|
|||||||
"repo.pulls.no_merge_desc": "Cette demande d’ajout ne peut être fusionnée car toutes les options de fusion du dépôt sont désactivées.",
|
"repo.pulls.no_merge_desc": "Cette demande d’ajout ne peut être fusionnée car toutes les options de fusion du dépôt sont désactivées.",
|
||||||
"repo.pulls.no_merge_helper": "Activez des options de fusion dans les paramètres du dépôt ou fusionnez la demande manuellement.",
|
"repo.pulls.no_merge_helper": "Activez des options de fusion dans les paramètres du dépôt ou fusionnez la demande manuellement.",
|
||||||
"repo.pulls.no_merge_wip": "Cette demande d’ajout ne peut pas être fusionnée car elle est marquée en chantier.",
|
"repo.pulls.no_merge_wip": "Cette demande d’ajout ne peut pas être fusionnée car elle est marquée en chantier.",
|
||||||
"repo.pulls.no_merge_not_ready": "Cette demande d’ajout n’est pas prête à être fusionnée, vérifiez les évaluations et le contrôle qualité.",
|
"repo.pulls.no_merge_not_ready": "Cette demande d’ajout n’est pas prête à être fusionnée, vérifiez les évaluations et les signaux.",
|
||||||
"repo.pulls.no_merge_access": "Vous n'êtes pas autorisé⋅e à fusionner cette demande d'ajout.",
|
"repo.pulls.no_merge_access": "Vous n'êtes pas autorisé⋅e à fusionner cette demande d'ajout.",
|
||||||
"repo.pulls.merge_pull_request": "Créer une révision de fusion",
|
"repo.pulls.merge_pull_request": "Créer une révision de fusion",
|
||||||
"repo.pulls.rebase_merge_pull_request": "Rebaser puis rattraper",
|
"repo.pulls.rebase_merge_pull_request": "Rebaser puis rattraper",
|
||||||
@@ -1867,19 +1868,19 @@
|
|||||||
"repo.pulls.push_rejected_summary": "Message de rejet complet",
|
"repo.pulls.push_rejected_summary": "Message de rejet complet",
|
||||||
"repo.pulls.push_rejected_no_message": "Échec de la fusion : la soumission a été rejetée sans raison. Contrôler les déclencheurs Git pour ce dépôt.",
|
"repo.pulls.push_rejected_no_message": "Échec de la fusion : la soumission a été rejetée sans raison. Contrôler les déclencheurs Git pour ce dépôt.",
|
||||||
"repo.pulls.open_unmerged_pull_exists": "Vous ne pouvez pas rouvrir ceci car la demande d’ajout #%d, en attente, a des propriétés identiques.",
|
"repo.pulls.open_unmerged_pull_exists": "Vous ne pouvez pas rouvrir ceci car la demande d’ajout #%d, en attente, a des propriétés identiques.",
|
||||||
"repo.pulls.status_checking": "Certains contrôles sont en attente",
|
"repo.pulls.status_checking": "Certains signaux sont en attente",
|
||||||
"repo.pulls.status_checks_success": "Tous les contrôles ont réussi",
|
"repo.pulls.status_checks_success": "Tous les signaux ont réussi",
|
||||||
"repo.pulls.status_checks_warning": "Quelques vérifications ont signalé des avertissements",
|
"repo.pulls.status_checks_warning": "Des signaux déclarent des avertissements",
|
||||||
"repo.pulls.status_checks_failure_required": "Des vérifications obligatoires ont échoué",
|
"repo.pulls.status_checks_failure_required": "Des signaux requis ont échoués",
|
||||||
"repo.pulls.status_checks_failure_optional": "Des vérifications optionnelles ont échoué",
|
"repo.pulls.status_checks_failure_optional": "Des signaux optionnels ont échoués",
|
||||||
"repo.pulls.status_checks_error": "Quelques vérifications ont signalé des erreurs",
|
"repo.pulls.status_checks_error": "Des signaux rapportent des erreurs",
|
||||||
"repo.pulls.status_checks_requested": "Requis",
|
"repo.pulls.status_checks_requested": "Requis",
|
||||||
"repo.pulls.status_checks_details": "Détails",
|
"repo.pulls.status_checks_details": "Détails",
|
||||||
"repo.pulls.status_checks_hide_all": "Masquer toutes les vérifications",
|
"repo.pulls.status_checks_hide_all": "Masquer les signaux",
|
||||||
"repo.pulls.status_checks_show_all": "Afficher toutes les vérifications",
|
"repo.pulls.status_checks_show_all": "Afficher toutes les vérifications",
|
||||||
"repo.pulls.status_checks_approve_all": "Accepter tous les flux de travail",
|
"repo.pulls.status_checks_approve_all": "Approuver toutes les procédures",
|
||||||
"repo.pulls.status_checks_need_approvals": "%d flux de travail en attente d’approbation",
|
"repo.pulls.status_checks_need_approvals": "%d procédure(s) en attente d’approbation",
|
||||||
"repo.pulls.status_checks_need_approvals_helper": "Ce flux de travail ne s’exécutera qu’après l’approbation par le mainteneur du dépôt.",
|
"repo.pulls.status_checks_need_approvals_helper": "Cette procédure ne s’executera qu’après l’approbation par le mainteneur du dépôt.",
|
||||||
"repo.pulls.update_branch": "Actualiser la branche par fusion",
|
"repo.pulls.update_branch": "Actualiser la branche par fusion",
|
||||||
"repo.pulls.update_branch_rebase": "Actualiser la branche par rebasage",
|
"repo.pulls.update_branch_rebase": "Actualiser la branche par rebasage",
|
||||||
"repo.pulls.update_branch_success": "La mise à jour de la branche a réussi",
|
"repo.pulls.update_branch_success": "La mise à jour de la branche a réussi",
|
||||||
@@ -1963,8 +1964,8 @@
|
|||||||
"repo.ext_wiki.desc": "Lier un wiki externe.",
|
"repo.ext_wiki.desc": "Lier un wiki externe.",
|
||||||
"repo.wiki": "Wiki",
|
"repo.wiki": "Wiki",
|
||||||
"repo.wiki.welcome": "Bienvenue sur le Wiki.",
|
"repo.wiki.welcome": "Bienvenue sur le Wiki.",
|
||||||
"repo.wiki.welcome_desc": "Le wiki vous permet d'écrire ou de partager de la documentation avec vos collaborateurs.",
|
"repo.wiki.welcome_desc": "Le wiki vous permet de rédiger et partager de la documentation avec des collaborateurs.",
|
||||||
"repo.wiki.desc": "Écrire et partager de la documentation avec vos collaborateurs.",
|
"repo.wiki.desc": "Rédiger et partager de la documentation avec des collaborateurs.",
|
||||||
"repo.wiki.create_first_page": "Créer la première page",
|
"repo.wiki.create_first_page": "Créer la première page",
|
||||||
"repo.wiki.page": "Page",
|
"repo.wiki.page": "Page",
|
||||||
"repo.wiki.filter_page": "Filtrer la page",
|
"repo.wiki.filter_page": "Filtrer la page",
|
||||||
@@ -1986,7 +1987,7 @@
|
|||||||
"repo.wiki.pages": "Pages",
|
"repo.wiki.pages": "Pages",
|
||||||
"repo.wiki.last_updated": "Dernière mise à jour: %s",
|
"repo.wiki.last_updated": "Dernière mise à jour: %s",
|
||||||
"repo.wiki.page_name_desc": "Entrez un nom pour cette page Wiki. Certains noms spéciaux sont « Home », « _Sidebar » et « _Footer ».",
|
"repo.wiki.page_name_desc": "Entrez un nom pour cette page Wiki. Certains noms spéciaux sont « Home », « _Sidebar » et « _Footer ».",
|
||||||
"repo.wiki.original_git_entry_tooltip": "Voir le fichier Git original au lieu d'utiliser un lien convivial.",
|
"repo.wiki.original_git_entry_tooltip": "Voir le fichier Git original au lieu d’utiliser un lien convivial.",
|
||||||
"repo.activity": "Activité",
|
"repo.activity": "Activité",
|
||||||
"repo.activity.navbar.pulse": "Impulsion",
|
"repo.activity.navbar.pulse": "Impulsion",
|
||||||
"repo.activity.navbar.code_frequency": "Fréquence du code",
|
"repo.activity.navbar.code_frequency": "Fréquence du code",
|
||||||
@@ -2066,23 +2067,23 @@
|
|||||||
"repo.settings.public_access": "Accès public",
|
"repo.settings.public_access": "Accès public",
|
||||||
"repo.settings.public_access_desc": "Configurer les permissions des visiteurs publics remplaçant les valeurs par défaut de ce dépôt.",
|
"repo.settings.public_access_desc": "Configurer les permissions des visiteurs publics remplaçant les valeurs par défaut de ce dépôt.",
|
||||||
"repo.settings.public_access.docs.not_set": "Non défini : ne donne aucune permission supplémentaire. Les règles du dépôt et les permissions des utilisateurs font foi.",
|
"repo.settings.public_access.docs.not_set": "Non défini : ne donne aucune permission supplémentaire. Les règles du dépôt et les permissions des utilisateurs font foi.",
|
||||||
"repo.settings.public_access.docs.anonymous_read": "Lecture anonyme : les utilisateurs qui ne sont pas connectés peuvent consulter la ressource.",
|
"repo.settings.public_access.docs.anonymous_read": "Accès anonyme : les visiteurs non connectés peuvent consulter cette section.",
|
||||||
"repo.settings.public_access.docs.everyone_read": "Consultation collective : tous les utilisateurs connectés peuvent consulter la ressource. Mettre les tickets et demandes d’ajouts en accès public signifie que les utilisateurs connectés peuvent en créer.",
|
"repo.settings.public_access.docs.everyone_read": "Consultation collective : tous les utilisateurs connectés peuvent consulter cette section. Pour les Tickets et les Demandes d’ajouts, cela permet aussi aux utilisateurs connectés d’en créer.",
|
||||||
"repo.settings.public_access.docs.everyone_write": "Participation collective : tous les utilisateurs connectés ont la permission d’écrire sur la ressource. Seule le Wiki supporte cette autorisation.",
|
"repo.settings.public_access.docs.everyone_write": "Participation collective : tous les utilisateurs connectés peuvent participer à la section. Seul le Wiki supporte cette permission.",
|
||||||
"repo.settings.collaboration": "Collaborateurs",
|
"repo.settings.collaboration": "Collaborateurs",
|
||||||
"repo.settings.collaboration.admin": "Administrateur",
|
"repo.settings.collaboration.admin": "Administrateur",
|
||||||
"repo.settings.collaboration.write": "Écriture",
|
"repo.settings.collaboration.write": "Écriture",
|
||||||
"repo.settings.collaboration.read": "Lecture",
|
"repo.settings.collaboration.read": "Lecture",
|
||||||
"repo.settings.collaboration.owner": "Propriétaire",
|
"repo.settings.collaboration.owner": "Propriétaire",
|
||||||
"repo.settings.collaboration.undefined": "Indéfini",
|
"repo.settings.collaboration.undefined": "Indéfini",
|
||||||
"repo.settings.collaboration.per_unit": "Permissions de ressource",
|
"repo.settings.collaboration.per_unit": "Permissions de section",
|
||||||
"repo.settings.hooks": "Déclencheurs web",
|
"repo.settings.hooks": "Déclencheurs web",
|
||||||
"repo.settings.githooks": "Déclencheurs Git",
|
"repo.settings.githooks": "Déclencheurs Git",
|
||||||
"repo.settings.basic_settings": "Paramètres de base",
|
"repo.settings.basic_settings": "Paramètres de base",
|
||||||
"repo.settings.mirror_settings": "Réglages Miroir",
|
"repo.settings.mirror_settings": "Réglages Miroir",
|
||||||
"repo.settings.mirror_settings.docs": "Configurez votre dépôt pour synchroniser automatiquement les révisions, étiquettes et branches avec un autre dépôt.",
|
"repo.settings.mirror_settings.docs": "Configurez votre dépôt pour synchroniser automatiquement les révisions, étiquettes et branches avec un autre dépôt.",
|
||||||
"repo.settings.mirror_settings.docs.disabled_pull_mirror.instructions": "Configurez votre projet pour soumettre automatiquement les révisions, étiquettes et branches vers un autre dépôt. Les miroirs ont été désactivés par l'administrateur de votre site.",
|
"repo.settings.mirror_settings.docs.disabled_pull_mirror.instructions": "Configurez votre projet pour soumettre automatiquement les révisions, étiquettes et branches vers un autre dépôt. Les miroirs ont été désactivés par l’administrateur de votre site.",
|
||||||
"repo.settings.mirror_settings.docs.disabled_push_mirror.instructions": "Configurez votre projet pour synchroniser automatiquement les révisions, étiquettes et branches d'un autre dépôt.",
|
"repo.settings.mirror_settings.docs.disabled_push_mirror.instructions": "Configurez votre projet pour synchroniser automatiquement les révisions, étiquettes et branches d’un autre dépôt.",
|
||||||
"repo.settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning": "Pour l’instant, cela ne peut être fait que dans le menu « Nouvelle migration ». Pour plus d’informations, veuillez consulter :",
|
"repo.settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning": "Pour l’instant, cela ne peut être fait que dans le menu « Nouvelle migration ». Pour plus d’informations, veuillez consulter :",
|
||||||
"repo.settings.mirror_settings.docs.disabled_push_mirror.info": "Les miroirs push ont été désactivés par l’administrateur de votre site.",
|
"repo.settings.mirror_settings.docs.disabled_push_mirror.info": "Les miroirs push ont été désactivés par l’administrateur de votre site.",
|
||||||
"repo.settings.mirror_settings.docs.no_new_mirrors": "Votre dépôt se synchronise avec un dépôt distant. Vous ne pouvez pas créer de nouveaux miroirs pour le moment.",
|
"repo.settings.mirror_settings.docs.no_new_mirrors": "Votre dépôt se synchronise avec un dépôt distant. Vous ne pouvez pas créer de nouveaux miroirs pour le moment.",
|
||||||
@@ -2201,11 +2202,13 @@
|
|||||||
"repo.settings.trust_model.default.desc": "Utiliser le niveau de confiance configuré par défaut pour cette instance Gitea.",
|
"repo.settings.trust_model.default.desc": "Utiliser le niveau de confiance configuré par défaut pour cette instance Gitea.",
|
||||||
"repo.settings.trust_model.collaborator": "Collaborateur",
|
"repo.settings.trust_model.collaborator": "Collaborateur",
|
||||||
"repo.settings.trust_model.collaborator.long": "Collaborateur : ne se fier qu'aux signatures des collaborateurs du dépôt",
|
"repo.settings.trust_model.collaborator.long": "Collaborateur : ne se fier qu'aux signatures des collaborateurs du dépôt",
|
||||||
"repo.settings.trust_model.collaborator.desc": "La signature d’une révision est dite « fiable » si elle correspond à un collaborateur du dépôt, indépendamment de son auteur. À défaut, si elle correspond à l’auteur de la révision, elle sera « dilettante », et « discordante » sinon.",
|
"repo.settings.trust_model.collaborator.desc": "Une révision est réputée authentifiée si elle est signée par un collaborateur du dépôt. Si elle n‘est que signée par son auteur, elle sera réputée dilettante, et discordante sinon.",
|
||||||
"repo.settings.trust_model.committer": "Auteur",
|
"repo.settings.trust_model.committer": "Auteur",
|
||||||
"repo.settings.trust_model.committer.long": "Auteur : ne se fier qu’aux signatures des auteurs des révisions (mimique GitHub en forçant Gitea à co-signer ses révisions).",
|
"repo.settings.trust_model.committer.long": "Auteur : ne se fier qu’aux signatures des auteurs des révisions (mimique GitHub en forçant Gitea à co-signer ses révisions).",
|
||||||
|
"repo.settings.trust_model.committer.desc": "Une révision est réputée authentifiée si elle est signée par son auteur, et discordante si les signatures diffèrent. Cela force Gitea à signer ses propres révisions en créditant l’auteur original en pied de révision \"Co-authored-by:\" et \"Co-committed-by:\". La clé par défaut de Gitea doit correspondre à celle d’un utilisateur existant.",
|
||||||
"repo.settings.trust_model.collaboratorcommitter": "Collaborateur et Auteur",
|
"repo.settings.trust_model.collaboratorcommitter": "Collaborateur et Auteur",
|
||||||
"repo.settings.trust_model.collaboratorcommitter.long": "Collaborateur et Auteur : ne se fier qu'aux signatures des auteurs collaborant au dépôt",
|
"repo.settings.trust_model.collaboratorcommitter.long": "Collaborateur et Auteur : ne se fier qu'aux signatures des auteurs collaborant au dépôt",
|
||||||
|
"repo.settings.trust_model.collaboratorcommitter.desc": "Une révision est réputée authentifiée si est elle signée par son auteur étant lui-même collaborateur du dépôt. Si elle n‘est que signée par son auteur, elle sera réputée dilettante, et discordante sinon. Cela force Gitea à signer ses propres révisions en créditant l’auteur original en pied de révision \"Co-authored-by:\". La clé par défaut de Gitea doit correspondre à celle d’un utilisateur existant.",
|
||||||
"repo.settings.wiki_delete": "Supprimer les données du Wiki",
|
"repo.settings.wiki_delete": "Supprimer les données du Wiki",
|
||||||
"repo.settings.wiki_delete_desc": "Supprimer les données du wiki d'un dépôt est permanent. Cette action est irréversible.",
|
"repo.settings.wiki_delete_desc": "Supprimer les données du wiki d'un dépôt est permanent. Cette action est irréversible.",
|
||||||
"repo.settings.wiki_delete_notices_1": "- Ceci supprimera de manière permanente et désactivera le wiki de dépôt pour %s.",
|
"repo.settings.wiki_delete_notices_1": "- Ceci supprimera de manière permanente et désactivera le wiki de dépôt pour %s.",
|
||||||
@@ -2218,7 +2221,7 @@
|
|||||||
"repo.settings.delete_notices_fork_1": "- Les bifurcations de ce dépôt deviendront indépendants après suppression.",
|
"repo.settings.delete_notices_fork_1": "- Les bifurcations de ce dépôt deviendront indépendants après suppression.",
|
||||||
"repo.settings.deletion_success": "Le dépôt a été supprimé.",
|
"repo.settings.deletion_success": "Le dépôt a été supprimé.",
|
||||||
"repo.settings.update_settings_success": "Les options du dépôt ont été mises à jour.",
|
"repo.settings.update_settings_success": "Les options du dépôt ont été mises à jour.",
|
||||||
"repo.settings.update_settings_no_unit": "Impossible de désactiver toutes les fonctionnalités d'un dépôt. Vous ne pourrez gère l'utiliser.",
|
"repo.settings.update_settings_no_unit": "Si vous désactivez toutes les sections du dépôt, vous ne pourrez gère plus l’utiliser.",
|
||||||
"repo.settings.confirm_delete": "Supprimer le dépôt",
|
"repo.settings.confirm_delete": "Supprimer le dépôt",
|
||||||
"repo.settings.add_collaborator": "Ajouter un collaborateur",
|
"repo.settings.add_collaborator": "Ajouter un collaborateur",
|
||||||
"repo.settings.add_collaborator_success": "Le collaborateur a été ajouté.",
|
"repo.settings.add_collaborator_success": "Le collaborateur a été ajouté.",
|
||||||
@@ -2292,7 +2295,7 @@
|
|||||||
"repo.settings.event_release": "Publication",
|
"repo.settings.event_release": "Publication",
|
||||||
"repo.settings.event_release_desc": "Publication publiée, mise à jour ou supprimée.",
|
"repo.settings.event_release_desc": "Publication publiée, mise à jour ou supprimée.",
|
||||||
"repo.settings.event_push": "Soumission",
|
"repo.settings.event_push": "Soumission",
|
||||||
"repo.settings.event_force_push": "Poussée forcée",
|
"repo.settings.event_force_push": "Soumission forcée",
|
||||||
"repo.settings.event_push_desc": "Soumission Git.",
|
"repo.settings.event_push_desc": "Soumission Git.",
|
||||||
"repo.settings.event_repository": "Dépôt",
|
"repo.settings.event_repository": "Dépôt",
|
||||||
"repo.settings.event_repository_desc": "Dépôt créé ou supprimé.",
|
"repo.settings.event_repository_desc": "Dépôt créé ou supprimé.",
|
||||||
@@ -2326,11 +2329,11 @@
|
|||||||
"repo.settings.event_pull_request_review_request_desc": "Création ou suppresion de demandes d’évaluation.",
|
"repo.settings.event_pull_request_review_request_desc": "Création ou suppresion de demandes d’évaluation.",
|
||||||
"repo.settings.event_pull_request_approvals": "Approbations de demande d'ajout",
|
"repo.settings.event_pull_request_approvals": "Approbations de demande d'ajout",
|
||||||
"repo.settings.event_pull_request_merge": "Fusion de demande d'ajout",
|
"repo.settings.event_pull_request_merge": "Fusion de demande d'ajout",
|
||||||
"repo.settings.event_header_workflow": "Événements du flux de travail",
|
"repo.settings.event_header_workflow": "Événements de procédure",
|
||||||
"repo.settings.event_workflow_run": "Exécution du flux de travail",
|
"repo.settings.event_workflow_run": "Exécution de procédure",
|
||||||
"repo.settings.event_workflow_run_desc": "Tâche du flux de travail Gitea Actions ajoutée, en attente, en cours ou terminée.",
|
"repo.settings.event_workflow_run_desc": "Exécution des procédures des Actions Gitea ajoutée, en attente, en cours ou terminées.",
|
||||||
"repo.settings.event_workflow_job": "Tâches du flux de travail",
|
"repo.settings.event_workflow_job": "Missions de la procédure",
|
||||||
"repo.settings.event_workflow_job_desc": "Tâches du flux de travail Gitea Actions en file d’attente, en attente, en cours ou terminée.",
|
"repo.settings.event_workflow_job_desc": "Les missions ajoutées, en attente, en cours ou terminées des Actions Gitea.",
|
||||||
"repo.settings.event_package": "Paquet",
|
"repo.settings.event_package": "Paquet",
|
||||||
"repo.settings.event_package_desc": "Paquet créé ou supprimé.",
|
"repo.settings.event_package_desc": "Paquet créé ou supprimé.",
|
||||||
"repo.settings.branch_filter": "Filtre de branche",
|
"repo.settings.branch_filter": "Filtre de branche",
|
||||||
@@ -2390,44 +2393,44 @@
|
|||||||
"repo.settings.protected_branch_can_push_no": "Vous ne pouvez pas soumettre",
|
"repo.settings.protected_branch_can_push_no": "Vous ne pouvez pas soumettre",
|
||||||
"repo.settings.branch_protection": "Paramètres de protection de branches pour la branche <b>%s</b>",
|
"repo.settings.branch_protection": "Paramètres de protection de branches pour la branche <b>%s</b>",
|
||||||
"repo.settings.protect_this_branch": "Activer la protection de branche",
|
"repo.settings.protect_this_branch": "Activer la protection de branche",
|
||||||
"repo.settings.protect_this_branch_desc": "Empêche les suppressions et limite les poussées et fusions sur cette branche.",
|
"repo.settings.protect_this_branch_desc": "Empêche la suppression et Git de soumettre et fusionner sur cette branche.",
|
||||||
"repo.settings.protect_disable_push": "Désactiver la soumission",
|
"repo.settings.protect_disable_push": "Désactiver la soumission",
|
||||||
"repo.settings.protect_disable_push_desc": "Aucune soumission ne sera possible sur cette branche.",
|
"repo.settings.protect_disable_push_desc": "Aucune soumission ne sera possible sur cette branche.",
|
||||||
"repo.settings.protect_disable_force_push": "Désactiver les poussés forcées",
|
"repo.settings.protect_disable_force_push": "Désactiver la soumission forcée",
|
||||||
"repo.settings.protect_disable_force_push_desc": "Aucune poussée forcée ne sera possible sur cette branche.",
|
"repo.settings.protect_disable_force_push_desc": "Aucune soumission forcée ne sera possible sur cette branche.",
|
||||||
"repo.settings.protect_enable_push": "Activer la soumission",
|
"repo.settings.protect_enable_push": "Activer la soumission",
|
||||||
"repo.settings.protect_enable_push_desc": "Toute personne ayant un accès en écriture sera autorisée à soumettre sur cette branche (sans forcer).",
|
"repo.settings.protect_enable_push_desc": "Toute personne ayant un accès en écriture sera autorisée à soumettre sur cette branche (sans forcer).",
|
||||||
"repo.settings.protect_enable_force_push_all": "Activer les poussées forcées",
|
"repo.settings.protect_enable_force_push_all": "Activer la soumission forcée",
|
||||||
"repo.settings.protect_enable_force_push_all_desc": "Toute personne pouvant pousser pourra forcer sur cette branche.",
|
"repo.settings.protect_enable_force_push_all_desc": "Toute personne pouvant soumettre pourra forcer sur cette branche.",
|
||||||
"repo.settings.protect_enable_force_push_allowlist": "Soumission forcée sur autorisation uniquement",
|
"repo.settings.protect_enable_force_push_allowlist": "Soumission forcée sur autorisation uniquement",
|
||||||
"repo.settings.protect_enable_force_push_allowlist_desc": "Seuls les utilisateurs ou équipes autorisés ayants un droit de pousser seront autorisés à pousser en force sur cette branche.",
|
"repo.settings.protect_enable_force_push_allowlist_desc": "Seuls les utilisateurs ou équipes autorisés ayants un droit de soumission seront autorisés à soumettre en force sur cette branche.",
|
||||||
"repo.settings.protect_enable_merge": "Activer la fusion",
|
"repo.settings.protect_enable_merge": "Activer la fusion",
|
||||||
"repo.settings.protect_enable_merge_desc": "Toute personne ayant un accès en écriture sera autorisée à fusionner les demandes d'ajout dans cette branche.",
|
"repo.settings.protect_enable_merge_desc": "Toute personne ayant un accès en écriture sera autorisée à fusionner les demandes d'ajout dans cette branche.",
|
||||||
"repo.settings.protect_whitelist_committers": "Soumissions sur autorisation uniquement",
|
"repo.settings.protect_whitelist_committers": "Soumissions sur autorisation uniquement",
|
||||||
"repo.settings.protect_whitelist_committers_desc": "Seuls les utilisateurs ou les équipes autorisés pourront pousser sur cette branche (sans forcer).",
|
"repo.settings.protect_whitelist_committers_desc": "Seuls les utilisateurs ou les équipes autorisés pourront soumettre sur cette branche (sans forcer).",
|
||||||
"repo.settings.protect_whitelist_deploy_keys": "Clés de déploiement pouvant écrire autorisées à pousser.",
|
"repo.settings.protect_whitelist_deploy_keys": "Clés de déploiement pouvant écrire autorisées à pousser.",
|
||||||
"repo.settings.protect_whitelist_users": "Utilisateurs autorisés à pousser :",
|
"repo.settings.protect_whitelist_users": "Utilisateurs autorisés à pousser :",
|
||||||
"repo.settings.protect_whitelist_teams": "Équipes autorisées à pousser :",
|
"repo.settings.protect_whitelist_teams": "Équipes autorisées à pousser :",
|
||||||
"repo.settings.protect_force_push_allowlist_users": "Utilisateurs autorisés à pousser en force :",
|
"repo.settings.protect_force_push_allowlist_users": "Utilisateurs autorisés à soumettre en force :",
|
||||||
"repo.settings.protect_force_push_allowlist_teams": "Équipes autorisées à pousser en force :",
|
"repo.settings.protect_force_push_allowlist_teams": "Équipes autorisées à soumettre en force :",
|
||||||
"repo.settings.protect_force_push_allowlist_deploy_keys": "Clés de déploiement pouvant pousser autorisées à pousser en force.",
|
"repo.settings.protect_force_push_allowlist_deploy_keys": "Inclure aussi les clés de déploiement autorisées à soumettre.",
|
||||||
"repo.settings.protect_merge_whitelist_committers": "Fusion sur autorisation uniquement",
|
"repo.settings.protect_merge_whitelist_committers": "Fusion sur autorisation uniquement",
|
||||||
"repo.settings.protect_merge_whitelist_committers_desc": "N’autoriser que les utilisateurs et les équipes listés à appliquer les demandes de fusion sur cette branche.",
|
"repo.settings.protect_merge_whitelist_committers_desc": "N’autoriser que les utilisateurs et les équipes listés à appliquer les demandes de fusion sur cette branche.",
|
||||||
"repo.settings.protect_merge_whitelist_users": "Utilisateurs autorisés à fusionner :",
|
"repo.settings.protect_merge_whitelist_users": "Utilisateurs autorisés à fusionner :",
|
||||||
"repo.settings.protect_merge_whitelist_teams": "Équipes autorisées à fusionner :",
|
"repo.settings.protect_merge_whitelist_teams": "Équipes autorisées à fusionner :",
|
||||||
"repo.settings.protect_bypass_allowlist": "Contourner la protection de la branche",
|
"repo.settings.protect_bypass_allowlist": "Contourner la protection de la branche",
|
||||||
"repo.settings.protect_enable_bypass_allowlist": "Autoriser des utilisateurs et des équipes à contourner les restrictions de branche",
|
"repo.settings.protect_enable_bypass_allowlist": "Autoriser des utilisateurs et des équipes à contourner les restrictions de branche",
|
||||||
"repo.settings.protect_enable_bypass_allowlist_desc": "Les utilisateurs ou équipes autorisés peuvent fusionner ou pousser des changements nonobstant les règles d’approbations, de vérifications de statut et les protections fichiers.",
|
"repo.settings.protect_enable_bypass_allowlist_desc": "Les utilisateurs ou équipes autorisés peuvent fusionner ou soumettre des changements nonobstant les règles d’approbations, de vérifications des signaux et les protections de fichiers.",
|
||||||
"repo.settings.protect_bypass_allowlist_users": "Liste d’utilisateurs autorisés à contourner les protections :",
|
"repo.settings.protect_bypass_allowlist_users": "Liste d’utilisateurs autorisés à contourner les protections :",
|
||||||
"repo.settings.protect_bypass_allowlist_teams": "Liste d’équipes autorisées à contourner les protections :",
|
"repo.settings.protect_bypass_allowlist_teams": "Liste d’équipes autorisées à contourner les protections :",
|
||||||
"repo.settings.protect_check_status_contexts": "Activer le Contrôle Qualité",
|
"repo.settings.protect_check_status_contexts": "Activer les signaux",
|
||||||
"repo.settings.protect_status_check_patterns": "Motifs de vérification des statuts :",
|
"repo.settings.protect_status_check_patterns": "Motifs de signal :",
|
||||||
"repo.settings.protect_status_check_patterns_desc": "Entrez des motifs pour spécifier quelles vérifications doivent réussir avant que des branches puissent être fusionnées. Un motif par ligne. Un motif ne peut être vide.",
|
"repo.settings.protect_status_check_patterns_desc": "Entrez des motifs pour spécifier quelles signaux doivent réussir avant que des branches puissent être fusionnées. Un motif par ligne. Un motif ne peut être vide.",
|
||||||
"repo.settings.protect_check_status_contexts_desc": "Exiger le status « succès » avant de fusionner. Quand activée, une branche protégée ne peux accepter que des soumissions ou des fusions ayant le status « succès ». Lorsqu'il n’y a pas de contexte, la dernière révision fait foi.",
|
"repo.settings.protect_check_status_contexts_desc": "Exiger la réussite des signaux avant de fusionner. Quand activé, une branche protégée ne peux accepter que des soumissions ou des fusions ayant le status « succès ». Lorsqu'il n’y a pas de contexte, la dernière révision fait foi.",
|
||||||
"repo.settings.protect_check_status_contexts_list": "Contrôles qualité trouvés au cours de la semaine dernière pour ce dépôt",
|
"repo.settings.protect_check_status_contexts_list": "Signaux trouvés au cours de la semaine passée pour ce dépôt",
|
||||||
"repo.settings.protect_status_check_matched": "Correspondant",
|
"repo.settings.protect_status_check_matched": "Correspondant",
|
||||||
"repo.settings.protect_invalid_status_check_pattern": "Motif de vérification des statuts incorrect : « %s ».",
|
"repo.settings.protect_invalid_status_check_pattern": "Motif de signal invalide : « %s ».",
|
||||||
"repo.settings.protect_no_valid_status_check_patterns": "Aucun motif de vérification des statuts valide.",
|
"repo.settings.protect_no_valid_status_check_patterns": "Aucun motif de signaux valide.",
|
||||||
"repo.settings.protect_required_approvals": "Minimum d'approbations requis :",
|
"repo.settings.protect_required_approvals": "Minimum d'approbations requis :",
|
||||||
"repo.settings.protect_required_approvals_desc": "Permet de fusionner les demandes d’ajout lorsque suffisamment d’évaluation sont positives.",
|
"repo.settings.protect_required_approvals_desc": "Permet de fusionner les demandes d’ajout lorsque suffisamment d’évaluation sont positives.",
|
||||||
"repo.settings.protect_approvals_whitelist_enabled": "Restreindre les approbations sur autorisation uniquement",
|
"repo.settings.protect_approvals_whitelist_enabled": "Restreindre les approbations sur autorisation uniquement",
|
||||||
@@ -2453,15 +2456,15 @@
|
|||||||
"repo.settings.remove_protected_branch_success": "La règle de protection de branche \"%s\" a été retirée.",
|
"repo.settings.remove_protected_branch_success": "La règle de protection de branche \"%s\" a été retirée.",
|
||||||
"repo.settings.remove_protected_branch_failed": "Impossible de retirer la règle de protection de branche \"%s\".",
|
"repo.settings.remove_protected_branch_failed": "Impossible de retirer la règle de protection de branche \"%s\".",
|
||||||
"repo.settings.protected_branch_deletion": "Désactiver la protection de branche",
|
"repo.settings.protected_branch_deletion": "Désactiver la protection de branche",
|
||||||
"repo.settings.protected_branch_deletion_desc": "Désactiver la protection de branche permet aux utilisateurs ayant accès en écriture de pousser des modifications sur la branche. Continuer ?",
|
"repo.settings.protected_branch_deletion_desc": "Désactiver la protection de branche permet aux utilisateurs ayant accès en écriture de soumettre des modifications sur la branche. Continuer ?",
|
||||||
"repo.settings.block_rejected_reviews": "Bloquer la fusion en cas d’évaluations négatives",
|
"repo.settings.block_rejected_reviews": "Bloquer la fusion en cas d’évaluations négatives",
|
||||||
"repo.settings.block_rejected_reviews_desc": "La fusion ne sera pas possible lorsque des modifications sont demandées par les évaluateurs officiels, même s'il y a suffisamment d’approbations.",
|
"repo.settings.block_rejected_reviews_desc": "La fusion ne sera pas possible lorsque des modifications sont demandées par les évaluateurs officiels, même s'il y a suffisamment d’approbations.",
|
||||||
"repo.settings.block_on_official_review_requests": "Bloquer la fusion en cas de demande d’évaluation officielle",
|
"repo.settings.block_on_official_review_requests": "Bloquer la fusion en cas de demande d’évaluation officielle",
|
||||||
"repo.settings.block_on_official_review_requests_desc": "La fusion ne sera pas possible tant qu’elle aura des demandes d’évaluations officielles, même s'il y a suffisamment d’approbations.",
|
"repo.settings.block_on_official_review_requests_desc": "La fusion ne sera pas possible tant qu’elle aura des demandes d’évaluations officielles, même s'il y a suffisamment d’approbations.",
|
||||||
"repo.settings.block_outdated_branch": "Bloquer la fusion si la demande d'ajout est obsolète",
|
"repo.settings.block_outdated_branch": "Bloquer la fusion si la demande d'ajout est obsolète",
|
||||||
"repo.settings.block_outdated_branch_desc": "La fusion ne sera pas possible lorsque la branche principale est derrière la branche de base.",
|
"repo.settings.block_outdated_branch_desc": "La fusion ne sera pas possible lorsque la branche principale est derrière la branche de base.",
|
||||||
"repo.settings.block_admin_merge_override": "Les administrateurs doivent respecter les règles de protection des branches",
|
"repo.settings.block_admin_merge_override": "Inclure les administrateurs dans l’application de la règle",
|
||||||
"repo.settings.block_admin_merge_override_desc": "Les administrateurs sont également soumis aux règles de protection des branches. En activant la liste d’autorisation de contournement, les utilisateurs et équipes qui y figurent peuvent toujours contourner ces règles.",
|
"repo.settings.block_admin_merge_override_desc": "Les administrateurs sont également soumis aux règles de protection des branches. En revanche, les utilisateurs et équipes qui figurent sur la liste dérogatoire y sont exemptés.",
|
||||||
"repo.settings.default_branch_desc": "Sélectionnez une branche par défaut pour les révisions.",
|
"repo.settings.default_branch_desc": "Sélectionnez une branche par défaut pour les révisions.",
|
||||||
"repo.settings.default_target_branch_desc": "Les demandes d’ajout peuvent utiliser une branche cible différente, telle que définie dans la section Demandes d’ajouts des Paramètres avancés du dépôt.",
|
"repo.settings.default_target_branch_desc": "Les demandes d’ajout peuvent utiliser une branche cible différente, telle que définie dans la section Demandes d’ajouts des Paramètres avancés du dépôt.",
|
||||||
"repo.settings.merge_style_desc": "Styles de fusion",
|
"repo.settings.merge_style_desc": "Styles de fusion",
|
||||||
@@ -2596,7 +2599,9 @@
|
|||||||
"repo.diff.review.reject": "Demander des changements",
|
"repo.diff.review.reject": "Demander des changements",
|
||||||
"repo.diff.review.self_approve": "Les auteurs d’une demande d’ajout ne peuvent pas approuver leur propre demande d’ajout",
|
"repo.diff.review.self_approve": "Les auteurs d’une demande d’ajout ne peuvent pas approuver leur propre demande d’ajout",
|
||||||
"repo.diff.committed_by": "révisé par",
|
"repo.diff.committed_by": "révisé par",
|
||||||
|
"repo.diff.coauthored_by": "coécrit par",
|
||||||
"repo.commits.avatar_stack_and": "et",
|
"repo.commits.avatar_stack_and": "et",
|
||||||
|
"repo.commits.avatar_stack_people": "%d personne(s)",
|
||||||
"repo.diff.protected": "Protégé",
|
"repo.diff.protected": "Protégé",
|
||||||
"repo.diff.image.side_by_side": "Côte à côte",
|
"repo.diff.image.side_by_side": "Côte à côte",
|
||||||
"repo.diff.image.swipe": "Glisser",
|
"repo.diff.image.swipe": "Glisser",
|
||||||
@@ -2716,7 +2721,7 @@
|
|||||||
"repo.error.csv.too_large": "Impossible de visualiser le fichier car il est trop volumineux.",
|
"repo.error.csv.too_large": "Impossible de visualiser le fichier car il est trop volumineux.",
|
||||||
"repo.error.csv.unexpected": "Impossible de visualiser ce fichier car il contient un caractère inattendu ligne %d, colonne %d.",
|
"repo.error.csv.unexpected": "Impossible de visualiser ce fichier car il contient un caractère inattendu ligne %d, colonne %d.",
|
||||||
"repo.error.csv.invalid_field_count": "Impossible de visualiser ce fichier car il contient un nombre de champs incorrect à la ligne %d.",
|
"repo.error.csv.invalid_field_count": "Impossible de visualiser ce fichier car il contient un nombre de champs incorrect à la ligne %d.",
|
||||||
"repo.error.broken_git_hook": "Les crochets Git de ce dépôt semblent cassés. Veuillez suivre la <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">documentation</a> pour les corriger, puis pousser des révisions pour actualiser le statut.",
|
"repo.error.broken_git_hook": "Les crochets Git de ce dépôt semblent cassés. Veuillez suivre la <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">documentation</a> pour les corriger, puis soummettre des révisions pour actualiser le statut.",
|
||||||
"graphs.component_loading": "Chargement de %s…",
|
"graphs.component_loading": "Chargement de %s…",
|
||||||
"graphs.component_loading_failed": "Impossible de charger %s.",
|
"graphs.component_loading_failed": "Impossible de charger %s.",
|
||||||
"graphs.component_loading_info": "Ça prend son temps…",
|
"graphs.component_loading_info": "Ça prend son temps…",
|
||||||
@@ -2724,6 +2729,7 @@
|
|||||||
"graphs.code_frequency.what": "fréquence du code",
|
"graphs.code_frequency.what": "fréquence du code",
|
||||||
"graphs.contributors.what": "contributions",
|
"graphs.contributors.what": "contributions",
|
||||||
"graphs.recent_commits.what": "révisions récentes",
|
"graphs.recent_commits.what": "révisions récentes",
|
||||||
|
"graphs.chart_zoom_hint": "Glisser : zoom, Maj + Glisser : pano, Double-clic : recentrer",
|
||||||
"org.org_name_holder": "Nom de l'organisation",
|
"org.org_name_holder": "Nom de l'organisation",
|
||||||
"org.org_full_name_holder": "Nom complet de l'organisation",
|
"org.org_full_name_holder": "Nom complet de l'organisation",
|
||||||
"org.org_name_helper": "Le nom de l'organisation doit être court et mémorable.",
|
"org.org_name_helper": "Le nom de l'organisation doit être court et mémorable.",
|
||||||
@@ -2745,8 +2751,8 @@
|
|||||||
"org.team_desc_helper": "Décrire le but ou le rôle de l’équipe.",
|
"org.team_desc_helper": "Décrire le but ou le rôle de l’équipe.",
|
||||||
"org.team_access_desc": "Accès au dépôt",
|
"org.team_access_desc": "Accès au dépôt",
|
||||||
"org.team_permission_desc": "Autorisation",
|
"org.team_permission_desc": "Autorisation",
|
||||||
"org.team_unit_desc": "Permettre l’accès aux Sections du dépôt",
|
"org.team_unit_desc": "Permettre l’accès aux sections du dépôt",
|
||||||
"org.team_unit_disabled": "(Désactivé)",
|
"org.team_unit_disabled": "(Désactivée)",
|
||||||
"org.form.name_been_taken": "Le nom d’organisation « %s » a déjà été utilisé.",
|
"org.form.name_been_taken": "Le nom d’organisation « %s » a déjà été utilisé.",
|
||||||
"org.form.name_reserved": "Le nom d'organisation \"%s\" est réservé.",
|
"org.form.name_reserved": "Le nom d'organisation \"%s\" est réservé.",
|
||||||
"org.form.name_pattern_not_allowed": "Le motif « %s » n'est pas autorisé dans un nom d'organisation.",
|
"org.form.name_pattern_not_allowed": "Le motif « %s » n'est pas autorisé dans un nom d'organisation.",
|
||||||
@@ -2814,15 +2820,15 @@
|
|||||||
"org.teams.can_create_org_repo": "Créer des dépôts",
|
"org.teams.can_create_org_repo": "Créer des dépôts",
|
||||||
"org.teams.can_create_org_repo_helper": "Les membres peuvent créer de nouveaux dépôts dans l'organisation. Le créateur obtiendra l'accès administrateur au nouveau dépôt.",
|
"org.teams.can_create_org_repo_helper": "Les membres peuvent créer de nouveaux dépôts dans l'organisation. Le créateur obtiendra l'accès administrateur au nouveau dépôt.",
|
||||||
"org.teams.none_access": "Aucun accès",
|
"org.teams.none_access": "Aucun accès",
|
||||||
"org.teams.none_access_helper": "Les membres ne peuvent voir ou faire quoi que ce soit sur cette partie. Sans effet pour les dépôts publics.",
|
"org.teams.none_access_helper": "Les membres ne peuvent ni consulter ni participer à cette section. Ne s’applique pas pour les dépôts publics.",
|
||||||
"org.teams.general_access": "Accès général",
|
"org.teams.general_access": "Accès général",
|
||||||
"org.teams.general_access_helper": "Les permissions des membres seront déterminées par la table des permissions ci-dessous.",
|
"org.teams.general_access_helper": "Les permissions des membres seront déterminées par la table des permissions ci-dessous.",
|
||||||
"org.teams.read_access": "Lecture",
|
"org.teams.read_access": "Lecture",
|
||||||
"org.teams.read_access_helper": "Les membres peuvent voir et cloner les dépôts de l'équipe.",
|
"org.teams.read_access_helper": "Les membres peuvent voir et cloner les dépôts de l’équipe.",
|
||||||
"org.teams.write_access": "Écriture",
|
"org.teams.write_access": "Écriture",
|
||||||
"org.teams.write_access_helper": "Les membres peuvent voir et pousser dans les dépôts de l'équipe.",
|
"org.teams.write_access_helper": "Les membres peuvent consulter et soumettre sur les dépôts de l’équipe.",
|
||||||
"org.teams.admin_access": "Accès Administrateur",
|
"org.teams.admin_access": "Accès Administrateur",
|
||||||
"org.teams.admin_access_helper": "Les membres peuvent tirer et pousser des modifications vers les dépôts de l'équipe, et y ajouter des collaborateurs.",
|
"org.teams.admin_access_helper": "Les membres peuvent extraire et soumettre les dépôts de l’équipe et y ajouter des collaborateurs.",
|
||||||
"org.teams.no_desc": "Aucune description",
|
"org.teams.no_desc": "Aucune description",
|
||||||
"org.teams.settings": "Paramètres",
|
"org.teams.settings": "Paramètres",
|
||||||
"org.teams.owners_permission_desc": "Les propriétaires ont un accès complet à <strong>tous les dépôts</strong> et disposent <strong> d'un accès administrateur</strong> de l'organisation.",
|
"org.teams.owners_permission_desc": "Les propriétaires ont un accès complet à <strong>tous les dépôts</strong> et disposent <strong> d'un accès administrateur</strong> de l'organisation.",
|
||||||
@@ -2839,8 +2845,8 @@
|
|||||||
"org.teams.delete_team_desc": "Supprimer une équipe supprime l'accès aux dépôts à ses membres. Continuer ?",
|
"org.teams.delete_team_desc": "Supprimer une équipe supprime l'accès aux dépôts à ses membres. Continuer ?",
|
||||||
"org.teams.delete_team_success": "L’équipe a été supprimée.",
|
"org.teams.delete_team_success": "L’équipe a été supprimée.",
|
||||||
"org.teams.read_permission_desc": "Cette équipe permet l'accès en <strong>lecture</strong> : les membres peuvent voir et dupliquer ses dépôts.",
|
"org.teams.read_permission_desc": "Cette équipe permet l'accès en <strong>lecture</strong> : les membres peuvent voir et dupliquer ses dépôts.",
|
||||||
"org.teams.write_permission_desc": "Cette équipe permet l'accès en <strong>écriture</strong> : les membres peuvent participer à ses dépôts.",
|
"org.teams.write_permission_desc": "Cette équipe accorde un accès en <strong>écriture</strong> : les membres peuvent participer à ses dépôts.",
|
||||||
"org.teams.admin_permission_desc": "Cette équipe permet l'accès <strong>administrateur</strong> : les membres peuvent voir, participer et ajouter des collaborateurs à ses dépôts.",
|
"org.teams.admin_permission_desc": "Cette équipe accorde un accès <strong>administrateur</strong> : les membres peuvent consulter, participer et ajouter des collaborateurs à ses dépôts.",
|
||||||
"org.teams.create_repo_permission_desc": "De plus, cette équipe accorde la permission <strong>Créer un dépôt</strong> : les membres peuvent créer de nouveaux dépôts dans l'organisation.",
|
"org.teams.create_repo_permission_desc": "De plus, cette équipe accorde la permission <strong>Créer un dépôt</strong> : les membres peuvent créer de nouveaux dépôts dans l'organisation.",
|
||||||
"org.teams.repositories": "Dépôts de l'Équipe",
|
"org.teams.repositories": "Dépôts de l'Équipe",
|
||||||
"org.teams.remove_all_repos_title": "Supprimer tous les dépôts de l'équipe",
|
"org.teams.remove_all_repos_title": "Supprimer tous les dépôts de l'équipe",
|
||||||
@@ -2857,8 +2863,8 @@
|
|||||||
"org.teams.all_repositories": "Tous les dépôts",
|
"org.teams.all_repositories": "Tous les dépôts",
|
||||||
"org.teams.all_repositories_helper": "L'équipe a accès à tous les dépôts. Sélectionner ceci <strong>ajoutera tous les dépôts existants</strong> à l'équipe.",
|
"org.teams.all_repositories_helper": "L'équipe a accès à tous les dépôts. Sélectionner ceci <strong>ajoutera tous les dépôts existants</strong> à l'équipe.",
|
||||||
"org.teams.all_repositories_read_permission_desc": "Cette équipe accorde l'accès <strong>en lecture</strong> à <strong>tous les dépôts</strong> : les membres peuvent voir et cloner les dépôts.",
|
"org.teams.all_repositories_read_permission_desc": "Cette équipe accorde l'accès <strong>en lecture</strong> à <strong>tous les dépôts</strong> : les membres peuvent voir et cloner les dépôts.",
|
||||||
"org.teams.all_repositories_write_permission_desc": "Cette équipe accorde l'accès <strong>en écriture</strong> à <strong>tous les dépôts</strong> : les membres peuvent lire et écrire dans les dépôts.",
|
"org.teams.all_repositories_write_permission_desc": "Cette équipe accorde un accès <strong>en écriture</strong> à <strong>tous les dépôts</strong> : les membres peuvent participer aux dépôts.",
|
||||||
"org.teams.all_repositories_admin_permission_desc": "Cette équipe accorde l'accès <strong>administrateur</strong> à <strong>tous les dépôts</strong> : les membres peuvent lire, écrire dans et ajouter des collaborateurs aux dépôts.",
|
"org.teams.all_repositories_admin_permission_desc": "Cette équipe accorde un accès <strong>administrateur</strong> à <strong>tous les dépôts</strong> : les membres peuvent consulter, participer et ajouter des collaborateurs aux dépôts.",
|
||||||
"org.teams.visibility": "Visibilité",
|
"org.teams.visibility": "Visibilité",
|
||||||
"org.teams.visibility_private": "Privé",
|
"org.teams.visibility_private": "Privé",
|
||||||
"org.teams.visibility_private_helper": "Visible uniquement aux membres de l’équipe et aux propriétaires de l’organisation.",
|
"org.teams.visibility_private_helper": "Visible uniquement aux membres de l’équipe et aux propriétaires de l’organisation.",
|
||||||
@@ -3008,7 +3014,7 @@
|
|||||||
"admin.dashboard.gc_lfs": "Purger les métaobjets LFS",
|
"admin.dashboard.gc_lfs": "Purger les métaobjets LFS",
|
||||||
"admin.dashboard.stop_zombie_tasks": "Arrêter les tâches zombies",
|
"admin.dashboard.stop_zombie_tasks": "Arrêter les tâches zombies",
|
||||||
"admin.dashboard.stop_endless_tasks": "Arrêter les tâches interminables",
|
"admin.dashboard.stop_endless_tasks": "Arrêter les tâches interminables",
|
||||||
"admin.dashboard.cancel_abandoned_jobs": "Annuler les actions des tâches abandonnés",
|
"admin.dashboard.cancel_abandoned_jobs": "Annuler les actions des missions abandonnées",
|
||||||
"admin.dashboard.start_schedule_tasks": "Démarrer les tâches planifiées",
|
"admin.dashboard.start_schedule_tasks": "Démarrer les tâches planifiées",
|
||||||
"admin.dashboard.sync_branch.started": "Début de la synchronisation des branches",
|
"admin.dashboard.sync_branch.started": "Début de la synchronisation des branches",
|
||||||
"admin.dashboard.sync_tag.started": "Synchronisation des étiquettes",
|
"admin.dashboard.sync_tag.started": "Synchronisation des étiquettes",
|
||||||
@@ -3445,7 +3451,7 @@
|
|||||||
"action.merge_pull_request": "a fusionné la demande d’ajout <a href=\"%[1]s\">%[3]s#%[2]s</a>",
|
"action.merge_pull_request": "a fusionné la demande d’ajout <a href=\"%[1]s\">%[3]s#%[2]s</a>",
|
||||||
"action.auto_merge_pull_request": "a fusionné automatiquement la demande d’ajout <a href=\"%[1]s\">%[3]s#%[2]s</a>",
|
"action.auto_merge_pull_request": "a fusionné automatiquement la demande d’ajout <a href=\"%[1]s\">%[3]s#%[2]s</a>",
|
||||||
"action.transfer_repo": "a transféré le dépôt <code>%s</code> vers <a href=\"%s\">%s</a>",
|
"action.transfer_repo": "a transféré le dépôt <code>%s</code> vers <a href=\"%s\">%s</a>",
|
||||||
"action.push_tag": "a poussé l’étiquette <a href=\"%[2]s\">%[3]s</a> de <a href=\"%[1]s\">%[4]s</a>",
|
"action.push_tag": "a soumis l’étiquette <a href=\"%[2]s\">%[3]s</a> de <a href=\"%[1]s\">%[4]s</a>",
|
||||||
"action.delete_tag": "a supprimé l’étiquette %[2]s de <a href=\"%[1]s\">%[3]s</a>",
|
"action.delete_tag": "a supprimé l’étiquette %[2]s de <a href=\"%[1]s\">%[3]s</a>",
|
||||||
"action.delete_branch": "a supprimé la branche %[2]s de <a href=\"%[1]s\">%[3]s</a>",
|
"action.delete_branch": "a supprimé la branche %[2]s de <a href=\"%[1]s\">%[3]s</a>",
|
||||||
"action.compare_branch": "Comparer",
|
"action.compare_branch": "Comparer",
|
||||||
@@ -3505,9 +3511,9 @@
|
|||||||
"gpg.error.failed_retrieval_gpg_keys": "Impossible de récupérer la clé liée au compte de l'auteur",
|
"gpg.error.failed_retrieval_gpg_keys": "Impossible de récupérer la clé liée au compte de l'auteur",
|
||||||
"gpg.error.probable_bad_signature": "AVERTISSEMENT ! Bien qu’il y ait une clé avec cet ID dans la base de données, elle ne vérifie pas cette révision ! Cette révision est SUSPECTE.",
|
"gpg.error.probable_bad_signature": "AVERTISSEMENT ! Bien qu’il y ait une clé avec cet ID dans la base de données, elle ne vérifie pas cette révision ! Cette révision est SUSPECTE.",
|
||||||
"gpg.error.probable_bad_default_signature": "AVERTISSEMENT ! Bien que la clé par défaut ait cet ID, elle ne vérifie pas cette révision ! Cette révision est SUSPECTE.",
|
"gpg.error.probable_bad_default_signature": "AVERTISSEMENT ! Bien que la clé par défaut ait cet ID, elle ne vérifie pas cette révision ! Cette révision est SUSPECTE.",
|
||||||
"units.unit": "Ressource",
|
"units.unit": "Section",
|
||||||
"units.error.no_unit_allowed_repo": "Vous n'êtes pas autorisé à accéder à n'importe quelle section de ce dépôt.",
|
"units.error.no_unit_allowed_repo": "Vous n’êtes pas autorisé à accéder à quelconque section de ce dépôt.",
|
||||||
"units.error.unit_not_allowed": "Vous n'êtes pas autorisé à accéder à cette section du dépôt.",
|
"units.error.unit_not_allowed": "Vous n’êtes pas autorisé à accéder à cette section du dépôt.",
|
||||||
"packages.title": "Paquets",
|
"packages.title": "Paquets",
|
||||||
"packages.desc": "Gérer les paquets du dépôt.",
|
"packages.desc": "Gérer les paquets du dépôt.",
|
||||||
"packages.empty": "Il n'y pas de paquet pour le moment.",
|
"packages.empty": "Il n'y pas de paquet pour le moment.",
|
||||||
@@ -3721,64 +3727,66 @@
|
|||||||
"actions.status.cancelling": "Annulation",
|
"actions.status.cancelling": "Annulation",
|
||||||
"actions.status.skipped": "Ignoré",
|
"actions.status.skipped": "Ignoré",
|
||||||
"actions.status.blocked": "Bloqué",
|
"actions.status.blocked": "Bloqué",
|
||||||
"actions.runners": "Exécuteurs",
|
"actions.runners": "Opérateurs",
|
||||||
"actions.runners.runner_manage_panel": "Gestion des exécuteurs",
|
"actions.runners.runner_manage_panel": "Gestion des opérateurs",
|
||||||
"actions.runners.new": "Créer un nouvel exécuteur",
|
"actions.runners.new": "Créer un nouvel opérateur",
|
||||||
"actions.runners.new_notice": "Comment démarrer un exécuteur",
|
"actions.runners.new_notice": "Comment démarrer un opérateur",
|
||||||
"actions.runners.status": "Statut",
|
"actions.runners.status": "Statut",
|
||||||
"actions.runners.id": "ID",
|
"actions.runners.id": "ID",
|
||||||
"actions.runners.name": "Nom",
|
"actions.runners.name": "Nom",
|
||||||
"actions.runners.owner_type": "Type",
|
"actions.runners.owner_type": "Type",
|
||||||
"actions.runners.availability": "Disponibilité",
|
"actions.runners.availability": "Activation",
|
||||||
"actions.runners.description": "Description",
|
"actions.runners.description": "Description",
|
||||||
"actions.runners.labels": "Labels",
|
"actions.runners.labels": "Libellés",
|
||||||
"actions.runners.last_online": "Dernière fois en ligne",
|
"actions.runners.last_online": "Dernière fois en ligne",
|
||||||
"actions.runners.runner_title": "Exécuteur",
|
"actions.runners.runner_title": "Opérateur",
|
||||||
"actions.runners.task_list": "Tâches récentes sur cet exécuteur",
|
"actions.runners.task_list": "Tâches récentes de cet opérateur",
|
||||||
"actions.runners.task_list.no_tasks": "Il n'y a pas de tâche ici.",
|
"actions.runners.task_list.no_tasks": "Il n'y a pas de tâche ici.",
|
||||||
"actions.runners.task_list.run": "Exécuter",
|
"actions.runners.task_list.run": "Exécution",
|
||||||
"actions.runners.task_list.status": "Statut",
|
"actions.runners.task_list.status": "Statut",
|
||||||
"actions.runners.task_list.repository": "Dépôt",
|
"actions.runners.task_list.repository": "Dépôt",
|
||||||
"actions.runners.task_list.commit": "Révision",
|
"actions.runners.task_list.commit": "Révision",
|
||||||
"actions.runners.task_list.done_at": "Fait à",
|
"actions.runners.task_list.done_at": "Fait à",
|
||||||
"actions.runners.edit_runner": "Éditer l'Exécuteur",
|
"actions.runners.edit_runner": "Éditer l’opérateur",
|
||||||
"actions.runners.update_runner": "Appliquer les modifications",
|
"actions.runners.update_runner": "Appliquer les modifications",
|
||||||
"actions.runners.update_runner_success": "Exécuteur mis à jour avec succès",
|
"actions.runners.update_runner_success": "Opérateur mis à jour avec succès",
|
||||||
"actions.runners.update_runner_failed": "Impossible d'actualiser l'Exécuteur",
|
"actions.runners.update_runner_failed": "Impossible d'actualiser l’opérateur",
|
||||||
"actions.runners.enable_runner": "Activer cet exécuteur",
|
"actions.runners.enable_runner": "Activer cet opérateur",
|
||||||
"actions.runners.enable_runner_success": "Exécuteur activé avec succès",
|
"actions.runners.enable_runner_success": "Opérateur activé avec succès",
|
||||||
"actions.runners.enable_runner_failed": "Impossible d’activer l’exécuteur",
|
"actions.runners.enable_runner_failed": "Impossible d’activer l’opérateur",
|
||||||
"actions.runners.disable_runner": "Désactiver cet exécuteur",
|
"actions.runners.disable_runner": "Désactiver cet opérateur",
|
||||||
"actions.runners.disable_runner_success": "Exécuteur désactivé avec succès",
|
"actions.runners.disable_runner_success": "Opérateur désactivé avec succès",
|
||||||
"actions.runners.disable_runner_failed": "Impossible de désactiver l’exécuteur",
|
"actions.runners.disable_runner_failed": "Impossible de désactiver l’opérateur",
|
||||||
"actions.runners.delete_runner": "Supprimer cet exécuteur",
|
"actions.runners.delete_runner": "Supprimer cet opérateur",
|
||||||
"actions.runners.delete_runner_success": "Exécuteur supprimé avec succès",
|
"actions.runners.delete_runner_success": "Opérateur supprimé avec succès",
|
||||||
"actions.runners.delete_runner_failed": "Impossible de supprimer l'Exécuteur",
|
"actions.runners.delete_runner_failed": "Impossible de supprimer l’opérateur",
|
||||||
"actions.runners.delete_runner_header": "Êtes-vous sûr de vouloir supprimer cet exécuteur ?",
|
"actions.runners.delete_runner_header": "Êtes-vous sûr de vouloir supprimer cet opérateur ?",
|
||||||
"actions.runners.delete_runner_notice": "Si une tâche est en cours sur cet exécuteur, elle sera terminée et marquée comme échouée. Cela risque d’interrompre le flux de travail.",
|
"actions.runners.delete_runner_notice": "Si des tâches sont en cours de réalisation par cet opérateur, elles seront abandonnées et marquées en échec, pouvant également entrainer l’échec des procédures appelantes.",
|
||||||
"actions.runners.none": "Aucun exécuteur disponible",
|
"actions.runners.none": "Aucun opérateur disponible",
|
||||||
"actions.runners.status.unspecified": "Inconnu",
|
"actions.runners.status.unspecified": "Inconnu",
|
||||||
"actions.runners.status.idle": "Inactif",
|
"actions.runners.status.idle": "Disponible",
|
||||||
"actions.runners.status.active": "Actif",
|
"actions.runners.status.active": "Occupé",
|
||||||
"actions.runners.status.offline": "Hors-ligne",
|
"actions.runners.status.offline": "Hors-ligne",
|
||||||
"actions.runners.version": "Version",
|
"actions.runners.version": "Version",
|
||||||
"actions.runners.reset_registration_token": "Réinitialiser le jeton d'enregistrement",
|
"actions.runners.reset_registration_token": "Réinitialiser le jeton d’enregistrement",
|
||||||
"actions.runners.reset_registration_token_confirm": "Voulez-vous révoquer le jeton actuel et en générer un nouveau ?",
|
"actions.runners.reset_registration_token_confirm": "Voulez-vous révoquer le jeton actuel et en générer un nouveau ?",
|
||||||
"actions.runners.reset_registration_token_success": "Le jeton d’inscription de l’exécuteur a été réinitialisé avec succès",
|
"actions.runners.reset_registration_token_success": "Le jeton d’inscription de l’opérateur a été réinitialisé avec succès",
|
||||||
"actions.runs.all_workflows": "Tous les flux de travail",
|
"actions.runs.all_workflows": "Toutes les procédures",
|
||||||
"actions.runs.other_workflows": "Autres flux de travail",
|
"actions.runs.other_workflows": "Autres procédures",
|
||||||
"actions.runs.other_workflows_tooltip": "Les flux de travail qui ont été exécutés dans ce dépôt mais qui n’existent pas dans la branche par défaut.",
|
"actions.runs.other_workflows_tooltip": "Les procédures qui ont été exécutées dans ce dépôt mais qui n’existent pas dans la branche par défaut.",
|
||||||
"actions.runs.workflow_run_count_1": "%d exécution du workflow",
|
"actions.runs.workflow_run_count_1": "%d exécution de procédure",
|
||||||
"actions.runs.workflow_run_count_n": "%d exécutions du workflow",
|
"actions.runs.workflow_run_count_n": "%d exécutions de procédure",
|
||||||
"actions.runs.commit": "Révision",
|
"actions.runs.commit": "Révision",
|
||||||
"actions.runs.run_details": "Détails de l’exécution",
|
"actions.runs.run_details": "Détails de l’exécution",
|
||||||
"actions.runs.workflow_file": "Fichier de flux de travail",
|
"actions.runs.workflow_file": "Déclaration de la procédure",
|
||||||
|
"actions.runs.workflow_file_no_permission": "Pas de permission pour voir la procédure",
|
||||||
"actions.runs.scheduled": "Planifié",
|
"actions.runs.scheduled": "Planifié",
|
||||||
"actions.runs.pushed_by": "soumis par",
|
"actions.runs.pushed_by": "soumis par",
|
||||||
"actions.runs.invalid_workflow_helper": "La configuration du flux de travail est invalide. Veuillez vérifier votre fichier %s.",
|
"actions.runs.invalid_workflow_helper": "La déclaration de la procédure est invalide. Veuillez vérifier le fichier « %s ».",
|
||||||
"actions.runs.no_matching_online_runner_helper": "Aucun exécuteur en ligne correspondant au libellé %s",
|
"actions.runs.no_matching_online_runner_helper": "Aucun opérateur disponible correspondant au libellé « %s »",
|
||||||
"actions.runs.no_job_without_needs": "Le flux de travail doit contenir au moins une tâche sans dépendance.",
|
"actions.runs.no_job_without_needs": "La procédure doit contenir au moins une mission sans dépendance.",
|
||||||
"actions.runs.no_job": "Le flux de travail doit contenir au moins une tâche",
|
"actions.runs.no_job": "La procédure doit contenir au moins une mission.",
|
||||||
|
"actions.runs.invalid_reusable_workflow_uses": "Clause \"uses\" invalide dans la procédure : %s",
|
||||||
"actions.runs.actor": "Acteur",
|
"actions.runs.actor": "Acteur",
|
||||||
"actions.runs.status": "Statut",
|
"actions.runs.status": "Statut",
|
||||||
"actions.runs.actors_no_select": "Tous les acteurs",
|
"actions.runs.actors_no_select": "Tous les acteurs",
|
||||||
@@ -3786,45 +3794,82 @@
|
|||||||
"actions.runs.branch": "Branche",
|
"actions.runs.branch": "Branche",
|
||||||
"actions.runs.branches_no_select": "Toutes les branches",
|
"actions.runs.branches_no_select": "Toutes les branches",
|
||||||
"actions.runs.no_results": "Aucun résultat correspondant.",
|
"actions.runs.no_results": "Aucun résultat correspondant.",
|
||||||
"actions.runs.no_workflows": "Il n'y a pas encore de workflows.",
|
"actions.runs.no_workflows": "Il n’y a pas de procédure ici.",
|
||||||
"actions.runs.no_workflows.quick_start": "Vous découvrez les Actions Gitea ? Consultez <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">le didacticiel</a>.",
|
"actions.runs.no_workflows.quick_start": "Vous découvrez les Actions Gitea ? Consultez <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">le didacticiel</a>.",
|
||||||
"actions.runs.no_workflows.documentation": "Pour plus d’informations sur les actions Gitea, voir <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">la documentation</a>.",
|
"actions.runs.no_workflows.documentation": "Pour plus d’informations sur les Actions Gitea, voir <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">la documentation</a>.",
|
||||||
"actions.runs.no_runs": "Le flux de travail n'a pas encore d'exécution.",
|
"actions.runs.no_runs": "Cette procédure n’a pas encore été exécutée.",
|
||||||
"actions.runs.empty_commit_message": "(message de révision vide)",
|
"actions.runs.empty_commit_message": "(message de révision vide)",
|
||||||
"actions.runs.expire_log_message": "Les journaux ont été supprimés car ils étaient trop anciens.",
|
"actions.runs.expire_log_message": "Les journaux ont été supprimés car ils étaient trop anciens.",
|
||||||
"actions.runs.delete": "Supprimer cette exécution",
|
"actions.runs.delete": "Effacer l’execution de cette procédure",
|
||||||
"actions.runs.cancel": "Annuler l’exécution du flux",
|
"actions.runs.cancel": "Annuler l’exécution de cette procédure",
|
||||||
"actions.runs.delete.description": "Êtes-vous sûr de vouloir supprimer définitivement cette exécution ? Cette action ne peut pas être annulée.",
|
"actions.runs.delete.description": "Êtes-vous sûr de vouloir supprimer définitivement cette exécution ? Cette action ne peut pas être annulée.",
|
||||||
"actions.runs.not_done": "Cette exécution du flux de travail n’est pas terminée.",
|
"actions.runs.not_done": "Cette procédure n’est pas terminée.",
|
||||||
"actions.runs.view_workflow_file": "Voir le fichier du flux de travail",
|
"actions.runs.view_workflow_file": "Voir la déclaration de la procédure",
|
||||||
"actions.runs.summary": "Résumé",
|
"actions.runs.summary": "Résumé",
|
||||||
"actions.runs.all_jobs": "Toutes les tâches",
|
"actions.runs.all_jobs": "Toutes les missions",
|
||||||
|
"actions.runs.job_summaries": "Résumé des missions",
|
||||||
|
"actions.runs.expand_caller_jobs": "Afficher les missions de cette procédure réutilisable",
|
||||||
|
"actions.runs.collapse_caller_jobs": "Masquer les missions de cette procédure réutilisable",
|
||||||
"actions.runs.attempt": "Tentative",
|
"actions.runs.attempt": "Tentative",
|
||||||
"actions.runs.latest": "Dernière",
|
"actions.runs.latest": "Dernière",
|
||||||
"actions.runs.latest_attempt": "Dernière tentative",
|
"actions.runs.latest_attempt": "Dernière tentative",
|
||||||
"actions.runs.triggered_via": "Déclenché via %s",
|
"actions.runs.triggered_via": "Déclenché via %s",
|
||||||
|
"actions.runs.rerun_triggered": "Relance enclenchée",
|
||||||
|
"actions.runs.back_to_pull_request": "Retour à la demande d’ajout",
|
||||||
|
"actions.runs.back_to_workflow": "Retour à la procédure",
|
||||||
|
"actions.runs.total_duration": "Durée totale :",
|
||||||
|
"actions.runs.workflow_dependencies": "Dépendances de la procédure",
|
||||||
|
"actions.runs.graph_jobs_count_1": "%d mission",
|
||||||
|
"actions.runs.graph_jobs_count_n": "%d missions",
|
||||||
"actions.runs.graph_dependencies_count_1": "%d dépendance",
|
"actions.runs.graph_dependencies_count_1": "%d dépendance",
|
||||||
"actions.runs.graph_dependencies_count_n": "%d dépendances",
|
"actions.runs.graph_dependencies_count_n": "%d dépendances",
|
||||||
"actions.runs.graph_success_rate": "%s succès",
|
"actions.runs.graph_success_rate": "%s succès",
|
||||||
"actions.runs.graph_zoom_in": "Zoomer (Ctrl/⌘ + défilement)",
|
"actions.runs.graph_zoom_in": "Zoomer (Ctrl/⌘ + défilement)",
|
||||||
"actions.runs.graph_zoom_max": "Déjà zoomé à 100%",
|
"actions.runs.graph_zoom_max": "Déjà à 100%",
|
||||||
"actions.runs.graph_zoom_out": "Dézoomer (Ctrl/⌘ + défilement)",
|
"actions.runs.graph_zoom_out": "Dézoomer (Ctrl/⌘ + défilement)",
|
||||||
"actions.workflow.disable": "Désactiver le flux de travail",
|
"actions.runs.graph_reset_view": "Rétablir",
|
||||||
"actions.workflow.disable_success": "Le flux de travail « %s » a bien été désactivé.",
|
"actions.workflow.disable": "Désactiver la procédure",
|
||||||
"actions.workflow.enable": "Activer le flux de travail",
|
"actions.workflow.disable_success": "La procédure « %s » a bien été désactivée.",
|
||||||
"actions.workflow.enable_success": "Le flux de travail « %s » a bien été activé.",
|
"actions.workflow.enable": "Activer la procédure",
|
||||||
"actions.workflow.disabled": "Le flux de travail est désactivé.",
|
"actions.workflow.enable_success": "La procédure « %s » a bien été activée.",
|
||||||
|
"actions.workflow.disabled": "La procédure est désactivée.",
|
||||||
"actions.workflow.scope_owner": "Propriétaire",
|
"actions.workflow.scope_owner": "Propriétaire",
|
||||||
"actions.workflow.scope_global": "Global",
|
"actions.workflow.scope_global": "Global",
|
||||||
"actions.workflow.required": "Requis",
|
"actions.workflow.required": "Requis",
|
||||||
"actions.workflow.run": "Exécuter le flux de travail",
|
"actions.workflow.scoped_required_cannot_disable": "Cette procédure transversale est requise.",
|
||||||
"actions.workflow.not_found": "Flux de travail « %s » introuvable.",
|
"actions.scoped_workflows": "Procédures transversales",
|
||||||
"actions.workflow.run_success": "Le flux de travail « %s » s’est correctement exécuté.",
|
"actions.scoped_workflows.desc_org": "Enrôlez un dépôt afin de rendre ses procédures accessibles à votre organisation. Toutes les procédures de la branche principale de ce dépôt seront ainsi exécutées dans chaque dépôt de cette organisation, comme si elles y avaient été créées.",
|
||||||
"actions.workflow.from_ref": "Utiliser le flux de travail depuis",
|
"actions.scoped_workflows.desc_user": "Enrôlez un dépôt afin de rendre ses procédures accessibles à votre compte. Toutes les procédures de la branche principale de ce dépôt seront ainsi exécutées dans chaque dépôt que vous possédez, comme si elles y avaient été créées.",
|
||||||
"actions.workflow.has_workflow_dispatch": "Ce flux de travail a un déclencheur d’événement workflow_dispatch.",
|
"actions.scoped_workflows.desc_global": "Enrôlez un dépôt afin de rendre ses procédures accessibles à l’ensemble du serveur. Toutes les procédures de la branche principale de ce dépôt seront ainsi exécutées dans chaque dépôt, comme si elles y avaient été créées. Sur un serveur volumineux, ces procédures peuvent lourdement solliciter les ressources du système.",
|
||||||
"actions.workflow.has_no_workflow_dispatch": "Le flux de travail %s n’a pas de déclencheur d’événement workflow_dispatch.",
|
"actions.scoped_workflows.add_help": "Pour rendre des procédures transversales, soumettez leurs déclarations dans le dossier <code>%s</code> sur la branche par défaut de ce dépôt, puis enrôlez celui-ci ci-dessous.",
|
||||||
"actions.need_approval_desc": "Besoin d’approbation pour exécuter des flux de travail pour une demande d’ajout de bifurcation.",
|
"actions.scoped_workflows.security_note": "Parce qu’une procédure transversale opère sur d’autres dépôts que le sien, ses extrants sont journalisés sur ces dépôts et sont donc consultable par leurs utilisateurs. Ainsi, une procédure issue d’un dépôt privé peut-être reconstruit à partir des journaux qu'elle produit. Exposer une procédure transversale peut donc compromètre la confidentialité de son dépôt hôte. Si une procédure transversale référence une autre procédure issue d’un dépôt privé, assurez-vous que les dépôts affectés puissent également s'y référer, sans quoi ces procédures échoueront.",
|
||||||
"actions.approve_all_success": "Tous les flux de travail ont été acceptés.",
|
"actions.scoped_workflows.source.add": "Enrôler un dépôt",
|
||||||
|
"actions.scoped_workflows.source.add_success": "Dépôt enrôlé.",
|
||||||
|
"actions.scoped_workflows.source.remove_success": "Dépôt retiré.",
|
||||||
|
"actions.scoped_workflows.source.not_found": "Dépôt introuvable.",
|
||||||
|
"actions.scoped_workflows.required.update_success": "Procédure requise mise à jour.",
|
||||||
|
"actions.scoped_workflows.required.label": "Marquer les procédures comme requises (elles ne pourront être désactivés depuis un dépôt) :",
|
||||||
|
"actions.scoped_workflows.required.patterns": "Motifs de signal requis",
|
||||||
|
"actions.scoped_workflows.required.patterns_aria": "Motifs de signal requis pour « %s »",
|
||||||
|
"actions.scoped_workflows.required.patterns_note": "est uniquement appliqué lorsque la procédure est requise",
|
||||||
|
"actions.scoped_workflows.required.patterns_hint": "Marquez la procédure comme requise pour configurer ses motifs de signal.",
|
||||||
|
"actions.scoped_workflows.required.patterns_help": "Un motif de signal par ligne. Une demande d’ajout concernée peut être fusionnée à condition qu’au moins un signal par motif ait réussi. Cela est imposé pour toutes les branches affectées ayant des règles de protections (même désactivées), mais pas les branches sans protections.",
|
||||||
|
"actions.scoped_workflows.required.patterns_empty": "Chaque procédure requise nécessite au moins un motif de signal.",
|
||||||
|
"actions.scoped_workflows.required.missing_file": "Le fichier n’est plus dans le dépôt enrôlé.",
|
||||||
|
"actions.scoped_workflows.required.expected_contexts": "Signaux attendus (un signal qui correspond au motif est marqué)",
|
||||||
|
"actions.scoped_workflows.required.no_status_contexts": "Comme cette procédure ne publie aucun signal, la rentre obligatoire empêchera toutes demandes d’ajouts concernées d’être fusionnées. Préférez laisser cette procédure facultative.",
|
||||||
|
"actions.scoped_workflows.no_files": "Aucune procédure transversale n'a été trouvé dans la branche par défaut.",
|
||||||
|
"actions.workflow.run": "Réaliser la procédure",
|
||||||
|
"actions.workflow.create_status_badge": "Créer un badge d’état",
|
||||||
|
"actions.workflow.status_badge": "Badge d’état",
|
||||||
|
"actions.workflow.status_badge_url": "URL du badge",
|
||||||
|
"actions.workflow.not_found": "La procédure « %s » est introuvable.",
|
||||||
|
"actions.workflow.run_success": "La procédure « %s » est accomplie.",
|
||||||
|
"actions.workflow.from_ref": "Utiliser la procédure depuis",
|
||||||
|
"actions.workflow.has_workflow_dispatch": "Cette procédure dispose d’un déclencheur workflow_dispatch.",
|
||||||
|
"actions.workflow.has_no_workflow_dispatch": "La procédure « %s » n’a pas de déclencheur workflow_dispatch.",
|
||||||
|
"actions.need_approval_desc": "Une approbation est nécessaire pour exécuter les procédures d‘une demande d’ajout de bifurcation.",
|
||||||
|
"actions.approve_all_success": "Toutes les procédures ont été approuvées.",
|
||||||
"actions.variables": "Variables",
|
"actions.variables": "Variables",
|
||||||
"actions.variables.management": "Gestion des variables",
|
"actions.variables.management": "Gestion des variables",
|
||||||
"actions.variables.creation": "Ajouter une variable",
|
"actions.variables.creation": "Ajouter une variable",
|
||||||
@@ -3845,7 +3890,7 @@
|
|||||||
"actions.general": "Général",
|
"actions.general": "Général",
|
||||||
"actions.general.enable_actions": "Activer les actions",
|
"actions.general.enable_actions": "Activer les actions",
|
||||||
"actions.general.collaborative_owners_management": "Gestion des collaborateurs",
|
"actions.general.collaborative_owners_management": "Gestion des collaborateurs",
|
||||||
"actions.general.collaborative_owners_management_help": "Un collaborateur est un utilisateur ou une organisation dont le dépôt privé peut accéder aux actions et flux de travail de ce dépôt.",
|
"actions.general.collaborative_owners_management_help": "Un collaborateur est un utilisateur ou une organisation dont le dépôt privé peut accéder aux actions et procédures de ce dépôt.",
|
||||||
"actions.general.add_collaborative_owner": "Ajouter un collaborateur",
|
"actions.general.add_collaborative_owner": "Ajouter un collaborateur",
|
||||||
"actions.general.collaborative_owner_not_exist": "Le collaborateur n’existe pas.",
|
"actions.general.collaborative_owner_not_exist": "Le collaborateur n’existe pas.",
|
||||||
"actions.general.remove_collaborative_owner": "Supprimer le collaborateur",
|
"actions.general.remove_collaborative_owner": "Supprimer le collaborateur",
|
||||||
@@ -3863,21 +3908,21 @@
|
|||||||
"git.filemode.symbolic_link": "Lien symbolique",
|
"git.filemode.symbolic_link": "Lien symbolique",
|
||||||
"git.filemode.submodule": "Sous-module",
|
"git.filemode.submodule": "Sous-module",
|
||||||
"org.repos.none": "Aucun dépôt.",
|
"org.repos.none": "Aucun dépôt.",
|
||||||
"actions.general.permissions": "Permissions du jeton des actions",
|
"actions.general.permissions": "Permissions intégrées au jeton d’Actions",
|
||||||
"actions.general.token_permissions.mode": "Permissions par défaut du jeton",
|
"actions.general.token_permissions.mode": "Régime de restriction par défaut",
|
||||||
"actions.general.token_permissions.mode.desc": "Une tâche d’Actions utilisera les permissions par défaut si aucune n’est déclarée dans le fichier du flux de travail.",
|
"actions.general.token_permissions.mode.desc": "Lorsqu’une procédure ne contient pas de restriction, elle est alors restreinte au régime par défaut du dépôt.",
|
||||||
"actions.general.token_permissions.mode.permissive": "Permissif",
|
"actions.general.token_permissions.mode.permissive": "Régime permissif",
|
||||||
"actions.general.token_permissions.mode.permissive.desc": "Permissions en lecture et écriture sur le dépôt de la tâche.",
|
"actions.general.token_permissions.mode.permissive.desc": "Les missions peuvent consulter et modifier ce dépôt.",
|
||||||
"actions.general.token_permissions.mode.restricted": "Restreint",
|
"actions.general.token_permissions.mode.restricted": "Régime limitant",
|
||||||
"actions.general.token_permissions.mode.restricted.desc": "Permissions en lecture seule pour le contenu (code, publications) sur le dépôt de la tâche.",
|
"actions.general.token_permissions.mode.restricted.desc": "Les missions ne peuvent que consulter le contenu (code et publications) de ce dépôt.",
|
||||||
"actions.general.token_permissions.override_owner": "Écraser la configuration faite par le propriétaire",
|
"actions.general.token_permissions.override_owner": "Outrepasser la configuration du propriétaire",
|
||||||
"actions.general.token_permissions.override_owner_desc": "Si actif, ce dépôt utilisera sa propre configuration pour les actions au lieu de respecter celle du propriétaire (utilisateur ou organisation).",
|
"actions.general.token_permissions.override_owner_desc": "Si actif, ce dépôt donnera ses propres restrictions à la place de celles du propriétaire (utilisateur ou organisation).",
|
||||||
"actions.general.token_permissions.maximum": "Permissions maximales du jeton",
|
"actions.general.token_permissions.maximum": "Restrictions affinées",
|
||||||
"actions.general.token_permissions.maximum.description": "Les permissions effectives de la tâche des actions seront limitées par les permissions maximales.",
|
"actions.general.token_permissions.maximum.description": "Pour protéger plus finement le dépôt, les restrictions appliquées aux missions peuvent être définies pour chaque section individuellement.",
|
||||||
"actions.general.token_permissions.fork_pr_note": "Si une tâche est démarrée par une demande de fusion depuis une bifurcation, ses permissions effectives ne dépasseront pas les permissions en lecture-seule.",
|
"actions.general.token_permissions.fork_pr_note": "Lorsqu’une mission est initiée par une demande d’ajout depuis une bifurcation, ses restrictions effectives ne peuvent dépasser la lecture seule.",
|
||||||
"actions.general.token_permissions.customize_max_permissions": "Personnaliser les permissions maximales",
|
"actions.general.token_permissions.customize_max_permissions": "Affiner les restrictions",
|
||||||
"actions.general.cross_repo": "Accès inter-dépôt",
|
"actions.general.cross_repo": "Accès inter-dépôt",
|
||||||
"actions.general.cross_repo_desc": "Permet aux dépôts sélectionnés d’être visible en lecture-seule par tous les dépôts de ce propriétaire à l’aide de GITEA_TOKEN lors de l’exécution des tâches d’actions.",
|
"actions.general.cross_repo_desc": "Permet aux dépôts de ce propriétaire de consulter (en lecture seule) les dépôts listés ci-dessous pendant l’exécution des missions d’Actions (nécessite un jeton GITEA_TOKEN).",
|
||||||
"actions.general.cross_repo_selected": "Dépôts sélectionnés",
|
"actions.general.cross_repo_selected": "Dépôts sélectionnés",
|
||||||
"actions.general.cross_repo_target_repos": "Dépôts cibles",
|
"actions.general.cross_repo_target_repos": "Dépôts cibles",
|
||||||
"actions.general.cross_repo_add": "Ajouter un dépôt cible"
|
"actions.general.cross_repo_add": "Ajouter un dépôt cible"
|
||||||
|
|||||||
@@ -3779,6 +3779,7 @@
|
|||||||
"actions.runs.commit": "Tiomantas",
|
"actions.runs.commit": "Tiomantas",
|
||||||
"actions.runs.run_details": "Sonraí Rith",
|
"actions.runs.run_details": "Sonraí Rith",
|
||||||
"actions.runs.workflow_file": "Comhad sreabhadh oibre",
|
"actions.runs.workflow_file": "Comhad sreabhadh oibre",
|
||||||
|
"actions.runs.workflow_file_no_permission": "Gan cead chun an comhad sreafa oibre a fheiceáil",
|
||||||
"actions.runs.scheduled": "Sceidealaithe",
|
"actions.runs.scheduled": "Sceidealaithe",
|
||||||
"actions.runs.pushed_by": "bhrú ag",
|
"actions.runs.pushed_by": "bhrú ag",
|
||||||
"actions.runs.invalid_workflow_helper": "Tá comhad cumraíochta sreabhadh oibre nebhailí. Seiceáil do chomhad cumraithe le do thoil: %s",
|
"actions.runs.invalid_workflow_helper": "Tá comhad cumraíochta sreabhadh oibre nebhailí. Seiceáil do chomhad cumraithe le do thoil: %s",
|
||||||
@@ -3835,7 +3836,33 @@
|
|||||||
"actions.workflow.scope_owner": "Úinéir",
|
"actions.workflow.scope_owner": "Úinéir",
|
||||||
"actions.workflow.scope_global": "Domhanda",
|
"actions.workflow.scope_global": "Domhanda",
|
||||||
"actions.workflow.required": "Riachtanach",
|
"actions.workflow.required": "Riachtanach",
|
||||||
|
"actions.workflow.scoped_required_cannot_disable": "Tá an sreabhadh oibre raonaithe seo riachtanach agus ní féidir é a dhíchumasú.",
|
||||||
|
"actions.scoped_workflows": "Sreafaí Oibre Raonaithe",
|
||||||
|
"actions.scoped_workflows.desc_org": "Cláraigh stórtha mar fhoinsí sreabhadh oibre raonta. Ritheann comhaid sreabhadh oibre faoi eolairí sreabhadh oibre raonta brainse réamhshocraithe stórtha foinse ar gach stórtha den eagraíocht seo, i gcomhthéacs an stórtha sin féin.",
|
||||||
|
"actions.scoped_workflows.desc_user": "Cláraigh stórtha mar fhoinsí sreabhadh oibre raonta. Ritheann comhaid sreabhadh oibre faoi eolairí sreabhadh oibre raonta brainse réamhshocraithe stórtha foinse ar gach stór atá i do sheilbh, i gcomhthéacs an stórtha sin féin.",
|
||||||
|
"actions.scoped_workflows.desc_global": "Cláraigh stórtha mar fhoinsí sreabhadh oibre raonta. Ritheann comhaid sreabhadh oibre faoi eolairí sreabhadh oibre raonta brainse réamhshocraithe stórtha foinse ar gach stór ar an gcás seo, i gcomhthéacs an stórtha sin féin. Ós rud é go ndéantar foinsí ar leibhéal an chás a mheas ar imeachtaí gach stórtha, is féidir le clárú na gcomhad sin forchostais a chur leis ar chásanna móra.",
|
||||||
|
"actions.scoped_workflows.add_help": "Chun sreafaí oibre raonta a sholáthar ó stór, cuir na comhaid sreafa oibre faoi <code>%s</code> ar a bhrainse réamhshocraithe, agus ansin cláraigh an stór mar fhoinse thíos.",
|
||||||
|
"actions.scoped_workflows.security_note": "Déantar ábhar sreafa oibre stórais foinse a fhorghníomhú i ngach stórais lena mbaineann sé, agus scríobhtar a scripteanna céime agus a n-aschur chuig logaí Gníomhartha an stórais sin agus is féidir le duine ar bith ar féidir leo Gníomhartha an stórais ídigh a fheiceáil iad a léamh. Dá bhrí sin, nochtar loighic a sreafa oibre trí na logaí sin nuair a chláraítear stórais phríobháidigh mar fhoinse. Ní chláraítear ach stórais a bhféadfadh a n-ábhar sreafa oibre a bheith roinnte le gach stórais ídigh. Má thagraíonn sreabhadh oibre raonaithe do shreabhadh oibre in-athúsáidte ó stórais phríobháidigh, déan cinnte gur féidir le gach stórais ídigh é a léamh, nó teipfidh ar an sreabhadh oibre ansin.",
|
||||||
|
"actions.scoped_workflows.source.add": "Cuir stór foinse leis",
|
||||||
|
"actions.scoped_workflows.source.add_success": "Stór foinse curtha leis.",
|
||||||
|
"actions.scoped_workflows.source.remove_success": "Baineadh an stór foinse.",
|
||||||
|
"actions.scoped_workflows.source.not_found": "Níor aimsíodh an stórlann.",
|
||||||
|
"actions.scoped_workflows.required.update_success": "Sreafaí oibre riachtanacha nuashonraithe.",
|
||||||
|
"actions.scoped_workflows.required.label": "Marcáil sreafaí oibre mar riachtanacha (ní féidir le stórtha sreabhadh oibre riachtanach a dhíchumasú):",
|
||||||
|
"actions.scoped_workflows.required.patterns": "Patrúin seiceála stádais riachtanacha",
|
||||||
|
"actions.scoped_workflows.required.patterns_aria": "Patrúin seiceála stádais riachtanacha do %s",
|
||||||
|
"actions.scoped_workflows.required.patterns_note": "i bhfeidhm ach amháin nuair a bhíonn an sreabhadh oibre ag teastáil",
|
||||||
|
"actions.scoped_workflows.required.patterns_hint": "Marcáil an sreabhadh oibre mar is gá chun a phatrúin seiceála stádais a chumrú.",
|
||||||
|
"actions.scoped_workflows.required.patterns_help": "Patrún seiceála stádais amháin (glob) in aghaidh an líne. Ní féidir iarratas tarraingthe íditheach a chumasc ach amháin nuair a bheidh stádas a mheaitseálann gach patrún rite. Cuirtear é seo i bhfeidhm ar aon bhrainse sprice a bhfuil riail chosanta aige, fiú ceann a bhfuil a sheiceálacha stádais féin díchumasaithe; ní dhéantar geataíocht ar bhrainse sprice gan aon riail chosanta.",
|
||||||
|
"actions.scoped_workflows.required.patterns_empty": "Teastaíonn patrún seiceála stádais amháin ar a laghad ó gach sreabhadh oibre riachtanach.",
|
||||||
|
"actions.scoped_workflows.required.missing_file": "níl an comhad sa bhunleagan a thuilleadh",
|
||||||
|
"actions.scoped_workflows.required.expected_contexts": "Seiceálacha stádais ionchais (marcáiltear seic a mheaitseálann patrún)",
|
||||||
|
"actions.scoped_workflows.required.no_status_contexts": "Ní phostálann an sreabhadh oibre seo aon seiceálacha stádais, mar sin má mharcálann tú é mar riachtanas, chuirfeadh sé bac ar gach iarratas tarraingthe atá á úsáid ó chumasc. Díthiceáil Riachtanach.",
|
||||||
|
"actions.scoped_workflows.no_files": "Ní bhfuarthas aon chomhaid sreabha oibre raonaithe ar an mbrainse réamhshocraithe.",
|
||||||
"actions.workflow.run": "Rith Sreabhadh Oibre",
|
"actions.workflow.run": "Rith Sreabhadh Oibre",
|
||||||
|
"actions.workflow.create_status_badge": "Cruthaigh suaitheantas stádais",
|
||||||
|
"actions.workflow.status_badge": "Suaitheantas Stádais",
|
||||||
|
"actions.workflow.status_badge_url": "URL suaitheantais",
|
||||||
"actions.workflow.not_found": "Níor aimsíodh sreabhadh oibre '%s'.",
|
"actions.workflow.not_found": "Níor aimsíodh sreabhadh oibre '%s'.",
|
||||||
"actions.workflow.run_success": "Ritheann sreabhadh oibre '%s' go rathúil.",
|
"actions.workflow.run_success": "Ritheann sreabhadh oibre '%s' go rathúil.",
|
||||||
"actions.workflow.from_ref": "Úsáid sreabhadh oibre ó",
|
"actions.workflow.from_ref": "Úsáid sreabhadh oibre ó",
|
||||||
|
|||||||
@@ -2872,6 +2872,7 @@
|
|||||||
"org.teams.visibility_limited_helper": "この組織のすべてのメンバーに表示されます。",
|
"org.teams.visibility_limited_helper": "この組織のすべてのメンバーに表示されます。",
|
||||||
"org.teams.visibility_public": "公開",
|
"org.teams.visibility_public": "公開",
|
||||||
"org.teams.visibility_public_helper": "サインインしているすべてのユーザーに表示されます。",
|
"org.teams.visibility_public_helper": "サインインしているすべてのユーザーに表示されます。",
|
||||||
|
"org.teams.owners_visibility_fixed": "Ownersチームの公開範囲は変更できません。",
|
||||||
"org.teams.invite.title": "あなたは組織 <strong>%[2]s</strong> 内のチーム <strong>%[1]s</strong> への参加に招待されました。",
|
"org.teams.invite.title": "あなたは組織 <strong>%[2]s</strong> 内のチーム <strong>%[1]s</strong> への参加に招待されました。",
|
||||||
"org.teams.invite.by": "%s からの招待",
|
"org.teams.invite.by": "%s からの招待",
|
||||||
"org.teams.invite.description": "下のボタンをクリックしてチームに参加してください。",
|
"org.teams.invite.description": "下のボタンをクリックしてチームに参加してください。",
|
||||||
@@ -3778,6 +3779,7 @@
|
|||||||
"actions.runs.commit": "コミット",
|
"actions.runs.commit": "コミット",
|
||||||
"actions.runs.run_details": "実行の詳細",
|
"actions.runs.run_details": "実行の詳細",
|
||||||
"actions.runs.workflow_file": "ワークフローのファイル",
|
"actions.runs.workflow_file": "ワークフローのファイル",
|
||||||
|
"actions.runs.workflow_file_no_permission": "ワークフローファイルを表示する権限がありません",
|
||||||
"actions.runs.scheduled": "スケジュール済み",
|
"actions.runs.scheduled": "スケジュール済み",
|
||||||
"actions.runs.pushed_by": "pushed by",
|
"actions.runs.pushed_by": "pushed by",
|
||||||
"actions.runs.invalid_workflow_helper": "ワークフロー設定ファイルは無効です。あなたの設定ファイルを確認してください: %s",
|
"actions.runs.invalid_workflow_helper": "ワークフロー設定ファイルは無効です。あなたの設定ファイルを確認してください: %s",
|
||||||
@@ -3834,7 +3836,12 @@
|
|||||||
"actions.workflow.scope_owner": "オーナー",
|
"actions.workflow.scope_owner": "オーナー",
|
||||||
"actions.workflow.scope_global": "グローバル",
|
"actions.workflow.scope_global": "グローバル",
|
||||||
"actions.workflow.required": "必須",
|
"actions.workflow.required": "必須",
|
||||||
|
"actions.workflow.scoped_required_cannot_disable": "このスコープ付きワークフローは必須で、無効にできません。",
|
||||||
|
"actions.scoped_workflows": "スコープ付きワークフロー",
|
||||||
"actions.workflow.run": "ワークフローを実行",
|
"actions.workflow.run": "ワークフローを実行",
|
||||||
|
"actions.workflow.create_status_badge": "ステータスバッジを作成する",
|
||||||
|
"actions.workflow.status_badge": "ステータスバッジ",
|
||||||
|
"actions.workflow.status_badge_url": "バッジURL",
|
||||||
"actions.workflow.not_found": "ワークフロー '%s' が見つかりません。",
|
"actions.workflow.not_found": "ワークフロー '%s' が見つかりません。",
|
||||||
"actions.workflow.run_success": "ワークフロー '%s' は正常に実行されました。",
|
"actions.workflow.run_success": "ワークフロー '%s' は正常に実行されました。",
|
||||||
"actions.workflow.from_ref": "使用するワークフローの取得元",
|
"actions.workflow.from_ref": "使用するワークフローの取得元",
|
||||||
|
|||||||
@@ -1584,7 +1584,7 @@
|
|||||||
"repo.issues.label_archived_filter": "아카이빙된 레이블 표시",
|
"repo.issues.label_archived_filter": "아카이빙된 레이블 표시",
|
||||||
"repo.issues.label_archive_tooltip": "아카아빙된 레이블은 레이블로 검색할 때 기본적으로 제안 목록에서 제외됩니다.",
|
"repo.issues.label_archive_tooltip": "아카아빙된 레이블은 레이블로 검색할 때 기본적으로 제안 목록에서 제외됩니다.",
|
||||||
"repo.issues.label_exclusive_desc": "레이블명을 <code>스코프/항목</code>으로 지정하여 다른 <code>스코프/</code> 레이블과 상호 배타적으로 만드세요.",
|
"repo.issues.label_exclusive_desc": "레이블명을 <code>스코프/항목</code>으로 지정하여 다른 <code>스코프/</code> 레이블과 상호 배타적으로 만드세요.",
|
||||||
"repo.issues.label_exclusive_warning": "이슈 또는 풀 리퀘스트의 레이블을 편집할 때 충돌하는 스코프 지정 레이블은 모두 제거됩니다.",
|
"repo.issues.label_exclusive_warning": "이슈 또는 풀 리퀘스트의 레이블을 편집할 때 충돌하는 범위지정 레이블은 모두 제거됩니다.",
|
||||||
"repo.issues.label_exclusive_order": "정렬 순서",
|
"repo.issues.label_exclusive_order": "정렬 순서",
|
||||||
"repo.issues.label_exclusive_order_tooltip": "같은 스코프 내의 독점 레이블은 이 숫자 순서에 따라 정렬됩니다.",
|
"repo.issues.label_exclusive_order_tooltip": "같은 스코프 내의 독점 레이블은 이 숫자 순서에 따라 정렬됩니다.",
|
||||||
"repo.issues.label_count": "레이블 %d개",
|
"repo.issues.label_count": "레이블 %d개",
|
||||||
@@ -3820,7 +3820,12 @@
|
|||||||
"actions.workflow.scope_owner": "소유자",
|
"actions.workflow.scope_owner": "소유자",
|
||||||
"actions.workflow.scope_global": "글로벌",
|
"actions.workflow.scope_global": "글로벌",
|
||||||
"actions.workflow.required": "필수 항목",
|
"actions.workflow.required": "필수 항목",
|
||||||
|
"actions.workflow.scoped_required_cannot_disable": "범위지정 워크플로우가 요구되며 비활성화할 수 없습니다.",
|
||||||
|
"actions.scoped_workflows": "범위지정 워크플로우",
|
||||||
"actions.workflow.run": "워크플로 실행",
|
"actions.workflow.run": "워크플로 실행",
|
||||||
|
"actions.workflow.create_status_badge": "상태 배지 생성",
|
||||||
|
"actions.workflow.status_badge": "상태 배지",
|
||||||
|
"actions.workflow.status_badge_url": "배지 URL",
|
||||||
"actions.workflow.not_found": "워크플로 '%s'를 찾을 수 없습니다.",
|
"actions.workflow.not_found": "워크플로 '%s'를 찾을 수 없습니다.",
|
||||||
"actions.workflow.run_success": "워크플로 '%s'가 성공적으로 실행되었습니다.",
|
"actions.workflow.run_success": "워크플로 '%s'가 성공적으로 실행되었습니다.",
|
||||||
"actions.workflow.from_ref": "다음에서 워크플로 사용",
|
"actions.workflow.from_ref": "다음에서 워크플로 사용",
|
||||||
|
|||||||
@@ -3776,6 +3776,7 @@
|
|||||||
"actions.runs.commit": "Cometimento",
|
"actions.runs.commit": "Cometimento",
|
||||||
"actions.runs.run_details": "Detalhes da execução",
|
"actions.runs.run_details": "Detalhes da execução",
|
||||||
"actions.runs.workflow_file": "Ficheiro de sequência de trabalho",
|
"actions.runs.workflow_file": "Ficheiro de sequência de trabalho",
|
||||||
|
"actions.runs.workflow_file_no_permission": "Sem permissão para ver o ficheiro da sequência de trabalho",
|
||||||
"actions.runs.scheduled": "Agendadas",
|
"actions.runs.scheduled": "Agendadas",
|
||||||
"actions.runs.pushed_by": "enviado por",
|
"actions.runs.pushed_by": "enviado por",
|
||||||
"actions.runs.invalid_workflow_helper": "O ficheiro de configuração da sequência de trabalho é inválido. Verifique o seu ficheiro de configuração: %s",
|
"actions.runs.invalid_workflow_helper": "O ficheiro de configuração da sequência de trabalho é inválido. Verifique o seu ficheiro de configuração: %s",
|
||||||
@@ -3832,6 +3833,29 @@
|
|||||||
"actions.workflow.scope_owner": "Proprietário(a)",
|
"actions.workflow.scope_owner": "Proprietário(a)",
|
||||||
"actions.workflow.scope_global": "Global",
|
"actions.workflow.scope_global": "Global",
|
||||||
"actions.workflow.required": "Obrigatório",
|
"actions.workflow.required": "Obrigatório",
|
||||||
|
"actions.workflow.scoped_required_cannot_disable": "Esta sequência de trabalho de âmbito específico é obrigatória e não pode ser desabilitada.",
|
||||||
|
"actions.scoped_workflows": "Sequências de trabalho de âmbito específico",
|
||||||
|
"actions.scoped_workflows.desc_org": "Registe repositórios como fontes de sequências de trabalho de âmbito específico. Os ficheiros das sequências de trabalho dentro de pastas das sequências de trabalho de âmbito específico do ramo principal de um repositório fonte são executados em todos os repositórios desta organização, no próprio contexto desse repositório.",
|
||||||
|
"actions.scoped_workflows.desc_user": "Registe repositórios como fontes de sequências de trabalho de âmbito específico. Os ficheiros das sequências de trabalho dentro de pastas das sequências de trabalho de âmbito específico do ramo principal de um repositório fonte são executados em todos os seus repositórios, no próprio contexto desse repositório.",
|
||||||
|
"actions.scoped_workflows.desc_global": "Registe repositórios como fontes de sequências de trabalho de âmbito específico. Os ficheiros das sequências de trabalho dentro de pastas das sequências de trabalho de âmbito específico do ramo principal de um repositório fonte são executados em todos os repositórios desta instância, no próprio contexto desse repositório. Uma vez que as fontes ao nível da instância são avaliadas em todos os eventos do repositório, registá-las poderá acrescentar uma sobrecarga em instâncias grandes.",
|
||||||
|
"actions.scoped_workflows.add_help": "Para fornecer sequências de trabalho de âmbito específico a partir de um repositório, cometa os ficheiros da sequência de trabalho sob <code>%s</code> no seu ramo principal e depois registe o repositório como uma fonte abaixo.",
|
||||||
|
"actions.scoped_workflows.security_note": "O conteúdo da sequência de trabalho de um repositório de origem é executado em todos os repositórios aos quais se aplica e os seus scripts de etapas, bem como os seus resultados, são escritos nos registos das operações desse repositório e podem ser lidos por qualquer pessoa que tenha permissão para ver as operações do repositório consumidor. Portanto, registar um repositório privado como uma fonte divulga a lógica da sequência de trabalho através desses registos. Registe apenas repositórios cujo conteúdo da sequência de trabalho possa ser partilhado com todos os repositórios consumidores. Se uma sequência de trabalho de âmbito específico fizer referência a uma sequência de trabalho reutilizável de um repositório privado, certifique-se que todos os repositórios consumidores a podem ler, caso contrário a sequência de trabalho irá falhar aí.",
|
||||||
|
"actions.scoped_workflows.source.add": "Adicionar repositório de origem",
|
||||||
|
"actions.scoped_workflows.source.add_success": "Repositório de origem adicionado.",
|
||||||
|
"actions.scoped_workflows.source.remove_success": "Repositório de origem removido.",
|
||||||
|
"actions.scoped_workflows.source.not_found": "Repositório não encontrado.",
|
||||||
|
"actions.scoped_workflows.required.update_success": "As sequências de trabalho obrigatórias foram refrescadas.",
|
||||||
|
"actions.scoped_workflows.required.label": "Marcar sequências de trabalho como sendo obrigatórias (uma sequência de trabalho obrigatória não pode ser desabilitada pelos repositórios):",
|
||||||
|
"actions.scoped_workflows.required.patterns": "Padrões de verificação de estado obrigatórios",
|
||||||
|
"actions.scoped_workflows.required.patterns_aria": "Padrões de verificação de estado obrigatórios para %s",
|
||||||
|
"actions.scoped_workflows.required.patterns_note": "aplicada apenas enquanto a sequência de trabalho for obrigatória",
|
||||||
|
"actions.scoped_workflows.required.patterns_hint": "Marque a sequência de trabalho como sendo obrigatória para configurar os seus padrões de verificação de estado.",
|
||||||
|
"actions.scoped_workflows.required.patterns_help": "Um padrão de verificação de estado (glob) por linha. Um pedido de integração consumidor só pode ser executado depois de ter sido aprovado um estado que corresponda a todos os padrões. Esta regra é aplicada a qualquer ramo de destino que tenha uma regra de salvaguarda, mesmo que as suas próprias verificações de estado estejam desabilitadas; um ramo de destino sem regra de salvaguarda não está sujeito a restrições.",
|
||||||
|
"actions.scoped_workflows.required.patterns_empty": "Cada sequência de trabalho obrigatória precisa de pelo menos um padrão de verificação de estado.",
|
||||||
|
"actions.scoped_workflows.required.missing_file": "o ficheiro já não está na origem",
|
||||||
|
"actions.scoped_workflows.required.expected_contexts": "Verificações de estado esperadas (está marcada uma verificação que corresponde a um padrão)",
|
||||||
|
"actions.scoped_workflows.required.no_status_contexts": "Esta sequência de trabalho não faz verificações de estado; por isso, marcá-la como obrigatória impediria a execução de todos os pedidos de integração que a utilizam. Desmarque a opção «Obrigatória».",
|
||||||
|
"actions.scoped_workflows.no_files": "Não foram encontrados quaisquer ficheiros de sequência de trabalho de âmbito específico no ramo principal.",
|
||||||
"actions.workflow.run": "Executar sequência de trabalho",
|
"actions.workflow.run": "Executar sequência de trabalho",
|
||||||
"actions.workflow.create_status_badge": "Criar distintivo de estado",
|
"actions.workflow.create_status_badge": "Criar distintivo de estado",
|
||||||
"actions.workflow.status_badge": "Distintivo de estado",
|
"actions.workflow.status_badge": "Distintivo de estado",
|
||||||
|
|||||||
@@ -3779,6 +3779,7 @@
|
|||||||
"actions.runs.commit": "提交",
|
"actions.runs.commit": "提交",
|
||||||
"actions.runs.run_details": "运行详情",
|
"actions.runs.run_details": "运行详情",
|
||||||
"actions.runs.workflow_file": "工作流文件",
|
"actions.runs.workflow_file": "工作流文件",
|
||||||
|
"actions.runs.workflow_file_no_permission": "没有权限查看工作流文件",
|
||||||
"actions.runs.scheduled": "已计划的",
|
"actions.runs.scheduled": "已计划的",
|
||||||
"actions.runs.pushed_by": "推送者",
|
"actions.runs.pushed_by": "推送者",
|
||||||
"actions.runs.invalid_workflow_helper": "工作流配置文件无效。请检查您的配置文件:%s",
|
"actions.runs.invalid_workflow_helper": "工作流配置文件无效。请检查您的配置文件:%s",
|
||||||
@@ -3835,7 +3836,33 @@
|
|||||||
"actions.workflow.scope_owner": "拥有者",
|
"actions.workflow.scope_owner": "拥有者",
|
||||||
"actions.workflow.scope_global": "全局",
|
"actions.workflow.scope_global": "全局",
|
||||||
"actions.workflow.required": "必须",
|
"actions.workflow.required": "必须",
|
||||||
|
"actions.workflow.scoped_required_cannot_disable": "这个作用域工作流是必需的,不能被禁用。",
|
||||||
|
"actions.scoped_workflows": "作用域工作流",
|
||||||
|
"actions.scoped_workflows.desc_org": "将仓库注册为作用域工作流的源仓库。源仓库默认分支的「作用域工作流目录」中的工作流文件会在此组织中的每个仓库上运行,并使用对应仓库自身的上下文。",
|
||||||
|
"actions.scoped_workflows.desc_user": "将仓库注册为作用域工作流的源仓库。源仓库默认分支的「作用域工作流目录」中的工作流文件会在您拥有的每个仓库上运行,并使用对应仓库自身的上下文。",
|
||||||
|
"actions.scoped_workflows.desc_global": "将仓库注册为作用域工作流的源仓库。源仓库默认分支的「作用域工作流目录」中的工作流文件会在这个实例的每个仓库上运行,并使用对应仓库自身的上下文。由于实例级注册会针对每个仓库的事件进行检测,这在大型实例上可能会带来额外开销。",
|
||||||
|
"actions.scoped_workflows.add_help": "若要从仓库中提供作用域工作流,请在默认分支上提交工作流文件至 <code>%s</code> 然后将仓库注册为下面的源仓库。",
|
||||||
|
"actions.scoped_workflows.security_note": "一个源仓库的工作流会在它应用到的每个仓库上执行,它的 step 脚本和输出会被写到相关仓库的工作流日志里,并且能被任何有权查看这个仓库工作流的用户读取。因此,将一个私有仓库注册为源仓库会通过这些日志暴露工作流的逻辑。请仅注册那些工作流内容可以与所有相关仓库共享的仓库。如果一个作用域工作流从一个私有仓库引用了可复用工作流,请确保每个相关仓库都能读取它,否则该工作流会在这些仓库中运行失败。",
|
||||||
|
"actions.scoped_workflows.source.add": "添加源仓库",
|
||||||
|
"actions.scoped_workflows.source.add_success": "源仓库已添加。",
|
||||||
|
"actions.scoped_workflows.source.remove_success": "源仓库已删除。",
|
||||||
|
"actions.scoped_workflows.source.not_found": "未找到仓库。",
|
||||||
|
"actions.scoped_workflows.required.update_success": "「必需」工作流已更新。",
|
||||||
|
"actions.scoped_workflows.required.label": "把工作流标记为必需(「必需」工作流不能被仓库禁用):",
|
||||||
|
"actions.scoped_workflows.required.patterns": "「必需」状态检查表达式",
|
||||||
|
"actions.scoped_workflows.required.patterns_aria": "%s 的「必需」状态检查表达式",
|
||||||
|
"actions.scoped_workflows.required.patterns_note": "仅在工作流为「必需」时执行",
|
||||||
|
"actions.scoped_workflows.required.patterns_hint": "标记工作流为「必需」以配置它的状态检查表达式。",
|
||||||
|
"actions.scoped_workflows.required.patterns_help": "每行一个状态检查表达式(glob)。消费了这个工作流的合并请求只能在每条表达式都通过后才能合并。这仅适用于目标分支被分支保护规则保护的情况,即使分支保护规则的状态检查被禁用。没有被分支保护规则保护的目标分支不会被限制。\n每行填写一个状态检查表达式(glob)。只有当每个表达式都有一个匹配且已通过的状态检查时,相关的合并请求才能被合并。这仅适用于目标分支被分支保护规则保护的情况,即使分支保护规则的状态检查被禁用。没有被分支保护规则保护的目标分支不会被限制。",
|
||||||
|
"actions.scoped_workflows.required.patterns_empty": "每个「必需」工作流都需要至少一个状态检查表达式。",
|
||||||
|
"actions.scoped_workflows.required.missing_file": "文件不存在于源仓库",
|
||||||
|
"actions.scoped_workflows.required.expected_contexts": "预期状态检查(匹配表达式的状态检查会被标记)",
|
||||||
|
"actions.scoped_workflows.required.no_status_contexts": "此工作流不会报告任何状态检查,将其标记为「必需」后所有相关的合并请求都将无法合并。请不要将其标记为「必需」。",
|
||||||
|
"actions.scoped_workflows.no_files": "默认分支上未找到任何作用域工作流文件。",
|
||||||
"actions.workflow.run": "运行工作流",
|
"actions.workflow.run": "运行工作流",
|
||||||
|
"actions.workflow.create_status_badge": "创建状态徽章",
|
||||||
|
"actions.workflow.status_badge": "状态徽章",
|
||||||
|
"actions.workflow.status_badge_url": "徽章 URL",
|
||||||
"actions.workflow.not_found": "未找到工作流「%s」。",
|
"actions.workflow.not_found": "未找到工作流「%s」。",
|
||||||
"actions.workflow.run_success": "工作流「%s」已成功运行。",
|
"actions.workflow.run_success": "工作流「%s」已成功运行。",
|
||||||
"actions.workflow.from_ref": "使用工作流从",
|
"actions.workflow.from_ref": "使用工作流从",
|
||||||
|
|||||||
54
package.json
54
package.json
@@ -14,21 +14,21 @@
|
|||||||
"@codemirror/commands": "6.10.4",
|
"@codemirror/commands": "6.10.4",
|
||||||
"@codemirror/lang-json": "6.0.2",
|
"@codemirror/lang-json": "6.0.2",
|
||||||
"@codemirror/lang-markdown": "6.5.0",
|
"@codemirror/lang-markdown": "6.5.0",
|
||||||
"@codemirror/language": "6.12.3",
|
"@codemirror/language": "6.12.4",
|
||||||
"@codemirror/language-data": "6.5.2",
|
"@codemirror/language-data": "6.5.2",
|
||||||
"@codemirror/legacy-modes": "6.5.3",
|
"@codemirror/legacy-modes": "6.5.3",
|
||||||
"@codemirror/lint": "6.9.7",
|
"@codemirror/lint": "6.9.7",
|
||||||
"@codemirror/search": "6.7.1",
|
"@codemirror/search": "6.7.1",
|
||||||
"@codemirror/state": "6.7.0",
|
"@codemirror/state": "6.7.0",
|
||||||
"@codemirror/view": "6.43.2",
|
"@codemirror/view": "6.43.4",
|
||||||
"@deltablot/dropzone": "7.4.3",
|
"@deltablot/dropzone": "7.4.3",
|
||||||
"@github/markdown-toolbar-element": "2.2.3",
|
"@github/markdown-toolbar-element": "2.2.3",
|
||||||
"@github/paste-markdown": "1.5.3",
|
"@github/paste-markdown": "1.5.3",
|
||||||
"@github/text-expander-element": "2.9.4",
|
"@github/text-expander-element": "2.9.4",
|
||||||
"@lezer/highlight": "1.2.3",
|
"@lezer/highlight": "1.2.3",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-rc2",
|
"@mcaptcha/vanilla-glue": "0.1.0-rc2",
|
||||||
"@mermaid-js/layout-elk": "0.2.1",
|
"@mermaid-js/layout-elk": "0.2.2",
|
||||||
"@primer/octicons": "19.28.1",
|
"@primer/octicons": "19.29.1",
|
||||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||||
"@replit/codemirror-lang-nix": "6.0.1",
|
"@replit/codemirror-lang-nix": "6.0.1",
|
||||||
"@replit/codemirror-lang-svelte": "6.0.0",
|
"@replit/codemirror-lang-svelte": "6.0.0",
|
||||||
@@ -36,11 +36,11 @@
|
|||||||
"@resvg/resvg-wasm": "2.6.2",
|
"@resvg/resvg-wasm": "2.6.2",
|
||||||
"@vitejs/plugin-vue": "6.0.7",
|
"@vitejs/plugin-vue": "6.0.7",
|
||||||
"ansi_up": "6.0.6",
|
"ansi_up": "6.0.6",
|
||||||
"asciinema-player": "3.16.0",
|
"asciinema-player": "3.17.0",
|
||||||
"chart.js": "4.5.1",
|
"chart.js": "4.5.1",
|
||||||
"chartjs-adapter-dayjs-4": "1.0.4",
|
"chartjs-adapter-dayjs-4": "1.0.4",
|
||||||
"chartjs-plugin-zoom": "2.2.0",
|
"chartjs-plugin-zoom": "2.2.0",
|
||||||
"clippie": "4.2.0",
|
"clippie": "4.2.1",
|
||||||
"codemirror-lang-elixir": "4.0.1",
|
"codemirror-lang-elixir": "4.0.1",
|
||||||
"colord": "2.9.3",
|
"colord": "2.9.3",
|
||||||
"compare-versions": "6.1.1",
|
"compare-versions": "6.1.1",
|
||||||
@@ -50,13 +50,13 @@
|
|||||||
"esbuild": "0.28.1",
|
"esbuild": "0.28.1",
|
||||||
"idiomorph": "0.7.4",
|
"idiomorph": "0.7.4",
|
||||||
"jquery": "4.0.0",
|
"jquery": "4.0.0",
|
||||||
"js-yaml": "4.2.0",
|
"js-yaml": "5.2.1",
|
||||||
"katex": "0.17.0",
|
"katex": "0.17.0",
|
||||||
"mermaid": "11.15.0",
|
"mermaid": "11.16.0",
|
||||||
"online-3d-viewer": "0.18.0",
|
"online-3d-viewer": "0.18.0",
|
||||||
"pdfobject": "2.3.1",
|
"pdfobject": "2.3.1",
|
||||||
"perfect-debounce": "2.1.0",
|
"perfect-debounce": "2.1.0",
|
||||||
"postcss": "8.5.15",
|
"postcss": "8.5.16",
|
||||||
"rolldown-license-plugin": "3.0.9",
|
"rolldown-license-plugin": "3.0.9",
|
||||||
"sortablejs": "1.15.7",
|
"sortablejs": "1.15.7",
|
||||||
"swagger-ui-dist": "5.32.8",
|
"swagger-ui-dist": "5.32.8",
|
||||||
@@ -67,40 +67,36 @@
|
|||||||
"tributejs": "5.1.3",
|
"tributejs": "5.1.3",
|
||||||
"uint8-to-base64": "0.2.1",
|
"uint8-to-base64": "0.2.1",
|
||||||
"vanilla-colorful": "0.7.2",
|
"vanilla-colorful": "0.7.2",
|
||||||
"vite": "8.1.0",
|
"vite": "8.1.3",
|
||||||
"vite-string-plugin": "2.0.4",
|
"vite-string-plugin": "2.0.5",
|
||||||
"vue": "3.5.38",
|
"vue": "3.5.39",
|
||||||
"vue-bar-graph": "2.2.0",
|
|
||||||
"vue-chartjs": "5.3.3"
|
"vue-chartjs": "5.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint-community/eslint-plugin-eslint-comments": "4.7.2",
|
"@eslint-community/eslint-plugin-eslint-comments": "4.7.2",
|
||||||
"@eslint/json": "2.0.0",
|
"@eslint/json": "2.0.1",
|
||||||
"@playwright/test": "1.61.1",
|
"@playwright/test": "1.61.1",
|
||||||
"@stylistic/eslint-plugin": "5.10.0",
|
"@stylistic/eslint-plugin": "5.10.0",
|
||||||
"@stylistic/stylelint-plugin": "5.2.0",
|
"@stylistic/stylelint-plugin": "5.2.1",
|
||||||
"@types/codemirror": "5.60.17",
|
"@types/codemirror": "5.60.17",
|
||||||
"@types/jquery": "4.0.1",
|
"@types/jquery": "4.0.1",
|
||||||
"@types/js-yaml": "4.0.9",
|
|
||||||
"@types/katex": "0.16.8",
|
"@types/katex": "0.16.8",
|
||||||
"@types/node": "25.9.4",
|
"@types/node": "26.1.0",
|
||||||
"@types/pdfobject": "2.2.5",
|
"@types/pdfobject": "2.2.5",
|
||||||
"@types/sortablejs": "1.15.9",
|
"@types/sortablejs": "1.15.9",
|
||||||
"@types/swagger-ui-dist": "3.30.6",
|
"@types/swagger-ui-dist": "3.30.6",
|
||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
"@types/toastify-js": "1.12.4",
|
"@types/toastify-js": "1.12.4",
|
||||||
"@typescript-eslint/parser": "8.62.0",
|
"@typescript-eslint/parser": "8.62.1",
|
||||||
"@vitejs/plugin-vue": "6.0.7",
|
"@vitejs/plugin-vue": "6.0.7",
|
||||||
"@vitest/eslint-plugin": "1.6.20",
|
"@vitest/eslint-plugin": "1.6.20",
|
||||||
"eslint": "10.5.0",
|
"eslint": "10.6.0",
|
||||||
"eslint-import-resolver-typescript": "4.4.5",
|
"eslint-import-resolver-typescript": "4.4.5",
|
||||||
"eslint-plugin-array-func": "5.1.1",
|
"eslint-plugin-import-x": "4.17.1",
|
||||||
"eslint-plugin-de-morgan": "2.1.2",
|
|
||||||
"eslint-plugin-import-x": "4.17.0",
|
|
||||||
"eslint-plugin-playwright": "2.10.4",
|
"eslint-plugin-playwright": "2.10.4",
|
||||||
"eslint-plugin-regexp": "3.1.0",
|
"eslint-plugin-regexp": "3.1.1",
|
||||||
"eslint-plugin-sonarjs": "4.1.0",
|
"eslint-plugin-sonarjs": "4.1.0",
|
||||||
"eslint-plugin-unicorn": "68.0.0",
|
"eslint-plugin-unicorn": "70.0.0",
|
||||||
"eslint-plugin-vue": "10.9.2",
|
"eslint-plugin-vue": "10.9.2",
|
||||||
"eslint-plugin-vue-scoped-css": "3.1.1",
|
"eslint-plugin-vue-scoped-css": "3.1.1",
|
||||||
"eslint-plugin-wc": "3.1.0",
|
"eslint-plugin-wc": "3.1.0",
|
||||||
@@ -110,17 +106,17 @@
|
|||||||
"markdownlint-cli": "0.49.0",
|
"markdownlint-cli": "0.49.0",
|
||||||
"material-icon-theme": "5.36.1",
|
"material-icon-theme": "5.36.1",
|
||||||
"postcss-html": "1.8.1",
|
"postcss-html": "1.8.1",
|
||||||
"spectral-cli-bundle": "1.0.8",
|
"spectral-cli-bundle": "1.0.11",
|
||||||
"stylelint": "17.13.0",
|
"stylelint": "17.14.0",
|
||||||
"stylelint-config-recommended": "18.0.0",
|
"stylelint-config-recommended": "18.0.0",
|
||||||
"stylelint-declaration-block-no-ignored-properties": "3.0.0",
|
"stylelint-declaration-block-no-ignored-properties": "3.0.0",
|
||||||
"stylelint-declaration-strict-value": "1.11.1",
|
"stylelint-declaration-strict-value": "1.11.1",
|
||||||
"stylelint-value-no-unknown-custom-properties": "6.1.1",
|
"stylelint-value-no-unknown-custom-properties": "6.1.1",
|
||||||
"svgo": "4.0.1",
|
"svgo": "4.0.1",
|
||||||
"typescript": "6.0.3",
|
"typescript": "6.0.3",
|
||||||
"typescript-eslint": "8.62.0",
|
"typescript-eslint": "8.62.1",
|
||||||
"updates": "17.18.0",
|
"updates": "17.18.2",
|
||||||
"vitest": "4.1.9",
|
"vitest": "4.1.9",
|
||||||
"vue-tsc": "3.3.5"
|
"vue-tsc": "3.3.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
954
pnpm-lock.yaml
generated
954
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
1
public/assets/img/svg/octicon-graph-stacked-area.svg
generated
Normal file
1
public/assets/img/svg/octicon-graph-stacked-area.svg
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-graph-stacked-area" width="16" height="16" aria-hidden="true"><path d="M10.548 11.513a.775.775 0 0 1-1.096 0L7 9.06 2.56 13.5H14.5V7.56ZM14.72 1.22a.75.75 0 1 1 1.06 1.06l-5.232 5.233a.775.775 0 0 1-1.096 0L7 5.06l-5.72 5.72A.75.75 0 1 1 .22 9.72l6.232-6.233.059-.052a.775.775 0 0 1 1.037.052L10 5.94ZM16 14.225a.776.776 0 0 1-.775.775H.81a.775.775 0 0 1-.548-1.323l6.19-6.19.058-.052a.775.775 0 0 1 1.037.052L10 9.94l4.677-4.676.096-.082A.775.775 0 0 1 16 5.81Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 550 B |
1
public/assets/img/svg/octicon-project-check.svg
generated
Normal file
1
public/assets/img/svg/octicon-project-check.svg
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-project-check" width="16" height="16" aria-hidden="true"><path d="M14.25 0C15.216 0 16 .784 16 1.75v6.498a.75.75 0 1 1-1.5.002V6.5h-8v8h.75a.75.75 0 1 1-.001 1.5H1.75A1.75 1.75 0 0 1 0 14.25V1.75C0 .784.784 0 1.75 0ZM1.5 14.25c0 .138.112.25.25.25H5v-8H1.5ZM1.75 1.5a.25.25 0 0 0-.25.25V5H5V1.5Zm4.75 0V5h8V1.75a.25.25 0 0 0-.25-.25Z"/><path d="M15.963 11.737a.75.75 0 0 1-.202.524l-3.5 3.5a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l1.47 1.47 2.97-2.97a.75.75 0 0 1 1.261.536"/></svg>
|
||||||
|
After Width: | Height: | Size: 568 B |
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-repo-forked-locked" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M9 10.167V9a3 3 0 1 1 6 0v1.167c.591.281 1 .884 1 1.583v2.5A1.75 1.75 0 0 1 14.25 16h-4.5A1.75 1.75 0 0 1 8 14.25v-2.5c0-.699.409-1.302 1-1.583M13.5 10h-3V9a1.5 1.5 0 0 1 3 0z"/><path fill-rule="evenodd" d="M3.25 1a2.247 2.247 0 0 1 1.938 3.388 2.25 2.25 0 0 1-1.189.982v.881a.746.746 0 0 0 .75.748h3.095c-.052.428-.008.986.051 1.499H7.75v1.748c-.315.418-.5.938-.5 1.502v.294a1 1 0 0 0-.256-.041.75.75 0 0 0-.53.22.79.79 0 0 0-.22.547.746.746 0 0 0 .75.748.8.8 0 0 0 .25-.042v.796q.002.376.107.72-.177.03-.357.031a2.247 2.247 0 0 1-1.938-3.388c.268-.456.69-.805 1.19-.983V8.5h-2.5a2.23 2.23 0 0 1-1.586-.66 2.25 2.25 0 0 1-.66-1.591v-.881A2.255 2.255 0 0 1 .03 2.867c.088-.523.36-.998.768-1.34C1.205 1.187 1.72 1 2.25 1zm0 1.499a.75.75 0 0 0-.53.22.8.8 0 0 0-.22.547.75.75 0 0 0 1.5 0 .8.8 0 0 0-.22-.547.75.75 0 0 0-.53-.22m7.5.75a2.247 2.247 0 0 1 1.938 3.388 2.25 2.25 0 0 1-1.189.982V8.96c0 .414.336.75.75.75h.5a.75.75 0 0 1 0 1.5h-.5a3 3 0 0 1-3-3v.54a.746.746 0 0 1 .75-.748h1.01V5a1.5 1.5 0 0 1 1.5-1.5zm0 1.499a.75.75 0 0 0-.53.22.8.8 0 0 0-.22.547.75.75 0 0 0 1.5 0 .8.8 0 0 0-.22-.547.75.75 0 0 0-.53-.22"/><path d="M8.63 7.749a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-repo-forked-locked" width="16" height="16" aria-hidden="true"><path d="M9 10.167V9a3 3 0 1 1 6 0v1.168c.591.281 1 .884 1 1.582v2.5A1.75 1.75 0 0 1 14.25 16h-4.5A1.75 1.75 0 0 1 8 14.249v-2.5c0-.698.409-1.3 1-1.582M3.25 1A2.251 2.251 0 0 1 4 5.371v.878c0 .414.336.75.75.75h3.096c-.051.429.008.987.067 1.5H7.75v1.749a2.5 2.5 0 0 0-.5 1.501v.295a.75.75 0 1 0 0 1.409v.796q.002.376.107.72a2.25 2.25 0 0 1-1.106-4.343V8.5h-1.5a2.25 2.25 0 0 1-2.25-2.25v-.878A2.25 2.25 0 0 1 3.25 1m7.5 0a2.25 2.25 0 0 1 1.94 3.388c-.222.379-.55.68-.94.874h-.003a2.22 2.22 0 0 1-1.82.079l-.003-.001A2.248 2.248 0 0 1 10.75 1m2.75 9V9a1.5 1.5 0 0 0-3 0v1ZM3.25 2.499a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5m7.5 0a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5"/><path d="M8.63 7.749a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0"/></svg>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 872 B |
1
public/assets/img/svg/octicon-view-files.svg
generated
Normal file
1
public/assets/img/svg/octicon-view-files.svg
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-view-files" width="16" height="16" aria-hidden="true"><path d="M1.75 10a.75.75 0 0 1 .75.75v2.5c0 .138.112.25.25.25h2.5a.75.75 0 1 1 0 1.5h-2.5A1.75 1.75 0 0 1 1 13.25v-2.5a.75.75 0 0 1 .75-.75m12.5 0a.75.75 0 0 1 .75.75v2.5A1.75 1.75 0 0 1 13.25 15h-2.5a.75.75 0 1 1 0-1.5h2.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 .75-.75m-6 0a.75.75 0 1 1 0 1.5h-3a.75.75 0 1 1 0-1.5Zm3-2.5a.75.75 0 1 1 0 1.5h-6a.75.75 0 0 1 0-1.5Zm-1-2.5a.75.75 0 1 1 0 1.5h-5a.75.75 0 0 1 0-1.5Zm-5-4a.75.75 0 0 1 0 1.5h-2.5a.25.25 0 0 0-.25.25v2.5a.75.75 0 0 1-1.5 0v-2.5C1 1.784 1.784 1 2.75 1Zm8 0c.966 0 1.75.784 1.75 1.75v2.5a.75.75 0 1 1-1.5 0v-2.5a.25.25 0 0 0-.25-.25h-2.5a.75.75 0 1 1 0-1.5Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 761 B |
@@ -5,7 +5,7 @@ requires-python = ">=3.10"
|
|||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"djlint==1.39.4",
|
"djlint==1.40.1",
|
||||||
"yamllint==1.38.0",
|
"yamllint==1.38.0",
|
||||||
"zizmor==1.26.1",
|
"zizmor==1.26.1",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -73,7 +73,11 @@
|
|||||||
"postUpdateOptions": ["gomodUpdateImportPaths"],
|
"postUpdateOptions": ["gomodUpdateImportPaths"],
|
||||||
"postUpgradeTasks": {
|
"postUpgradeTasks": {
|
||||||
"commands": ["make tidy"],
|
"commands": ["make tidy"],
|
||||||
"fileFilters": ["go.mod", "go.sum", "assets/go-licenses.json"],
|
"fileFilters": [
|
||||||
|
"go.mod",
|
||||||
|
"go.sum",
|
||||||
|
"assets/go-licenses.json",
|
||||||
|
],
|
||||||
"executionMode": "branch",
|
"executionMode": "branch",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -95,8 +99,15 @@
|
|||||||
"matchManagers": ["npm"],
|
"matchManagers": ["npm"],
|
||||||
"postUpdateOptions": ["pnpmDedupe"],
|
"postUpdateOptions": ["pnpmDedupe"],
|
||||||
"postUpgradeTasks": {
|
"postUpgradeTasks": {
|
||||||
"commands": ["make svg"],
|
"commands": ["make svg", "make generate-codemirror-languages"],
|
||||||
"fileFilters": ["package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml", "public/assets/img/svg/**", "options/fileicon/**"],
|
"fileFilters": [
|
||||||
|
"package.json",
|
||||||
|
"pnpm-lock.yaml",
|
||||||
|
"pnpm-workspace.yaml",
|
||||||
|
"public/assets/img/svg/**",
|
||||||
|
"options/fileicon/**",
|
||||||
|
"assets/codemirror-languages.json",
|
||||||
|
],
|
||||||
"executionMode": "branch",
|
"executionMode": "branch",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
actions_model "gitea.dev/models/actions"
|
actions_model "gitea.dev/models/actions"
|
||||||
auth_model "gitea.dev/models/auth"
|
auth_model "gitea.dev/models/auth"
|
||||||
@@ -45,14 +46,26 @@ var withRunner = connect.WithInterceptors(connect.UnaryInterceptorFunc(func(unar
|
|||||||
return nil, status.Error(codes.Unauthenticated, "unregistered runner")
|
return nil, status.Error(codes.Unauthenticated, "unregistered runner")
|
||||||
}
|
}
|
||||||
|
|
||||||
cols := []string{"last_online"}
|
now := time.Now()
|
||||||
runner.LastOnline = timeutil.TimeStampNow()
|
cols := make([]string, 0, 2)
|
||||||
if methodName == "UpdateTask" || methodName == "UpdateLog" {
|
// Debounce last_active too: while a runner streams logs, UpdateLog fires
|
||||||
runner.LastActive = timeutil.TimeStampNow()
|
// many times per second and writing on each is a major source of DB load.
|
||||||
|
// Persist only when stale enough to affect the active/idle status.
|
||||||
|
if (methodName == "UpdateTask" || methodName == "UpdateLog") &&
|
||||||
|
actions_model.ShouldPersistLastActive(runner.LastActive, now) {
|
||||||
|
runner.LastActive = timeutil.TimeStamp(now.Unix())
|
||||||
cols = append(cols, "last_active")
|
cols = append(cols, "last_active")
|
||||||
}
|
}
|
||||||
if err := actions_model.UpdateRunner(ctx, runner, cols...); err != nil {
|
// Debounce last_online: writing on every poll is a major source of DB load
|
||||||
log.Error("can't update runner status: %v", err)
|
// with many runners. Persist only when stale enough to affect offline status.
|
||||||
|
if actions_model.ShouldPersistLastOnline(runner.LastOnline, now) {
|
||||||
|
runner.LastOnline = timeutil.TimeStamp(now.Unix())
|
||||||
|
cols = append(cols, "last_online")
|
||||||
|
}
|
||||||
|
if len(cols) > 0 {
|
||||||
|
if err := actions_model.UpdateRunner(ctx, runner, cols...); err != nil {
|
||||||
|
log.Error("can't update runner status: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, runnerCtxKey{}, runner)
|
ctx = context.WithValue(ctx, runnerCtxKey{}, runner)
|
||||||
|
|||||||
@@ -194,9 +194,15 @@ func (s *Service) FetchTask(
|
|||||||
// if the task version in request is not equal to the version in db,
|
// if the task version in request is not equal to the version in db,
|
||||||
// it means there may still be some tasks that haven't been assigned.
|
// it means there may still be some tasks that haven't been assigned.
|
||||||
// try to pick a task for the runner that send the request.
|
// try to pick a task for the runner that send the request.
|
||||||
if t, ok, err := actions_service.PickTask(ctx, freshRunner); err != nil {
|
if t, ok, throttled, err := actions_service.TryPickTask(ctx, freshRunner); err != nil {
|
||||||
log.Error("pick task failed: %v", err)
|
log.Error("pick task failed: %v", err)
|
||||||
return nil, status.Errorf(codes.Internal, "pick task: %v", err)
|
return nil, status.Errorf(codes.Internal, "pick task: %v", err)
|
||||||
|
} else if throttled {
|
||||||
|
// Concurrency limit reached: don't advance the runner's tasks version,
|
||||||
|
// so it retries on its next poll instead of sleeping until the next bump.
|
||||||
|
latestVersion = tasksVersion
|
||||||
|
// A steady stream here means MAX_CONCURRENT_TASK_PICKS is too low for the fleet.
|
||||||
|
log.Debug("task pick throttled for runner %q (id %d); it will retry on its next poll", freshRunner.Name, freshRunner.ID)
|
||||||
} else if ok {
|
} else if ok {
|
||||||
task = t
|
task = t
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,8 +258,27 @@ func GetAllCommits(ctx *context.APIContext) {
|
|||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
} else if commitsCountTotal == 0 {
|
} else if commitsCountTotal == 0 {
|
||||||
ctx.APIErrorNotFound()
|
// when date filters are active, a zero count may just mean no
|
||||||
return
|
// commits in the requested range — not that the path is invalid
|
||||||
|
if since == "" && until == "" {
|
||||||
|
ctx.APIErrorNotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// verify the path actually exists in the revision history
|
||||||
|
totalWithoutDate, err := gitrepo.CommitsCount(ctx, ctx.Repo.Repository,
|
||||||
|
gitrepo.CommitsCountOptions{
|
||||||
|
Not: not,
|
||||||
|
Revision: []string{sha},
|
||||||
|
RelPath: []string{path},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if totalWithoutDate == 0 {
|
||||||
|
ctx.APIErrorNotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commits, _, err = ctx.Repo.GitRepo.CommitsByFileAndRange(
|
commits, _, err = ctx.Repo.GitRepo.CommitsByFileAndRange(
|
||||||
|
|||||||
@@ -12,13 +12,6 @@ type swaggerResponseSecretList struct {
|
|||||||
Body []api.Secret `json:"body"`
|
Body []api.Secret `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secret
|
|
||||||
// swagger:response Secret
|
|
||||||
type swaggerResponseSecret struct {
|
|
||||||
// in:body
|
|
||||||
Body api.Secret `json:"body"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActionVariable
|
// ActionVariable
|
||||||
// swagger:response ActionVariable
|
// swagger:response ActionVariable
|
||||||
type swaggerResponseActionVariable struct {
|
type swaggerResponseActionVariable struct {
|
||||||
|
|||||||
@@ -98,13 +98,6 @@ type swaggerIssueTemplates struct {
|
|||||||
Body []api.IssueTemplate `json:"body"`
|
Body []api.IssueTemplate `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StopWatch
|
|
||||||
// swagger:response StopWatch
|
|
||||||
type swaggerResponseStopWatch struct {
|
|
||||||
// in:body
|
|
||||||
Body api.StopWatch `json:"body"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopWatchList
|
// StopWatchList
|
||||||
// swagger:response StopWatchList
|
// swagger:response StopWatchList
|
||||||
type swaggerResponseStopWatchList struct {
|
type swaggerResponseStopWatchList struct {
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package swagger
|
|
||||||
|
|
||||||
import (
|
|
||||||
api "gitea.dev/modules/structs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NodeInfo
|
|
||||||
// swagger:response NodeInfo
|
|
||||||
type swaggerResponseNodeInfo struct {
|
|
||||||
// in:body
|
|
||||||
Body api.NodeInfo `json:"body"`
|
|
||||||
}
|
|
||||||
@@ -8,11 +8,12 @@ import (
|
|||||||
"gitea.dev/services/forms"
|
"gitea.dev/services/forms"
|
||||||
)
|
)
|
||||||
|
|
||||||
// not actually a response, just a hack to get go-swagger to include definitions
|
// not actually a set of parameters, just a hack to get go-swagger to include
|
||||||
// of the various XYZOption structs
|
// definitions of the various XYZOption structs. The annotation below uses an
|
||||||
|
// operation id that matches no route, so the spec carries no unused response
|
||||||
|
// or parameter for it while still emitting the referenced definitions.
|
||||||
|
|
||||||
// parameterBodies
|
// swagger:parameters parameterBodies
|
||||||
// swagger:response parameterBodies
|
|
||||||
type swaggerParameterBodies struct {
|
type swaggerParameterBodies struct {
|
||||||
// in:body
|
// in:body
|
||||||
AddCollaboratorOption api.AddCollaboratorOption
|
AddCollaboratorOption api.AddCollaboratorOption
|
||||||
@@ -235,4 +236,7 @@ type swaggerParameterBodies struct {
|
|||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
LockIssueOption api.LockIssueOption
|
LockIssueOption api.LockIssueOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
MergeUpstreamRequest api.MergeUpstreamRequest
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,13 +84,6 @@ type swaggerResponseTagProtection struct {
|
|||||||
Body api.TagProtection `json:"body"`
|
Body api.TagProtection `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reference
|
|
||||||
// swagger:response Reference
|
|
||||||
type swaggerResponseReference struct {
|
|
||||||
// in:body
|
|
||||||
Body api.Reference `json:"body"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReferenceList
|
// ReferenceList
|
||||||
// swagger:response ReferenceList
|
// swagger:response ReferenceList
|
||||||
type swaggerResponseReferenceList struct {
|
type swaggerResponseReferenceList struct {
|
||||||
@@ -511,12 +504,6 @@ type swaggerCompare struct {
|
|||||||
Body api.Compare `json:"body"`
|
Body api.Compare `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// swagger:response MergeUpstreamRequest
|
|
||||||
type swaggerMergeUpstreamRequest struct {
|
|
||||||
// in:body
|
|
||||||
Body api.MergeUpstreamRequest `json:"body"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// swagger:response MergeUpstreamResponse
|
// swagger:response MergeUpstreamResponse
|
||||||
type swaggerMergeUpstreamResponse struct {
|
type swaggerMergeUpstreamResponse struct {
|
||||||
// in:body
|
// in:body
|
||||||
|
|||||||
@@ -147,6 +147,9 @@ func MustInitSessioner() func(next http.Handler) http.Handler {
|
|||||||
Secure: setting.SessionConfig.Secure,
|
Secure: setting.SessionConfig.Secure,
|
||||||
SameSite: setting.SessionConfig.SameSite,
|
SameSite: setting.SessionConfig.SameSite,
|
||||||
Domain: setting.SessionConfig.Domain,
|
Domain: setting.SessionConfig.Domain,
|
||||||
|
|
||||||
|
// in the future, if websocket is used, the websocket handler should manage its own session sync (release)
|
||||||
|
IgnoreReleaseForWebSocket: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("common.Sessioner failed: %v", err)
|
log.Fatal("common.Sessioner failed: %v", err)
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ import (
|
|||||||
type preReceiveContext struct {
|
type preReceiveContext struct {
|
||||||
*gitea_context.PrivateContext
|
*gitea_context.PrivateContext
|
||||||
|
|
||||||
// loadedPusher indicates that where the following information are loaded
|
user *user_model.User // the "pusher", it's the org user if a DeployKey is used
|
||||||
loadedPusher bool
|
|
||||||
user *user_model.User // it's the org user if a DeployKey is used
|
|
||||||
userPerm access_model.Permission
|
userPerm access_model.Permission
|
||||||
deployKeyAccessMode perm_model.AccessMode
|
deployKeyAccessMode perm_model.AccessMode
|
||||||
|
|
||||||
@@ -53,10 +51,7 @@ type preReceiveContext struct {
|
|||||||
|
|
||||||
func (ctx *preReceiveContext) canWriteCodeUnit() bool {
|
func (ctx *preReceiveContext) canWriteCodeUnit() bool {
|
||||||
if ctx.canWriteCodeUnitCached == nil {
|
if ctx.canWriteCodeUnitCached == nil {
|
||||||
var canWrite bool
|
canWrite := ctx.userPerm.CanWrite(unit.TypeCode) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite
|
||||||
if ctx.loadPusherAndPermission() {
|
|
||||||
canWrite = ctx.userPerm.CanWrite(unit.TypeCode) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite
|
|
||||||
}
|
|
||||||
ctx.canWriteCodeUnitCached = &canWrite
|
ctx.canWriteCodeUnitCached = &canWrite
|
||||||
}
|
}
|
||||||
return *ctx.canWriteCodeUnitCached
|
return *ctx.canWriteCodeUnitCached
|
||||||
@@ -91,9 +86,6 @@ func (ctx *preReceiveContext) assertCanWriteRef(refFullName git.RefName) bool {
|
|||||||
// CanCreatePullRequest returns true if pusher can create pull requests
|
// CanCreatePullRequest returns true if pusher can create pull requests
|
||||||
func (ctx *preReceiveContext) CanCreatePullRequest() bool {
|
func (ctx *preReceiveContext) CanCreatePullRequest() bool {
|
||||||
if !ctx.checkedCanCreatePullRequest {
|
if !ctx.checkedCanCreatePullRequest {
|
||||||
if !ctx.loadPusherAndPermission() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
ctx.canCreatePullRequest = ctx.userPerm.CanRead(unit.TypePullRequests)
|
ctx.canCreatePullRequest = ctx.userPerm.CanRead(unit.TypePullRequests)
|
||||||
ctx.checkedCanCreatePullRequest = true
|
ctx.checkedCanCreatePullRequest = true
|
||||||
}
|
}
|
||||||
@@ -124,6 +116,10 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
|
|||||||
opts: opts,
|
opts: opts,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !ourCtx.loadPusherAndPermission() {
|
||||||
|
return // if error occurs, loadPusherAndPermission had written the error response
|
||||||
|
}
|
||||||
|
|
||||||
// Iterate across the provided old commit IDs
|
// Iterate across the provided old commit IDs
|
||||||
for i := range opts.OldCommitIDs {
|
for i := range opts.OldCommitIDs {
|
||||||
oldCommitID := opts.OldCommitIDs[i]
|
oldCommitID := opts.OldCommitIDs[i]
|
||||||
@@ -281,18 +277,10 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
|||||||
canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys)
|
canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
user, err := user_model.GetUserByID(ctx, ctx.opts.UserID)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to GetUserByID for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err)
|
|
||||||
ctx.JSON(http.StatusInternalServerError, private.Response{
|
|
||||||
Err: fmt.Sprintf("Unable to GetUserByID for commits from %s to %s: %v", oldCommitID, newCommitID, err),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isForcePush {
|
if isForcePush {
|
||||||
canPush = !changedProtectedfiles && protectBranch.CanUserForcePush(ctx, user)
|
canPush = !changedProtectedfiles && protectBranch.CanUserForcePush(ctx, ctx.user)
|
||||||
} else {
|
} else {
|
||||||
canPush = !changedProtectedfiles && protectBranch.CanUserPush(ctx, user)
|
canPush = !changedProtectedfiles && protectBranch.CanUserPush(ctx, ctx.user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,12 +342,6 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// although we should have called `loadPusherAndPermission` before, here we call it explicitly again because we need to access ctx.user below
|
|
||||||
if !ctx.loadPusherAndPermission() {
|
|
||||||
// if error occurs, loadPusherAndPermission had written the error response
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now check if the user is allowed to merge PRs for this repository
|
// Now check if the user is allowed to merge PRs for this repository
|
||||||
// Note: we can use ctx.perm and ctx.user directly as they will have been loaded above
|
// Note: we can use ctx.perm and ctx.user directly as they will have been loaded above
|
||||||
allowedMerge, err := pull_service.IsUserAllowedToMerge(ctx, pr, ctx.userPerm, ctx.user)
|
allowedMerge, err := pull_service.IsUserAllowedToMerge(ctx, pr, ctx.userPerm, ctx.user)
|
||||||
@@ -499,10 +481,6 @@ func generateGitEnv(opts *private.HookOptions) (env []string) {
|
|||||||
|
|
||||||
// loadPusherAndPermission returns false if an error occurs, and it writes the error response
|
// loadPusherAndPermission returns false if an error occurs, and it writes the error response
|
||||||
func (ctx *preReceiveContext) loadPusherAndPermission() bool {
|
func (ctx *preReceiveContext) loadPusherAndPermission() bool {
|
||||||
if ctx.loadedPusher {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.opts.UserID == user_model.ActionsUserID {
|
if ctx.opts.UserID == user_model.ActionsUserID {
|
||||||
taskID := ctx.opts.ActionsTaskID
|
taskID := ctx.opts.ActionsTaskID
|
||||||
ctx.user = user_model.NewActionsUserWithTaskID(taskID)
|
ctx.user = user_model.NewActionsUserWithTaskID(taskID)
|
||||||
@@ -555,7 +533,5 @@ func (ctx *preReceiveContext) loadPusherAndPermission() bool {
|
|||||||
}
|
}
|
||||||
ctx.deployKeyAccessMode = deployKey.Mode
|
ctx.deployKeyAccessMode = deployKey.Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.loadedPusher = true
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ func TestPreReceiveCanWriteCodePerBranch(t *testing.T) {
|
|||||||
mockCtx, _ := contexttest.MockPrivateContext(t, "/")
|
mockCtx, _ := contexttest.MockPrivateContext(t, "/")
|
||||||
ctx := &preReceiveContext{
|
ctx := &preReceiveContext{
|
||||||
PrivateContext: mockCtx,
|
PrivateContext: mockCtx,
|
||||||
loadedPusher: true,
|
|
||||||
user: maintainer,
|
user: maintainer,
|
||||||
userPerm: headPerm,
|
userPerm: headPerm,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -450,27 +450,9 @@ func EditUserPost(ctx *context.Context) {
|
|||||||
log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, u.Name)
|
log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, u.Name)
|
||||||
|
|
||||||
if form.Reset2FA {
|
if form.Reset2FA {
|
||||||
tf, err := auth.GetTwoFactorByUID(ctx, u.ID)
|
if _, _, err := auth.DisableTwoFactor(ctx, u.ID); err != nil {
|
||||||
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
|
ctx.ServerError("auth.DisableTwoFactor", err)
|
||||||
ctx.ServerError("auth.GetTwoFactorByUID", err)
|
|
||||||
return
|
return
|
||||||
} else if tf != nil {
|
|
||||||
if err := auth.DeleteTwoFactorByID(ctx, tf.ID, u.ID); err != nil {
|
|
||||||
ctx.ServerError("auth.DeleteTwoFactorByID", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wn, err := auth.GetWebAuthnCredentialsByUID(ctx, u.ID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("auth.GetTwoFactorByUID", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, cred := range wn {
|
|
||||||
if _, err := auth.DeleteCredential(ctx, cred.ID, u.ID); err != nil {
|
|
||||||
ctx.ServerError("auth.DeleteCredential", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ func autoSignIn(ctx *context.Context) (bool, error) {
|
|||||||
|
|
||||||
ctx.SetSiteCookie(setting.CookieRememberName, nt.ID+":"+token, setting.LogInRememberDays*timeutil.Day)
|
ctx.SetSiteCookie(setting.CookieRememberName, nt.ID+":"+token, setting.LogInRememberDays*timeutil.Day)
|
||||||
|
|
||||||
if err := updateSession(ctx, nil, map[string]any{
|
if err := regenerateSession(ctx, nil, map[string]any{
|
||||||
session.KeyUID: u.ID,
|
session.KeyUID: u.ID,
|
||||||
session.KeyUname: u.Name,
|
session.KeyUname: u.Name,
|
||||||
session.KeyUserHasTwoFactorAuth: userHasTwoFactorAuth,
|
session.KeyUserHasTwoFactorAuth: userHasTwoFactorAuth,
|
||||||
@@ -357,7 +357,7 @@ func SignInPost(ctx *context.Context) {
|
|||||||
// User will need to use WebAuthn, save data
|
// User will need to use WebAuthn, save data
|
||||||
updates["totpEnrolled"] = u.ID
|
updates["totpEnrolled"] = u.ID
|
||||||
}
|
}
|
||||||
if err := updateSession(ctx, nil, updates); err != nil {
|
if err := regenerateSession(ctx, nil, updates); err != nil {
|
||||||
ctx.ServerError("UserSignIn: Unable to update session", err)
|
ctx.ServerError("UserSignIn: Unable to update session", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -398,7 +398,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updateSession(ctx, []string{
|
if err := regenerateSession(ctx, []string{
|
||||||
// Delete the openid, 2fa and link_account data
|
// Delete the openid, 2fa and link_account data
|
||||||
"openid_verified_uri",
|
"openid_verified_uri",
|
||||||
"openid_signin_remember",
|
"openid_signin_remember",
|
||||||
@@ -884,7 +884,7 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
|
|||||||
|
|
||||||
log.Trace("User activated: %s", user.Name)
|
log.Trace("User activated: %s", user.Name)
|
||||||
|
|
||||||
if err := updateSession(ctx, nil, map[string]any{
|
if err := regenerateSession(ctx, nil, map[string]any{
|
||||||
"uid": user.ID,
|
"uid": user.ID,
|
||||||
"uname": user.Name,
|
"uname": user.Name,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -936,7 +936,7 @@ func ActivateEmail(ctx *context.Context) {
|
|||||||
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
|
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSession(ctx *context.Context, deletes []string, updates map[string]any) error {
|
func regenerateSession(ctx *context.Context, deletes []string, updates map[string]any) error {
|
||||||
if _, err := session.RegenerateSession(ctx.Resp, ctx.Req); err != nil {
|
if _, err := session.RegenerateSession(ctx.Resp, ctx.Req); err != nil {
|
||||||
return fmt.Errorf("regenerate session: %w", err)
|
return fmt.Errorf("regenerate session: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,7 +164,12 @@ func oauth2LinkAccount(ctx *context.Context, u *user_model.User, linkAccountData
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updateSession(ctx, nil, map[string]any{
|
if err := Oauth2SetLinkAccountData(ctx, *linkAccountData); err != nil {
|
||||||
|
ctx.ServerError("Oauth2SetLinkAccountData", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := regenerateSession(ctx, nil, map[string]any{
|
||||||
// User needs to use 2FA, save data and redirect to 2FA page.
|
// User needs to use 2FA, save data and redirect to 2FA page.
|
||||||
"twofaUid": u.ID,
|
"twofaUid": u.ID,
|
||||||
"twofaRemember": remember,
|
"twofaRemember": remember,
|
||||||
|
|||||||
@@ -285,9 +285,7 @@ func oauth2GetLinkAccountData(ctx *context.Context) *LinkAccountData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Oauth2SetLinkAccountData(ctx *context.Context, linkAccountData LinkAccountData) error {
|
func Oauth2SetLinkAccountData(ctx *context.Context, linkAccountData LinkAccountData) error {
|
||||||
return updateSession(ctx, nil, map[string]any{
|
return ctx.Session.Set("linkAccountData", linkAccountData)
|
||||||
"linkAccountData": linkAccountData,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func showLinkingLogin(ctx *context.Context, authSourceID int64, gothUser goth.User) {
|
func showLinkingLogin(ctx *context.Context, authSourceID int64, gothUser goth.User) {
|
||||||
@@ -409,7 +407,7 @@ func handleOAuth2SignIn(ctx *context.Context, authSource *auth.Source, u *user_m
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updateSession(ctx, nil, map[string]any{
|
if err := regenerateSession(ctx, nil, map[string]any{
|
||||||
session.KeyUID: u.ID,
|
session.KeyUID: u.ID,
|
||||||
session.KeyUname: u.Name,
|
session.KeyUname: u.Name,
|
||||||
session.KeyUserHasTwoFactorAuth: userHasTwoFactorAuth,
|
session.KeyUserHasTwoFactorAuth: userHasTwoFactorAuth,
|
||||||
@@ -434,7 +432,7 @@ func handleOAuth2SignIn(ctx *context.Context, authSource *auth.Source, u *user_m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updateSession(ctx, nil, map[string]any{
|
if err := regenerateSession(ctx, nil, map[string]any{
|
||||||
// User needs to use 2FA, save data and redirect to 2FA page.
|
// User needs to use 2FA, save data and redirect to 2FA page.
|
||||||
"twofaUid": u.ID,
|
"twofaUid": u.ID,
|
||||||
"twofaRemember": false,
|
"twofaRemember": false,
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ func signInOpenIDVerify(ctx *context.Context) {
|
|||||||
if u != nil {
|
if u != nil {
|
||||||
nickname = u.LowerName
|
nickname = u.LowerName
|
||||||
}
|
}
|
||||||
if err := updateSession(ctx, nil, map[string]any{
|
if err := regenerateSession(ctx, nil, map[string]any{
|
||||||
"openid_verified_uri": id,
|
"openid_verified_uri": id,
|
||||||
"openid_determined_email": email,
|
"openid_determined_email": email,
|
||||||
"openid_determined_username": nickname,
|
"openid_determined_username": nickname,
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ func ViewProject(ctx *context.Context) {
|
|||||||
ctx.Data["MilestoneID"] = milestoneID
|
ctx.Data["MilestoneID"] = milestoneID
|
||||||
|
|
||||||
// Get assignees.
|
// Get assignees.
|
||||||
assigneeUsers, err := project_service.LoadIssuesAssigneesForProject(ctx, issuesMap)
|
assigneeUsers, err := project_service.LoadIssuesAssigneesForProject(ctx, project.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("LoadIssuesAssigneesForProject", err)
|
ctx.ServerError("LoadIssuesAssigneesForProject", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -186,6 +186,22 @@ func ServeAttachment(ctx *context.Context, uuid string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draft release attachments must not be exposed to anyone without write
|
||||||
|
// access, matching the API-side canAccessReleaseDraft gate. Otherwise the
|
||||||
|
// UUID-based web endpoints would leak draft attachments to any recipient of
|
||||||
|
// the (leaked) download URL.
|
||||||
|
if unitType == unit.TypeReleases && attach.ReleaseID != 0 && !perm.CanWrite(unit.TypeReleases) {
|
||||||
|
rel, err := repo_model.GetReleaseByID(ctx, attach.ReleaseID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetReleaseByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if rel.IsDraft {
|
||||||
|
ctx.HTTPError(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if requiredScope, ok := attachmentReadScope(unitType); ok {
|
if requiredScope, ok := attachmentReadScope(unitType); ok {
|
||||||
context.CheckTokenScopes(ctx, repo, requiredScope)
|
context.CheckTokenScopes(ctx, repo, requiredScope)
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
|
|||||||
@@ -383,7 +383,9 @@ func autoTitleFromBranchName(name string) string {
|
|||||||
|
|
||||||
func prepareNewPullRequestTitleContent(ci *git_service.CompareInfo, commits []*git_model.SignCommitWithStatuses, defaultTitleSource string) (title, content string) {
|
func prepareNewPullRequestTitleContent(ci *git_service.CompareInfo, commits []*git_model.SignCommitWithStatuses, defaultTitleSource string) (title, content string) {
|
||||||
useFirstCommitAsTitle := len(commits) == 1 || (defaultTitleSource == setting.RepoPRTitleSourceFirstCommit && len(commits) > 0)
|
useFirstCommitAsTitle := len(commits) == 1 || (defaultTitleSource == setting.RepoPRTitleSourceFirstCommit && len(commits) > 0)
|
||||||
if useFirstCommitAsTitle {
|
if defaultTitleSource == setting.RepoPRTitleSourceBranchName {
|
||||||
|
title = ci.HeadRef.ShortName()
|
||||||
|
} else if useFirstCommitAsTitle {
|
||||||
// the "commits" are from "ShowPrettyFormatLogToList", which is ordered from newest to oldest, here take the oldest one
|
// the "commits" are from "ShowPrettyFormatLogToList", which is ordered from newest to oldest, here take the oldest one
|
||||||
c := commits[len(commits)-1]
|
c := commits[len(commits)-1]
|
||||||
title = c.UserCommit.GitCommit.MessageTitle()
|
title = c.UserCommit.GitCommit.MessageTitle()
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ func TestNewPullRequestTitleContent(t *testing.T) {
|
|||||||
assert.Equal(t, "Head branch", title)
|
assert.Equal(t, "Head branch", title)
|
||||||
assert.Empty(t, content)
|
assert.Empty(t, content)
|
||||||
|
|
||||||
|
title, content = prepareNewPullRequestTitleContent(ci, nil, setting.RepoPRTitleSourceBranchName)
|
||||||
|
assert.Equal(t, "head-branch", title)
|
||||||
|
assert.Empty(t, content)
|
||||||
|
|
||||||
// single commit
|
// single commit
|
||||||
title, content = prepareNewPullRequestTitleContent(ci, []*git_model.SignCommitWithStatuses{mockCommit("single-commit-title\nbody")}, setting.RepoPRTitleSourceAuto)
|
title, content = prepareNewPullRequestTitleContent(ci, []*git_model.SignCommitWithStatuses{mockCommit("single-commit-title\nbody")}, setting.RepoPRTitleSourceAuto)
|
||||||
assert.Equal(t, "single-commit-title", title)
|
assert.Equal(t, "single-commit-title", title)
|
||||||
|
|||||||
@@ -14,17 +14,6 @@ import (
|
|||||||
"gitea.dev/services/context"
|
"gitea.dev/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type userSearchInfo struct {
|
|
||||||
UserID int64 `json:"user_id"`
|
|
||||||
UserName string `json:"username"`
|
|
||||||
AvatarLink string `json:"avatar_link"`
|
|
||||||
FullName string `json:"full_name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type userSearchResponse struct {
|
|
||||||
Results []*userSearchInfo `json:"results"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func IssuePullPosters(ctx *context.Context) {
|
func IssuePullPosters(ctx *context.Context) {
|
||||||
isPullList := ctx.PathParam("type") == "pulls"
|
isPullList := ctx.PathParam("type") == "pulls"
|
||||||
issuePosters(ctx, isPullList)
|
issuePosters(ctx, isPullList)
|
||||||
@@ -46,14 +35,5 @@ func issuePosters(ctx *context.Context, isPullList bool) {
|
|||||||
posters = append(posters, ctx.Doer)
|
posters = append(posters, ctx.Doer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ctx.JSON(http.StatusOK, shared_user.ToSearchUserResponse(ctx, ctx.Doer, posters))
|
||||||
posters = shared_user.MakeSelfOnTop(ctx.Doer, posters)
|
|
||||||
|
|
||||||
resp := &userSearchResponse{}
|
|
||||||
resp.Results = make([]*userSearchInfo, len(posters))
|
|
||||||
for i, user := range posters {
|
|
||||||
resp.Results[i] = &userSearchInfo{UserID: user.ID, UserName: user.Name, AvatarLink: user.AvatarLink(ctx)}
|
|
||||||
resp.Results[i].FullName = user.FullName
|
|
||||||
}
|
|
||||||
ctx.JSON(http.StatusOK, resp)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import (
|
|||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
"gitea.dev/modules/web"
|
"gitea.dev/modules/web"
|
||||||
"gitea.dev/routers/web/feed"
|
"gitea.dev/routers/web/feed"
|
||||||
shared_user "gitea.dev/routers/web/shared/user"
|
|
||||||
"gitea.dev/services/context"
|
"gitea.dev/services/context"
|
||||||
"gitea.dev/services/context/upload"
|
"gitea.dev/services/context/upload"
|
||||||
"gitea.dev/services/forms"
|
"gitea.dev/services/forms"
|
||||||
@@ -338,7 +337,6 @@ func LatestRelease(ctx *context.Context) {
|
|||||||
|
|
||||||
func newReleaseCommon(ctx *context.Context) {
|
func newReleaseCommon(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
|
ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
|
||||||
ctx.Data["PageIsReleaseList"] = true
|
|
||||||
|
|
||||||
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
|
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -346,17 +344,8 @@ func newReleaseCommon(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Data["Tags"] = tags
|
ctx.Data["Tags"] = tags
|
||||||
|
|
||||||
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
||||||
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetRepoAssignees", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Data["Assignees"] = shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers)
|
|
||||||
|
|
||||||
upload.AddUploadContext(ctx, "release")
|
upload.AddUploadContext(ctx, "release")
|
||||||
|
|
||||||
PrepareBranchList(ctx) // for New Release page
|
PrepareBranchList(ctx) // for New Release page
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,8 +414,8 @@ func GenerateReleaseNotes(ctx *context.Context) {
|
|||||||
|
|
||||||
// NewReleasePost response for creating a release
|
// NewReleasePost response for creating a release
|
||||||
func NewReleasePost(ctx *context.Context) {
|
func NewReleasePost(ctx *context.Context) {
|
||||||
newReleaseCommon(ctx)
|
if ctx.HasError() {
|
||||||
if ctx.Written() {
|
ctx.JSONError(ctx.GetErrMsg())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,35 +439,28 @@ func NewReleasePost(ctx *context.Context) {
|
|||||||
// Or another choice is "always show the tag-only button" if error occurs.
|
// Or another choice is "always show the tag-only button" if error occurs.
|
||||||
ctx.Data["ShowCreateTagOnlyButton"] = form.TagOnly || rel == nil
|
ctx.Data["ShowCreateTagOnlyButton"] = form.TagOnly || rel == nil
|
||||||
|
|
||||||
// do some form checks
|
|
||||||
if ctx.HasError() {
|
|
||||||
ctx.HTML(http.StatusOK, tplReleaseNew)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
form.Target = util.IfZero(form.Target, ctx.Repo.Repository.DefaultBranch)
|
form.Target = util.IfZero(form.Target, ctx.Repo.Repository.DefaultBranch)
|
||||||
if exist, _ := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, form.Target); !exist {
|
if exist, _ := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, form.Target); !exist {
|
||||||
ctx.RenderWithErrDeprecated(ctx.Tr("form.target_branch_not_exist"), tplReleaseNew, &form)
|
ctx.JSONError(ctx.Tr("form.target_branch_not_exist"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !form.TagOnly && form.Title == "" {
|
if !form.TagOnly && form.Title == "" {
|
||||||
// if not "tag only", then the title of the release cannot be empty
|
// if not "tag only", then the title of the release cannot be empty
|
||||||
ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.title_empty"), tplReleaseNew, &form)
|
ctx.JSONError(ctx.Tr("repo.release.title_empty"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTagReleaseError := func(err error) {
|
handleTagReleaseError := func(err error) {
|
||||||
ctx.Data["Err_TagName"] = true
|
|
||||||
switch {
|
switch {
|
||||||
case release_service.IsErrTagAlreadyExists(err):
|
case release_service.IsErrTagAlreadyExists(err):
|
||||||
ctx.RenderWithErrDeprecated(ctx.Tr("repo.branch.tag_collision", form.TagName), tplReleaseNew, &form)
|
ctx.JSONError(ctx.Tr("repo.branch.tag_collision", form.TagName))
|
||||||
case repo_model.IsErrReleaseAlreadyExist(err):
|
case repo_model.IsErrReleaseAlreadyExist(err):
|
||||||
ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
|
ctx.JSONError(ctx.Tr("repo.release.tag_name_already_exist"))
|
||||||
case release_service.IsErrInvalidTagName(err):
|
case release_service.IsErrInvalidTagName(err):
|
||||||
ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.tag_name_invalid"), tplReleaseNew, &form)
|
ctx.JSONError(ctx.Tr("repo.release.tag_name_invalid"))
|
||||||
case release_service.IsErrProtectedTagName(err):
|
case release_service.IsErrProtectedTagName(err):
|
||||||
ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.tag_name_protected"), tplReleaseNew, &form)
|
ctx.JSONError(ctx.Tr("repo.release.tag_name_protected"))
|
||||||
default:
|
default:
|
||||||
ctx.ServerError("handleTagReleaseError", err)
|
ctx.ServerError("handleTagReleaseError", err)
|
||||||
}
|
}
|
||||||
@@ -497,7 +479,7 @@ func NewReleasePost(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Flash.Success(ctx.Tr("repo.tag.create_success", form.TagName))
|
ctx.Flash.Success(ctx.Tr("repo.tag.create_success", form.TagName))
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/src/tag/" + util.PathEscapeSegments(form.TagName))
|
ctx.JSONRedirect(ctx.Repo.RepoLink + "/src/tag/" + util.PathEscapeSegments(form.TagName))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,7 +504,7 @@ func NewReleasePost(ctx *context.Context) {
|
|||||||
handleTagReleaseError(err)
|
handleTagReleaseError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/releases")
|
ctx.JSONRedirect(ctx.Repo.RepoLink + "/releases")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,8 +512,7 @@ func NewReleasePost(ctx *context.Context) {
|
|||||||
// old logic: if the release is not a tag (it is a real release), do not update it on the "new release" page
|
// old logic: if the release is not a tag (it is a real release), do not update it on the "new release" page
|
||||||
// add new logic: if tag-only, do not convert the tag to a release
|
// add new logic: if tag-only, do not convert the tag to a release
|
||||||
if form.TagOnly || !rel.IsTag {
|
if form.TagOnly || !rel.IsTag {
|
||||||
ctx.Data["Err_TagName"] = true
|
ctx.JSONError(ctx.Tr("repo.release.tag_name_already_exist"))
|
||||||
ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,7 +528,7 @@ func NewReleasePost(ctx *context.Context) {
|
|||||||
handleTagReleaseError(err)
|
handleTagReleaseError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/releases")
|
ctx.JSONRedirect(ctx.Repo.RepoLink + "/releases")
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditRelease render release edit page
|
// EditRelease render release edit page
|
||||||
@@ -589,55 +570,39 @@ func EditRelease(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Data["attachments"] = rel.Attachments
|
ctx.Data["attachments"] = rel.Attachments
|
||||||
|
|
||||||
// Get assignees.
|
|
||||||
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, rel.Repo)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetRepoAssignees", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Data["Assignees"] = shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers)
|
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplReleaseNew)
|
ctx.HTML(http.StatusOK, tplReleaseNew)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditReleasePost response for edit release
|
// EditReleasePost response for edit release
|
||||||
func EditReleasePost(ctx *context.Context) {
|
func EditReleasePost(ctx *context.Context) {
|
||||||
form := web.GetForm(ctx).(*forms.EditReleaseForm)
|
if ctx.HasError() {
|
||||||
|
ctx.JSONError(ctx.GetErrMsg())
|
||||||
newReleaseCommon(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.release.edit_release")
|
form := web.GetForm(ctx).(*forms.EditReleaseForm)
|
||||||
ctx.Data["PageIsEditRelease"] = true
|
|
||||||
|
|
||||||
tagName := ctx.PathParam("*")
|
tagName := ctx.PathParam("*")
|
||||||
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName)
|
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tagName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if repo_model.IsErrReleaseNotExist(err) {
|
if repo_model.IsErrReleaseNotExist(err) {
|
||||||
ctx.NotFound(err)
|
ctx.JSONErrorNotFound(err.Error())
|
||||||
} else {
|
} else {
|
||||||
ctx.ServerError("GetRelease", err)
|
ctx.ServerError("GetRelease", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if rel.IsTag {
|
if rel.IsTag {
|
||||||
ctx.NotFound(err) // for a pure tag release, don't allow to edit it as a release
|
ctx.JSONErrorNotFound() // for a pure tag release, don't allow to edit it as a release
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["tag_name"] = rel.TagName
|
ctx.Data["tag_name"] = rel.TagName
|
||||||
ctx.Data["tag_target"] = util.IfZero(rel.Target, ctx.Repo.Repository.DefaultBranch)
|
ctx.Data["tag_target"] = util.IfZero(rel.Target, ctx.Repo.Repository.DefaultBranch)
|
||||||
ctx.Data["title"] = rel.Title
|
ctx.Data["title"] = rel.Title
|
||||||
ctx.Data["content"] = rel.Note
|
ctx.Data["content"] = rel.Note
|
||||||
ctx.Data["prerelease"] = rel.IsPrerelease
|
ctx.Data["prerelease"] = rel.IsPrerelease
|
||||||
|
|
||||||
if ctx.HasError() {
|
|
||||||
ctx.HTML(http.StatusOK, tplReleaseNew)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const delPrefix = "attachment-del-"
|
const delPrefix = "attachment-del-"
|
||||||
const editPrefix = "attachment-edit-"
|
const editPrefix = "attachment-edit-"
|
||||||
var addAttachmentUUIDs, delAttachmentUUIDs []string
|
var addAttachmentUUIDs, delAttachmentUUIDs []string
|
||||||
@@ -659,10 +624,14 @@ func EditReleasePost(ctx *context.Context) {
|
|||||||
rel.IsPrerelease = form.Prerelease
|
rel.IsPrerelease = form.Prerelease
|
||||||
if err = release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo,
|
if err = release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo,
|
||||||
rel, addAttachmentUUIDs, delAttachmentUUIDs, editAttachments); err != nil {
|
rel, addAttachmentUUIDs, delAttachmentUUIDs, editAttachments); err != nil {
|
||||||
ctx.ServerError("UpdateRelease", err)
|
if upload.IsErrFileTypeForbidden(err) {
|
||||||
|
ctx.JSONError(err.Error())
|
||||||
|
} else {
|
||||||
|
ctx.ServerError("UpdateRelease", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/releases")
|
ctx.JSONRedirect(ctx.Repo.RepoLink + "/releases")
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRelease deletes a release
|
// DeleteRelease deletes a release
|
||||||
|
|||||||
@@ -4,12 +4,14 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
repo_model "gitea.dev/models/repo"
|
repo_model "gitea.dev/models/repo"
|
||||||
"gitea.dev/models/unit"
|
"gitea.dev/models/unit"
|
||||||
"gitea.dev/models/unittest"
|
"gitea.dev/models/unittest"
|
||||||
|
"gitea.dev/modules/test"
|
||||||
"gitea.dev/modules/web"
|
"gitea.dev/modules/web"
|
||||||
"gitea.dev/services/context"
|
"gitea.dev/services/context"
|
||||||
"gitea.dev/services/contexttest"
|
"gitea.dev/services/contexttest"
|
||||||
@@ -39,15 +41,15 @@ func TestNewReleasePost(t *testing.T) {
|
|||||||
assert.NotEmpty(t, ctx.Data["ShowCreateTagOnlyButton"])
|
assert.NotEmpty(t, ctx.Data["ShowCreateTagOnlyButton"])
|
||||||
})
|
})
|
||||||
|
|
||||||
post := func(t *testing.T, form forms.NewReleaseForm) *context.Context {
|
post := func(t *testing.T, form forms.NewReleaseForm) (*context.Context, *httptest.ResponseRecorder) {
|
||||||
ctx, _ := contexttest.MockContext(t, "user2/repo1/releases/new")
|
ctx, resp := contexttest.MockContext(t, "user2/repo1/releases/new")
|
||||||
contexttest.LoadUser(t, ctx, 2)
|
contexttest.LoadUser(t, ctx, 2)
|
||||||
contexttest.LoadRepo(t, ctx, 1)
|
contexttest.LoadRepo(t, ctx, 1)
|
||||||
contexttest.LoadGitRepo(t, ctx)
|
contexttest.LoadGitRepo(t, ctx)
|
||||||
defer ctx.Repo.GitRepo.Close()
|
defer ctx.Repo.GitRepo.Close()
|
||||||
web.SetForm(ctx, &form)
|
web.SetForm(ctx, &form)
|
||||||
NewReleasePost(ctx)
|
NewReleasePost(ctx)
|
||||||
return ctx
|
return ctx, resp
|
||||||
}
|
}
|
||||||
|
|
||||||
loadRelease := func(t *testing.T, tagName string) *repo_model.Release {
|
loadRelease := func(t *testing.T, tagName string) *repo_model.Release {
|
||||||
@@ -70,7 +72,7 @@ func TestNewReleasePost(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("ReleaseExistsDoUpdate(non-tag)", func(t *testing.T) {
|
t.Run("ReleaseExistsDoUpdate(non-tag)", func(t *testing.T) {
|
||||||
ctx := post(t, forms.NewReleaseForm{
|
_, resp := post(t, forms.NewReleaseForm{
|
||||||
TagName: "v1.1",
|
TagName: "v1.1",
|
||||||
Target: "master",
|
Target: "master",
|
||||||
Title: "updated-title",
|
Title: "updated-title",
|
||||||
@@ -80,11 +82,11 @@ func TestNewReleasePost(t *testing.T) {
|
|||||||
require.NotNil(t, rel)
|
require.NotNil(t, rel)
|
||||||
assert.False(t, rel.IsTag)
|
assert.False(t, rel.IsTag)
|
||||||
assert.Equal(t, "testing-release", rel.Title)
|
assert.Equal(t, "testing-release", rel.Title)
|
||||||
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
|
assert.NotEmpty(t, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("ReleaseExistsDoUpdate(tag-only)", func(t *testing.T) {
|
t.Run("ReleaseExistsDoUpdate(tag-only)", func(t *testing.T) {
|
||||||
ctx := post(t, forms.NewReleaseForm{
|
ctx, resp := post(t, forms.NewReleaseForm{
|
||||||
TagName: "delete-tag", // a strange name, but it is the only "is_tag=true" fixture
|
TagName: "delete-tag", // a strange name, but it is the only "is_tag=true" fixture
|
||||||
Target: "master",
|
Target: "master",
|
||||||
Title: "updated-title",
|
Title: "updated-title",
|
||||||
@@ -95,12 +97,12 @@ func TestNewReleasePost(t *testing.T) {
|
|||||||
require.NotNil(t, rel)
|
require.NotNil(t, rel)
|
||||||
assert.True(t, rel.IsTag) // the record should not be updated because the request is "tag-only". TODO: need to improve the logic?
|
assert.True(t, rel.IsTag) // the record should not be updated because the request is "tag-only". TODO: need to improve the logic?
|
||||||
assert.Equal(t, "delete-tag", rel.Title)
|
assert.Equal(t, "delete-tag", rel.Title)
|
||||||
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
|
assert.NotEmpty(t, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
|
||||||
assert.NotEmpty(t, ctx.Data["ShowCreateTagOnlyButton"]) // still show the "tag-only" button
|
assert.NotEmpty(t, ctx.Data["ShowCreateTagOnlyButton"]) // still show the "tag-only" button
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("ReleaseExistsDoUpdate(tag-release)", func(t *testing.T) {
|
t.Run("ReleaseExistsDoUpdate(tag-release)", func(t *testing.T) {
|
||||||
ctx := post(t, forms.NewReleaseForm{
|
ctx, _ := post(t, forms.NewReleaseForm{
|
||||||
TagName: "delete-tag", // a strange name, but it is the only "is_tag=true" fixture
|
TagName: "delete-tag", // a strange name, but it is the only "is_tag=true" fixture
|
||||||
Target: "master",
|
Target: "master",
|
||||||
Title: "updated-title",
|
Title: "updated-title",
|
||||||
@@ -114,7 +116,7 @@ func TestNewReleasePost(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("TagOnly", func(t *testing.T) {
|
t.Run("TagOnly", func(t *testing.T) {
|
||||||
ctx := post(t, forms.NewReleaseForm{
|
ctx, _ := post(t, forms.NewReleaseForm{
|
||||||
TagName: "new-tag-only",
|
TagName: "new-tag-only",
|
||||||
Target: "master",
|
Target: "master",
|
||||||
Title: "title",
|
Title: "title",
|
||||||
@@ -128,7 +130,7 @@ func TestNewReleasePost(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("TagOnlyConflict", func(t *testing.T) {
|
t.Run("TagOnlyConflict", func(t *testing.T) {
|
||||||
ctx := post(t, forms.NewReleaseForm{
|
_, resp := post(t, forms.NewReleaseForm{
|
||||||
TagName: "v1.1",
|
TagName: "v1.1",
|
||||||
Target: "master",
|
Target: "master",
|
||||||
Title: "title",
|
Title: "title",
|
||||||
@@ -138,7 +140,7 @@ func TestNewReleasePost(t *testing.T) {
|
|||||||
rel := loadRelease(t, "v1.1")
|
rel := loadRelease(t, "v1.1")
|
||||||
require.NotNil(t, rel)
|
require.NotNil(t, rel)
|
||||||
assert.False(t, rel.IsTag)
|
assert.False(t, rel.IsTag)
|
||||||
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
|
assert.NotEmpty(t, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -429,7 +429,7 @@ func telegramHookParams(ctx *context.Context) webhookParams {
|
|||||||
|
|
||||||
return webhookParams{
|
return webhookParams{
|
||||||
Type: webhook_module.TELEGRAM,
|
Type: webhook_module.TELEGRAM,
|
||||||
URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&message_thread_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID), url.QueryEscape(form.ThreadID)),
|
URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendRichMessage?chat_id=%s&message_thread_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID), url.QueryEscape(form.ThreadID)),
|
||||||
ContentType: webhook.ContentTypeJSON,
|
ContentType: webhook.ContentTypeJSON,
|
||||||
WebhookForm: form.WebhookForm,
|
WebhookForm: form.WebhookForm,
|
||||||
Meta: &webhook_service.TelegramMeta{
|
Meta: &webhook_service.TelegramMeta{
|
||||||
|
|||||||
@@ -11,22 +11,44 @@ import (
|
|||||||
"gitea.dev/models/user"
|
"gitea.dev/models/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SearchUserInfo struct {
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
UserName string `json:"username"`
|
||||||
|
AvatarLink string `json:"avatar_link"`
|
||||||
|
FullName string `json:"full_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchUserResponse struct {
|
||||||
|
Results []*SearchUserInfo `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
func MakeSelfOnTop(doer *user.User, users []*user.User) []*user.User {
|
func MakeSelfOnTop(doer *user.User, users []*user.User) []*user.User {
|
||||||
if doer != nil {
|
if doer == nil {
|
||||||
idx := slices.IndexFunc(users, func(u *user.User) bool {
|
return users
|
||||||
return u.ID == doer.ID
|
}
|
||||||
})
|
idx := slices.IndexFunc(users, func(u *user.User) bool {
|
||||||
if idx > 0 {
|
return u.ID == doer.ID
|
||||||
newUsers := make([]*user.User, len(users))
|
})
|
||||||
newUsers[0] = users[idx]
|
if idx > 0 {
|
||||||
copy(newUsers[1:], users[:idx])
|
newUsers := make([]*user.User, len(users))
|
||||||
copy(newUsers[idx+1:], users[idx+1:])
|
newUsers[0] = users[idx]
|
||||||
return newUsers
|
copy(newUsers[1:], users[:idx])
|
||||||
}
|
copy(newUsers[idx+1:], users[idx+1:])
|
||||||
|
return newUsers
|
||||||
}
|
}
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToSearchUserResponse(ctx context.Context, self *user.User, users []*user.User) (ret *SearchUserResponse) {
|
||||||
|
infos := MakeSelfOnTop(self, users)
|
||||||
|
resp := &SearchUserResponse{Results: make([]*SearchUserInfo, len(infos))}
|
||||||
|
for i, u := range infos {
|
||||||
|
resp.Results[i] = &SearchUserInfo{UserID: u.ID, UserName: u.Name, AvatarLink: u.AvatarLink(ctx)}
|
||||||
|
resp.Results[i].FullName = u.FullName
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
// GetFilterUserIDByName tries to get the user ID from the given username.
|
// GetFilterUserIDByName tries to get the user ID from the given username.
|
||||||
// Before, the "issue filter" passes user ID to query the list, but in many cases, it's impossible to pre-fetch the full user list.
|
// Before, the "issue filter" passes user ID to query the list, but in many cases, it's impossible to pre-fetch the full user list.
|
||||||
// So it's better to make it work like GitHub: users could input username directly.
|
// So it's better to make it work like GitHub: users could input username directly.
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ import (
|
|||||||
repo_model "gitea.dev/models/repo"
|
repo_model "gitea.dev/models/repo"
|
||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
actions_module "gitea.dev/modules/actions"
|
actions_module "gitea.dev/modules/actions"
|
||||||
|
"gitea.dev/modules/actions/jobparser"
|
||||||
"gitea.dev/modules/commitstatus"
|
"gitea.dev/modules/commitstatus"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
|
api "gitea.dev/modules/structs"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
webhook_module "gitea.dev/modules/webhook"
|
webhook_module "gitea.dev/modules/webhook"
|
||||||
commitstatus_service "gitea.dev/services/repository/commitstatus"
|
commitstatus_service "gitea.dev/services/repository/commitstatus"
|
||||||
@@ -147,21 +149,78 @@ func createCommitStatus(ctx context.Context, repo *repo_model.Repository, event,
|
|||||||
// scopedPrefix is computed once per run by the caller. The settings page derives the same string to preview expected checks.
|
// scopedPrefix is computed once per run by the caller. The settings page derives the same string to preview expected checks.
|
||||||
ctxName = actions_module.ScopedWorkflowStatusContextName(scopedPrefix, displayName, job.Name, event)
|
ctxName = actions_module.ScopedWorkflowStatusContextName(scopedPrefix, displayName, job.Name, event)
|
||||||
}
|
}
|
||||||
|
targetURL := fmt.Sprintf("%s/jobs/%d", run.Link(), job.ID)
|
||||||
|
return createWorkflowCommitStatus(ctx, repo, commitID, ctxName, run.WorkflowID, toCommitStatus(job.Status), targetURL, toCommitStatusDescription(job))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSkippedCommitStatusForFilteredWorkflow posts a skipped commit status for each job of a
|
||||||
|
// workflow that matched the triggering event but was excluded by a branch/paths filter.
|
||||||
|
// This lets a required status check tied to that context be satisfied without the workflow running.
|
||||||
|
// No ActionRun is created, so the status has no target URL (there is no run/job to link to).
|
||||||
|
// A non-empty scopedPrefix prefixes each context with its source repo, matching scoped runs.
|
||||||
|
func CreateSkippedCommitStatusForFilteredWorkflow(ctx context.Context, repo *repo_model.Repository, event webhook_module.HookEventType, triggerEvent, workflowID string, content []byte, payload api.Payloader, scopedPrefix string) error {
|
||||||
|
// Derive the status event name and target commit from the payload.
|
||||||
|
// TODO: this mirrors getCommitStatusEventNameAndCommitID, which derives the same from a persisted run. Should merge the logic if possible.
|
||||||
|
var statusEvent, commitID string
|
||||||
|
switch event {
|
||||||
|
case webhook_module.HookEventPush:
|
||||||
|
if p, ok := payload.(*api.PushPayload); ok && p.HeadCommit != nil {
|
||||||
|
statusEvent, commitID = "push", p.HeadCommit.ID
|
||||||
|
}
|
||||||
|
case webhook_module.HookEventPullRequest,
|
||||||
|
webhook_module.HookEventPullRequestSync,
|
||||||
|
webhook_module.HookEventPullRequestAssign,
|
||||||
|
webhook_module.HookEventPullRequestLabel,
|
||||||
|
webhook_module.HookEventPullRequestReviewRequest,
|
||||||
|
webhook_module.HookEventPullRequestMilestone:
|
||||||
|
if p, ok := payload.(*api.PullRequestPayload); ok && p.PullRequest != nil && p.PullRequest.Head != nil {
|
||||||
|
statusEvent, commitID = "pull_request", p.PullRequest.Head.Sha
|
||||||
|
if triggerEvent == actions_module.GithubEventPullRequestTarget {
|
||||||
|
statusEvent = "pull_request_target"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if statusEvent == "" || commitID == "" {
|
||||||
|
return nil // unsupported event or missing commit id, nothing to post
|
||||||
|
}
|
||||||
|
|
||||||
|
workflows, err := jobparser.Parse(content)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("jobparser.Parse: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName := actions_module.WorkflowDisplayName(workflowID, content)
|
||||||
|
for _, sw := range workflows {
|
||||||
|
_, job := sw.Job()
|
||||||
|
if job == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
jobName := util.EllipsisDisplayString(job.Name, 255) // run creation truncates job names the same way
|
||||||
|
ctxName := actions_module.WorkflowStatusContextName(displayName, jobName, statusEvent)
|
||||||
|
if scopedPrefix != "" {
|
||||||
|
ctxName = actions_module.ScopedWorkflowStatusContextName(scopedPrefix, displayName, jobName, statusEvent)
|
||||||
|
}
|
||||||
|
// "Skipped" mirrors toCommitStatusDescription for StatusSkipped.
|
||||||
|
if err := createWorkflowCommitStatus(ctx, repo, commitID, ctxName, workflowID, commitstatus.CommitStatusSkipped, "", "Skipped"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createWorkflowCommitStatus posts the commit status for one workflow-job context.
|
||||||
|
func createWorkflowCommitStatus(ctx context.Context, repo *repo_model.Repository, commitID, ctxName, workflowID string, state commitstatus.CommitStatusState, targetURL, description string) error {
|
||||||
// Mix the workflow file path into the hash so two workflow files that
|
// Mix the workflow file path into the hash so two workflow files that
|
||||||
// share the same `name:` and job name produce distinct commit statuses
|
// share the same `name:` and job name produce distinct commit statuses
|
||||||
// even though they render identically — matching GitHub's behavior
|
// even though they render identically — matching GitHub's behavior
|
||||||
// (issue #35699).
|
// (issue #35699).
|
||||||
ctxHash := git_model.HashCommitStatusContext(ctxName + "\x00" + run.WorkflowID)
|
ctxHash := git_model.HashCommitStatusContext(ctxName + "\x00" + workflowID)
|
||||||
// Pre-fix rows were hashed from Context alone. If a pre-existing row with
|
// Pre-fix rows were hashed from Context alone. If a pre-existing row with
|
||||||
// the legacy hash is still the "latest" for this SHA, reuse that hash so
|
// the legacy hash is still the "latest" for this SHA, reuse that hash so
|
||||||
// the new row supersedes it; otherwise the old pending status would stay
|
// the new row supersedes it; otherwise the old pending status would stay
|
||||||
// stuck forever (it lives in its own dedupe group). Only relevant for
|
// stuck forever (it lives in its own dedupe group). Only relevant for
|
||||||
// in-flight workflows at upgrade time.
|
// in-flight workflows at upgrade time.
|
||||||
legacyHash := git_model.HashCommitStatusContext(ctxName)
|
legacyHash := git_model.HashCommitStatusContext(ctxName)
|
||||||
state := toCommitStatus(job.Status)
|
|
||||||
targetURL := fmt.Sprintf("%s/jobs/%d", run.Link(), job.ID)
|
|
||||||
description := toCommitStatusDescription(job)
|
|
||||||
|
|
||||||
statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, commitID, db.ListOptionsAll)
|
statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, commitID, db.ListOptionsAll)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -183,8 +183,9 @@ func notify(ctx context.Context, input *notifyInput) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var detectedWorkflows []*actions_module.DetectedWorkflow
|
var detectedWorkflows []*actions_module.DetectedWorkflow
|
||||||
|
var filteredWorkflows []*actions_module.DetectedWorkflow
|
||||||
actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
|
actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
|
||||||
workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit,
|
workflows, schedules, filtered, err := actions_module.DetectWorkflows(gitRepo, commit,
|
||||||
input.Event,
|
input.Event,
|
||||||
input.Payload,
|
input.Payload,
|
||||||
shouldDetectSchedules,
|
shouldDetectSchedules,
|
||||||
@@ -212,6 +213,17 @@ func notify(ctx context.Context, input *notifyInput) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, wf := range filtered {
|
||||||
|
if actionsConfig.IsWorkflowDisabled(wf.EntryName) {
|
||||||
|
log.Trace("repo %s has disable workflows %s", input.Repo.RelativePath(), wf.EntryName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if wf.TriggerEvent.Name != actions_module.GithubEventPullRequestTarget {
|
||||||
|
filteredWorkflows = append(filteredWorkflows, wf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if input.PullRequest != nil {
|
if input.PullRequest != nil {
|
||||||
// detect pull_request_target workflows
|
// detect pull_request_target workflows
|
||||||
baseRef := git.BranchPrefix + input.PullRequest.BaseBranch
|
baseRef := git.BranchPrefix + input.PullRequest.BaseBranch
|
||||||
@@ -219,7 +231,7 @@ func notify(ctx context.Context, input *notifyInput) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("gitRepo.GetCommit: %w", err)
|
return fmt.Errorf("gitRepo.GetCommit: %w", err)
|
||||||
}
|
}
|
||||||
baseWorkflows, _, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload, false)
|
baseWorkflows, _, baseFiltered, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("DetectWorkflows: %w", err)
|
return fmt.Errorf("DetectWorkflows: %w", err)
|
||||||
}
|
}
|
||||||
@@ -227,11 +239,24 @@ func notify(ctx context.Context, input *notifyInput) error {
|
|||||||
log.Trace("repo %s with commit %s couldn't find pull_request_target workflows", input.Repo.RelativePath(), baseCommit.ID)
|
log.Trace("repo %s with commit %s couldn't find pull_request_target workflows", input.Repo.RelativePath(), baseCommit.ID)
|
||||||
} else {
|
} else {
|
||||||
for _, wf := range baseWorkflows {
|
for _, wf := range baseWorkflows {
|
||||||
|
if actionsConfig.IsWorkflowDisabled(wf.EntryName) {
|
||||||
|
log.Trace("repo %s has disable workflows %s", input.Repo.RelativePath(), wf.EntryName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if wf.TriggerEvent.Name == actions_module.GithubEventPullRequestTarget {
|
if wf.TriggerEvent.Name == actions_module.GithubEventPullRequestTarget {
|
||||||
detectedWorkflows = append(detectedWorkflows, wf)
|
detectedWorkflows = append(detectedWorkflows, wf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, wf := range baseFiltered {
|
||||||
|
if actionsConfig.IsWorkflowDisabled(wf.EntryName) {
|
||||||
|
log.Trace("repo %s has disable workflows %s", input.Repo.RelativePath(), wf.EntryName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if wf.TriggerEvent.Name == actions_module.GithubEventPullRequestTarget {
|
||||||
|
filteredWorkflows = append(filteredWorkflows, wf)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if shouldDetectSchedules {
|
if shouldDetectSchedules {
|
||||||
@@ -244,6 +269,8 @@ func notify(ctx context.Context, input *notifyInput) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleFilteredWorkflows(ctx, input, filteredWorkflows)
|
||||||
|
|
||||||
return detectAndHandleScopedWorkflows(ctx, input, ref, gitRepo, commit)
|
return detectAndHandleScopedWorkflows(ctx, input, ref, gitRepo, commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,6 +396,16 @@ func buildApproveAndInsertRun(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleFilteredWorkflows posts a skipped commit status for each workflow that matched the event but was excluded by a branch/paths filter.
|
||||||
|
func handleFilteredWorkflows(ctx context.Context, input *notifyInput, filteredWorkflows []*actions_module.DetectedWorkflow) {
|
||||||
|
for _, dwf := range filteredWorkflows {
|
||||||
|
if err := CreateSkippedCommitStatusForFilteredWorkflow(ctx, input.Repo, input.Event, dwf.TriggerEvent.Name, dwf.EntryName, dwf.Content, input.Payload, ""); err != nil {
|
||||||
|
log.Error("repo %s: skipped commit status for workflow %s: %v", input.Repo.RelativePath(), dwf.EntryName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newNotifyInputFromIssue(issue *issues_model.Issue, event webhook_module.HookEventType) *notifyInput {
|
func newNotifyInputFromIssue(issue *issues_model.Issue, event webhook_module.HookEventType) *notifyInput {
|
||||||
return newNotifyInput(issue.Repo, issue.Poster, event)
|
return newNotifyInput(issue.Repo, issue.Poster, event)
|
||||||
}
|
}
|
||||||
@@ -640,7 +677,7 @@ func detectAndHandleScopedWorkflows(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceCommitSHA, detected, err := detectScopedWorkflowsForSource(ctx, input, consumerGitRepo, consumerCommit, sourceRepo)
|
sourceCommitSHA, detected, filtered, err := detectScopedWorkflowsForSource(ctx, input, consumerGitRepo, consumerCommit, sourceRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("scoped workflows: source %d for consumer %s: %v", sourceRepoID, input.Repo.RelativePath(), err)
|
log.Error("scoped workflows: source %d for consumer %s: %v", sourceRepoID, input.Repo.RelativePath(), err)
|
||||||
continue
|
continue
|
||||||
@@ -658,23 +695,40 @@ func detectAndHandleScopedWorkflows(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A filtered-out scoped workflow posts a skipped commit status.
|
||||||
|
if len(filtered) > 0 {
|
||||||
|
scopedPrefix := actions_model.ScopedStatusContextPrefix(ctx, sourceRepo.ID)
|
||||||
|
for _, dwf := range filtered {
|
||||||
|
if actions_model.ScopedWorkflowOptedOut(actionsConfig, sources, sourceRepo.ID, dwf.EntryName) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := CreateSkippedCommitStatusForFilteredWorkflow(ctx, input.Repo, input.Event, dwf.TriggerEvent.Name, dwf.EntryName, dwf.Content, input.Payload, scopedPrefix); err != nil {
|
||||||
|
log.Error("scoped workflows: skipped commit status for source %s workflow %s: %v", sourceRepo.RelativePath(), dwf.EntryName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// detectScopedWorkflowsForSource detects the scoped workflows from the source repo at its default branch
|
// detectScopedWorkflowsForSource detects the scoped workflows from the source repo at its default branch.
|
||||||
|
// detected are the workflows to run; filtered matched the event but were excluded by a branch/paths
|
||||||
|
// filter and post a skipped commit status.
|
||||||
func detectScopedWorkflowsForSource(
|
func detectScopedWorkflowsForSource(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
input *notifyInput,
|
input *notifyInput,
|
||||||
consumerGitRepo *git.Repository,
|
consumerGitRepo *git.Repository,
|
||||||
consumerCommit *git.Commit,
|
consumerCommit *git.Commit,
|
||||||
sourceRepo *repo_model.Repository,
|
sourceRepo *repo_model.Repository,
|
||||||
) (sourceCommitSHA string, detected []*actions_module.DetectedWorkflow, err error) {
|
) (sourceCommitSHA string, detected, filtered []*actions_module.DetectedWorkflow, err error) {
|
||||||
// scoped workflow content is always taken from the source repo's default branch; the parse is cached per (source, default-branch SHA) and reused across consuming repos/events
|
// scoped workflow content is always taken from the source repo's default branch; the parse is cached per (source, default-branch SHA) and reused across consuming repos/events
|
||||||
sourceCommitSHA, parsed, err := LoadParsedScopedWorkflows(ctx, sourceRepo)
|
sourceCommitSHA, parsed, err := LoadParsedScopedWorkflows(ctx, sourceRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, nil, err
|
||||||
}
|
}
|
||||||
return sourceCommitSHA, actions_module.MatchScopedWorkflows(parsed, consumerGitRepo, consumerCommit, input.Event, input.Payload), nil
|
detected, filtered = actions_module.MatchScopedWorkflows(parsed, consumerGitRepo, consumerCommit, input.Event, input.Payload)
|
||||||
|
return sourceCommitSHA, detected, filtered, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,47 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
|
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
|
||||||
actions_model "gitea.dev/models/actions"
|
actions_model "gitea.dev/models/actions"
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
secret_model "gitea.dev/models/secret"
|
secret_model "gitea.dev/models/secret"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
|
"gitea.dev/modules/setting"
|
||||||
|
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
taskPickSem chan struct{}
|
||||||
|
taskPickSemOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func taskPickLimiter() chan struct{} {
|
||||||
|
taskPickSemOnce.Do(func() {
|
||||||
|
taskPickSem = make(chan struct{}, setting.Actions.MaxConcurrentTaskPicks)
|
||||||
|
})
|
||||||
|
return taskPickSem
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryPickTask attempts to assign a task to the runner, bounding the number of
|
||||||
|
// concurrent assignment transactions to avoid a thundering herd when many
|
||||||
|
// runners poll at once. When the concurrency limit is reached it returns
|
||||||
|
// throttled=true without touching the DB, so the caller can let the runner
|
||||||
|
// retry on its next poll instead of advancing its tasks version.
|
||||||
|
func TryPickTask(ctx context.Context, runner *actions_model.ActionRunner) (task *runnerv1.Task, ok, throttled bool, err error) {
|
||||||
|
sem := taskPickLimiter()
|
||||||
|
select {
|
||||||
|
case sem <- struct{}{}:
|
||||||
|
defer func() { <-sem }()
|
||||||
|
default:
|
||||||
|
return nil, false, true, nil
|
||||||
|
}
|
||||||
|
task, ok, err = PickTask(ctx, runner)
|
||||||
|
return task, ok, false, err
|
||||||
|
}
|
||||||
|
|
||||||
func PickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv1.Task, bool, error) {
|
func PickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv1.Task, bool, error) {
|
||||||
var (
|
var (
|
||||||
task *runnerv1.Task
|
task *runnerv1.Task
|
||||||
@@ -76,6 +107,15 @@ func PickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv
|
|||||||
NotifyWorkflowRunStatusUpdateWithReload(ctx, job.RepoID, job.RunID)
|
NotifyWorkflowRunStatusUpdateWithReload(ctx, job.RepoID, job.RunID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The job is claimed and its payload assembled, but if the request context was cancelled meanwhile, response can no longer reach the runner.
|
||||||
|
// Release the claim so another runner can pick the job up.
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
if relErr := actions_model.ReleaseTaskForRunner(ctx, t); relErr != nil {
|
||||||
|
log.Error("ReleaseTaskForRunner [task_id: %d]: %v", t.ID, relErr)
|
||||||
|
}
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
return task, true, nil
|
return task, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
34
services/actions/task_test.go
Normal file
34
services/actions/task_test.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
actions_model "gitea.dev/models/actions"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTryPickTaskThrottled(t *testing.T) {
|
||||||
|
sem := taskPickLimiter()
|
||||||
|
|
||||||
|
// Saturate every assignment slot so the next attempt must be throttled.
|
||||||
|
for range cap(sem) {
|
||||||
|
sem <- struct{}{}
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
for range cap(sem) {
|
||||||
|
<-sem
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// No DB access happens on the throttled path, so this is safe without fixtures.
|
||||||
|
task, ok, throttled, err := TryPickTask(t.Context(), &actions_model.ActionRunner{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Nil(t, task)
|
||||||
|
assert.False(t, ok)
|
||||||
|
assert.True(t, throttled)
|
||||||
|
}
|
||||||
@@ -104,10 +104,6 @@ type APINotFound struct{}
|
|||||||
// swagger:response conflict
|
// swagger:response conflict
|
||||||
type APIConflict struct{}
|
type APIConflict struct{}
|
||||||
|
|
||||||
// APIRedirect is a redirect response
|
|
||||||
// swagger:response redirect
|
|
||||||
type APIRedirect struct{}
|
|
||||||
|
|
||||||
// APIString is a string response
|
// APIString is a string response
|
||||||
// swagger:response string
|
// swagger:response string
|
||||||
type APIString string
|
type APIString string
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func pruneBrokenReferences(ctx context.Context, m *repo_model.Mirror, gitRepo gitrepo.Repository, timeout time.Duration) error {
|
func pruneBrokenReferences(ctx context.Context, m *repo_model.Mirror, gitRepo gitrepo.Repository, timeout time.Duration) error {
|
||||||
cmd := gitcmd.NewCommand("remote", "prune").AddDynamicArguments(m.GetRemoteName()).WithTimeout(timeout)
|
// Never follow HTTP redirects, see cmdFetch in runSync.
|
||||||
|
cmd := gitcmd.NewCommand("remote", "prune").AddConfig("http.followRedirects", "false").AddDynamicArguments(m.GetRemoteName()).WithTimeout(timeout)
|
||||||
stdout, _, pruneErr := gitrepo.RunCmdString(ctx, gitRepo, cmd)
|
stdout, _, pruneErr := gitrepo.RunCmdString(ctx, gitRepo, cmd)
|
||||||
if pruneErr != nil {
|
if pruneErr != nil {
|
||||||
// sanitize the output, since it may contain the remote address, which may contain a password
|
// sanitize the output, since it may contain the remote address, which may contain a password
|
||||||
@@ -119,7 +120,9 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*repo_module.SyncResu
|
|||||||
|
|
||||||
// use fetch but not remote update because git fetch support --tags but remote update doesn't
|
// use fetch but not remote update because git fetch support --tags but remote update doesn't
|
||||||
cmdFetch := func() *gitcmd.Command {
|
cmdFetch := func() *gitcmd.Command {
|
||||||
cmd := gitcmd.NewCommand("fetch", "--tags")
|
// Never follow HTTP redirects: a mirror remote that later starts redirecting to an
|
||||||
|
// otherwise-blocked address would be an SSRF/exfiltration vector on scheduled syncs.
|
||||||
|
cmd := gitcmd.NewCommand("fetch", "--tags").AddConfig("http.followRedirects", "false")
|
||||||
if m.EnablePrune {
|
if m.EnablePrune {
|
||||||
cmd.AddArguments("--prune")
|
cmd.AddArguments("--prune")
|
||||||
}
|
}
|
||||||
@@ -200,7 +203,8 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*repo_module.SyncResu
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmdRemoteUpdatePrune := func() *gitcmd.Command {
|
cmdRemoteUpdatePrune := func() *gitcmd.Command {
|
||||||
return gitcmd.NewCommand("remote", "update", "--prune").
|
// Never follow HTTP redirects, see cmdFetch above.
|
||||||
|
return gitcmd.NewCommand("remote", "update", "--prune").AddConfig("http.followRedirects", "false").
|
||||||
AddDynamicArguments(m.GetRemoteName()).WithTimeout(timeout).WithEnv(envs)
|
AddDynamicArguments(m.GetRemoteName()).WithTimeout(timeout).WithEnv(envs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ import (
|
|||||||
issues_model "gitea.dev/models/issues"
|
issues_model "gitea.dev/models/issues"
|
||||||
project_model "gitea.dev/models/project"
|
project_model "gitea.dev/models/project"
|
||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
"gitea.dev/modules/container"
|
|
||||||
"gitea.dev/modules/optional"
|
"gitea.dev/modules/optional"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MoveIssuesOnProjectColumn moves or keeps issues in a column and sorts them inside that column
|
// MoveIssuesOnProjectColumn moves or keeps issues in a column and sorts them inside that column
|
||||||
@@ -100,28 +101,15 @@ func MoveIssuesOnProjectColumn(ctx context.Context, doer *user_model.User, colum
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadIssuesAssigneesForProject(ctx context.Context, issuesMap map[int64]issues_model.IssueList) ([]*user_model.User, error) {
|
func LoadIssuesAssigneesForProject(ctx context.Context, projectID int64) (users []*user_model.User, _ error) {
|
||||||
var issueList issues_model.IssueList
|
sub := builder.Select("distinct issue_assignees.assignee_id").
|
||||||
for _, colIssues := range issuesMap {
|
From("project_issue").Join("INNER", "issue_assignees", "project_issue.issue_id=issue_assignees.issue_id").
|
||||||
issueList = append(issueList, colIssues...)
|
Where(builder.Eq{"project_issue.project_id": projectID})
|
||||||
}
|
err := db.GetEngine(ctx).Table("`user`").Where(builder.In("id", sub)).Find(&users)
|
||||||
err := issueList.LoadAssignees(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
users := make([]*user_model.User, 0, len(issueList))
|
slices.SortFunc(users, func(a, b *user_model.User) int { return strings.Compare(a.Name, b.Name) })
|
||||||
usersAdded := container.Set[int64]{}
|
|
||||||
for _, issue := range issueList {
|
|
||||||
for _, assignee := range issue.Assignees {
|
|
||||||
if !usersAdded.Contains(assignee.ID) {
|
|
||||||
usersAdded.Add(assignee.ID)
|
|
||||||
users = append(users, assignee)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
slices.SortFunc(users, func(a, b *user_model.User) int {
|
|
||||||
return strings.Compare(a.Name, b.Name)
|
|
||||||
})
|
|
||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Projects(t *testing.T) {
|
func Test_Projects(t *testing.T) {
|
||||||
@@ -198,18 +197,4 @@ func Test_Projects(t *testing.T) {
|
|||||||
assert.Len(t, columnIssues[3], 1)
|
assert.Len(t, columnIssues[3], 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("LoadIssuesAssigneesForProject", func(t *testing.T) {
|
|
||||||
issuesMap := map[int64]issues_model.IssueList{}
|
|
||||||
issue1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
|
||||||
issue6 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 6})
|
|
||||||
issuesMap[1] = issues_model.IssueList{issue1}
|
|
||||||
issuesMap[2] = issues_model.IssueList{issue6}
|
|
||||||
assignees, err := LoadIssuesAssigneesForProject(t.Context(), issuesMap)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, assignees, 3)
|
|
||||||
require.Equal(t, "user1", assignees[0].Name)
|
|
||||||
require.Equal(t, "user10", assignees[1].Name)
|
|
||||||
require.Equal(t, "user2", assignees[2].Name)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ import (
|
|||||||
"gitea.dev/modules/graceful"
|
"gitea.dev/modules/graceful"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/repository"
|
"gitea.dev/modules/repository"
|
||||||
|
"gitea.dev/modules/setting"
|
||||||
"gitea.dev/modules/storage"
|
"gitea.dev/modules/storage"
|
||||||
"gitea.dev/modules/timeutil"
|
"gitea.dev/modules/timeutil"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
|
"gitea.dev/services/context/upload"
|
||||||
notify_service "gitea.dev/services/notify"
|
notify_service "gitea.dev/services/notify"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -319,13 +321,17 @@ func UpdateRelease(ctx context.Context, doer *user_model.User, gitRepo *git.Repo
|
|||||||
}
|
}
|
||||||
|
|
||||||
for uuid, newName := range editAttachments {
|
for uuid, newName := range editAttachments {
|
||||||
if !deletedUUIDs.Contains(uuid) {
|
if deletedUUIDs.Contains(uuid) {
|
||||||
if err = repo_model.UpdateAttachmentByUUID(ctx, &repo_model.Attachment{
|
continue
|
||||||
UUID: uuid,
|
}
|
||||||
Name: newName,
|
if err = upload.Verify(nil, newName, setting.Repository.Release.AllowedTypes); err != nil {
|
||||||
}, "name"); err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
if err = repo_model.UpdateAttachmentByUUID(ctx, &repo_model.Attachment{
|
||||||
|
UUID: uuid,
|
||||||
|
Name: newName,
|
||||||
|
}, "name"); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,11 @@ import (
|
|||||||
"gitea.dev/models/unittest"
|
"gitea.dev/models/unittest"
|
||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
"gitea.dev/modules/gitrepo"
|
"gitea.dev/modules/gitrepo"
|
||||||
|
"gitea.dev/modules/setting"
|
||||||
|
"gitea.dev/modules/test"
|
||||||
"gitea.dev/modules/timeutil"
|
"gitea.dev/modules/timeutil"
|
||||||
"gitea.dev/services/attachment"
|
"gitea.dev/services/attachment"
|
||||||
|
"gitea.dev/services/context/upload"
|
||||||
|
|
||||||
_ "gitea.dev/models/actions"
|
_ "gitea.dev/models/actions"
|
||||||
|
|
||||||
@@ -270,6 +273,17 @@ func TestRelease_Update(t *testing.T) {
|
|||||||
assert.Equal(t, release.ID, release.Attachments[0].ReleaseID)
|
assert.Equal(t, release.ID, release.Attachments[0].ReleaseID)
|
||||||
assert.Equal(t, "test2.txt", release.Attachments[0].Name)
|
assert.Equal(t, "test2.txt", release.Attachments[0].Name)
|
||||||
|
|
||||||
|
defer test.MockVariableValue(&setting.Repository.Release.AllowedTypes, ".zip")()
|
||||||
|
err = UpdateRelease(t.Context(), user, gitRepo, release, nil, nil, map[string]string{
|
||||||
|
attach.UUID: "test.exe",
|
||||||
|
})
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.True(t, upload.IsErrFileTypeForbidden(err))
|
||||||
|
release.Attachments = nil
|
||||||
|
assert.NoError(t, repo_model.GetReleaseAttachments(t.Context(), release))
|
||||||
|
assert.Len(t, release.Attachments, 1)
|
||||||
|
assert.Equal(t, "test2.txt", release.Attachments[0].Name)
|
||||||
|
|
||||||
// delete the attachment
|
// delete the attachment
|
||||||
assert.NoError(t, UpdateRelease(t.Context(), user, gitRepo, release, nil, []string{attach.UUID}, nil))
|
assert.NoError(t, UpdateRelease(t.Context(), user, gitRepo, release, nil, []string{attach.UUID}, nil))
|
||||||
release.Attachments = nil
|
release.Attachments = nil
|
||||||
|
|||||||
@@ -42,6 +42,38 @@ type ArchiveRequest struct {
|
|||||||
archiveRefShortName string // the ref short name to download the archive, for example: "master", "v1.0.0", "commit id"
|
archiveRefShortName string // the ref short name to download the archive, for example: "master", "v1.0.0", "commit id"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type archiveQueueItem struct {
|
||||||
|
RepoID int64 `json:"RepoID"`
|
||||||
|
Type repo_model.ArchiveType `json:"Type"`
|
||||||
|
CommitID string `json:"CommitID"`
|
||||||
|
Paths []string `json:"Paths,omitempty"`
|
||||||
|
ArchiveRefShortName string `json:"ArchiveRefShortName,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aReq *ArchiveRequest) toQueueItem() *archiveQueueItem {
|
||||||
|
return &archiveQueueItem{
|
||||||
|
RepoID: aReq.Repo.ID,
|
||||||
|
Type: aReq.Type,
|
||||||
|
CommitID: aReq.CommitID,
|
||||||
|
Paths: aReq.Paths,
|
||||||
|
ArchiveRefShortName: aReq.archiveRefShortName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *archiveQueueItem) toArchiveRequest(ctx context.Context) (*ArchiveRequest, error) {
|
||||||
|
repo, err := repo_model.GetRepositoryByID(ctx, item.RepoID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ArchiveRequest{
|
||||||
|
Repo: repo,
|
||||||
|
Type: item.Type,
|
||||||
|
CommitID: item.CommitID,
|
||||||
|
Paths: item.Paths,
|
||||||
|
archiveRefShortName: item.ArchiveRefShortName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewRequest creates an archival request, based on the URI. The
|
// NewRequest creates an archival request, based on the URI. The
|
||||||
// resulting ArchiveRequest is suitable for being passed to Await()
|
// resulting ArchiveRequest is suitable for being passed to Await()
|
||||||
// if it's determined that the request still needs to be satisfied.
|
// if it's determined that the request still needs to be satisfied.
|
||||||
@@ -227,13 +259,18 @@ func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver
|
|||||||
return archiver, nil
|
return archiver, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var archiverQueue *queue.WorkerPoolQueue[*ArchiveRequest]
|
var archiverQueue *queue.WorkerPoolQueue[*archiveQueueItem]
|
||||||
|
|
||||||
// Init initializes archiver
|
// Init initializes archiver
|
||||||
func Init(ctx context.Context) error {
|
func Init(ctx context.Context) error {
|
||||||
handler := func(items ...*ArchiveRequest) []*ArchiveRequest {
|
handler := func(items ...*archiveQueueItem) []*archiveQueueItem {
|
||||||
for _, archiveReq := range items {
|
for _, item := range items {
|
||||||
log.Trace("ArchiverData Process: %#v", archiveReq)
|
log.Trace("ArchiverData Process: %#v", item)
|
||||||
|
archiveReq, err := item.toArchiveRequest(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Archive repo %d: %v", item.RepoID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if archiver, err := doArchive(ctx, archiveReq); err != nil {
|
if archiver, err := doArchive(ctx, archiveReq); err != nil {
|
||||||
log.Error("Archive %v failed: %v", archiveReq, err)
|
log.Error("Archive %v failed: %v", archiveReq, err)
|
||||||
} else {
|
} else {
|
||||||
@@ -254,14 +291,15 @@ func Init(ctx context.Context) error {
|
|||||||
|
|
||||||
// StartArchive push the archive request to the queue
|
// StartArchive push the archive request to the queue
|
||||||
func StartArchive(request *ArchiveRequest) error {
|
func StartArchive(request *ArchiveRequest) error {
|
||||||
has, err := archiverQueue.Has(request)
|
item := request.toQueueItem()
|
||||||
|
has, err := archiverQueue.Has(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if has {
|
if has {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return archiverQueue.Push(request)
|
return archiverQueue.Push(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteOldRepoArchiver(ctx context.Context, archiver *repo_model.RepoArchiver) error {
|
func deleteOldRepoArchiver(ctx context.Context, archiver *repo_model.RepoArchiver) error {
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
repo_model "gitea.dev/models/repo"
|
||||||
"gitea.dev/models/unittest"
|
"gitea.dev/models/unittest"
|
||||||
|
"gitea.dev/modules/json"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
"gitea.dev/services/contexttest"
|
"gitea.dev/services/contexttest"
|
||||||
|
|
||||||
@@ -21,6 +23,22 @@ func TestMain(m *testing.M) {
|
|||||||
unittest.MainTest(m)
|
unittest.MainTest(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestArchiveQueueItemJSON(t *testing.T) {
|
||||||
|
orig := &archiveQueueItem{
|
||||||
|
RepoID: 7,
|
||||||
|
Type: repo_model.ArchiveZip,
|
||||||
|
CommitID: "abc123",
|
||||||
|
Paths: []string{"agents"},
|
||||||
|
ArchiveRefShortName: "main",
|
||||||
|
}
|
||||||
|
bs, err := json.Marshal(orig)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var decoded archiveQueueItem
|
||||||
|
require.NoError(t, json.Unmarshal(bs, &decoded))
|
||||||
|
assert.Equal(t, *orig, decoded)
|
||||||
|
}
|
||||||
|
|
||||||
func TestArchive_Basic(t *testing.T) {
|
func TestArchive_Basic(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
|||||||
@@ -187,6 +187,18 @@ func (m msteamsConvertor) IssueComment(p *api.IssueCommentPayload) (MSTeamsPaylo
|
|||||||
func (m msteamsConvertor) PullRequest(p *api.PullRequestPayload) (MSTeamsPayload, error) {
|
func (m msteamsConvertor) PullRequest(p *api.PullRequestPayload) (MSTeamsPayload, error) {
|
||||||
title, _, extraMarkdown, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false)
|
title, _, extraMarkdown, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false)
|
||||||
|
|
||||||
|
facts := []*MSTeamsFact{
|
||||||
|
{"Pull request:", fmt.Sprintf("[#%d](%s)", p.PullRequest.Index, p.PullRequest.HTMLURL)},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.Action == api.HookIssueReviewRequested || p.Action == api.HookIssueReviewRequestRemoved) && p.RequestedReviewer != nil {
|
||||||
|
reviewerName := p.RequestedReviewer.UserName
|
||||||
|
if p.RequestedReviewer.FullName != "" {
|
||||||
|
reviewerName += " (" + p.RequestedReviewer.FullName + ")"
|
||||||
|
}
|
||||||
|
facts = append(facts, &MSTeamsFact{"Requested Reviewer:", reviewerName})
|
||||||
|
}
|
||||||
|
|
||||||
return createMSTeamsPayload(
|
return createMSTeamsPayload(
|
||||||
p.Repository,
|
p.Repository,
|
||||||
p.Sender,
|
p.Sender,
|
||||||
@@ -194,7 +206,7 @@ func (m msteamsConvertor) PullRequest(p *api.PullRequestPayload) (MSTeamsPayload
|
|||||||
extraMarkdown,
|
extraMarkdown,
|
||||||
p.PullRequest.HTMLURL,
|
p.PullRequest.HTMLURL,
|
||||||
color,
|
color,
|
||||||
&MSTeamsFact{"Pull request #:", strconv.FormatInt(p.PullRequest.ID, 10)},
|
facts...,
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +243,7 @@ func (m msteamsConvertor) Review(p *api.PullRequestPayload, event webhook_module
|
|||||||
text,
|
text,
|
||||||
p.PullRequest.HTMLURL,
|
p.PullRequest.HTMLURL,
|
||||||
color,
|
color,
|
||||||
&MSTeamsFact{"Pull request #:", strconv.FormatInt(p.PullRequest.ID, 10)},
|
&MSTeamsFact{"Pull request:", fmt.Sprintf("[#%d](%s)", p.PullRequest.Index, p.PullRequest.HTMLURL)},
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +283,6 @@ func (m msteamsConvertor) Wiki(p *api.WikiPayload) (MSTeamsPayload, error) {
|
|||||||
"",
|
"",
|
||||||
p.Repository.HTMLURL+"/wiki/"+url.PathEscape(p.Page),
|
p.Repository.HTMLURL+"/wiki/"+url.PathEscape(p.Page),
|
||||||
color,
|
color,
|
||||||
&MSTeamsFact{"Repository:", p.Repository.FullName},
|
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,16 +357,18 @@ func (msteamsConvertor) WorkflowJob(p *api.WorkflowJobPayload) (MSTeamsPayload,
|
|||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTarget string, color int, fact *MSTeamsFact) MSTeamsPayload {
|
func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTarget string, color int, extraFacts ...*MSTeamsFact) MSTeamsPayload {
|
||||||
facts := make([]MSTeamsFact, 0, 2)
|
facts := make([]MSTeamsFact, 0, len(extraFacts)+1)
|
||||||
if r != nil {
|
if r != nil {
|
||||||
facts = append(facts, MSTeamsFact{
|
facts = append(facts, MSTeamsFact{
|
||||||
Name: "Repository:",
|
Name: "Repository:",
|
||||||
Value: r.FullName,
|
Value: fmt.Sprintf("[%s](%s)", r.FullName, r.HTMLURL),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if fact != nil {
|
for _, f := range extraFacts {
|
||||||
facts = append(facts, *fact)
|
if f != nil {
|
||||||
|
facts = append(facts, *f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return MSTeamsPayload{
|
return MSTeamsPayload{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package webhook
|
package webhook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
webhook_model "gitea.dev/models/webhook"
|
webhook_model "gitea.dev/models/webhook"
|
||||||
@@ -31,7 +32,7 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections[0].Facts, 2)
|
assert.Len(t, pl.Sections[0].Facts, 2)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repo.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repo.FullName, p.Repo.HTMLURL), fact.Value)
|
||||||
} else if fact.Name == "branch:" {
|
} else if fact.Name == "branch:" {
|
||||||
assert.Equal(t, "test", fact.Value)
|
assert.Equal(t, "test", fact.Value)
|
||||||
} else {
|
} else {
|
||||||
@@ -57,7 +58,7 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections[0].Facts, 2)
|
assert.Len(t, pl.Sections[0].Facts, 2)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repo.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repo.FullName, p.Repo.HTMLURL), fact.Value)
|
||||||
} else if fact.Name == "branch:" {
|
} else if fact.Name == "branch:" {
|
||||||
assert.Equal(t, "test", fact.Value)
|
assert.Equal(t, "test", fact.Value)
|
||||||
} else {
|
} else {
|
||||||
@@ -83,7 +84,7 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections[0].Facts, 2)
|
assert.Len(t, pl.Sections[0].Facts, 2)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repo.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repo.FullName, p.Repo.HTMLURL), fact.Value)
|
||||||
} else if fact.Name == "Forkee:" {
|
} else if fact.Name == "Forkee:" {
|
||||||
assert.Equal(t, p.Forkee.FullName, fact.Value)
|
assert.Equal(t, p.Forkee.FullName, fact.Value)
|
||||||
} else {
|
} else {
|
||||||
@@ -109,7 +110,7 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections[0].Facts, 2)
|
assert.Len(t, pl.Sections[0].Facts, 2)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repo.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repo.FullName, p.Repo.HTMLURL), fact.Value)
|
||||||
} else if fact.Name == "Commit count:" {
|
} else if fact.Name == "Commit count:" {
|
||||||
assert.Equal(t, "2", fact.Value)
|
assert.Equal(t, "2", fact.Value)
|
||||||
} else {
|
} else {
|
||||||
@@ -136,7 +137,7 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections[0].Facts, 2)
|
assert.Len(t, pl.Sections[0].Facts, 2)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repository.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value)
|
||||||
} else if fact.Name == "Issue #:" {
|
} else if fact.Name == "Issue #:" {
|
||||||
assert.Equal(t, "2", fact.Value)
|
assert.Equal(t, "2", fact.Value)
|
||||||
} else {
|
} else {
|
||||||
@@ -159,7 +160,7 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections[0].Facts, 2)
|
assert.Len(t, pl.Sections[0].Facts, 2)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repository.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value)
|
||||||
} else if fact.Name == "Issue #:" {
|
} else if fact.Name == "Issue #:" {
|
||||||
assert.Equal(t, "2", fact.Value)
|
assert.Equal(t, "2", fact.Value)
|
||||||
} else {
|
} else {
|
||||||
@@ -185,7 +186,7 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections[0].Facts, 2)
|
assert.Len(t, pl.Sections[0].Facts, 2)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repository.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value)
|
||||||
} else if fact.Name == "Issue #:" {
|
} else if fact.Name == "Issue #:" {
|
||||||
assert.Equal(t, "2", fact.Value)
|
assert.Equal(t, "2", fact.Value)
|
||||||
} else {
|
} else {
|
||||||
@@ -211,9 +212,9 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections[0].Facts, 2)
|
assert.Len(t, pl.Sections[0].Facts, 2)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repository.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value)
|
||||||
} else if fact.Name == "Pull request #:" {
|
} else if fact.Name == "Pull request:" {
|
||||||
assert.Equal(t, "12", fact.Value)
|
assert.Equal(t, fmt.Sprintf("[#%d](%s)", p.PullRequest.Index, p.PullRequest.HTMLURL), fact.Value)
|
||||||
} else {
|
} else {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
@@ -223,6 +224,34 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.PotentialAction[0].Targets[0].URI)
|
assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.PotentialAction[0].Targets[0].URI)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("PullRequestReviewRequest", func(t *testing.T) {
|
||||||
|
p := pullRequestTestPayload()
|
||||||
|
p.Action = api.HookIssueReviewRequested
|
||||||
|
p.RequestedReviewer = &api.User{
|
||||||
|
UserName: "reviewer1",
|
||||||
|
FullName: "Reviewer One",
|
||||||
|
}
|
||||||
|
|
||||||
|
pl, err := mc.PullRequest(p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, pl.Sections[0].Facts, 3)
|
||||||
|
var hasReviewer bool
|
||||||
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
|
if fact.Name == "Repository:" {
|
||||||
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value)
|
||||||
|
} else if fact.Name == "Pull request:" {
|
||||||
|
assert.Equal(t, fmt.Sprintf("[#%d](%s)", p.PullRequest.Index, p.PullRequest.HTMLURL), fact.Value)
|
||||||
|
} else if fact.Name == "Requested Reviewer:" {
|
||||||
|
assert.Equal(t, "reviewer1 (Reviewer One)", fact.Value)
|
||||||
|
hasReviewer = true
|
||||||
|
} else {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, hasReviewer)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("PullRequestComment", func(t *testing.T) {
|
t.Run("PullRequestComment", func(t *testing.T) {
|
||||||
p := pullRequestCommentTestPayload()
|
p := pullRequestCommentTestPayload()
|
||||||
|
|
||||||
@@ -237,7 +266,7 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections[0].Facts, 2)
|
assert.Len(t, pl.Sections[0].Facts, 2)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repository.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value)
|
||||||
} else if fact.Name == "Issue #:" {
|
} else if fact.Name == "Issue #:" {
|
||||||
assert.Equal(t, "12", fact.Value)
|
assert.Equal(t, "12", fact.Value)
|
||||||
} else {
|
} else {
|
||||||
@@ -264,9 +293,9 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections[0].Facts, 2)
|
assert.Len(t, pl.Sections[0].Facts, 2)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repository.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value)
|
||||||
} else if fact.Name == "Pull request #:" {
|
} else if fact.Name == "Pull request:" {
|
||||||
assert.Equal(t, "12", fact.Value)
|
assert.Equal(t, fmt.Sprintf("[#%d](%s)", p.PullRequest.Index, p.PullRequest.HTMLURL), fact.Value)
|
||||||
} else {
|
} else {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
@@ -290,7 +319,7 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections[0].Facts, 1)
|
assert.Len(t, pl.Sections[0].Facts, 1)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repository.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value)
|
||||||
} else {
|
} else {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
@@ -336,17 +365,14 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections, 1)
|
assert.Len(t, pl.Sections, 1)
|
||||||
assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
|
assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
|
||||||
assert.Empty(t, pl.Sections[0].Text)
|
assert.Empty(t, pl.Sections[0].Text)
|
||||||
assert.Len(t, pl.Sections[0].Facts, 2)
|
assert.Len(t, pl.Sections[0].Facts, 1)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repository.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value)
|
||||||
} else {
|
} else {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert.Len(t, pl.PotentialAction, 1)
|
|
||||||
assert.Len(t, pl.PotentialAction[0].Targets, 1)
|
|
||||||
assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.PotentialAction[0].Targets[0].URI)
|
|
||||||
|
|
||||||
p.Action = api.HookWikiEdited
|
p.Action = api.HookWikiEdited
|
||||||
pl, err = mc.Wiki(p)
|
pl, err = mc.Wiki(p)
|
||||||
@@ -357,10 +383,10 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections, 1)
|
assert.Len(t, pl.Sections, 1)
|
||||||
assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
|
assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
|
||||||
assert.Empty(t, pl.Sections[0].Text)
|
assert.Empty(t, pl.Sections[0].Text)
|
||||||
assert.Len(t, pl.Sections[0].Facts, 2)
|
assert.Len(t, pl.Sections[0].Facts, 1)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repository.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value)
|
||||||
} else {
|
} else {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
@@ -378,10 +404,10 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections, 1)
|
assert.Len(t, pl.Sections, 1)
|
||||||
assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
|
assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle)
|
||||||
assert.Empty(t, pl.Sections[0].Text)
|
assert.Empty(t, pl.Sections[0].Text)
|
||||||
assert.Len(t, pl.Sections[0].Facts, 2)
|
assert.Len(t, pl.Sections[0].Facts, 1)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repository.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value)
|
||||||
} else {
|
} else {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
@@ -405,7 +431,7 @@ func TestMSTeamsPayload(t *testing.T) {
|
|||||||
assert.Len(t, pl.Sections[0].Facts, 2)
|
assert.Len(t, pl.Sections[0].Facts, 2)
|
||||||
for _, fact := range pl.Sections[0].Facts {
|
for _, fact := range pl.Sections[0].Facts {
|
||||||
if fact.Name == "Repository:" {
|
if fact.Name == "Repository:" {
|
||||||
assert.Equal(t, p.Repository.FullName, fact.Value)
|
assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value)
|
||||||
} else if fact.Name == "Tag:" {
|
} else if fact.Name == "Tag:" {
|
||||||
assert.Equal(t, "v1.0", fact.Value)
|
assert.Equal(t, "v1.0", fact.Value)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
webhook_model "gitea.dev/models/webhook"
|
webhook_model "gitea.dev/models/webhook"
|
||||||
@@ -23,9 +24,12 @@ import (
|
|||||||
type (
|
type (
|
||||||
// TelegramPayload represents
|
// TelegramPayload represents
|
||||||
TelegramPayload struct {
|
TelegramPayload struct {
|
||||||
Message string `json:"text"`
|
RichMessage InputRichMessage `json:"rich_message"`
|
||||||
ParseMode string `json:"parse_mode"`
|
}
|
||||||
DisableWebPreview bool `json:"disable_web_page_preview"`
|
|
||||||
|
// InputRichMessage represents input rich message
|
||||||
|
InputRichMessage struct {
|
||||||
|
HTML string `json:"html"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TelegramMeta contains the telegram metadata
|
// TelegramMeta contains the telegram metadata
|
||||||
@@ -195,13 +199,21 @@ func (telegramConvertor) WorkflowJob(p *api.WorkflowJobPayload) (TelegramPayload
|
|||||||
func createTelegramPayloadHTML(msgHTML string) TelegramPayload {
|
func createTelegramPayloadHTML(msgHTML string) TelegramPayload {
|
||||||
// https://core.telegram.org/bots/api#formatting-options
|
// https://core.telegram.org/bots/api#formatting-options
|
||||||
return TelegramPayload{
|
return TelegramPayload{
|
||||||
Message: strings.TrimSpace(string(markup.Sanitize(msgHTML))),
|
RichMessage: InputRichMessage{
|
||||||
ParseMode: "HTML",
|
HTML: strings.TrimSpace(string(markup.Sanitize(msgHTML))),
|
||||||
DisableWebPreview: true,
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTelegramRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
func newTelegramRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||||
|
u, err := url.Parse(w.URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if urlPrefix, ok := strings.CutSuffix(u.Path, "/sendMessage"); ok {
|
||||||
|
u.Path = urlPrefix + "/sendRichMessage"
|
||||||
|
w.URL = u.String()
|
||||||
|
}
|
||||||
var pc payloadConvertor[TelegramPayload] = telegramConvertor{}
|
var pc payloadConvertor[TelegramPayload] = telegramConvertor{}
|
||||||
return newJSONRequest(pc, w, t, true)
|
return newJSONRequest(pc, w, t, true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ func TestTelegramPayload(t *testing.T) {
|
|||||||
t.Run("Correct webhook params", func(t *testing.T) {
|
t.Run("Correct webhook params", func(t *testing.T) {
|
||||||
p := createTelegramPayloadHTML(`<a href=".">testMsg</a> <bad>`)
|
p := createTelegramPayloadHTML(`<a href=".">testMsg</a> <bad>`)
|
||||||
assert.Equal(t, TelegramPayload{
|
assert.Equal(t, TelegramPayload{
|
||||||
Message: `<a href="." rel="nofollow">testMsg</a>`,
|
RichMessage: InputRichMessage{
|
||||||
ParseMode: "HTML",
|
HTML: `<a href="." rel="nofollow">testMsg</a>`,
|
||||||
DisableWebPreview: true,
|
},
|
||||||
}, p)
|
}, p)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ func TestTelegramPayload(t *testing.T) {
|
|||||||
pl, err := tc.Create(p)
|
pl, err := tc.Create(p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] branch <a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a> created`, pl.Message)
|
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] branch <a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a> created`, pl.RichMessage.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Delete", func(t *testing.T) {
|
t.Run("Delete", func(t *testing.T) {
|
||||||
@@ -42,7 +42,7 @@ func TestTelegramPayload(t *testing.T) {
|
|||||||
pl, err := tc.Delete(p)
|
pl, err := tc.Delete(p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] branch <a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a> deleted`, pl.Message)
|
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] branch <a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a> deleted`, pl.RichMessage.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Fork", func(t *testing.T) {
|
t.Run("Fork", func(t *testing.T) {
|
||||||
@@ -51,7 +51,7 @@ func TestTelegramPayload(t *testing.T) {
|
|||||||
pl, err := tc.Fork(p)
|
pl, err := tc.Fork(p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, `test/repo2 is forked to <a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>`, pl.Message)
|
assert.Equal(t, `test/repo2 is forked to <a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>`, pl.RichMessage.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Push", func(t *testing.T) {
|
t.Run("Push", func(t *testing.T) {
|
||||||
@@ -62,7 +62,7 @@ func TestTelegramPayload(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>:<a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a>] 2 new commits
|
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>:<a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a>] 2 new commits
|
||||||
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1
|
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1
|
||||||
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1`, pl.Message)
|
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1`, pl.RichMessage.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Issue", func(t *testing.T) {
|
t.Run("Issue", func(t *testing.T) {
|
||||||
@@ -74,13 +74,13 @@ func TestTelegramPayload(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Issue opened: <a href="http://localhost:3000/test/repo/issues/2" rel="nofollow">#2 crash</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Issue opened: <a href="http://localhost:3000/test/repo/issues/2" rel="nofollow">#2 crash</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
||||||
|
|
||||||
issue body`, pl.Message)
|
issue body`, pl.RichMessage.HTML)
|
||||||
|
|
||||||
p.Action = api.HookIssueClosed
|
p.Action = api.HookIssueClosed
|
||||||
pl, err = tc.Issue(p)
|
pl, err = tc.Issue(p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Issue closed: <a href="http://localhost:3000/test/repo/issues/2" rel="nofollow">#2 crash</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
|
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Issue closed: <a href="http://localhost:3000/test/repo/issues/2" rel="nofollow">#2 crash</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.RichMessage.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("IssueComment", func(t *testing.T) {
|
t.Run("IssueComment", func(t *testing.T) {
|
||||||
@@ -90,7 +90,7 @@ issue body`, pl.Message)
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New comment on issue <a href="http://localhost:3000/test/repo/issues/2" rel="nofollow">#2 crash</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New comment on issue <a href="http://localhost:3000/test/repo/issues/2" rel="nofollow">#2 crash</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
||||||
more info needed`, pl.Message)
|
more info needed`, pl.RichMessage.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("PullRequest", func(t *testing.T) {
|
t.Run("PullRequest", func(t *testing.T) {
|
||||||
@@ -100,7 +100,7 @@ more info needed`, pl.Message)
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Pull request opened: <a href="http://localhost:3000/test/repo/pulls/12" rel="nofollow">#12 Fix bug</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Pull request opened: <a href="http://localhost:3000/test/repo/pulls/12" rel="nofollow">#12 Fix bug</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
||||||
fixes bug #2`, pl.Message)
|
fixes bug #2`, pl.RichMessage.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("PullRequestComment", func(t *testing.T) {
|
t.Run("PullRequestComment", func(t *testing.T) {
|
||||||
@@ -110,7 +110,7 @@ fixes bug #2`, pl.Message)
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New comment on pull request <a href="http://localhost:3000/test/repo/pulls/12" rel="nofollow">#12 Fix bug</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New comment on pull request <a href="http://localhost:3000/test/repo/pulls/12" rel="nofollow">#12 Fix bug</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
||||||
changes requested`, pl.Message)
|
changes requested`, pl.RichMessage.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Review", func(t *testing.T) {
|
t.Run("Review", func(t *testing.T) {
|
||||||
@@ -121,7 +121,7 @@ changes requested`, pl.Message)
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, `[test/repo] Pull request review approved: #12 Fix bug
|
assert.Equal(t, `[test/repo] Pull request review approved: #12 Fix bug
|
||||||
good job`, pl.Message)
|
good job`, pl.RichMessage.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Repository", func(t *testing.T) {
|
t.Run("Repository", func(t *testing.T) {
|
||||||
@@ -130,7 +130,7 @@ good job`, pl.Message)
|
|||||||
pl, err := tc.Repository(p)
|
pl, err := tc.Repository(p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Repository created`, pl.Message)
|
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Repository created`, pl.RichMessage.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Package", func(t *testing.T) {
|
t.Run("Package", func(t *testing.T) {
|
||||||
@@ -139,7 +139,7 @@ good job`, pl.Message)
|
|||||||
pl, err := tc.Package(p)
|
pl, err := tc.Package(p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, `Package created: <a href="http://localhost:3000/user1/-/packages/container/GiteaContainer/latest" rel="nofollow">GiteaContainer:latest</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
|
assert.Equal(t, `Package created: <a href="http://localhost:3000/user1/-/packages/container/GiteaContainer/latest" rel="nofollow">GiteaContainer:latest</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.RichMessage.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Wiki", func(t *testing.T) {
|
t.Run("Wiki", func(t *testing.T) {
|
||||||
@@ -149,19 +149,19 @@ good job`, pl.Message)
|
|||||||
pl, err := tc.Wiki(p)
|
pl, err := tc.Wiki(p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New wiki page '<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>' (Wiki change comment) by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
|
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New wiki page '<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>' (Wiki change comment) by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.RichMessage.HTML)
|
||||||
|
|
||||||
p.Action = api.HookWikiEdited
|
p.Action = api.HookWikiEdited
|
||||||
pl, err = tc.Wiki(p)
|
pl, err = tc.Wiki(p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>' edited (Wiki change comment) by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
|
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>' edited (Wiki change comment) by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.RichMessage.HTML)
|
||||||
|
|
||||||
p.Action = api.HookWikiDeleted
|
p.Action = api.HookWikiDeleted
|
||||||
pl, err = tc.Wiki(p)
|
pl, err = tc.Wiki(p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>' deleted by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
|
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>' deleted by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.RichMessage.HTML)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Release", func(t *testing.T) {
|
t.Run("Release", func(t *testing.T) {
|
||||||
@@ -170,7 +170,7 @@ good job`, pl.Message)
|
|||||||
pl, err := tc.Release(p)
|
pl, err := tc.Release(p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Release created: <a href="http://localhost:3000/test/repo/releases/tag/v1.0" rel="nofollow">v1.0</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
|
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Release created: <a href="http://localhost:3000/test/repo/releases/tag/v1.0" rel="nofollow">v1.0</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.RichMessage.HTML)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ func TestTelegramJSONPayload(t *testing.T) {
|
|||||||
RepoID: 3,
|
RepoID: 3,
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
Type: webhook_module.TELEGRAM,
|
Type: webhook_module.TELEGRAM,
|
||||||
URL: "https://telegram.example.com/",
|
URL: "https://telegram.example.com/sendMessage",
|
||||||
Meta: ``,
|
Meta: ``,
|
||||||
HTTPMethod: "POST",
|
HTTPMethod: "POST",
|
||||||
}
|
}
|
||||||
@@ -200,7 +200,7 @@ func TestTelegramJSONPayload(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "POST", req.Method)
|
assert.Equal(t, "POST", req.Method)
|
||||||
assert.Equal(t, "https://telegram.example.com/", req.URL.String())
|
assert.Equal(t, "https://telegram.example.com/sendRichMessage", req.URL.String())
|
||||||
assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
|
assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
|
||||||
assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
|
assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
|
||||||
var body TelegramPayload
|
var body TelegramPayload
|
||||||
@@ -208,5 +208,5 @@ func TestTelegramJSONPayload(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>:<a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a>] 2 new commits
|
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>:<a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a>] 2 new commits
|
||||||
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1
|
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1
|
||||||
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1`, body.Message)
|
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1`, body.RichMessage.HTML)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ base: core24
|
|||||||
adopt-info: gitea
|
adopt-info: gitea
|
||||||
|
|
||||||
platforms:
|
platforms:
|
||||||
armhf:
|
|
||||||
build-on: [armhf]
|
|
||||||
build-for: [armhf]
|
|
||||||
amd64:
|
amd64:
|
||||||
build-on: [amd64]
|
build-on: [amd64]
|
||||||
build-for: [amd64]
|
build-for: [amd64]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user