mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-10 06:53:25 +09:00
Compare commits
41
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ae42ca9c4 | ||
|
|
5e5f5f3116 | ||
|
|
4ce63a1d57 | ||
|
|
07b18467c0 | ||
|
|
e68ee61879 | ||
|
|
0c67849e68 | ||
|
|
762c674bc5 | ||
|
|
8ff71a5e52 | ||
|
|
8343c47bd1 | ||
|
|
c6b2394585 | ||
|
|
4f41ad7b91 | ||
|
|
4812e35486 | ||
|
|
98c61942aa | ||
|
|
cc1df1976b | ||
|
|
1c718da16c | ||
|
|
ce8cf22af9 | ||
|
|
5b9251150c | ||
|
|
1d43b736b5 | ||
|
|
f46c9a9769 | ||
|
|
c9920b7bd0 | ||
|
|
0319358e5e | ||
|
|
9540292596 | ||
|
|
d392fb1438 | ||
|
|
0f5102427e | ||
|
|
cbe1b703dc | ||
|
|
d5e6f273f0 | ||
|
|
15ee850ede | ||
|
|
16c3216dc6 | ||
|
|
b565f3e00a | ||
|
|
122ebcf0a8 | ||
|
|
1b0992eb2e | ||
|
|
c2f130d352 | ||
|
|
2e1be0b114 | ||
|
|
ef927f9fa3 | ||
|
|
59d4825a95 | ||
|
|
10da460c1b | ||
|
|
2003cf4e87 | ||
|
|
736ab982c8 | ||
|
|
08a18d36a6 | ||
|
|
8a6697123f | ||
|
|
2cd4506120 |
@@ -13,7 +13,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: ./.github/actions/free-disk-space
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
@@ -9,7 +9,7 @@ inputs:
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
- if: ${{ inputs.cache == 'true' }}
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
gobuild:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/go-setup
|
||||
- run: make deps-backend deps-tools
|
||||
- run: TAGS="bindata" make backend
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
include:
|
||||
- { tags: "bindata", target: "lint-backend" }
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/go-setup
|
||||
with:
|
||||
lint-cache: "true"
|
||||
|
||||
@@ -12,8 +12,8 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
@@ -20,8 +20,8 @@ jobs:
|
||||
if: github.repository == 'go-gitea/gitea' # prevent running on forks
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: renovatebot/github-action@8217b3fc286df088d7c27f3255fe8414463bc0fd # v46.1.15
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: renovatebot/github-action@6d859fc95779be83a0335ca704879b47e5d79641 # v46.1.16
|
||||
with:
|
||||
renovate-version: ${{ env.RENOVATE_VERSION }}
|
||||
configurationFile: renovate.json5
|
||||
|
||||
@@ -12,8 +12,8 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2.16.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: crowdin/github-action@52aa776766211d83d975df51f3b9c53c2f8ba35f # v2.16.3
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
e2e: ${{ steps.changes.outputs.e2e }}
|
||||
shell: ${{ steps.changes.outputs.shell }}
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
|
||||
id: changes
|
||||
with:
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: go-gitea/giteabot@f8a6f4c14d46920b4b5448852be3de72d00066f0 # v1.0.3
|
||||
- uses: go-gitea/giteabot@912675d47455ac93be82d8bda4667a02b20a6fe4 # v1.0.4
|
||||
with:
|
||||
github_token: ${{ secrets.GITEABOT_TOKEN }}
|
||||
gitea_fork: giteabot/gitea
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
steps:
|
||||
# pull_request_review runs without repository secrets on fork PRs, so fall
|
||||
# back to the workflow token for the non-backport checks handled here.
|
||||
- uses: go-gitea/giteabot@f8a6f4c14d46920b4b5448852be3de72d00066f0 # v1.0.3
|
||||
- uses: go-gitea/giteabot@912675d47455ac93be82d8bda4667a02b20a6fe4 # v1.0.4
|
||||
with:
|
||||
github_token: ${{ secrets.GITEABOT_TOKEN || github.token }}
|
||||
checks: ${{ github.event.inputs.checks || 'labels,merge_queue,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions' }}
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/go-setup
|
||||
with:
|
||||
lint-cache: "true"
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/go-setup
|
||||
with:
|
||||
cache: "false"
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/go-setup
|
||||
- run: make deps-backend deps-tools
|
||||
- run: make --always-make checks-backend # ensure the "go-licenses" make target runs
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/node-setup
|
||||
- run: make deps-frontend
|
||||
- run: make lint-frontend
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/go-setup
|
||||
- run: make deps-backend generate-go
|
||||
# no frontend build here as backend should be able to build, even without any frontend files
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
ports:
|
||||
- "9000:9000"
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/go-setup
|
||||
- uses: ./.github/actions/pgsql-shard
|
||||
with:
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
ports:
|
||||
- "9000:9000"
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/go-setup
|
||||
- uses: ./.github/actions/pgsql-shard
|
||||
with:
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/go-setup
|
||||
- run: make deps-backend
|
||||
- run: make backend
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
ports:
|
||||
- "7700:7700"
|
||||
redis:
|
||||
image: redis:latest@sha256:a505f8b9d8ac3ff7b0848055b4abf1901d6d77606774aa1e38bd37f1197ed2b5
|
||||
image: redis:latest@sha256:c904002d182255b6db3cbe3a1e8ce6c187d15390c39500b59fc07181aabff7bf
|
||||
options: >- # wait until redis has started
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 5s
|
||||
@@ -151,7 +151,7 @@ jobs:
|
||||
ports:
|
||||
- 10000:10000
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/go-setup
|
||||
- name: Add hosts to /etc/hosts
|
||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 minio devstoreaccount1.azurite.local mysql elasticsearch meilisearch smtpimap" | sudo tee -a /etc/hosts'
|
||||
@@ -208,7 +208,7 @@ jobs:
|
||||
- "587:587"
|
||||
- "993:993"
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/go-setup
|
||||
- name: Add hosts to /etc/hosts
|
||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch smtpimap" | sudo tee -a /etc/hosts'
|
||||
@@ -241,7 +241,7 @@ jobs:
|
||||
ports:
|
||||
- 10000:10000
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/go-setup
|
||||
- name: Add hosts to /etc/hosts
|
||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mssql devstoreaccount1.azurite.local" | sudo tee -a /etc/hosts'
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
needs: [files-changed]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/docker-dryrun
|
||||
with:
|
||||
platform: linux/amd64
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
needs: [files-changed]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/docker-dryrun
|
||||
with:
|
||||
platform: linux/arm64
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
needs: [files-changed]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/docker-dryrun
|
||||
with:
|
||||
platform: linux/riscv64
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: ./.github/actions/go-setup
|
||||
- uses: ./.github/actions/node-setup
|
||||
- run: make deps-frontend
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
# Base-branch checkout only: pull_request_target runs with elevated token; never run PR-head code here.
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
|
||||
@@ -17,11 +17,15 @@ jobs:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
|
||||
- name: Install snapcraft
|
||||
run: sudo snap install snapcraft --classic
|
||||
|
||||
- name: Authenticate snapcraft
|
||||
shell: bash
|
||||
run: snapcraft login --with <(printf '%s' "$SNAPCRAFT_STORE_CREDENTIALS")
|
||||
|
||||
- name: Remote build
|
||||
run: |
|
||||
snapcraft remote-build \
|
||||
|
||||
@@ -13,16 +13,17 @@ jobs:
|
||||
runs-on: namespace-profile-gitea-release-binary
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
# 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
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 24
|
||||
@@ -33,6 +34,8 @@ jobs:
|
||||
- run: make release
|
||||
env:
|
||||
TAGS: bindata
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||
- name: import gpg key
|
||||
id: import_gpg
|
||||
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
|
||||
@@ -45,6 +48,7 @@ jobs:
|
||||
GPG_PASSPHRASE: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
||||
run: |
|
||||
for f in dist/release/*; do
|
||||
cosign sign-blob "$f" --bundle "$f.sigstore.json" --yes
|
||||
echo "$GPG_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u "$GPG_FINGERPRINT" --output "$f.asc" "$f"
|
||||
done
|
||||
# clean branch name to get the folder name in S3
|
||||
@@ -75,7 +79,7 @@ jobs:
|
||||
contents: read
|
||||
packages: write # to publish to ghcr.io
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
# 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
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
|
||||
@@ -14,16 +14,17 @@ jobs:
|
||||
runs-on: namespace-profile-gitea-release-binary
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
# 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
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 24
|
||||
@@ -34,6 +35,8 @@ jobs:
|
||||
- run: make release
|
||||
env:
|
||||
TAGS: bindata
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||
- name: import gpg key
|
||||
id: import_gpg
|
||||
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
|
||||
@@ -46,6 +49,7 @@ jobs:
|
||||
GPG_PASSPHRASE: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
||||
run: |
|
||||
for f in dist/release/*; do
|
||||
cosign sign-blob "$f" --bundle "$f.sigstore.json" --yes
|
||||
echo "$GPG_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u "$GPG_FINGERPRINT" --output "$f.asc" "$f"
|
||||
done
|
||||
# clean branch name to get the folder name in S3
|
||||
@@ -86,7 +90,7 @@ jobs:
|
||||
contents: read
|
||||
packages: write # to publish to ghcr.io
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
# 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
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
|
||||
@@ -17,16 +17,17 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # to publish to ghcr.io
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
# 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
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 24
|
||||
@@ -37,6 +38,8 @@ jobs:
|
||||
- run: make release
|
||||
env:
|
||||
TAGS: bindata
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||
- name: import gpg key
|
||||
id: import_gpg
|
||||
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
|
||||
@@ -49,6 +52,7 @@ jobs:
|
||||
GPG_PASSPHRASE: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
||||
run: |
|
||||
for f in dist/release/*; do
|
||||
cosign sign-blob "$f" --bundle "$f.sigstore.json" --yes
|
||||
echo "$GPG_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u "$GPG_FINGERPRINT" --output "$f.asc" "$f"
|
||||
done
|
||||
# clean branch name to get the folder name in S3
|
||||
@@ -89,7 +93,7 @@ jobs:
|
||||
contents: read
|
||||
packages: write # to publish to ghcr.io
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
# 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
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
|
||||
@@ -107,6 +107,7 @@ linters:
|
||||
- -QF1008
|
||||
testifylint:
|
||||
disable:
|
||||
- empty
|
||||
- go-require
|
||||
- require-error
|
||||
usetesting:
|
||||
|
||||
@@ -18,7 +18,7 @@ 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
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.34.1 # renovate: datasource=go
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@v1.9.0 # renovate: datasource=go
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.3.0 # renovate: datasource=go
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.4.0 # 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
|
||||
|
||||
@@ -127,6 +127,7 @@ BINDATA_DEST_WILDCARD := modules/migration/bindata.* modules/public/bindata.* mo
|
||||
GENERATED_GO_DEST := modules/charset/invisible_gen.go modules/charset/ambiguous_gen.go
|
||||
|
||||
SVG_DEST_DIR := public/assets/img/svg
|
||||
SVG_DEST_DIRS := $(SVG_DEST_DIR) options/fileicon
|
||||
|
||||
AIR_TMP_DIR := .air
|
||||
|
||||
@@ -633,10 +634,10 @@ svg: node_modules ## build svg files
|
||||
|
||||
.PHONY: svg-check
|
||||
svg-check: svg
|
||||
@git add $(SVG_DEST_DIR)
|
||||
@diff=$$(git diff --color=always --cached $(SVG_DEST_DIR)); \
|
||||
@git add $(SVG_DEST_DIRS)
|
||||
@diff=$$(git diff --color=always --cached $(SVG_DEST_DIRS)); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make svg' and 'git add $(SVG_DEST_DIR)' and commit the result:"; \
|
||||
echo "Please run 'make svg' and 'git add $(SVG_DEST_DIRS)' and commit the result:"; \
|
||||
printf "%s" "$${diff}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
+12
-1
@@ -203,7 +203,7 @@ func TestCliCmdError(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 1, r.ExitCode)
|
||||
assert.Empty(t, r.Stdout)
|
||||
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr)
|
||||
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n", r.Stderr)
|
||||
|
||||
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }})
|
||||
r, err = runTestApp(app, "./gitea", "test-cmd")
|
||||
@@ -235,3 +235,14 @@ func TestCliCmdBefore(t *testing.T) {
|
||||
assert.Equal(t, "/tmp/any.ini", configValues["before"], "BeforeFunc must be called before preparing config")
|
||||
assert.Equal(t, "/dev/null", configValues["action"])
|
||||
}
|
||||
|
||||
func TestCliCmdCompletion(t *testing.T) {
|
||||
app := newTestApp(cli.Command{
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error { return nil },
|
||||
})
|
||||
res, err := runTestApp(app, "./gitea", "completion", "bash", "--nonexist")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 1, res.ExitCode)
|
||||
assert.Equal(t, "", res.Stdout)
|
||||
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -nonexist\n", res.Stderr)
|
||||
}
|
||||
|
||||
+25
-4
@@ -5,10 +5,10 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
@@ -154,16 +154,37 @@ func NewMainApp(appVer AppVersion) *cli.Command {
|
||||
return app
|
||||
}
|
||||
|
||||
// usageErr marks a usage error already reported by cliOnUsageError, so RunMainApp does not print it again.
|
||||
type usageErr struct{ err error }
|
||||
|
||||
func (e usageErr) Error() string { return e.err.Error() }
|
||||
func (e usageErr) Unwrap() error { return e.err }
|
||||
|
||||
// cliOnUsageError reports usage errors itself instead of letting urfave/cli dump the full help to stdout (since urfave/cli v3.10).
|
||||
func cliOnUsageError(_ context.Context, cmd *cli.Command, err error, _ bool) error {
|
||||
_, _ = fmt.Fprintf(cmd.Root().ErrWriter, "Incorrect Usage: %s\n", err.Error())
|
||||
return usageErr{err}
|
||||
}
|
||||
|
||||
func setCLIOnUsageError(cmd *cli.Command) {
|
||||
_ = cmd.Walk(func(c *cli.Command) error {
|
||||
c.OnUsageError = cliOnUsageError
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func RunMainApp(app *cli.Command, args ...string) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
setCLIOnUsageError(app)
|
||||
// the completion subcommands are built during app.Run, after the Walk above, so cover them via this hook
|
||||
app.ConfigureShellCompletionCommand = setCLIOnUsageError
|
||||
err := app.Run(ctx, args)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(err.Error(), "flag provided but not defined:") {
|
||||
// the cli package should already have output the error message, so just exit
|
||||
cli.OsExiter(1)
|
||||
if _, ok := errors.AsType[usageErr](err); ok {
|
||||
cli.OsExiter(1) // cliOnUsageError already reported it
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintf(app.ErrWriter, "Command error: %v\n", err)
|
||||
|
||||
@@ -762,7 +762,12 @@ LEVEL = Info
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Git config options
|
||||
;; This section only does "set" config, a removed config key from this section won't be removed from git config automatically. The format is `some.configKey = value`.
|
||||
;; The format is `some.configKey = value`.
|
||||
;; These options will be written into the gitconfig file under "[git] HOME_PATH" when Gitea web starts.
|
||||
;; ATTENTION:
|
||||
;; * It only does "set" config, a removed config key from this section won't be removed from git config automatically.
|
||||
;; * Some config options might affect the behavior of git and fail Gitea's git operation,
|
||||
;; make sure you know what you are doing before making changes.
|
||||
;[git.config]
|
||||
;diff.algorithm = histogram
|
||||
;core.logAllRefUpdates = true
|
||||
@@ -2997,6 +3002,10 @@ LEVEL = Info
|
||||
;; Comma-separated list of workflow directories, the first one to exist
|
||||
;; in a repo is used to find Actions workflow files
|
||||
;WORKFLOW_DIRS = .gitea/workflows,.github/workflows
|
||||
;; Comma-separated list of scoped workflow directories in a source repository, the first one to exist is used.
|
||||
;; Files here are picked up only when the repo is registered as a scoped-workflow source; in any other repo they neither run repo-level nor scope-level.
|
||||
;; Must not overlap with WORKFLOW_DIRS. Leave empty so no directory is scanned; no scoped workflows are found or run.
|
||||
;SCOPED_WORKFLOW_DIRS = .gitea/scoped_workflows
|
||||
;; Maximum number of attempts a single workflow run can have. Default value is 50.
|
||||
;MAX_RERUN_ATTEMPTS = 50
|
||||
|
||||
|
||||
@@ -23,17 +23,17 @@ require (
|
||||
github.com/ProtonMail/go-crypto v1.4.1
|
||||
github.com/PuerkitoBio/goquery v1.12.0
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0
|
||||
github.com/alecthomas/chroma/v2 v2.26.1
|
||||
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/service/codecommit v1.34.4
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
github.com/blevesearch/bleve/v2 v2.6.0
|
||||
github.com/bohde/codel v0.2.0
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8
|
||||
github.com/caddyserver/certmagic v0.25.3
|
||||
github.com/caddyserver/certmagic v0.25.4
|
||||
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260309112543-12416315a635
|
||||
github.com/chi-middleware/proxy v1.1.1
|
||||
github.com/dlclark/regexp2/v2 v2.2.1
|
||||
github.com/dlclark/regexp2/v2 v2.2.2
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4
|
||||
@@ -73,7 +73,7 @@ require (
|
||||
github.com/lib/pq v1.12.3
|
||||
github.com/markbates/goth v1.82.0
|
||||
github.com/mattn/go-isatty v0.0.22
|
||||
github.com/mattn/go-sqlite3 v1.14.45
|
||||
github.com/mattn/go-sqlite3 v1.14.47
|
||||
github.com/meilisearch/meilisearch-go v0.36.3
|
||||
github.com/mholt/archives v0.1.5
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
@@ -86,7 +86,7 @@ require (
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/quasoft/websspi v1.1.2
|
||||
github.com/redis/go-redis/v9 v9.20.0
|
||||
github.com/redis/go-redis/v9 v9.21.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
||||
github.com/sassoftware/go-rpmutils v0.4.0
|
||||
@@ -96,15 +96,15 @@ require (
|
||||
github.com/tstranex/u2f v1.0.0
|
||||
github.com/ulikunitz/xz v0.5.15
|
||||
github.com/urfave/cli-docs/v3 v3.1.0
|
||||
github.com/urfave/cli/v3 v3.9.1
|
||||
github.com/urfave/cli/v3 v3.10.0
|
||||
github.com/wneessen/go-mail v0.7.3
|
||||
github.com/yohcop/openid-go v1.0.1
|
||||
github.com/yuin/goldmark v1.8.2
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.38.0
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.42.0
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.5
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/image v0.42.0
|
||||
golang.org/x/image v0.43.0
|
||||
golang.org/x/mod v0.37.0
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
@@ -114,7 +114,7 @@ require (
|
||||
google.golang.org/grpc v1.81.1
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/ini.v1 v1.67.3
|
||||
modernc.org/sqlite v1.52.0
|
||||
modernc.org/sqlite v1.53.0
|
||||
mvdan.cc/xurls/v2 v2.6.0
|
||||
strk.kbt.io/projects/go/libravatar v0.0.0-20260301104140-add494e31dab
|
||||
xorm.io/builder v0.3.13
|
||||
@@ -273,7 +273,7 @@ require (
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.73.0 // indirect
|
||||
modernc.org/libc v1.73.4 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
@@ -76,8 +76,8 @@ github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0/go.mod h1:I28hc9eaiqK
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/chroma/v2 v2.26.1 h1:2X21EdxGZNv5GF9mG5u+uzc02GCFyGxbcBm3Grd9A78=
|
||||
github.com/alecthomas/chroma/v2 v2.26.1/go.mod h1:lxhRRa9H4hPmRLOOdYga4zkQIQjq3dtrrdwQeCfu78Y=
|
||||
github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs=
|
||||
github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
@@ -187,8 +187,8 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8 h1:QN/daUob6cmK8GcdKnwn9+YTlPr1vNj+oeAIiJK6fPc=
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8/go.mod h1:+k1KVKROZocrTLsEQ9PEf9A+8+X8uaVV5iO1ZIOwKYM=
|
||||
github.com/caddyserver/certmagic v0.25.3 h1:mGf5ba8F7xA4c5jfDZZbK2buY1VEkbnwpMDixaju94A=
|
||||
github.com/caddyserver/certmagic v0.25.3/go.mod h1:YVs43D5+H/Dckt4bTga1KSO/xYfFBfVZainGDywYPAA=
|
||||
github.com/caddyserver/certmagic v0.25.4 h1:8eIXh0HC3MsGnNo8One+BCxMGTbe5zb/oz+2KsxBFQg=
|
||||
github.com/caddyserver/certmagic v0.25.4/go.mod h1:YVs43D5+H/Dckt4bTga1KSO/xYfFBfVZainGDywYPAA=
|
||||
github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE=
|
||||
github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
|
||||
@@ -240,8 +240,8 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
|
||||
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0=
|
||||
github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
|
||||
github.com/dlclark/regexp2/v2 v2.2.2 h1:MYWvNYw8okuqNhwTYO587EZMiDruVa2vhV6fsGpfya0=
|
||||
github.com/dlclark/regexp2/v2 v2.2.2/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
@@ -514,8 +514,8 @@ github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/a
|
||||
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-shellwords v1.0.13 h1:DC0OMEpGjm6LfNFU4ckYcvbQKyp2vE8atyFGXNtDcf4=
|
||||
github.com/mattn/go-shellwords v1.0.13/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.45 h1:6KA/spDguL3KV8rnybG7ezSaE4SeMR3KC9VbUoAQaIk=
|
||||
github.com/mattn/go-sqlite3 v1.14.45/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||
github.com/mattn/go-sqlite3 v1.14.47 h1:jOBI62gS7nKeZv+as1oGEy0+1qISgXwH/QBlR6KbfIo=
|
||||
github.com/mattn/go-sqlite3 v1.14.47/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
|
||||
github.com/meilisearch/meilisearch-go v0.36.3 h1:Yx1aTY5jDgtbStPVkhJTDoLnZTy5sejQSPyjfNMy6e4=
|
||||
github.com/meilisearch/meilisearch-go v0.36.3/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM=
|
||||
github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk=
|
||||
@@ -618,8 +618,8 @@ github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4Ul
|
||||
github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw=
|
||||
github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/redis/go-redis/v9 v9.20.0 h1:WnQYxLkgO2xiXTCJY0ldIiI8dNqCDlQAG+AtaH7a2a0=
|
||||
github.com/redis/go-redis/v9 v9.20.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||
github.com/redis/go-redis/v9 v9.21.0 h1:FPBE4hhbAke+TLmcY3WkpbDffJEomdqPn3HYiqAtL9E=
|
||||
github.com/redis/go-redis/v9 v9.21.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||
github.com/redis/rueidis v1.0.71 h1:pODtnAR5GAB7j4ekhldZ29HKOxe4Hph0GTDGk1ayEQY=
|
||||
github.com/redis/rueidis v1.0.71/go.mod h1:lfdcZzJ1oKGKL37vh9fO3ymwt+0TdjkkUCJxbgpmcgQ=
|
||||
github.com/redis/rueidis/rueidiscompat v1.0.71 h1:wNZ//kEjMZgBM0KCk7ncOX8KmAgROU2kDdDNpwheG4w=
|
||||
@@ -709,8 +709,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/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/v3 v3.9.1 h1:OLU13atWZ0M+a4xmyBuBNOLZsSRYXyPeMeNjOvgYP54=
|
||||
github.com/urfave/cli/v3 v3.9.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/urfave/cli/v3 v3.10.0 h1:0aU8yOObVDMkM13Cj4G+zb4P0PdeJMec65f81Ak1ioM=
|
||||
github.com/urfave/cli/v3 v3.10.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
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/go.mod h1:QGhBX0yNbc1J+Mkjcu7z2rpj4B4l+BmDY8gYznPC9sk=
|
||||
@@ -740,8 +740,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/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.38.0 h1:gZSMTTnLcUeY5mH4z3G6GEzbaBTOCUfBCAJXMRyuzEM=
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.38.0/go.mod h1:SKUbKSS59KPt6WeGNJoYF8HDaf/rFMUSITlftj/HkLg=
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.42.0 h1:Bq5YIYgUJVbt4Hbh7ibBwNR4SNEafsyDVhIXl7dXDdg=
|
||||
gitlab.com/gitlab-org/api/client-go/v2 v2.42.0/go.mod h1:SKUbKSS59KPt6WeGNJoYF8HDaf/rFMUSITlftj/HkLg=
|
||||
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/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
@@ -780,8 +780,8 @@ golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
|
||||
golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY=
|
||||
golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
||||
golang.org/x/image v0.43.0 h1:FLxcP4ec2350nTfOC8ysKtqYSIFbk/QGjw1ZHNP4tsY=
|
||||
golang.org/x/image v0.43.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@@ -940,8 +940,8 @@ modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc=
|
||||
modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.73.0 h1:Y/KmTxbIN5T3x+NFjYOzV/+Ha7wKClfIecmTCTuYlqQ=
|
||||
modernc.org/libc v1.73.0/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8=
|
||||
modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA=
|
||||
modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -950,8 +950,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.52.0 h1:p4dhYh2tXZCiyaqHwRVJDjIGKWyXayiQpThxgDzJaxo=
|
||||
modernc.org/sqlite v1.52.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
|
||||
modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M=
|
||||
modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
+21
-10
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -21,6 +22,8 @@ import (
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
webhook_module "gitea.dev/modules/webhook"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ActionRun represents a run of a workflow file
|
||||
@@ -48,6 +51,13 @@ type ActionRun struct {
|
||||
Version int `xorm:"version default 0"` // Status could be updated concomitantly, so an optimistic lock is needed
|
||||
RawConcurrency string // raw concurrency
|
||||
|
||||
// WorkflowRepoID/WorkflowCommitSHA record the (repo, commit) the run's workflow file content came from.
|
||||
// Always filled (repo-level run = the repo itself; scoped run = the source repo).
|
||||
WorkflowRepoID int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
WorkflowCommitSHA string `xorm:"VARCHAR(64) NOT NULL DEFAULT ''"`
|
||||
|
||||
IsScopedRun bool `xorm:"NOT NULL DEFAULT false"` // IsScopedRun explicitly classifies scoped runs.
|
||||
|
||||
// Started and Stopped are identical to the latest attempt after ActionRunAttempt was introduced.
|
||||
// When a rerun creates a new latest attempt, they are reset until the new attempt starts and stops.
|
||||
Started timeutil.TimeStamp
|
||||
@@ -86,7 +96,11 @@ func (run *ActionRun) WorkflowLink() string {
|
||||
if run.Repo == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s/actions/?workflow=%s", run.Repo.Link(), run.WorkflowID)
|
||||
// A scoped run's workflow is disambiguated by its source repo, so carry scoped_workflow_source_repo_id back to the run list
|
||||
if run.IsScopedRun {
|
||||
return fmt.Sprintf("%s/actions/?workflow=%s&scoped_workflow_source_repo_id=%d", run.Repo.Link(), url.QueryEscape(run.WorkflowID), run.WorkflowRepoID)
|
||||
}
|
||||
return fmt.Sprintf("%s/actions/?workflow=%s", run.Repo.Link(), url.QueryEscape(run.WorkflowID))
|
||||
}
|
||||
|
||||
// RefLink return the url of run's ref
|
||||
@@ -264,11 +278,7 @@ func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, er
|
||||
}
|
||||
|
||||
func GetRunByRepoAndIndex(ctx context.Context, repoID, runIndex int64) (*ActionRun, error) {
|
||||
run := &ActionRun{
|
||||
RepoID: repoID,
|
||||
Index: runIndex,
|
||||
}
|
||||
has, err := db.GetEngine(ctx).Get(run)
|
||||
run, has, err := db.Get[ActionRun](ctx, builder.Eq{"repo_id": repoID, "`index`": runIndex})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
@@ -279,9 +289,7 @@ func GetRunByRepoAndIndex(ctx context.Context, repoID, runIndex int64) (*ActionR
|
||||
}
|
||||
|
||||
func GetLatestRun(ctx context.Context, repoID int64) (*ActionRun, error) {
|
||||
run := &ActionRun{
|
||||
RepoID: repoID,
|
||||
}
|
||||
run := &ActionRun{}
|
||||
has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).Desc("index").Get(run)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -295,7 +303,10 @@ func GetWorkflowLatestRun(ctx context.Context, repoID int64, workflowFile, branc
|
||||
var run ActionRun
|
||||
q := db.GetEngine(ctx).Where("repo_id=?", repoID).
|
||||
And("ref = ?", branch).
|
||||
And("workflow_id = ?", workflowFile)
|
||||
And("workflow_id = ?", workflowFile).
|
||||
// TODO: the badge only reflects the repo's own (repo-level) runs; a same-named scoped run must not leak in.
|
||||
// Support a scoped-workflow badge later by making this source-aware.
|
||||
And("is_scoped_run = ?", false)
|
||||
if event != "" {
|
||||
q.And("event = ?", event)
|
||||
}
|
||||
|
||||
@@ -361,6 +361,14 @@ func CollectAllDescendantJobs(parent *ActionRunJob, allJobs []*ActionRunJob) []*
|
||||
return out
|
||||
}
|
||||
|
||||
// hasWaitingJobsToPick reports whether any waiting, unclaimed, non-reusable job
|
||||
// remains in the repo, i.e. work that an idle runner could still pick up.
|
||||
func hasWaitingJobsToPick(ctx context.Context, repoID int64) (bool, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Where("repo_id = ? AND task_id = ? AND status = ? AND is_reusable_caller = ?", repoID, 0, StatusWaiting, false).
|
||||
Exist(&ActionRunJob{})
|
||||
}
|
||||
|
||||
func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, cols ...string) (int64, error) {
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
@@ -385,14 +393,6 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
|
||||
return affected, nil
|
||||
}
|
||||
|
||||
// Reusable workflow caller jobs are never picked up by runners, so they don't need a task-version bump.
|
||||
if statusUpdated && job.Status.IsWaiting() && !job.IsReusableCaller {
|
||||
// if the status of job changes to waiting again, increase tasks version.
|
||||
if err := IncreaseTaskVersion(ctx, job.OwnerID, job.RepoID); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if job.RunID == 0 {
|
||||
var err error
|
||||
if job, err = GetRunJobByRepoAndID(ctx, job.RepoID, job.ID); err != nil {
|
||||
@@ -400,6 +400,37 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
|
||||
}
|
||||
}
|
||||
|
||||
// Reusable workflow caller jobs are never picked up by runners, so they don't need a task-version bump.
|
||||
if statusUpdated && !job.IsReusableCaller {
|
||||
switch {
|
||||
case job.Status.IsWaiting():
|
||||
// A job returning to the waiting queue is work a runner can pick up, so bump the
|
||||
// version to wake idle runners whose tasksVersion already equals latestVersion.
|
||||
if err := IncreaseTaskVersion(ctx, job.OwnerID, job.RepoID); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case job.Status.IsDone():
|
||||
// When a job finishes, bump the version so that idle runners — whose
|
||||
// tasksVersion already equals the current latestVersion — learn that
|
||||
// remaining waiting jobs are still available and attempt PickTask again.
|
||||
// Without this bump, runners that completed their tasks would see
|
||||
// tasksVersion==latestVersion and skip PickTask, leaving the other jobs
|
||||
// permanently unassigned until the version changes for another reason.
|
||||
// Only bump when waiting work actually remains for this repo, otherwise
|
||||
// every job completion would needlessly bump the global version and wake
|
||||
// every idle runner instance-wide for nothing.
|
||||
hasWaiting, err := hasWaitingJobsToPick(ctx, job.RepoID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if hasWaiting {
|
||||
if err := IncreaseTaskVersion(ctx, job.OwnerID, job.RepoID); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if statusUpdated && job.ParentJobID > 0 {
|
||||
// Reusable workflow caller's children cascade their status changes upward to the parent caller.
|
||||
parent, err := GetRunJobByRunAndID(ctx, job.RunID, job.ParentJobID)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/container"
|
||||
"gitea.dev/modules/optional"
|
||||
"gitea.dev/modules/translation"
|
||||
webhook_module "gitea.dev/modules/webhook"
|
||||
|
||||
@@ -61,7 +62,9 @@ type FindRunOptions struct {
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
WorkflowID string
|
||||
Ref string // the commit/tag/… that caused this workflow
|
||||
WorkflowRepoID int64 // source-aware filter: the repo a run's workflow content came from (0 = any)
|
||||
IsScopedRun optional.Option[bool] // is the run from a scoped workflow
|
||||
Ref string // the commit/tag/… that caused this workflow
|
||||
TriggerUserID int64
|
||||
TriggerEvent webhook_module.HookEventType
|
||||
Status []Status
|
||||
@@ -77,6 +80,12 @@ func (opts FindRunOptions) ToConds() builder.Cond {
|
||||
if opts.WorkflowID != "" {
|
||||
cond = cond.And(builder.Eq{"`action_run`.workflow_id": opts.WorkflowID})
|
||||
}
|
||||
if opts.WorkflowRepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"`action_run`.workflow_repo_id": opts.WorkflowRepoID})
|
||||
}
|
||||
if opts.IsScopedRun.Has() {
|
||||
cond = cond.And(builder.Eq{"`action_run`.is_scoped_run": opts.IsScopedRun.Value()})
|
||||
}
|
||||
if opts.TriggerUserID > 0 {
|
||||
cond = cond.And(builder.Eq{"`action_run`.trigger_user_id": opts.TriggerUserID})
|
||||
}
|
||||
@@ -106,6 +115,15 @@ func (opts FindRunOptions) ToJoins() []db.JoinFunc {
|
||||
}
|
||||
|
||||
func (opts FindRunOptions) ToOrders() string {
|
||||
// When scoped to a repo, sort by `index`: it reuses the unique
|
||||
// `repo_index` (repo_id, index) index, so the query seeks repo_id and
|
||||
// walks index descending instead of filesorting all matching rows.
|
||||
// Within a repo `index` is co-monotonic with `id`, so the order is the same.
|
||||
if opts.RepoID > 0 {
|
||||
return "`action_run`.`index` DESC"
|
||||
}
|
||||
// `index` is scoped per repo, so it is meaningless across repos. With no
|
||||
// RepoID, sort by the global, PK-indexed `id` for a deterministic order.
|
||||
return "`action_run`.`id` DESC"
|
||||
}
|
||||
|
||||
@@ -147,9 +165,20 @@ func GetRunBranches(ctx context.Context, repoID int64) ([]string, error) {
|
||||
// GetRunWorkflowIDs returns all distinct WorkflowIDs that have at least
|
||||
// one ActionRun in the given repo.
|
||||
func GetRunWorkflowIDs(ctx context.Context, repoID int64) ([]string, error) {
|
||||
return getRunWorkflowIDs(ctx, repoID, builder.NewCond())
|
||||
}
|
||||
|
||||
// GetRepoRunWorkflowIDs returns all distinct WorkflowIDs that have at least
|
||||
// one repo-level ActionRun in the given repo.
|
||||
func GetRepoRunWorkflowIDs(ctx context.Context, repoID int64) ([]string, error) {
|
||||
return getRunWorkflowIDs(ctx, repoID, builder.Eq{"is_scoped_run": false})
|
||||
}
|
||||
|
||||
func getRunWorkflowIDs(ctx context.Context, repoID int64, extraCond builder.Cond) ([]string, error) {
|
||||
ids := make([]string, 0, 10)
|
||||
cond := builder.Eq{"repo_id": repoID}
|
||||
return ids, db.GetEngine(ctx).Table("action_run").
|
||||
Where(builder.Eq{"repo_id": repoID}).
|
||||
Where(cond.And(extraCond)).
|
||||
Distinct("workflow_id").
|
||||
Cols("workflow_id").
|
||||
Asc("workflow_id").
|
||||
|
||||
@@ -6,10 +6,13 @@ package actions
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/optional"
|
||||
"gitea.dev/modules/translation"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetRunWorkflowIDs(t *testing.T) {
|
||||
@@ -24,6 +27,46 @@ func TestGetRunWorkflowIDs(t *testing.T) {
|
||||
assert.Empty(t, ids)
|
||||
}
|
||||
|
||||
func TestGetRepoRunWorkflowIDs(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
const (
|
||||
repoID = int64(4)
|
||||
repoWorkflowID = "repo-orphan.yaml"
|
||||
scopedWorkflowID = "scoped-only.yaml"
|
||||
sharedWorkflowID = "shared-name.yaml"
|
||||
scopedWorkflowRepo = int64(111)
|
||||
)
|
||||
for _, spec := range []struct {
|
||||
id int64
|
||||
workflowID string
|
||||
workflowRepoID int64
|
||||
isScopedRun bool
|
||||
}{
|
||||
{99811, repoWorkflowID, repoID, false},
|
||||
{99812, scopedWorkflowID, scopedWorkflowRepo, true},
|
||||
{99813, sharedWorkflowID, repoID, false},
|
||||
{99814, sharedWorkflowID, scopedWorkflowRepo, true},
|
||||
} {
|
||||
require.NoError(t, db.Insert(t.Context(), &ActionRun{
|
||||
ID: spec.id,
|
||||
Index: spec.id,
|
||||
RepoID: repoID,
|
||||
OwnerID: 1,
|
||||
TriggerUserID: 1,
|
||||
WorkflowID: spec.workflowID,
|
||||
WorkflowRepoID: spec.workflowRepoID,
|
||||
IsScopedRun: spec.isScopedRun,
|
||||
}))
|
||||
}
|
||||
|
||||
ids, err := GetRepoRunWorkflowIDs(t.Context(), repoID)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, ids, repoWorkflowID)
|
||||
assert.Contains(t, ids, sharedWorkflowID)
|
||||
assert.NotContains(t, ids, scopedWorkflowID)
|
||||
}
|
||||
|
||||
func TestGetStatusInfoList(t *testing.T) {
|
||||
statusInfoList := GetStatusInfoList(t.Context(), translation.MockLocale{})
|
||||
|
||||
@@ -35,3 +78,85 @@ func TestGetStatusInfoList(t *testing.T) {
|
||||
{Status: int(StatusCancelling), StatusName: StatusCancelling.String(), DisplayedStatus: "actions.status.cancelling"},
|
||||
}, statusInfoList)
|
||||
}
|
||||
|
||||
// TestFindRunOptions_WorkflowRepoID: two runs share the bare WorkflowID but come from different content-source repos;
|
||||
// the source-aware WorkflowRepoID filter must separate them.
|
||||
func TestFindRunOptions_WorkflowRepoID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
const (
|
||||
repoID = int64(4)
|
||||
sourceA = int64(111)
|
||||
sourceB = int64(222)
|
||||
workflowID = "u3-shared.yaml"
|
||||
)
|
||||
for _, spec := range []struct{ id, workflowRepoID int64 }{
|
||||
{99801, sourceA},
|
||||
{99802, sourceB},
|
||||
} {
|
||||
require.NoError(t, db.Insert(t.Context(), &ActionRun{
|
||||
ID: spec.id,
|
||||
Index: spec.id,
|
||||
RepoID: repoID,
|
||||
OwnerID: 1,
|
||||
TriggerUserID: 1,
|
||||
WorkflowID: workflowID,
|
||||
WorkflowRepoID: spec.workflowRepoID,
|
||||
IsScopedRun: true,
|
||||
}))
|
||||
}
|
||||
|
||||
// no source filter -> both
|
||||
all, err := db.Find[ActionRun](t.Context(), FindRunOptions{RepoID: repoID, WorkflowID: workflowID})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, all, 2)
|
||||
|
||||
// filter by source A -> only the run whose content came from A
|
||||
onlyA, err := db.Find[ActionRun](t.Context(), FindRunOptions{RepoID: repoID, WorkflowID: workflowID, WorkflowRepoID: sourceA})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, onlyA, 1)
|
||||
assert.EqualValues(t, 99801, onlyA[0].ID)
|
||||
|
||||
// filter by source B -> only the run whose content came from B
|
||||
onlyB, err := db.Find[ActionRun](t.Context(), FindRunOptions{RepoID: repoID, WorkflowID: workflowID, WorkflowRepoID: sourceB})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, onlyB, 1)
|
||||
assert.EqualValues(t, 99802, onlyB[0].ID)
|
||||
}
|
||||
|
||||
func TestFindRunOptions_IsScopedRun(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
const (
|
||||
repoID = int64(4)
|
||||
workflowID = "scoped-flag.yaml"
|
||||
)
|
||||
for _, spec := range []struct {
|
||||
id int64
|
||||
scoped bool
|
||||
}{
|
||||
{99821, false},
|
||||
{99822, true},
|
||||
} {
|
||||
require.NoError(t, db.Insert(t.Context(), &ActionRun{
|
||||
ID: spec.id,
|
||||
Index: spec.id,
|
||||
RepoID: repoID,
|
||||
OwnerID: 1,
|
||||
TriggerUserID: 1,
|
||||
WorkflowID: workflowID,
|
||||
WorkflowRepoID: repoID,
|
||||
IsScopedRun: spec.scoped,
|
||||
}))
|
||||
}
|
||||
|
||||
repoLevel, err := db.Find[ActionRun](t.Context(), FindRunOptions{RepoID: repoID, WorkflowID: workflowID, IsScopedRun: optional.Some(false)})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, repoLevel, 1)
|
||||
assert.EqualValues(t, 99821, repoLevel[0].ID)
|
||||
|
||||
scoped, err := db.Find[ActionRun](t.Context(), FindRunOptions{RepoID: repoID, WorkflowID: workflowID, IsScopedRun: optional.Some(true)})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, scoped, 1)
|
||||
assert.EqualValues(t, 99822, scoped[0].ID)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUpdateRepoRunsNumbers(t *testing.T) {
|
||||
@@ -44,3 +45,57 @@ func TestActionRun_Duration_NonNegative(t *testing.T) {
|
||||
}
|
||||
assert.Equal(t, time.Duration(0), run.Duration())
|
||||
}
|
||||
|
||||
func TestActionRun_WorkflowLink(t *testing.T) {
|
||||
repo := &repo_model.Repository{OwnerName: "org", Name: "consumer"}
|
||||
|
||||
// a repo-level run links by file name only
|
||||
repoLevel := &ActionRun{Repo: repo, WorkflowID: "ci.yaml", WorkflowRepoID: repo.ID}
|
||||
assert.Equal(t, repo.Link()+"/actions/?workflow=ci.yaml", repoLevel.WorkflowLink())
|
||||
|
||||
// a scoped run carries its source repo id back, so the list stays filtered to that source
|
||||
scoped := &ActionRun{Repo: repo, WorkflowID: "ci.yaml", WorkflowRepoID: 42, IsScopedRun: true}
|
||||
assert.Equal(t, repo.Link()+"/actions/?workflow=ci.yaml&scoped_workflow_source_repo_id=42", scoped.WorkflowLink())
|
||||
}
|
||||
|
||||
func TestGetWorkflowLatestRun_RepoLevelOnly(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
const (
|
||||
repoID = int64(4)
|
||||
workflowID = "badge-source-aware.yaml"
|
||||
ref = "refs/heads/main"
|
||||
)
|
||||
require.NoError(t, db.Insert(t.Context(), &ActionRun{
|
||||
ID: 99811,
|
||||
Index: 99811,
|
||||
RepoID: repoID,
|
||||
OwnerID: 1,
|
||||
TriggerUserID: 1,
|
||||
WorkflowID: workflowID,
|
||||
Ref: ref,
|
||||
Event: "push",
|
||||
Status: StatusSuccess,
|
||||
WorkflowRepoID: repoID,
|
||||
WorkflowCommitSHA: "repo-level-sha",
|
||||
}))
|
||||
require.NoError(t, db.Insert(t.Context(), &ActionRun{
|
||||
ID: 99812,
|
||||
Index: 99812,
|
||||
RepoID: repoID,
|
||||
OwnerID: 1,
|
||||
TriggerUserID: 1,
|
||||
WorkflowID: workflowID,
|
||||
Ref: ref,
|
||||
Event: "push",
|
||||
Status: StatusFailure,
|
||||
WorkflowRepoID: 111,
|
||||
WorkflowCommitSHA: "scoped-sha",
|
||||
IsScopedRun: true,
|
||||
}))
|
||||
|
||||
run, err := GetWorkflowLatestRun(t.Context(), repoID, workflowID, ref, "push")
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 99811, run.ID)
|
||||
assert.False(t, run.IsScopedRun)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ActionScopedWorkflowSource registers a repository as a source of scoped workflows, either for an owner (user/org) or for the whole instance.
|
||||
type ActionScopedWorkflowSource struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
|
||||
// OwnerID is the scope the source applies to: a user/org ID (applies to that owner's repos), or 0 for instance-level (applies to every repo).
|
||||
OwnerID int64 `xorm:"UNIQUE(owner_repo) NOT NULL DEFAULT 0"`
|
||||
// SourceRepoID is the source repository providing the workflow files; always non-zero.
|
||||
SourceRepoID int64 `xorm:"INDEX UNIQUE(owner_repo) NOT NULL DEFAULT 0"`
|
||||
|
||||
// WorkflowConfigs maps a workflow ID (entry name) to its merge-gate config.
|
||||
WorkflowConfigs map[string]*ScopedWorkflowConfig `xorm:"JSON TEXT 'workflow_configs'"`
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
||||
// ScopedWorkflowConfig is one scoped workflow's config within a source registration.
|
||||
type ScopedWorkflowConfig struct {
|
||||
Required bool `json:"required"`
|
||||
Patterns []string `json:"patterns"` // the status-check patterns that must be present and pass, only effective when Required is true
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(ActionScopedWorkflowSource))
|
||||
}
|
||||
|
||||
// IsWorkflowRequired reports whether the given workflow ID (entry name) is marked required in this source.
|
||||
func (s *ActionScopedWorkflowSource) IsWorkflowRequired(workflowID string) bool {
|
||||
c, ok := s.WorkflowConfigs[workflowID]
|
||||
return ok && c.Required
|
||||
}
|
||||
|
||||
type FindScopedWorkflowSourceOpts struct {
|
||||
db.ListOptions
|
||||
OwnerIDs []int64
|
||||
SourceRepoID int64
|
||||
}
|
||||
|
||||
func (opts FindScopedWorkflowSourceOpts) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if len(opts.OwnerIDs) > 0 {
|
||||
cond = cond.And(builder.In("owner_id", opts.OwnerIDs))
|
||||
}
|
||||
if opts.SourceRepoID != 0 {
|
||||
cond = cond.And(builder.Eq{"source_repo_id": opts.SourceRepoID})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
// GetEffectiveScopedWorkflowSources returns the scoped-workflow sources effective for a repo owned by repoOwnerID:
|
||||
// the owner's own sources plus instance-level (owner_id=0) sources.
|
||||
func GetEffectiveScopedWorkflowSources(ctx context.Context, repoOwnerID int64) ([]*ActionScopedWorkflowSource, error) {
|
||||
owners := []int64{0}
|
||||
if repoOwnerID != 0 {
|
||||
owners = append(owners, repoOwnerID)
|
||||
}
|
||||
return db.Find[ActionScopedWorkflowSource](ctx, FindScopedWorkflowSourceOpts{OwnerIDs: owners})
|
||||
}
|
||||
|
||||
// IsScopedWorkflowSourceEffective reports whether sourceRepoID is a scoped-workflow source effective for a repo owned by repoOwnerID.
|
||||
func IsScopedWorkflowSourceEffective(ctx context.Context, repoOwnerID, sourceRepoID int64) (bool, error) {
|
||||
owners := []int64{0}
|
||||
if repoOwnerID != 0 {
|
||||
owners = append(owners, repoOwnerID)
|
||||
}
|
||||
return db.Exist[ActionScopedWorkflowSource](ctx, FindScopedWorkflowSourceOpts{OwnerIDs: owners, SourceRepoID: sourceRepoID}.ToConds())
|
||||
}
|
||||
|
||||
// IsWorkflowRequiredInSources reports whether workflowID from sourceRepoID is required by any of the given sources.
|
||||
func IsWorkflowRequiredInSources(sources []*ActionScopedWorkflowSource, sourceRepoID int64, workflowID string) bool {
|
||||
for _, s := range sources {
|
||||
if s.SourceRepoID == sourceRepoID && s.IsWorkflowRequired(workflowID) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ScopedStatusContextPrefix returns the source-repo prefix that makes a scoped run's commit-status context distinct from same-named workflows.
|
||||
func ScopedStatusContextPrefix(ctx context.Context, sourceRepoID int64) string {
|
||||
if sourceRepo, err := repo_model.GetRepositoryByID(ctx, sourceRepoID); err == nil {
|
||||
return sourceRepo.FullName()
|
||||
}
|
||||
return fmt.Sprintf("scoped:%d", sourceRepoID)
|
||||
}
|
||||
|
||||
// IsScopedWorkflowRequired reports whether workflowID from sourceRepoID is required for a repo owned by consumerOwnerID.
|
||||
func IsScopedWorkflowRequired(ctx context.Context, consumerOwnerID, sourceRepoID int64, workflowID string) (bool, error) {
|
||||
sources, err := GetEffectiveScopedWorkflowSources(ctx, consumerOwnerID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return IsWorkflowRequiredInSources(sources, sourceRepoID, workflowID), nil
|
||||
}
|
||||
|
||||
// IsScopedWorkflowOptedOutloads the consumer's effective sources then calls ScopedWorkflowOptedOut
|
||||
func IsScopedWorkflowOptedOut(ctx context.Context, cfg *repo_model.ActionsConfig, consumerOwnerID, sourceRepoID int64, workflowID string) (bool, error) {
|
||||
if !cfg.IsScopedWorkflowDisabled(sourceRepoID, workflowID) {
|
||||
return false, nil
|
||||
}
|
||||
sources, err := GetEffectiveScopedWorkflowSources(ctx, consumerOwnerID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return ScopedWorkflowOptedOut(cfg, sources, sourceRepoID, workflowID), nil
|
||||
}
|
||||
|
||||
// ScopedWorkflowOptedOut reports whether a consumer's opt-out of (sourceRepoID, workflowID) is in effect.
|
||||
func ScopedWorkflowOptedOut(cfg *repo_model.ActionsConfig, sources []*ActionScopedWorkflowSource, sourceRepoID int64, workflowID string) bool {
|
||||
return !IsWorkflowRequiredInSources(sources, sourceRepoID, workflowID) && cfg.IsScopedWorkflowDisabled(sourceRepoID, workflowID)
|
||||
}
|
||||
|
||||
// GetScopedWorkflowSourcesByOwner returns the sources an owner (user/org, or 0 for instance) registered.
|
||||
func GetScopedWorkflowSourcesByOwner(ctx context.Context, ownerID int64) ([]*ActionScopedWorkflowSource, error) {
|
||||
return db.Find[ActionScopedWorkflowSource](ctx, FindScopedWorkflowSourceOpts{OwnerIDs: []int64{ownerID}})
|
||||
}
|
||||
|
||||
// GetScopedWorkflowSource returns the (owner, repo) source registration or a NotExist error.
|
||||
func GetScopedWorkflowSource(ctx context.Context, ownerID, repoID int64) (*ActionScopedWorkflowSource, error) {
|
||||
src := &ActionScopedWorkflowSource{}
|
||||
has, err := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID).Get(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, util.NewNotExistErrorf("scoped workflow source (owner %d, repo %d) does not exist", ownerID, repoID)
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// AddScopedWorkflowSource registers repoID as a source for ownerID (no-op if already registered).
|
||||
func AddScopedWorkflowSource(ctx context.Context, ownerID, repoID int64) error {
|
||||
exists, err := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID).Exist(new(ActionScopedWorkflowSource))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
if err := db.Insert(ctx, &ActionScopedWorkflowSource{OwnerID: ownerID, SourceRepoID: repoID}); err != nil {
|
||||
// Re-check and treat an already-present row as the intended no-op.
|
||||
if exists, existErr := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID).Exist(new(ActionScopedWorkflowSource)); existErr == nil && exists {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetScopedWorkflowSourceConfigs replaces the per-workflow merge-gate configs (workflow ID -> config).
|
||||
func SetScopedWorkflowSourceConfigs(ctx context.Context, ownerID, repoID int64, configs map[string]*ScopedWorkflowConfig) error {
|
||||
_, err := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID).
|
||||
Cols("workflow_configs").
|
||||
Update(&ActionScopedWorkflowSource{WorkflowConfigs: configs})
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveScopedWorkflowSource removes the (owner, repo) source registration.
|
||||
func RemoveScopedWorkflowSource(ctx context.Context, ownerID, repoID int64) error {
|
||||
_, err := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID).Delete(new(ActionScopedWorkflowSource))
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestScopedWorkflowSource_IsWorkflowRequired(t *testing.T) {
|
||||
src := &ActionScopedWorkflowSource{WorkflowConfigs: map[string]*ScopedWorkflowConfig{
|
||||
"a.yml": {Required: true, Patterns: []string{"p"}},
|
||||
"b.yml": {Required: true, Patterns: []string{"p"}},
|
||||
"c.yml": {Required: false, Patterns: []string{"p"}}, // patterns kept as history, not required
|
||||
}}
|
||||
assert.True(t, src.IsWorkflowRequired("a.yml"))
|
||||
assert.True(t, src.IsWorkflowRequired("b.yml"))
|
||||
assert.False(t, src.IsWorkflowRequired("c.yml"), "config kept as history but not required")
|
||||
assert.False(t, src.IsWorkflowRequired("d.yml"))
|
||||
|
||||
empty := &ActionScopedWorkflowSource{}
|
||||
assert.False(t, empty.IsWorkflowRequired("a.yml"))
|
||||
}
|
||||
|
||||
func TestIsWorkflowRequiredInSources(t *testing.T) {
|
||||
// repo 100 registered twice (org optional + instance required).
|
||||
sources := []*ActionScopedWorkflowSource{
|
||||
{OwnerID: 2, SourceRepoID: 100, WorkflowConfigs: nil},
|
||||
{OwnerID: 0, SourceRepoID: 100, WorkflowConfigs: map[string]*ScopedWorkflowConfig{"a.yml": {Required: true, Patterns: []string{"p"}}}},
|
||||
{OwnerID: 0, SourceRepoID: 200, WorkflowConfigs: map[string]*ScopedWorkflowConfig{"b.yml": {Required: true, Patterns: []string{"p"}}}},
|
||||
}
|
||||
|
||||
assert.True(t, IsWorkflowRequiredInSources(sources, 100, "a.yml"), "required at instance level wins over org optional")
|
||||
assert.False(t, IsWorkflowRequiredInSources(sources, 100, "z.yml"))
|
||||
assert.False(t, IsWorkflowRequiredInSources(sources, 200, "a.yml"), "a.yml is required for repo 100, not repo 200")
|
||||
assert.True(t, IsWorkflowRequiredInSources(sources, 200, "b.yml"))
|
||||
assert.False(t, IsWorkflowRequiredInSources(sources, 999, "a.yml"), "unknown source repo")
|
||||
}
|
||||
|
||||
func TestGetEffectiveScopedWorkflowSources(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
ctx := t.Context()
|
||||
|
||||
rows := []*ActionScopedWorkflowSource{
|
||||
{OwnerID: 2, SourceRepoID: 100, WorkflowConfigs: nil}, // org 2 registers repo 100 (optional)
|
||||
{OwnerID: 0, SourceRepoID: 100, WorkflowConfigs: map[string]*ScopedWorkflowConfig{"a.yml": {Required: true, Patterns: []string{"p"}}}}, // instance also registers repo 100 (required)
|
||||
{OwnerID: 0, SourceRepoID: 200, WorkflowConfigs: map[string]*ScopedWorkflowConfig{"b.yml": {Required: true, Patterns: []string{"p"}}}}, // instance source 200
|
||||
{OwnerID: 3, SourceRepoID: 300, WorkflowConfigs: map[string]*ScopedWorkflowConfig{"c.yml": {Required: true, Patterns: []string{"p"}}}}, // a different owner's source
|
||||
}
|
||||
for _, r := range rows {
|
||||
require.NoError(t, db.Insert(ctx, r))
|
||||
}
|
||||
|
||||
// owner 2 sees its own sources plus instance-level ones, but not owner 3's.
|
||||
owner2, err := GetEffectiveScopedWorkflowSources(ctx, 2)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, owner2, 3)
|
||||
|
||||
required, err := IsScopedWorkflowRequired(ctx, 2, 100, "a.yml")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, required, "instance marks a.yml required → required for owner 2 even though org left it optional")
|
||||
|
||||
required, err = IsScopedWorkflowRequired(ctx, 2, 100, "x.yml")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, required)
|
||||
|
||||
required, err = IsScopedWorkflowRequired(ctx, 2, 200, "b.yml")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, required)
|
||||
|
||||
// owner 3's source must not be effective for owner 2.
|
||||
required, err = IsScopedWorkflowRequired(ctx, 2, 300, "c.yml")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, required)
|
||||
|
||||
// IsScopedWorkflowSourceEffective: owner-level and instance-level sources are effective; another owner's is not.
|
||||
effective, err := IsScopedWorkflowSourceEffective(ctx, 2, 100)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, effective, "owner 2's own source")
|
||||
|
||||
effective, err = IsScopedWorkflowSourceEffective(ctx, 2, 200)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, effective, "instance-level source is effective for any owner")
|
||||
|
||||
effective, err = IsScopedWorkflowSourceEffective(ctx, 2, 300)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, effective, "owner 3's source is not effective for owner 2")
|
||||
|
||||
effective, err = IsScopedWorkflowSourceEffective(ctx, 2, 999)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, effective, "unknown source repo")
|
||||
|
||||
effective, err = IsScopedWorkflowSourceEffective(ctx, 3, 300)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, effective, "owner 3's own source is effective for owner 3")
|
||||
}
|
||||
|
||||
func TestScopedWorkflowSourceCRUD(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
ctx := t.Context()
|
||||
|
||||
// add is idempotent
|
||||
require.NoError(t, AddScopedWorkflowSource(ctx, 5, 10))
|
||||
require.NoError(t, AddScopedWorkflowSource(ctx, 5, 10))
|
||||
sources, err := GetScopedWorkflowSourcesByOwner(ctx, 5)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, sources, 1)
|
||||
|
||||
// set the per-workflow configs (entry name -> {required, patterns}); a.yml required, b.yml kept as history (not required)
|
||||
configs := map[string]*ScopedWorkflowConfig{
|
||||
"a.yml": {Required: true, Patterns: []string{"src: a.yml / *"}},
|
||||
"b.yml": {Required: false, Patterns: []string{"src: b.yml / build (push)"}},
|
||||
}
|
||||
require.NoError(t, SetScopedWorkflowSourceConfigs(ctx, 5, 10, configs))
|
||||
src, err := GetScopedWorkflowSource(ctx, 5, 10)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, configs, src.WorkflowConfigs)
|
||||
|
||||
// clearing the configs works
|
||||
require.NoError(t, SetScopedWorkflowSourceConfigs(ctx, 5, 10, nil))
|
||||
src, err = GetScopedWorkflowSource(ctx, 5, 10)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, src.WorkflowConfigs)
|
||||
|
||||
// remove
|
||||
require.NoError(t, RemoveScopedWorkflowSource(ctx, 5, 10))
|
||||
_, err = GetScopedWorkflowSource(ctx, 5, 10)
|
||||
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||
sources, err = GetScopedWorkflowSourcesByOwner(ctx, 5)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, sources)
|
||||
}
|
||||
+137
-71
@@ -227,13 +227,18 @@ func makeTaskStepDisplayName(step *jobparser.Step, limit int) (name string) {
|
||||
return util.EllipsisDisplayString(name, limit) // database column has a length limit
|
||||
}
|
||||
|
||||
func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask, bool, error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer committer.Close()
|
||||
// errJobAlreadyClaimed is a sentinel used inside claimJobForRunner to signal that
|
||||
// another runner won the optimistic-lock race; it is never returned to callers.
|
||||
var errJobAlreadyClaimed = errors.New("job already claimed by another runner")
|
||||
|
||||
// CreateTaskForRunner finds a waiting job that matches the runner's labels and
|
||||
// atomically claims it. It iterates through all matching jobs so that a
|
||||
// concurrent claim by another runner (which would lose the optimistic lock on
|
||||
// job #1) does not leave the remaining jobs permanently unassigned.
|
||||
func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask, bool, error) {
|
||||
if db.InTransaction(ctx) {
|
||||
return nil, false, errors.New("CreateTaskForRunner must not be called within a database transaction")
|
||||
}
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
jobCond := builder.NewCond()
|
||||
@@ -254,83 +259,144 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
|
||||
}
|
||||
|
||||
// TODO: a more efficient way to filter labels
|
||||
var job *ActionRunJob
|
||||
log.Trace("runner labels: %v", runner.AgentLabels)
|
||||
for _, v := range jobs {
|
||||
if runner.CanMatchLabels(v.RunsOn) {
|
||||
job = v
|
||||
break
|
||||
if !runner.CanMatchLabels(v.RunsOn) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if job == nil {
|
||||
return nil, false, nil
|
||||
}
|
||||
if err := job.LoadAttributes(ctx); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
now := timeutil.TimeStampNow()
|
||||
job.Started = now
|
||||
job.Status = StatusRunning
|
||||
|
||||
task := &ActionTask{
|
||||
JobID: job.ID,
|
||||
Attempt: job.Attempt,
|
||||
RunnerID: runner.ID,
|
||||
Started: now,
|
||||
Status: StatusRunning,
|
||||
RepoID: job.RepoID,
|
||||
OwnerID: job.OwnerID,
|
||||
CommitSHA: job.CommitSHA,
|
||||
IsForkPullRequest: job.IsForkPullRequest,
|
||||
}
|
||||
task.GenerateAndFillToken()
|
||||
|
||||
workflowJob, err := job.ParseJob()
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("load job %d: %w", job.ID, err)
|
||||
}
|
||||
|
||||
if _, err := e.Insert(task); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
task.LogFilename = logFileName(job.Run.Repo.FullName(), task.ID)
|
||||
if err := UpdateTask(ctx, task, "log_filename"); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if len(workflowJob.Steps) > 0 {
|
||||
steps := make([]*ActionTaskStep, len(workflowJob.Steps))
|
||||
for i, v := range workflowJob.Steps {
|
||||
steps[i] = &ActionTaskStep{
|
||||
Name: makeTaskStepDisplayName(v, 255),
|
||||
TaskID: task.ID,
|
||||
Index: int64(i),
|
||||
RepoID: task.RepoID,
|
||||
Status: StatusWaiting,
|
||||
}
|
||||
}
|
||||
if _, err := e.Insert(steps); err != nil {
|
||||
task, ok, err := claimJobForRunner(ctx, runner, v)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
task.Steps = steps
|
||||
if ok {
|
||||
return task, true, nil
|
||||
}
|
||||
// Another runner claimed this job concurrently; try the next one.
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
job.TaskID = task.ID
|
||||
if n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}); err != nil {
|
||||
return nil, false, err
|
||||
} else if n != 1 {
|
||||
// claimJobForRunner attempts to atomically claim job for runner inside its own
|
||||
// transaction. Returns (task, true, nil) on success, or (nil, false, nil) when
|
||||
// another runner wins the optimistic-lock race (the caller should try the next
|
||||
// candidate job).
|
||||
func claimJobForRunner(ctx context.Context, runner *ActionRunner, job *ActionRunJob) (*ActionTask, bool, error) {
|
||||
var resultTask *ActionTask
|
||||
|
||||
err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
if err := job.LoadAttributes(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := timeutil.TimeStampNow()
|
||||
job.Started = now
|
||||
job.Status = StatusRunning
|
||||
|
||||
task := &ActionTask{
|
||||
JobID: job.ID,
|
||||
Attempt: job.Attempt,
|
||||
RunnerID: runner.ID,
|
||||
Started: now,
|
||||
Status: StatusRunning,
|
||||
RepoID: job.RepoID,
|
||||
OwnerID: job.OwnerID,
|
||||
CommitSHA: job.CommitSHA,
|
||||
IsForkPullRequest: job.IsForkPullRequest,
|
||||
}
|
||||
task.GenerateAndFillToken()
|
||||
|
||||
workflowJob, err := job.ParseJob()
|
||||
if err != nil {
|
||||
return fmt.Errorf("load job %d: %w", job.ID, err)
|
||||
}
|
||||
|
||||
if _, err := e.Insert(task); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
task.LogFilename = logFileName(job.Run.Repo.FullName(), task.ID)
|
||||
if err := UpdateTask(ctx, task, "log_filename"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(workflowJob.Steps) > 0 {
|
||||
steps := make([]*ActionTaskStep, len(workflowJob.Steps))
|
||||
for i, v := range workflowJob.Steps {
|
||||
steps[i] = &ActionTaskStep{
|
||||
Name: makeTaskStepDisplayName(v, 255),
|
||||
TaskID: task.ID,
|
||||
Index: int64(i),
|
||||
RepoID: task.RepoID,
|
||||
Status: StatusWaiting,
|
||||
}
|
||||
}
|
||||
if _, err := e.Insert(steps); err != nil {
|
||||
return err
|
||||
}
|
||||
task.Steps = steps
|
||||
}
|
||||
|
||||
job.TaskID = task.ID
|
||||
n, err := UpdateRunJob(ctx, job, builder.And(builder.Eq{"task_id": 0}, builder.Eq{"status": StatusWaiting}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != 1 {
|
||||
// Another runner claimed this job between our scan and this update;
|
||||
// signal the outer loop to move on without treating this as an error.
|
||||
return errJobAlreadyClaimed
|
||||
}
|
||||
|
||||
task.Job = job
|
||||
resultTask = task
|
||||
return nil
|
||||
})
|
||||
|
||||
if errors.Is(err, errJobAlreadyClaimed) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
task.Job = job
|
||||
|
||||
if err := committer.Commit(); err != nil {
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return resultTask, true, nil
|
||||
}
|
||||
|
||||
return task, true, nil
|
||||
// ReleaseTaskForRunner reverts a freshly-claimed but undelivered task: it deletes
|
||||
// the task together with its steps and returns the job to the waiting queue. It is
|
||||
// used when assembling the runner response fails after the job was already claimed,
|
||||
// so the job is not stranded in running state with no runner ever executing it.
|
||||
func ReleaseTaskForRunner(ctx context.Context, task *ActionTask) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
job, err := GetRunJobByRepoAndID(ctx, task.RepoID, task.JobID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
job.Status = StatusWaiting
|
||||
job.Started = 0
|
||||
job.TaskID = 0
|
||||
// Guard on task_id and status so we only release while the job still
|
||||
// references this task and has not progressed past running.
|
||||
n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": task.ID, "status": StatusRunning}, "status", "started", "task_id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != 1 {
|
||||
return fmt.Errorf("release task %d: job %d no longer references it", task.ID, task.JobID)
|
||||
}
|
||||
|
||||
if _, err := e.Delete(&ActionTaskStep{TaskID: task.ID}); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := e.ID(task.ID).Delete(&ActionTask{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error {
|
||||
|
||||
@@ -306,3 +306,68 @@ func TestStopTaskCancellingFallsBackForMissingRunner(t *testing.T) {
|
||||
assert.Equal(t, StatusCancelled, jobAfterStop.Status)
|
||||
assert.NotZero(t, jobAfterStop.Stopped)
|
||||
}
|
||||
|
||||
// TestReleaseTaskForRunner verifies that releasing a freshly-claimed task returns
|
||||
// its job to the waiting queue and deletes the task and its steps, so a failure
|
||||
// while assembling the runner response cannot strand the job in running state.
|
||||
func TestReleaseTaskForRunner(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
run := &ActionRun{
|
||||
Title: "release-task-test-run",
|
||||
RepoID: 1,
|
||||
OwnerID: 2,
|
||||
WorkflowID: "test.yaml",
|
||||
Index: 9902,
|
||||
TriggerUserID: 2,
|
||||
Ref: "refs/heads/main",
|
||||
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
|
||||
Event: "push",
|
||||
TriggerEvent: "push",
|
||||
Status: StatusWaiting,
|
||||
}
|
||||
require.NoError(t, db.Insert(t.Context(), run))
|
||||
|
||||
job := &ActionRunJob{
|
||||
RunID: run.ID,
|
||||
RepoID: run.RepoID,
|
||||
OwnerID: run.OwnerID,
|
||||
CommitSHA: run.CommitSHA,
|
||||
Name: "release-job",
|
||||
Attempt: 1,
|
||||
JobID: "release-job",
|
||||
Status: StatusWaiting,
|
||||
RunsOn: []string{"ubuntu-latest"},
|
||||
WorkflowPayload: []byte("on: push\njobs:\n release-job:\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"),
|
||||
}
|
||||
require.NoError(t, db.Insert(t.Context(), job))
|
||||
|
||||
runner := &ActionRunner{
|
||||
UUID: "release-runner-uuid",
|
||||
Name: "release-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: job.ID})
|
||||
require.Equal(t, StatusRunning, claimed.Status)
|
||||
require.Equal(t, task.ID, claimed.TaskID)
|
||||
|
||||
require.NoError(t, ReleaseTaskForRunner(t.Context(), task))
|
||||
|
||||
// Job is back in the waiting queue with no task assigned.
|
||||
released := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID})
|
||||
assert.Equal(t, StatusWaiting, released.Status)
|
||||
assert.Zero(t, released.TaskID)
|
||||
assert.Zero(t, released.Started)
|
||||
|
||||
// The task and its steps are gone.
|
||||
unittest.AssertNotExistsBean(t, &ActionTask{ID: task.ID})
|
||||
unittest.AssertNotExistsBean(t, &ActionTaskStep{TaskID: task.ID})
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// Task represents a task
|
||||
@@ -172,17 +174,13 @@ func (err ErrTaskDoesNotExist) Unwrap() error {
|
||||
|
||||
// GetMigratingTask returns the migrating task by repo's id
|
||||
func GetMigratingTask(ctx context.Context, repoID int64) (*Task, error) {
|
||||
task := Task{
|
||||
RepoID: repoID,
|
||||
Type: structs.TaskTypeMigrateRepo,
|
||||
}
|
||||
has, err := db.GetEngine(ctx).Get(&task)
|
||||
task, has, err := db.Get[Task](ctx, builder.Eq{"repo_id": repoID, "`type`": structs.TaskTypeMigrateRepo})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrTaskDoesNotExist{0, repoID, task.Type}
|
||||
return nil, ErrTaskDoesNotExist{0, repoID, structs.TaskTypeMigrateRepo}
|
||||
}
|
||||
return &task, nil
|
||||
return task, nil
|
||||
}
|
||||
|
||||
// CreateTask creates a task on database
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"gitea.dev/modules/setting"
|
||||
|
||||
"strk.kbt.io/projects/go/libravatar"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -99,12 +100,13 @@ func HashEmail(email string) string {
|
||||
// GetEmailForHash converts a provided md5sum to the email
|
||||
func GetEmailForHash(ctx context.Context, md5Sum string) (string, error) {
|
||||
return cache.GetString("Avatar:"+md5Sum, func() (string, error) {
|
||||
emailHash := EmailHash{
|
||||
Hash: strings.ToLower(strings.TrimSpace(md5Sum)),
|
||||
emailHash, has, err := db.Get[EmailHash](ctx, builder.Eq{"`hash`": strings.ToLower(strings.TrimSpace(md5Sum))})
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if !has {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
_, err := db.GetEngine(ctx).Get(&emailHash)
|
||||
return emailHash.Email, err
|
||||
return emailHash.Email, nil
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -323,13 +323,7 @@ type RenamedBranch struct {
|
||||
|
||||
// FindRenamedBranch check if a branch was renamed
|
||||
func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) {
|
||||
branch = &RenamedBranch{
|
||||
RepoID: repoID,
|
||||
From: from,
|
||||
}
|
||||
exist, err = db.GetEngine(ctx).Get(branch)
|
||||
|
||||
return branch, exist, err
|
||||
return db.Get[RenamedBranch](ctx, builder.Eq{"repo_id": repoID, "`from`": from})
|
||||
}
|
||||
|
||||
// RenameBranch rename a branch
|
||||
|
||||
+1
-2
@@ -126,8 +126,7 @@ func GetLFSMetaObjectByOid(ctx context.Context, repoID int64, oid string) (*LFSM
|
||||
return nil, ErrLFSObjectNotExist
|
||||
}
|
||||
|
||||
m := &LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}, RepositoryID: repoID}
|
||||
has, err := db.GetEngine(ctx).Get(m)
|
||||
m, has, err := db.Get[LFSMetaObject](ctx, builder.Eq{"repository_id": repoID, "oid": oid})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/modules/glob"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ProtectedTag struct
|
||||
@@ -111,8 +113,7 @@ func GetProtectedTagByID(ctx context.Context, id int64) (*ProtectedTag, error) {
|
||||
|
||||
// GetProtectedTagByNamePattern gets protected tag by name_pattern
|
||||
func GetProtectedTagByNamePattern(ctx context.Context, repoID int64, pattern string) (*ProtectedTag, error) {
|
||||
tag := &ProtectedTag{NamePattern: pattern, RepoID: repoID}
|
||||
has, err := db.GetEngine(ctx).Get(tag)
|
||||
tag, has, err := db.Get[ProtectedTag](ctx, builder.Eq{"name_pattern": pattern, "repo_id": repoID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -361,8 +361,8 @@ func (issue *Issue) ResetAttributesLoaded() {
|
||||
|
||||
// GetIsRead load the `IsRead` field of the issue
|
||||
func (issue *Issue) GetIsRead(ctx context.Context, userID int64) error {
|
||||
issueUser := &IssueUser{IssueID: issue.ID, UID: userID}
|
||||
if has, err := db.GetEngine(ctx).Get(issueUser); err != nil {
|
||||
issueUser, has, err := db.Get[IssueUser](ctx, builder.Eq{"issue_id": issue.ID, "uid": userID})
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
issue.IsRead = false
|
||||
@@ -499,11 +499,7 @@ func GetIssueByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
|
||||
if index < 1 {
|
||||
return nil, ErrIssueNotExist{}
|
||||
}
|
||||
issue := &Issue{
|
||||
RepoID: repoID,
|
||||
Index: index,
|
||||
}
|
||||
has, err := db.GetEngine(ctx).Get(issue)
|
||||
issue, has, err := db.Get[Issue](ctx, builder.Eq{"repo_id": repoID, "`index`": index})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
|
||||
@@ -599,7 +599,7 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u
|
||||
resolved[issue.Repo.Owner.LowerName+"/"+team.LowerName] = true
|
||||
continue
|
||||
}
|
||||
has, err := db.GetEngine(ctx).Get(&organization.TeamUnit{OrgID: issue.Repo.Owner.ID, TeamID: team.ID, Type: unittype})
|
||||
has, err := db.Exist[organization.TeamUnit](ctx, builder.Eq{"org_id": issue.Repo.Owner.ID, "team_id": team.ID, "`type`": unittype})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get team units (%d): %w", team.ID, err)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/references"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type crossReference struct {
|
||||
@@ -189,11 +191,11 @@ func (issue *Issue) updateCrossReferenceList(list []*crossReference, xref *cross
|
||||
func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossReferencesContext, repo *repo_model.Repository,
|
||||
ref references.IssueReference,
|
||||
) (*Issue, references.XRefAction, error) {
|
||||
refIssue := &Issue{RepoID: repo.ID, Index: ref.Index}
|
||||
refAction := ref.Action
|
||||
e := db.GetEngine(stdCtx)
|
||||
|
||||
if has, _ := e.Get(refIssue); !has {
|
||||
refIssue, has, err := db.Get[Issue](stdCtx, builder.Eq{"repo_id": repo.ID, "`index`": ref.Index})
|
||||
if err != nil {
|
||||
return nil, references.XRefActionNone, err
|
||||
} else if !has {
|
||||
return nil, references.XRefActionNone, nil
|
||||
}
|
||||
if err := refIssue.LoadRepo(stdCtx); err != nil {
|
||||
|
||||
@@ -535,12 +535,8 @@ func GetPullRequestByIndex(ctx context.Context, repoID, index int64) (*PullReque
|
||||
if index < 1 {
|
||||
return nil, ErrPullRequestNotExist{}
|
||||
}
|
||||
pr := &PullRequest{
|
||||
BaseRepoID: repoID,
|
||||
Index: index,
|
||||
}
|
||||
|
||||
has, err := db.GetEngine(ctx).Get(pr)
|
||||
pr, has, err := db.Get[PullRequest](ctx, builder.Eq{"base_repo_id": repoID, "`index`": index})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
|
||||
@@ -418,6 +418,8 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(338, "Expand legacy MSSQL issue/comment long-text columns", v1_27.ExpandIssueAndCommentLongTextFieldsForMSSQL),
|
||||
newMigration(339, "Extend action c_u index to include created_unix for faster dashboard feed queries", v1_27.AddCreatedUnixToActionUserIsDeletedIndex),
|
||||
newMigration(340, "Add ContinueOnError column to ActionRunJob", v1_27.AddContinueOnErrorToActionRunJob),
|
||||
newMigration(341, "Convert legacy MSSQL DATETIME columns to DATETIME2", v1_27.FixLegacyMSSQLDateTimeColumns),
|
||||
newMigration(342, "Add scoped workflows schema", v1_27.AddScopedWorkflowsSchema),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/migrations/base"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// legacyDateTimeColumns are the persisted real datetime columns that old Gitea
|
||||
// versions created as MSSQL DATETIME. Every other time value is stored as a
|
||||
// unix timestamp integer, so these are the only columns affected.
|
||||
var legacyDateTimeColumns = []struct {
|
||||
bean any
|
||||
column string
|
||||
}{
|
||||
{new(externalLoginUserWithExpiresAt), "expires_at"},
|
||||
{new(lfsLockWithCreated), "created"},
|
||||
}
|
||||
|
||||
type externalLoginUserWithExpiresAt struct {
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
func (externalLoginUserWithExpiresAt) TableName() string {
|
||||
return "external_login_user"
|
||||
}
|
||||
|
||||
type lfsLockWithCreated struct {
|
||||
Created time.Time `xorm:"created"`
|
||||
}
|
||||
|
||||
func (lfsLockWithCreated) TableName() string {
|
||||
return "lfs_lock"
|
||||
}
|
||||
|
||||
// FixLegacyMSSQLDateTimeColumns converts legacy locale-dependent DATETIME columns
|
||||
// to DATETIME2. Databases created by old Gitea versions stored these columns as
|
||||
// DATETIME, which fails to parse ISO datetime strings ('YYYY-MM-DD HH:MM:SS')
|
||||
// when the MSSQL session language is not English, breaking external account
|
||||
// linking and LFS lock creation. New installs already use DATETIME2, so only
|
||||
// legacy MSSQL columns need converting.
|
||||
func FixLegacyMSSQLDateTimeColumns(x db.EngineMigration) error {
|
||||
if x.Dialect().URI().DBType != schemas.MSSQL {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, c := range legacyDateTimeColumns {
|
||||
table, err := x.TableInfo(c.bean)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var dataType string
|
||||
has, err := x.SQL("SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ?", table.Name, c.column).Get(&dataType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has || !strings.EqualFold(dataType, "datetime") {
|
||||
continue
|
||||
}
|
||||
|
||||
column := table.GetColumn(c.column)
|
||||
if column == nil {
|
||||
return fmt.Errorf("column %s does not exist in table %s", c.column, table.Name)
|
||||
}
|
||||
if err := base.ModifyColumn(x, table.Name, column); err != nil {
|
||||
return fmt.Errorf("modify %s.%s: %w", table.Name, c.column, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/migrations/migrationtest"
|
||||
"gitea.dev/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type externalLoginUserBeforeDateTimeMigration struct {
|
||||
ExternalID string `xorm:"pk NOT NULL"`
|
||||
LoginSourceID int64 `xorm:"pk NOT NULL"`
|
||||
ExpiresAt time.Time // sync creates DATETIME2; downgraded to legacy DATETIME via raw SQL below
|
||||
}
|
||||
|
||||
func (externalLoginUserBeforeDateTimeMigration) TableName() string {
|
||||
return "external_login_user"
|
||||
}
|
||||
|
||||
type lfsLockBeforeDateTimeMigration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Created time.Time `xorm:"created"`
|
||||
}
|
||||
|
||||
func (lfsLockBeforeDateTimeMigration) TableName() string {
|
||||
return "lfs_lock"
|
||||
}
|
||||
|
||||
func Test_FixLegacyMSSQLDateTimeColumns(t *testing.T) {
|
||||
if !setting.Database.Type.IsMSSQL() {
|
||||
t.Skip("Only MSSQL needs to convert the legacy locale-dependent DATETIME columns")
|
||||
}
|
||||
|
||||
x, deferrable := migrationtest.PrepareTestEnv(t, 0,
|
||||
new(externalLoginUserBeforeDateTimeMigration),
|
||||
new(lfsLockBeforeDateTimeMigration),
|
||||
)
|
||||
defer deferrable()
|
||||
|
||||
// Force the legacy DATETIME column type that old Gitea versions created.
|
||||
_, err := x.Exec("ALTER TABLE [external_login_user] ALTER COLUMN [expires_at] DATETIME")
|
||||
require.NoError(t, err)
|
||||
_, err = x.Exec("ALTER TABLE [lfs_lock] ALTER COLUMN [created] DATETIME")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "datetime", mssqlColumnType(t, x, "external_login_user", "expires_at"))
|
||||
require.Equal(t, "datetime", mssqlColumnType(t, x, "lfs_lock", "created"))
|
||||
|
||||
require.NoError(t, FixLegacyMSSQLDateTimeColumns(x))
|
||||
require.NoError(t, FixLegacyMSSQLDateTimeColumns(x)) // idempotent
|
||||
|
||||
require.Equal(t, "datetime2", mssqlColumnType(t, x, "external_login_user", "expires_at"))
|
||||
require.Equal(t, "datetime2", mssqlColumnType(t, x, "lfs_lock", "created"))
|
||||
|
||||
// Inserting an ISO-formatted datetime must succeed even under a non-English
|
||||
// locale, which is the failure the legacy DATETIME columns produced. The
|
||||
// SET LANGUAGE and INSERT run in one Exec so they share a single connection.
|
||||
_, err = x.Exec("SET LANGUAGE German; " +
|
||||
"INSERT INTO [external_login_user] ([external_id], [login_source_id], [expires_at]) " +
|
||||
"VALUES ('ext-id', 1, '2026-06-25 11:58:39')")
|
||||
require.NoError(t, err)
|
||||
_, err = x.Exec("SET LANGUAGE German; " +
|
||||
"INSERT INTO [lfs_lock] ([created]) VALUES ('2026-06-25 11:58:39')")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func mssqlColumnType(t *testing.T, x db.EngineMigration, table, column string) string {
|
||||
t.Helper()
|
||||
var dataType string
|
||||
has, err := x.SQL("SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ?", table, column).Get(&dataType)
|
||||
require.NoError(t, err)
|
||||
require.True(t, has)
|
||||
return dataType
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddScopedWorkflowsSchema(x db.EngineMigration) error {
|
||||
// Create the action_scoped_workflow_source table
|
||||
type ScopedWorkflowConfig struct {
|
||||
Required bool `json:"required"`
|
||||
Patterns []string `json:"patterns"`
|
||||
}
|
||||
type ActionScopedWorkflowSource struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OwnerID int64 `xorm:"UNIQUE(owner_repo) NOT NULL DEFAULT 0"`
|
||||
SourceRepoID int64 `xorm:"INDEX UNIQUE(owner_repo) NOT NULL DEFAULT 0"`
|
||||
WorkflowConfigs map[string]*ScopedWorkflowConfig `xorm:"JSON TEXT 'workflow_configs'"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
if err := x.Sync(new(ActionScopedWorkflowSource)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the columns that record where a run's workflow content came from
|
||||
type ActionRun struct {
|
||||
WorkflowRepoID int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
WorkflowCommitSHA string `xorm:"VARCHAR(64) NOT NULL DEFAULT ''"`
|
||||
IsScopedRun bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
IgnoreConstrains: true,
|
||||
}, new(ActionRun))
|
||||
return err
|
||||
}
|
||||
@@ -411,11 +411,8 @@ func GetOrgByName(ctx context.Context, name string) (*Organization, error) {
|
||||
if len(name) == 0 {
|
||||
return nil, ErrOrgNotExist{0, name}
|
||||
}
|
||||
u := &Organization{
|
||||
LowerName: strings.ToLower(name),
|
||||
Type: user_model.UserTypeOrganization,
|
||||
}
|
||||
has, err := db.GetEngine(ctx).Get(u)
|
||||
|
||||
u, has, err := db.Get[Organization](ctx, builder.Eq{"lower_name": strings.ToLower(name), "`type`": user_model.UserTypeOrganization})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
|
||||
@@ -58,7 +58,7 @@ func GetProperties(ctx context.Context, refType PropertyType, refID int64) ([]*P
|
||||
// GetPropertiesByName gets all properties with a specific name
|
||||
func GetPropertiesByName(ctx context.Context, refType PropertyType, refID int64, name string) ([]*PackageProperty, error) {
|
||||
pps := make([]*PackageProperty, 0, 10)
|
||||
return pps, db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ? AND name = ?", refType, refID, name).OrderBy("id").Find(&pps)
|
||||
return pps, db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ? AND `name` = ?", refType, refID, name).OrderBy("id").Find(&pps)
|
||||
}
|
||||
|
||||
// UpdateProperty updates a property
|
||||
@@ -68,13 +68,12 @@ func UpdateProperty(ctx context.Context, pp *PackageProperty) error {
|
||||
}
|
||||
|
||||
func InsertOrUpdateProperty(ctx context.Context, refType PropertyType, refID int64, name, value string) error {
|
||||
pp := PackageProperty{RefType: refType, RefID: refID, Name: name}
|
||||
ok, err := db.GetEngine(ctx).Get(&pp)
|
||||
pp, ok, err := db.Get[PackageProperty](ctx, builder.Eq{"ref_type": refType, "ref_id": refID, "`name`": name})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
_, err = db.GetEngine(ctx).Where("ref_type=? AND ref_id=? AND name=?", refType, refID, name).Cols("value").Update(&PackageProperty{Value: value})
|
||||
_, err = db.GetEngine(ctx).ID(pp.ID).Cols("value").Update(&PackageProperty{Value: value})
|
||||
return err
|
||||
}
|
||||
_, err = InsertProperty(ctx, refType, refID, name, value)
|
||||
|
||||
@@ -164,9 +164,7 @@ func DeleteVersionsByPackageID(ctx context.Context, packageID int64) error {
|
||||
|
||||
// HasVersionFileReferences checks if there are associated files
|
||||
func HasVersionFileReferences(ctx context.Context, versionID int64) (bool, error) {
|
||||
return db.GetEngine(ctx).Get(&PackageFile{
|
||||
VersionID: versionID,
|
||||
})
|
||||
return db.Exist[PackageFile](ctx, builder.Eq{"version_id": versionID})
|
||||
}
|
||||
|
||||
// SearchValue describes a value to search
|
||||
|
||||
@@ -95,6 +95,40 @@ func TestGetActionsUserRepoPermission(t *testing.T) {
|
||||
assert.False(t, perm.CanRead(unit.TypeCode))
|
||||
})
|
||||
|
||||
t.Run("CollaborativeOwner_ForkPR_Denied", func(t *testing.T) {
|
||||
// Target repo15 trusts repo2's owner as a collaborative owner.
|
||||
repo15ActionsUnit := repo15.MustGetUnit(ctx, unit.TypeActions)
|
||||
repo15ActionsUnit.ActionsConfig().AddCollaborativeOwner(owner2.ID)
|
||||
require.NoError(t, repo_model.UpdateRepoUnitConfig(ctx, repo15ActionsUnit))
|
||||
|
||||
// Owner cross-repo policy does not allow repo15, so the only branch that
|
||||
// could grant access is the collaborative-owner one.
|
||||
require.NoError(t, actions_model.SetOwnerActionsConfig(ctx, owner2.ID, actions_model.OwnerActionsConfig{}))
|
||||
|
||||
task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53})
|
||||
|
||||
// Non-fork task is legitimately granted code-read via collaborative owner.
|
||||
task53.IsForkPullRequest = false
|
||||
require.NoError(t, actions_model.UpdateTask(ctx, task53, "is_fork_pull_request"))
|
||||
perm, err := GetActionsUserRepoPermission(ctx, repo15, actionsUser, task53.ID)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, perm.CanRead(unit.TypeCode))
|
||||
|
||||
// Fork PR must NOT be able to read a third private repo through the
|
||||
// collaborative-owner branch.
|
||||
task53.IsForkPullRequest = true
|
||||
require.NoError(t, actions_model.UpdateTask(ctx, task53, "is_fork_pull_request"))
|
||||
perm, err = GetActionsUserRepoPermission(ctx, repo15, actionsUser, task53.ID)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, perm.CanRead(unit.TypeCode))
|
||||
|
||||
// Restore state for subsequent subtests.
|
||||
task53.IsForkPullRequest = false
|
||||
require.NoError(t, actions_model.UpdateTask(ctx, task53, "is_fork_pull_request"))
|
||||
repo15ActionsUnit.ActionsConfig().RemoveCollaborativeOwner(owner2.ID)
|
||||
require.NoError(t, repo_model.UpdateRepoUnitConfig(ctx, repo15ActionsUnit))
|
||||
})
|
||||
|
||||
t.Run("Inheritance_And_Clamping", func(t *testing.T) {
|
||||
task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53})
|
||||
task53.IsForkPullRequest = false
|
||||
|
||||
@@ -369,7 +369,9 @@ func GetActionsUserRepoPermission(ctx context.Context, repo *repo_model.Reposito
|
||||
// 2. The Actions Bot user has been explicitly granted access and repository is private
|
||||
// 3. The repository is public (handled by botPerm above)
|
||||
|
||||
if taskRepo.IsPrivate {
|
||||
// Fork PRs are never allowed cross-repo access to other private repositories,
|
||||
// matching the discriminator enforced by checkSameOwnerCrossRepoAccess above.
|
||||
if taskRepo.IsPrivate && !task.IsForkPullRequest {
|
||||
actionsUnit := repo.MustGetUnit(ctx, unit.TypeActions)
|
||||
if actionsUnit.ActionsConfig().IsCollaborativeOwner(taskRepo.OwnerID) {
|
||||
return maxPerm, nil
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ViewedState stores for a file in which state it is currently viewed
|
||||
@@ -66,9 +68,14 @@ func (rs *ReviewState) GetViewedFileCount() int {
|
||||
// If the review didn't exist before in the database, it won't afterwards either.
|
||||
// The returned boolean shows whether the review exists in the database
|
||||
func GetReviewState(ctx context.Context, userID, pullID int64, commitSHA string) (*ReviewState, bool, error) {
|
||||
review := &ReviewState{UserID: userID, PullID: pullID, CommitSHA: commitSHA}
|
||||
has, err := db.GetEngine(ctx).Get(review)
|
||||
return review, has, err
|
||||
review, has, err := db.Get[ReviewState](ctx, builder.Eq{"user_id": userID, "pull_id": pullID, "commit_sha": commitSHA})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if review == nil {
|
||||
review = &ReviewState{UserID: userID, PullID: pullID, CommitSHA: commitSHA}
|
||||
}
|
||||
return review, has, nil
|
||||
}
|
||||
|
||||
// UpdateReviewState updates the given review inside the database, regardless of whether it existed before or not
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
"gitea.dev/modules/storage"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// Attachment represent a attachment of issue/comment/release.
|
||||
@@ -156,8 +158,7 @@ func GetAttachmentsByCommentID(ctx context.Context, commentID int64) ([]*Attachm
|
||||
|
||||
// GetAttachmentByReleaseIDFileName returns attachment by given releaseId and fileName.
|
||||
func GetAttachmentByReleaseIDFileName(ctx context.Context, releaseID int64, fileName string) (*Attachment, error) {
|
||||
attach := &Attachment{ReleaseID: releaseID, Name: fileName}
|
||||
has, err := db.GetEngine(ctx).Get(attach)
|
||||
attach, has, err := db.Get[Attachment](ctx, builder.Eq{"release_id": releaseID, "`name`": fileName})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
|
||||
@@ -102,20 +102,13 @@ func GetCollaborators(ctx context.Context, opts *FindCollaborationOptions) ([]*C
|
||||
|
||||
// GetCollaboration get collaboration for a repository id with a user id
|
||||
func GetCollaboration(ctx context.Context, repoID, uid int64) (*Collaboration, error) {
|
||||
collaboration := &Collaboration{
|
||||
RepoID: repoID,
|
||||
UserID: uid,
|
||||
}
|
||||
has, err := db.GetEngine(ctx).Get(collaboration)
|
||||
if !has {
|
||||
collaboration = nil
|
||||
}
|
||||
collaboration, _, err := db.Get[Collaboration](ctx, builder.Eq{"repo_id": repoID, "user_id": uid})
|
||||
return collaboration, err
|
||||
}
|
||||
|
||||
// IsCollaborator check if a user is a collaborator of a repository
|
||||
func IsCollaborator(ctx context.Context, repoID, userID int64) (bool, error) {
|
||||
return db.GetEngine(ctx).Get(&Collaboration{RepoID: repoID, UserID: userID})
|
||||
return db.Exist[Collaboration](ctx, builder.Eq{"repo_id": repoID, "user_id": userID})
|
||||
}
|
||||
|
||||
// ChangeCollaborationAccessMode sets new access mode for the collaboration.
|
||||
@@ -126,13 +119,7 @@ func ChangeCollaborationAccessMode(ctx context.Context, repo *Repository, uid in
|
||||
}
|
||||
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
collaboration := &Collaboration{
|
||||
RepoID: repo.ID,
|
||||
UserID: uid,
|
||||
}
|
||||
has, err := e.Get(collaboration)
|
||||
collaboration, has, err := db.Get[Collaboration](ctx, builder.Eq{"repo_id": repo.ID, "user_id": uid})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get collaboration: %w", err)
|
||||
} else if !has {
|
||||
@@ -144,12 +131,12 @@ func ChangeCollaborationAccessMode(ctx context.Context, repo *Repository, uid in
|
||||
}
|
||||
collaboration.Mode = mode
|
||||
|
||||
if _, err = e.
|
||||
if _, err = db.GetEngine(ctx).
|
||||
ID(collaboration.ID).
|
||||
Cols("mode").
|
||||
Update(collaboration); err != nil {
|
||||
return fmt.Errorf("update collaboration: %w", err)
|
||||
} else if _, err = e.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, uid, repo.ID); err != nil {
|
||||
} else if _, err = db.Exec(ctx, "UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, uid, repo.ID); err != nil {
|
||||
return fmt.Errorf("update access table: %w", err)
|
||||
}
|
||||
|
||||
@@ -174,5 +161,5 @@ func IsOwnerMemberCollaborator(ctx context.Context, repo *Repository, userID int
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return db.GetEngine(ctx).Get(&Collaboration{RepoID: repo.ID, UserID: userID})
|
||||
return db.Exist[Collaboration](ctx, builder.Eq{"repo_id": repo.ID, "user_id": userID})
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ErrMirrorNotExist mirror does not exist error
|
||||
@@ -76,8 +78,7 @@ func (m *Mirror) ScheduleNextUpdate() {
|
||||
|
||||
// GetMirrorByRepoID returns mirror information of a repository.
|
||||
func GetMirrorByRepoID(ctx context.Context, repoID int64) (*Mirror, error) {
|
||||
m := &Mirror{RepoID: repoID}
|
||||
has, err := db.GetEngine(ctx).Get(m)
|
||||
m, has, err := db.Get[Mirror](ctx, builder.Eq{"repo_id": repoID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ErrRedirectNotExist represents a "RedirectNotExist" kind of error.
|
||||
@@ -52,8 +54,8 @@ func init() {
|
||||
// LookupRedirect look up if a repository has a redirect name
|
||||
func LookupRedirect(ctx context.Context, ownerID int64, repoName string) (int64, error) {
|
||||
repoName = strings.ToLower(repoName)
|
||||
redirect := &Redirect{OwnerID: ownerID, LowerName: repoName}
|
||||
if has, err := db.GetEngine(ctx).Get(redirect); err != nil {
|
||||
redirect, has, err := db.Get[Redirect](ctx, builder.Eq{"owner_id": ownerID, "lower_name": repoName})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if !has {
|
||||
return 0, ErrRedirectNotExist{OwnerID: ownerID, RepoName: repoName}
|
||||
|
||||
@@ -216,8 +216,7 @@ func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs
|
||||
|
||||
// GetRelease returns release by given ID.
|
||||
func GetRelease(ctx context.Context, repoID int64, tagName string) (*Release, error) {
|
||||
rel := &Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}
|
||||
has, err := db.GetEngine(ctx).Get(rel)
|
||||
rel, has, err := db.Get[Release](ctx, builder.Eq{"repo_id": repoID, "lower_tag_name": strings.ToLower(tagName)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
|
||||
+3
-3
@@ -849,9 +849,9 @@ func GetRepositoriesMapByIDs(ctx context.Context, ids []int64) (map[int64]*Repos
|
||||
}
|
||||
|
||||
func IsRepositoryModelExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
|
||||
return db.GetEngine(ctx).Get(&Repository{
|
||||
OwnerID: u.ID,
|
||||
LowerName: strings.ToLower(repoName),
|
||||
return db.Exist[Repository](ctx, builder.Eq{
|
||||
"owner_id": u.ID,
|
||||
"lower_name": strings.ToLower(repoName),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@ func MakeRestrictedPermissions() ActionsTokenPermissions {
|
||||
|
||||
type ActionsConfig struct {
|
||||
DisabledWorkflows []string
|
||||
// DisabledScopedWorkflows maps a scoped workflow's source repository ID to the entry names opted out of in this repository.
|
||||
DisabledScopedWorkflows map[int64][]string
|
||||
// CollaborativeOwnerIDs is a list of owner IDs used to share actions from private repos.
|
||||
// Only workflows from the private repos whose owners are in CollaborativeOwnerIDs can access the current repo's actions.
|
||||
CollaborativeOwnerIDs []int64
|
||||
@@ -98,6 +100,29 @@ func (cfg *ActionsConfig) DisableWorkflow(file string) {
|
||||
cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file)
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) IsScopedWorkflowDisabled(sourceRepoID int64, workflowID string) bool {
|
||||
return slices.Contains(cfg.DisabledScopedWorkflows[sourceRepoID], workflowID)
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) DisableScopedWorkflow(sourceRepoID int64, workflowID string) {
|
||||
if slices.Contains(cfg.DisabledScopedWorkflows[sourceRepoID], workflowID) {
|
||||
return
|
||||
}
|
||||
if cfg.DisabledScopedWorkflows == nil {
|
||||
cfg.DisabledScopedWorkflows = make(map[int64][]string)
|
||||
}
|
||||
cfg.DisabledScopedWorkflows[sourceRepoID] = append(cfg.DisabledScopedWorkflows[sourceRepoID], workflowID)
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) EnableScopedWorkflow(sourceRepoID int64, workflowID string) {
|
||||
workflowIDs := util.SliceRemoveAll(cfg.DisabledScopedWorkflows[sourceRepoID], workflowID)
|
||||
if len(workflowIDs) == 0 {
|
||||
delete(cfg.DisabledScopedWorkflows, sourceRepoID)
|
||||
return
|
||||
}
|
||||
cfg.DisabledScopedWorkflows[sourceRepoID] = workflowIDs
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) AddCollaborativeOwner(ownerID int64) {
|
||||
if !slices.Contains(cfg.CollaborativeOwnerIDs, ownerID) {
|
||||
cfg.CollaborativeOwnerIDs = append(cfg.CollaborativeOwnerIDs, ownerID)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestActionsConfig_ScopedWorkflowOptOut(t *testing.T) {
|
||||
cfg := &ActionsConfig{}
|
||||
|
||||
assert.False(t, cfg.IsScopedWorkflowDisabled(100, "ci.yml"))
|
||||
|
||||
cfg.DisableScopedWorkflow(100, "ci.yml")
|
||||
assert.True(t, cfg.IsScopedWorkflowDisabled(100, "ci.yml"))
|
||||
|
||||
// idempotent
|
||||
cfg.DisableScopedWorkflow(100, "ci.yml")
|
||||
assert.Len(t, cfg.DisabledScopedWorkflows, 1)
|
||||
|
||||
// keyed by source repo: the same filename from a different source repo is independent
|
||||
assert.False(t, cfg.IsScopedWorkflowDisabled(200, "ci.yml"))
|
||||
|
||||
// must not collide with the repo-level DisabledWorkflows list (bare filename)
|
||||
assert.False(t, cfg.IsWorkflowDisabled("ci.yml"))
|
||||
cfg.DisableWorkflow("ci.yml")
|
||||
assert.True(t, cfg.IsWorkflowDisabled("ci.yml"))
|
||||
assert.True(t, cfg.IsScopedWorkflowDisabled(100, "ci.yml"), "repo-level disable must not touch the scoped entry")
|
||||
|
||||
cfg.EnableScopedWorkflow(100, "ci.yml")
|
||||
assert.False(t, cfg.IsScopedWorkflowDisabled(100, "ci.yml"))
|
||||
assert.True(t, cfg.IsWorkflowDisabled("ci.yml"), "enabling the scoped entry must not touch the repo-level disable")
|
||||
}
|
||||
|
||||
func TestActionsConfig_ScopedWorkflowSerialization(t *testing.T) {
|
||||
cfg := &ActionsConfig{}
|
||||
cfg.DisableScopedWorkflow(100, "ci.yml")
|
||||
cfg.DisableWorkflow("repo.yml")
|
||||
|
||||
bs, err := cfg.ToDB()
|
||||
require.NoError(t, err)
|
||||
|
||||
got := &ActionsConfig{}
|
||||
require.NoError(t, got.FromDB(bs))
|
||||
assert.True(t, got.IsScopedWorkflowDisabled(100, "ci.yml"))
|
||||
assert.True(t, got.IsWorkflowDisabled("repo.yml"))
|
||||
}
|
||||
+3
-1
@@ -9,6 +9,8 @@ import (
|
||||
"gitea.dev/models/db"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// Star represents a starred repo by a user.
|
||||
@@ -68,7 +70,7 @@ func StarRepo(ctx context.Context, doer *user_model.User, repo *Repository, star
|
||||
|
||||
// IsStaring checks if user has starred given repository.
|
||||
func IsStaring(ctx context.Context, userID, repoID int64) bool {
|
||||
has, _ := db.GetEngine(ctx).Get(&Star{UID: userID, RepoID: repoID})
|
||||
has, _ := db.Exist[Star](ctx, builder.Eq{"uid": userID, "repo_id": repoID})
|
||||
return has
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// WatchMode specifies what kind of watch the user has on a repository
|
||||
@@ -41,12 +43,14 @@ func init() {
|
||||
}
|
||||
|
||||
// GetWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found
|
||||
func GetWatch(ctx context.Context, userID, repoID int64) (Watch, error) {
|
||||
watch := Watch{UserID: userID, RepoID: repoID}
|
||||
has, err := db.GetEngine(ctx).Get(&watch)
|
||||
func GetWatch(ctx context.Context, userID, repoID int64) (*Watch, error) {
|
||||
watch, has, err := db.Get[Watch](ctx, builder.Eq{"user_id": userID, "repo_id": repoID})
|
||||
if err != nil {
|
||||
return watch, err
|
||||
}
|
||||
if watch == nil {
|
||||
watch = &Watch{UserID: userID, RepoID: repoID}
|
||||
}
|
||||
if !has {
|
||||
watch.Mode = WatchModeNone
|
||||
}
|
||||
@@ -64,7 +68,7 @@ func IsWatching(ctx context.Context, userID, repoID int64) bool {
|
||||
return err == nil && IsWatchMode(watch.Mode)
|
||||
}
|
||||
|
||||
func watchRepoMode(ctx context.Context, watch Watch, mode WatchMode) (err error) {
|
||||
func watchRepoMode(ctx context.Context, watch *Watch, mode WatchMode) (err error) {
|
||||
if watch.Mode == mode {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"context"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// AppState represents a state record in database
|
||||
@@ -44,9 +46,7 @@ func SaveAppStateContent(ctx context.Context, key, content string) error {
|
||||
|
||||
// GetAppStateContent gets an app state from database
|
||||
func GetAppStateContent(ctx context.Context, key string) (content string, err error) {
|
||||
e := db.GetEngine(ctx)
|
||||
appState := &AppState{ID: key}
|
||||
has, err := e.Get(appState)
|
||||
appState, has, err := db.Get[AppState](ctx, builder.Eq{"id": key})
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if !has {
|
||||
|
||||
@@ -348,8 +348,8 @@ func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddres
|
||||
opts := &TimeLimitCodeOptions{Purpose: TimeLimitCodeActivateEmail, NewEmail: email}
|
||||
data := makeTimeLimitCodeHashData(opts, user)
|
||||
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
|
||||
emailAddress := &EmailAddress{UID: user.ID, Email: email}
|
||||
if has, _ := db.GetEngine(ctx).Get(emailAddress); has {
|
||||
emailAddress, has, _ := db.Get[EmailAddress](ctx, builder.Eq{"uid": user.ID, "email": email})
|
||||
if has {
|
||||
return emailAddress
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// Follow represents relations of user and their followers.
|
||||
@@ -24,7 +26,7 @@ func init() {
|
||||
|
||||
// IsFollowing returns true if user is following followID.
|
||||
func IsFollowing(ctx context.Context, userID, followID int64) bool {
|
||||
has, _ := db.GetEngine(ctx).Get(&Follow{UserID: userID, FollowID: followID})
|
||||
has, _ := db.Exist[Follow](ctx, builder.Eq{"user_id": userID, "follow_id": followID})
|
||||
return has
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// UserOpenID is the list of all OpenID identities of a user.
|
||||
@@ -43,7 +45,7 @@ func isOpenIDUsed(ctx context.Context, uri string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return db.GetEngine(ctx).Get(&UserOpenID{URI: uri})
|
||||
return db.Exist[UserOpenID](ctx, builder.Eq{"uri": uri})
|
||||
}
|
||||
|
||||
// ErrOpenIDAlreadyUsed represents a "OpenIDAlreadyUsed" kind of error.
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ErrUserRedirectNotExist represents a "UserRedirectNotExist" kind of error.
|
||||
@@ -50,8 +52,8 @@ func init() {
|
||||
// LookupUserRedirect look up userID if a user has a redirect name
|
||||
func LookupUserRedirect(ctx context.Context, userName string) (int64, error) {
|
||||
userName = strings.ToLower(userName)
|
||||
redirect := &Redirect{LowerName: userName}
|
||||
if has, err := db.GetEngine(ctx).Get(redirect); err != nil {
|
||||
redirect, has, err := db.Get[Redirect](ctx, builder.Eq{"lower_name": userName})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if !has {
|
||||
return 0, ErrUserRedirectNotExist{Name: userName}
|
||||
|
||||
@@ -125,8 +125,7 @@ func GetUserSetting(ctx context.Context, userID int64, key string, def ...string
|
||||
return "", err
|
||||
}
|
||||
|
||||
setting := &Setting{UserID: userID, SettingKey: key}
|
||||
has, err := db.GetEngine(ctx).Get(setting)
|
||||
setting, has, err := db.Get[Setting](ctx, builder.Eq{"user_id": userID, "setting_key": key})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
+21
-6
@@ -1276,8 +1276,7 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) {
|
||||
|
||||
email = strings.ToLower(email)
|
||||
// Otherwise, check in alternative list for activated email addresses
|
||||
emailAddress := &EmailAddress{LowerEmail: email, IsActivated: true}
|
||||
has, err := db.GetEngine(ctx).Get(emailAddress)
|
||||
emailAddress, has, err := db.Get[EmailAddress](ctx, builder.Eq{"lower_email": email, "is_activated": true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1297,13 +1296,29 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) {
|
||||
return nil, ErrUserNotExist{Name: email}
|
||||
}
|
||||
|
||||
func GetIndividualUser(ctx context.Context, user *User) (bool, error) {
|
||||
// FIXME: the design is wrong, empty User fields won't apply, this function should be removed in the future
|
||||
has, err := db.GetEngine(ctx).Get(user)
|
||||
func GetIndividualUserByPrimaryEmail(ctx context.Context, email string) (*User, error) {
|
||||
email = strings.ToLower(strings.TrimSpace(email))
|
||||
if len(email) == 0 {
|
||||
return nil, ErrUserNotExist{Name: email}
|
||||
}
|
||||
|
||||
user, has, err := db.Get[User](ctx, builder.Eq{"email": email, "type": UserTypeIndividual})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, ErrUserNotExist{Name: email}
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func GetIndividualUserByLoginSource(ctx context.Context, loginType auth.Type, loginSource int64, loginName string) (*User, bool, error) {
|
||||
user, has, err := db.Get[User](ctx, builder.Eq{"login_type": loginType, "login_source": loginSource, "login_name": loginName})
|
||||
if has && user.Type != UserTypeIndividual {
|
||||
has = false
|
||||
user = nil
|
||||
}
|
||||
return has, err
|
||||
return user, has, err
|
||||
}
|
||||
|
||||
// GetUserByOpenID returns the user object by given OpenID if exists.
|
||||
|
||||
@@ -15,9 +15,11 @@ import (
|
||||
type UsesKind int
|
||||
|
||||
const (
|
||||
// UsesKindLocalSameRepo is "./.gitea/workflows/foo.yml" - a path inside the calling repository.
|
||||
// UsesKindLocalSameRepo is "./<dir>/foo.yml" - a path inside the calling repository.
|
||||
// For example: "./.gitea/workflows/foo.yml"
|
||||
UsesKindLocalSameRepo UsesKind = iota + 1
|
||||
// UsesKindLocalCrossRepo is "owner/repo/.gitea/workflows/foo.yml@ref" - a workflow in another repo on the same instance.
|
||||
// UsesKindLocalCrossRepo is "owner/repo/<dir>/foo.yml@ref" - a workflow in another repo on the same instance.
|
||||
// For example: "owner/repo/.gitea/workflows/foo.yml@ref"
|
||||
UsesKindLocalCrossRepo
|
||||
)
|
||||
|
||||
@@ -31,14 +33,16 @@ type UsesRef struct {
|
||||
}
|
||||
|
||||
var (
|
||||
reLocalSameRepo = regexp.MustCompile(`^\./\.(gitea|github)/workflows/([^@]+\.ya?ml)$`)
|
||||
reLocalCrossRepo = regexp.MustCompile(`^([-.\w]+)/([-.\w]+)/\.(gitea|github)/workflows/([^@]+\.ya?ml)@(.+)$`)
|
||||
reLocalSameRepo = regexp.MustCompile(`^\./([^@]+\.ya?ml)$`)
|
||||
reLocalCrossRepo = regexp.MustCompile(`^([-.\w]+)/([-.\w]+)/([^@]+\.ya?ml)@(.+)$`)
|
||||
)
|
||||
|
||||
// ParseUses parses a reusable workflow "uses:" value.
|
||||
// Only two forms are supported:
|
||||
// - "./.gitea/workflows/foo.yml" (UsesKindLocalSameRepo, no @ref)
|
||||
// - "OWNER/REPO/.gitea/workflows/foo.yml@REF" (UsesKindLocalCrossRepo)
|
||||
// ParseUses parses the SYNTAX of a reusable workflow "uses:" value into a UsesRef. Two forms are supported:
|
||||
// - "./<dir>/foo.yml" (UsesKindLocalSameRepo, no @ref)
|
||||
// - "OWNER/REPO/<dir>/foo.yml@REF" (UsesKindLocalCrossRepo)
|
||||
//
|
||||
// It deliberately does NOT validate that <dir> is an allowed workflow directory: the allowed directories are instance-configurable (WORKFLOW_DIRS / SCOPED_WORKFLOW_DIRS).
|
||||
// The caller (services/actions.ResolveUses) enforces the directory allowlist. The returned Path is the cleaned, repo-relative file path.
|
||||
func ParseUses(s string) (*UsesRef, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
@@ -48,9 +52,9 @@ func ParseUses(s string) (*UsesRef, error) {
|
||||
if strings.HasPrefix(s, "./") {
|
||||
m := reLocalSameRepo.FindStringSubmatch(s)
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf(`invalid local "uses:" %q (expect ./.gitea/workflows/<file>.yml)`, s)
|
||||
return nil, fmt.Errorf(`invalid local "uses:" %q (expect ./<dir>/<file>.yml)`, s)
|
||||
}
|
||||
p := fmt.Sprintf(".%s/workflows/%s", m[1], m[2])
|
||||
p := m[1]
|
||||
if path.Clean(p) != p {
|
||||
return nil, fmt.Errorf("invalid workflow path %q", s)
|
||||
}
|
||||
@@ -59,9 +63,9 @@ func ParseUses(s string) (*UsesRef, error) {
|
||||
|
||||
m := reLocalCrossRepo.FindStringSubmatch(s)
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf(`invalid cross-repo "uses:" %q (expect owner/repo/.gitea/workflows/<file>.yml@ref)`, s)
|
||||
return nil, fmt.Errorf(`invalid cross-repo "uses:" %q (expect owner/repo/<dir>/<file>.yml@ref)`, s)
|
||||
}
|
||||
p := fmt.Sprintf(".%s/workflows/%s", m[3], m[4])
|
||||
p := m[3]
|
||||
if path.Clean(p) != p {
|
||||
return nil, fmt.Errorf("invalid workflow path %q", s)
|
||||
}
|
||||
@@ -70,6 +74,6 @@ func ParseUses(s string) (*UsesRef, error) {
|
||||
Owner: m[1],
|
||||
Repo: m[2],
|
||||
Path: p,
|
||||
Ref: m[5],
|
||||
Ref: m[4],
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -42,6 +42,17 @@ func TestParseUses(t *testing.T) {
|
||||
in: "./.gitea/workflows/sub/build.yml",
|
||||
want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".gitea/workflows/sub/build.yml"},
|
||||
},
|
||||
{
|
||||
// ParseUses is dir-agnostic; the allowed directories (WORKFLOW_DIRS / SCOPED_WORKFLOW_DIRS) are enforced by ResolveUses.
|
||||
name: "scoped workflows dir parses",
|
||||
in: "./.gitea/scoped_workflows/lib.yml",
|
||||
want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".gitea/scoped_workflows/lib.yml"},
|
||||
},
|
||||
{
|
||||
name: "non-default dir parses (allowlist enforced downstream)",
|
||||
in: "./.gitea/custom_workflows/x.yaml",
|
||||
want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".gitea/custom_workflows/x.yaml"},
|
||||
},
|
||||
{
|
||||
name: "leading/trailing whitespace is trimmed",
|
||||
in: " ./.gitea/workflows/build.yml ",
|
||||
@@ -118,6 +129,17 @@ func TestParseUses(t *testing.T) {
|
||||
Ref: "v1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "scoped workflows dir parses (allowlist enforced by ResolveUses)",
|
||||
in: "owner/repo/.gitea/scoped_workflows/lib.yml@v1",
|
||||
want: UsesRef{
|
||||
Kind: UsesKindLocalCrossRepo,
|
||||
Owner: "owner",
|
||||
Repo: "repo",
|
||||
Path: ".gitea/scoped_workflows/lib.yml",
|
||||
Ref: "v1",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
@@ -136,23 +158,20 @@ func TestParseUses(t *testing.T) {
|
||||
{name: "empty string", in: ""},
|
||||
{name: "whitespace only", in: " "},
|
||||
|
||||
// Same-repo malformed
|
||||
// Same-repo malformed (note: a wrong *directory* parses and should be rejected by the caller)
|
||||
{name: "same-repo with @ref", in: "./.gitea/workflows/build.yml@v1"},
|
||||
{name: "same-repo wrong directory", in: "./not-workflows/build.yml"},
|
||||
{name: "same-repo wrong extension", in: "./.gitea/workflows/build.txt"},
|
||||
{name: "same-repo missing extension", in: "./.gitea/workflows/build"},
|
||||
{name: "same-repo absolute path", in: "/.gitea/workflows/build.yml"},
|
||||
{name: "same-repo path traversal", in: "./.gitea/workflows/../escape.yml"},
|
||||
{name: "same-repo double slash", in: "./.gitea/workflows//build.yml"},
|
||||
{name: "same-repo redundant ./", in: "./.gitea/workflows/./build.yml"},
|
||||
{name: "same-repo no filename", in: "./.gitea/workflows/.yml"},
|
||||
|
||||
// Cross-repo malformed
|
||||
{name: "cross-repo missing @ref", in: "owner/repo/.gitea/workflows/build.yml"},
|
||||
{name: "cross-repo empty ref", in: "owner/repo/.gitea/workflows/build.yml@"},
|
||||
{name: "cross-repo missing owner", in: "/repo/.gitea/workflows/build.yml@v1"},
|
||||
{name: "cross-repo missing repo", in: "owner//.gitea/workflows/build.yml@v1"},
|
||||
{name: "cross-repo wrong workflows dir", in: "owner/repo/workflows/build.yml@v1"},
|
||||
{name: "cross-repo wrong extension", in: "owner/repo/.gitea/workflows/build.txt@v1"},
|
||||
{name: "cross-repo path traversal", in: "owner/repo/.gitea/workflows/../escape.yml@v1"},
|
||||
{name: "cross-repo double slash in path", in: "owner/repo/.gitea/workflows//build.yml@v1"},
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"gitea.dev/modules/actions/jobparser"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
webhook_module "gitea.dev/modules/webhook"
|
||||
)
|
||||
|
||||
// ListScopedWorkflows lists scoped workflow files (under SCOPED_WORKFLOW_DIRS) at the given commit.
|
||||
func ListScopedWorkflows(commit *git.Commit) (string, git.Entries, error) {
|
||||
return listWorkflowsInDirs(commit, setting.Actions.ScopedWorkflowDirs)
|
||||
}
|
||||
|
||||
// ParsedScopedWorkflow is one scoped workflow's source-side parse result
|
||||
type ParsedScopedWorkflow struct {
|
||||
EntryName string
|
||||
DisplayName string // the workflow `name:` or base file name
|
||||
Content []byte // raw content of the workflow file
|
||||
Events []*jobparser.Event // decoded `on:` events
|
||||
}
|
||||
|
||||
// ParseScopedWorkflows lists and parses the scoped workflow files at sourceCommit (under SCOPED_WORKFLOW_DIRS).
|
||||
func ParseScopedWorkflows(sourceCommit *git.Commit) ([]*ParsedScopedWorkflow, error) {
|
||||
_, entries, err := ListScopedWorkflows(sourceCommit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsed := make([]*ParsedScopedWorkflow, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
content, err := GetContentFromEntry(entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// one workflow may have multiple events
|
||||
events, err := GetEventsFromContent(content)
|
||||
if err != nil {
|
||||
log.Warn("ignore invalid scoped workflow %q: %v", entry.Name(), err)
|
||||
continue
|
||||
}
|
||||
parsed = append(parsed, &ParsedScopedWorkflow{
|
||||
EntryName: entry.Name(),
|
||||
DisplayName: WorkflowDisplayName(entry.Name(), content),
|
||||
Content: content,
|
||||
Events: events,
|
||||
})
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
// MatchScopedWorkflows evaluates already-parsed scoped workflows against one consuming event, returning those whose `on:` matches.
|
||||
func MatchScopedWorkflows(
|
||||
parsed []*ParsedScopedWorkflow,
|
||||
consumerGitRepo *git.Repository,
|
||||
consumerCommit *git.Commit,
|
||||
triggedEvent webhook_module.HookEventType,
|
||||
payload api.Payloader,
|
||||
) []*DetectedWorkflow {
|
||||
workflows := make([]*DetectedWorkflow, 0, len(parsed))
|
||||
for _, p := range parsed {
|
||||
for _, evt := range p.Events {
|
||||
if evt.IsSchedule() {
|
||||
// schedule is a non-target for scoped workflows
|
||||
continue
|
||||
}
|
||||
if detectMatched(consumerGitRepo, consumerCommit, triggedEvent, payload, evt) {
|
||||
workflows = append(workflows, &DetectedWorkflow{
|
||||
EntryName: p.EntryName,
|
||||
TriggerEvent: evt,
|
||||
Content: p.Content,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return workflows
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsWorkflowInDirs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dirs []string
|
||||
path string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "default scoped dir with yml",
|
||||
dirs: []string{".gitea/scoped_workflows", ".github/scoped_workflows"},
|
||||
path: ".gitea/scoped_workflows/security.yml",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "default scoped dir with yaml",
|
||||
dirs: []string{".gitea/scoped_workflows", ".github/scoped_workflows"},
|
||||
path: ".github/scoped_workflows/lint.yaml",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "normal workflow path is not a scoped workflow",
|
||||
dirs: []string{".gitea/scoped_workflows"},
|
||||
path: ".gitea/workflows/ci.yml",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "non-yaml file",
|
||||
dirs: []string{".gitea/scoped_workflows"},
|
||||
path: ".gitea/scoped_workflows/readme.md",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "feature disabled (no scoped dirs)",
|
||||
dirs: []string{},
|
||||
path: ".gitea/scoped_workflows/security.yml",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "directory boundary",
|
||||
dirs: []string{".gitea/scoped_workflows"},
|
||||
path: ".gitea/scoped_workflows2/security.yml",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, isWorkflowInDirs(tt.path, tt.dirs))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ package actions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -38,11 +40,20 @@ func init() {
|
||||
}
|
||||
|
||||
func IsWorkflow(path string) bool {
|
||||
return isWorkflowInDirs(path, setting.Actions.WorkflowDirs)
|
||||
}
|
||||
|
||||
// IsWorkflowOrScopedWorkflow reports whether path is a workflow file under WORKFLOW_DIRS or SCOPED_WORKFLOW_DIRS.
|
||||
func IsWorkflowOrScopedWorkflow(path string) bool {
|
||||
return isWorkflowInDirs(path, setting.Actions.WorkflowDirs) || isWorkflowInDirs(path, setting.Actions.ScopedWorkflowDirs)
|
||||
}
|
||||
|
||||
func isWorkflowInDirs(path string, dirs []string) bool {
|
||||
if (!strings.HasSuffix(path, ".yaml")) && (!strings.HasSuffix(path, ".yml")) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, workflowDir := range setting.Actions.WorkflowDirs {
|
||||
for _, workflowDir := range dirs {
|
||||
if strings.HasPrefix(path, workflowDir+"/") {
|
||||
return true
|
||||
}
|
||||
@@ -51,10 +62,14 @@ func IsWorkflow(path string) bool {
|
||||
}
|
||||
|
||||
func ListWorkflows(commit *git.Commit) (string, git.Entries, error) {
|
||||
return listWorkflowsInDirs(commit, setting.Actions.WorkflowDirs)
|
||||
}
|
||||
|
||||
func listWorkflowsInDirs(commit *git.Commit, dirs []string) (string, git.Entries, error) {
|
||||
var tree *git.Tree
|
||||
var err error
|
||||
var workflowDir string
|
||||
for _, workflowDir = range setting.Actions.WorkflowDirs {
|
||||
for _, workflowDir = range dirs {
|
||||
tree, err = commit.SubTree(workflowDir)
|
||||
if err == nil {
|
||||
break
|
||||
@@ -117,6 +132,40 @@ func ValidateWorkflowContent(content []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// WorkflowDisplayName returns a workflow's display name: its `name:` if non-blank, otherwise the base file name.
|
||||
// This is the value used as the workflow segment of its commit-status context.
|
||||
func WorkflowDisplayName(file string, content []byte) string {
|
||||
displayName := path.Base(file)
|
||||
if wfs, err := jobparser.Parse(content); err == nil && len(wfs) > 0 {
|
||||
if name := strings.TrimSpace(wfs[0].Name); name != "" {
|
||||
displayName = name
|
||||
}
|
||||
}
|
||||
return displayName
|
||||
}
|
||||
|
||||
// WorkflowStatusContextName builds a workflow job's commit-status context name: "<display> / <job> (<event>)".
|
||||
func WorkflowStatusContextName(displayName, jobName, event string) string {
|
||||
return strings.TrimSpace(fmt.Sprintf("%s / %s (%s)", displayName, jobName, event))
|
||||
}
|
||||
|
||||
// ScopedWorkflowStatusContextName prefixes a scoped run's status-check context with its source repo, set off by a colon: "<prefix>: <display> / <job> (<event>)".
|
||||
func ScopedWorkflowStatusContextName(prefix, displayName, jobName, event string) string {
|
||||
return strings.TrimSpace(fmt.Sprintf("%s: %s", prefix, WorkflowStatusContextName(displayName, jobName, event)))
|
||||
}
|
||||
|
||||
// ShouldEventCreateCommitStatus reports whether a run triggered by the given workflow `on:` event posts a commit status,
|
||||
// so its context can serve as a required status check.
|
||||
// TODO: this allowlist duplicates the truth in services/actions.getCommitStatusEventNameAndCommitID, which decides the actual event string and whether a status is posted.
|
||||
// The two are kept in sync by hand and can drift; unify them into a single source so adding a status-producing event in one place automatically updates the other.
|
||||
func ShouldEventCreateCommitStatus(event string) bool {
|
||||
switch event {
|
||||
case "push", "pull_request", "pull_request_target", "pull_request_review", "pull_request_review_comment", "release":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func DetectWorkflows(
|
||||
gitRepo *git.Repository,
|
||||
commit *git.Commit,
|
||||
|
||||
+11
-9
@@ -21,14 +21,6 @@ func syncGitConfig(ctx context.Context) (err error) {
|
||||
return fmt.Errorf("unable to prepare git home directory %s, err: %w", gitcmd.HomeDir(), err)
|
||||
}
|
||||
|
||||
// first, write user's git config options to git config file
|
||||
// user config options could be overwritten by builtin values later, because if a value is builtin, it must have some special purposes
|
||||
for k, v := range setting.GitConfig.Options {
|
||||
if err = configSet(ctx, strings.ToLower(k), v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults"
|
||||
// TODO: need to confirm whether users really need to change these values manually. It seems that these values are dummy only and not really used.
|
||||
// If these values are not really used, then they can be set (overwritten) directly without considering about existence.
|
||||
@@ -111,8 +103,18 @@ func syncGitConfig(ctx context.Context) (err error) {
|
||||
}
|
||||
err = configUnsetAll(ctx, "uploadpack.allowAnySHA1InWant", "true")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
// Apply user's git config options last so they take precedence over builtin defaults
|
||||
for k, v := range setting.GitConfig.Options {
|
||||
if err = configSet(ctx, strings.ToLower(k), v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func configSet(ctx context.Context, key, value string) error {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -55,14 +56,18 @@ func TestGitConfig(t *testing.T) {
|
||||
assert.False(t, gitConfigContains("key-x = *"))
|
||||
}
|
||||
|
||||
func TestSyncConfig(t *testing.T) {
|
||||
oldGitConfig := setting.GitConfig
|
||||
defer func() {
|
||||
setting.GitConfig = oldGitConfig
|
||||
}()
|
||||
func TestSyncGitConfig(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.GitConfig)()
|
||||
|
||||
assert.Empty(t, setting.GitConfig.Options)
|
||||
assert.NoError(t, syncGitConfig(t.Context()))
|
||||
assert.True(t, gitConfigContains("commitGraph = true")) // builtin default config
|
||||
|
||||
setting.GitConfig.Options["sync-test.cfg-key-a"] = "CfgValA"
|
||||
setting.GitConfig.Options["core.commitgraph"] = "false"
|
||||
assert.NoError(t, syncGitConfig(t.Context()))
|
||||
assert.True(t, gitConfigContains("[sync-test]"))
|
||||
assert.True(t, gitConfigContains("cfg-key-a = CfgValA"))
|
||||
assert.False(t, gitConfigContains("commitGraph")) // builtin default config can be overridden
|
||||
assert.True(t, gitConfigContains("commitgraph = false")) // git config key is case-insensitive
|
||||
}
|
||||
|
||||
+9
-6
@@ -228,13 +228,16 @@ func (ref RefName) RefWebLinkPath() string {
|
||||
return string(refType) + "/" + util.PathEscapeSegments(ref.ShortName())
|
||||
}
|
||||
|
||||
func ParseRefSuffix(ref string) (string, string) {
|
||||
func ParseRefSuffix(ref string) (refName, refSuffix string) {
|
||||
// Partially support https://git-scm.com/docs/gitrevisions
|
||||
if idx := strings.Index(ref, "@{"); idx != -1 {
|
||||
return ref[:idx], ref[idx:]
|
||||
suffixIdx := -1 // earliest suffix mark, so a combined suffix like "main~2^" stays intact
|
||||
for _, mark := range []string{"@{", "^", "~"} {
|
||||
if idx := strings.Index(ref, mark); idx != -1 && (suffixIdx == -1 || idx < suffixIdx) {
|
||||
suffixIdx = idx
|
||||
}
|
||||
}
|
||||
if idx := strings.Index(ref, "^"); idx != -1 {
|
||||
return ref[:idx], ref[idx:]
|
||||
if suffixIdx == -1 {
|
||||
return ref, ""
|
||||
}
|
||||
return ref, ""
|
||||
return ref[:suffixIdx], ref[suffixIdx:]
|
||||
}
|
||||
|
||||
@@ -37,3 +37,22 @@ func TestRefWebLinkPath(t *testing.T) {
|
||||
assert.Equal(t, "tag/foo", RefName("refs/tags/foo").RefWebLinkPath())
|
||||
assert.Equal(t, "commit/c0ffee", RefName("c0ffee").RefWebLinkPath())
|
||||
}
|
||||
|
||||
func TestParseRefSuffix(t *testing.T) {
|
||||
cases := []struct {
|
||||
ref, name, suffix string
|
||||
}{
|
||||
{"main", "main", ""},
|
||||
{"main^", "main", "^"},
|
||||
{"main^2", "main", "^2"},
|
||||
{"main~3", "main", "~3"},
|
||||
{"main@{yesterday}", "main", "@{yesterday}"},
|
||||
{"main~2^", "main", "~2^"},
|
||||
{"main^~2", "main", "^~2"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
name, suffix := ParseRefSuffix(c.ref)
|
||||
assert.Equal(t, c.name, name, "ref: %s", c.ref)
|
||||
assert.Equal(t, c.suffix, suffix, "ref: %s", c.ref)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,14 @@ func CommitsCount(ctx context.Context, repo Repository, opts CommitsCountOptions
|
||||
cmd.AddOptionValues("--not", opts.Not)
|
||||
}
|
||||
|
||||
if opts.Since != "" {
|
||||
cmd.AddOptionFormat("--since=%s", opts.Since)
|
||||
}
|
||||
|
||||
if opts.Until != "" {
|
||||
cmd.AddOptionFormat("--until=%s", opts.Until)
|
||||
}
|
||||
|
||||
if len(opts.RelPath) > 0 {
|
||||
cmd.AddDashesAndList(opts.RelPath...)
|
||||
}
|
||||
|
||||
@@ -34,6 +34,37 @@ func TestCommitsCountWithoutBase(t *testing.T) {
|
||||
assert.Equal(t, int64(2), commitsCount)
|
||||
}
|
||||
|
||||
func TestCommitsCountWithSinceUntil(t *testing.T) {
|
||||
bareRepo1 := &mockRepository{path: "repo1_bare"}
|
||||
revision := []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"}
|
||||
|
||||
// The three commits on this revision are dated 2018-04-18, 2017-12-19 and 2017-12-19.
|
||||
cases := []struct {
|
||||
name string
|
||||
since string
|
||||
until string
|
||||
expected int64
|
||||
}{
|
||||
{name: "no filter", expected: 3},
|
||||
{name: "since keeps newer commits", since: "2018-01-01", expected: 1},
|
||||
{name: "until keeps older commits", until: "2018-01-01", expected: 2},
|
||||
{name: "since and until bound the range", since: "2017-12-19T22:16:00-08:00", until: "2018-01-01", expected: 1},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
commitsCount, err := CommitsCount(t.Context(), bareRepo1,
|
||||
CommitsCountOptions{
|
||||
Revision: revision,
|
||||
Since: tc.since,
|
||||
Until: tc.until,
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expected, commitsCount)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLatestCommitTime(t *testing.T) {
|
||||
bareRepo1 := &mockRepository{path: "repo1_bare"}
|
||||
lct, err := GetLatestCommitTime(t.Context(), bareRepo1)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"net/mail"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/validation"
|
||||
@@ -36,18 +37,36 @@ const (
|
||||
controlTar = "control.tar"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingControlFile = util.NewInvalidArgumentErrorf("control file is missing")
|
||||
ErrUnsupportedCompression = util.NewInvalidArgumentErrorf("unsupported compression algorithm")
|
||||
ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
|
||||
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
|
||||
ErrInvalidArchitecture = util.NewInvalidArgumentErrorf("package architecture is invalid")
|
||||
var GlobalVars = sync.OnceValue(func() (ret struct {
|
||||
ErrMissingControlFile error
|
||||
ErrUnsupportedCompression error
|
||||
ErrInvalidName error
|
||||
ErrInvalidVersion error
|
||||
ErrInvalidArchitecture error
|
||||
|
||||
namePattern *regexp.Regexp
|
||||
versionPattern *regexp.Regexp
|
||||
symbolPattern *regexp.Regexp
|
||||
},
|
||||
) {
|
||||
ret.ErrMissingControlFile = util.NewInvalidArgumentErrorf("control file is missing")
|
||||
ret.ErrUnsupportedCompression = util.NewInvalidArgumentErrorf("unsupported compression algorithm")
|
||||
ret.ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
|
||||
ret.ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
|
||||
ret.ErrInvalidArchitecture = util.NewInvalidArgumentErrorf("package architecture is invalid")
|
||||
|
||||
// https://www.debian.org/doc/debian-policy/ch-controlfields.html#source
|
||||
namePattern = regexp.MustCompile(`\A[a-z0-9][a-z0-9+-.]+\z`)
|
||||
ret.namePattern = regexp.MustCompile(`\A[a-z0-9][a-z0-9+-.]+\z`)
|
||||
// https://www.debian.org/doc/debian-policy/ch-controlfields.html#version
|
||||
versionPattern = regexp.MustCompile(`\A(?:(0|[1-9][0-9]*):)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`)
|
||||
)
|
||||
ret.versionPattern = regexp.MustCompile(`\A(?:(0|[1-9][0-9]*):)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`)
|
||||
|
||||
// distribution and component are taken from the request path and written
|
||||
// verbatim into the generated line-based Release and Packages indices (and
|
||||
// into the pool/<distribution>/<component> paths referenced from them), so
|
||||
// they must be restricted to a character set that cannot break that format.
|
||||
ret.symbolPattern = regexp.MustCompile(`\A[a-zA-Z0-9][a-zA-Z0-9.~+_-]*\z`)
|
||||
return ret
|
||||
})
|
||||
|
||||
type Package struct {
|
||||
Name string
|
||||
@@ -64,6 +83,10 @@ type Metadata struct {
|
||||
Dependencies []string `json:"dependencies,omitempty"`
|
||||
}
|
||||
|
||||
func IsValidDistributionOrComponent(s string) bool {
|
||||
return GlobalVars().symbolPattern.MatchString(s)
|
||||
}
|
||||
|
||||
// ParsePackage parses the Debian package file
|
||||
// https://manpages.debian.org/bullseye/dpkg-dev/deb.5.en.html
|
||||
func ParsePackage(r io.Reader) (*Package, error) {
|
||||
@@ -109,7 +132,7 @@ func ParsePackage(r io.Reader) (*Package, error) {
|
||||
|
||||
inner = zr
|
||||
default:
|
||||
return nil, ErrUnsupportedCompression
|
||||
return nil, GlobalVars().ErrUnsupportedCompression
|
||||
}
|
||||
|
||||
tr := tar.NewReader(inner)
|
||||
@@ -133,7 +156,7 @@ func ParsePackage(r io.Reader) (*Package, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrMissingControlFile
|
||||
return nil, GlobalVars().ErrMissingControlFile
|
||||
}
|
||||
|
||||
// ParseControlFile parses a Debian control file to retrieve the metadata
|
||||
@@ -210,14 +233,14 @@ func ParseControlFile(r io.Reader) (*Package, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !namePattern.MatchString(p.Name) {
|
||||
return nil, ErrInvalidName
|
||||
if !GlobalVars().namePattern.MatchString(p.Name) {
|
||||
return nil, GlobalVars().ErrInvalidName
|
||||
}
|
||||
if !versionPattern.MatchString(p.Version) {
|
||||
return nil, ErrInvalidVersion
|
||||
if !GlobalVars().versionPattern.MatchString(p.Version) {
|
||||
return nil, GlobalVars().ErrInvalidVersion
|
||||
}
|
||||
if p.Architecture == "" {
|
||||
return nil, ErrInvalidArchitecture
|
||||
return nil, GlobalVars().ErrInvalidArchitecture
|
||||
}
|
||||
|
||||
dependencies := strings.Split(depends.String(), ",")
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestParsePackage(t *testing.T) {
|
||||
|
||||
p, err := ParsePackage(data)
|
||||
assert.Nil(t, p)
|
||||
assert.ErrorIs(t, err, ErrMissingControlFile)
|
||||
assert.ErrorIs(t, err, GlobalVars().ErrMissingControlFile)
|
||||
})
|
||||
|
||||
t.Run("Compression", func(t *testing.T) {
|
||||
@@ -58,7 +58,7 @@ func TestParsePackage(t *testing.T) {
|
||||
|
||||
p, err := ParsePackage(data)
|
||||
assert.Nil(t, p)
|
||||
assert.ErrorIs(t, err, ErrUnsupportedCompression)
|
||||
assert.ErrorIs(t, err, GlobalVars().ErrUnsupportedCompression)
|
||||
})
|
||||
|
||||
var buf bytes.Buffer
|
||||
@@ -141,7 +141,7 @@ func TestParseControlFile(t *testing.T) {
|
||||
for _, name := range []string{"", "-cd"} {
|
||||
p, err := ParseControlFile(buildContent(name, packageVersion, packageArchitecture))
|
||||
assert.Nil(t, p)
|
||||
assert.ErrorIs(t, err, ErrInvalidName)
|
||||
assert.ErrorIs(t, err, GlobalVars().ErrInvalidName)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -149,14 +149,14 @@ func TestParseControlFile(t *testing.T) {
|
||||
for _, version := range []string{"", "1-", ":1.0", "1_0"} {
|
||||
p, err := ParseControlFile(buildContent(packageName, version, packageArchitecture))
|
||||
assert.Nil(t, p)
|
||||
assert.ErrorIs(t, err, ErrInvalidVersion)
|
||||
assert.ErrorIs(t, err, GlobalVars().ErrInvalidVersion)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("InvalidArchitecture", func(t *testing.T) {
|
||||
p, err := ParseControlFile(buildContent(packageName, packageVersion, ""))
|
||||
assert.Nil(t, p)
|
||||
assert.ErrorIs(t, err, ErrInvalidArchitecture)
|
||||
assert.ErrorIs(t, err, GlobalVars().ErrInvalidArchitecture)
|
||||
})
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
@@ -200,3 +200,35 @@ func TestParseControlFile(t *testing.T) {
|
||||
assert.NotContains(t, p.Control, "evil.deb")
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateDistributionOrComponent(t *testing.T) {
|
||||
bad := []string{
|
||||
"",
|
||||
".",
|
||||
"..",
|
||||
"-stable",
|
||||
".hidden",
|
||||
"a/b",
|
||||
"a b",
|
||||
"bookworm\nSigned-By: evil",
|
||||
"main\nFilename: pool/x",
|
||||
"a\tb",
|
||||
}
|
||||
for _, name := range bad {
|
||||
assert.False(t, IsValidDistributionOrComponent(name), "bad=%q", name)
|
||||
}
|
||||
|
||||
good := []string{
|
||||
"stable",
|
||||
"bookworm",
|
||||
"bookworm-backports",
|
||||
"stable-updates",
|
||||
"main",
|
||||
"non-free-firmware",
|
||||
"a",
|
||||
"1",
|
||||
}
|
||||
for _, name := range good {
|
||||
assert.True(t, IsValidDistributionOrComponent(name), "good=%q", name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ type PackageMetadataVersion struct {
|
||||
DevDependencies map[string]string `json:"devDependencies,omitempty"`
|
||||
PeerDependencies map[string]string `json:"peerDependencies,omitempty"`
|
||||
PeerDependenciesMeta map[string]any `json:"peerDependenciesMeta,omitempty"`
|
||||
Bin map[string]string `json:"bin,omitempty"`
|
||||
Bin Bin `json:"bin,omitempty"`
|
||||
OptionalDependencies map[string]string `json:"optionalDependencies,omitempty"`
|
||||
Readme string `json:"readme,omitempty"`
|
||||
Dist PackageDistribution `json:"dist"`
|
||||
@@ -188,6 +188,49 @@ type Repository struct {
|
||||
Directory string `json:"directory,omitempty"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON is needed because the repository field can be a string or an object.
|
||||
func (r *Repository) UnmarshalJSON(data []byte) error {
|
||||
switch data[0] {
|
||||
case '"':
|
||||
var value string
|
||||
if err := json.Unmarshal(data, &value); err != nil {
|
||||
return err
|
||||
}
|
||||
r.URL = value
|
||||
case '{':
|
||||
type repositoryAlias Repository // avoid recursion into this method
|
||||
var value repositoryAlias
|
||||
if err := json.Unmarshal(data, &value); err != nil {
|
||||
return err
|
||||
}
|
||||
*r = Repository(value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bin maps command names to executable files. npm also allows a single string,
|
||||
// in which case the command is named after the package (resolved in ParsePackage).
|
||||
type Bin map[string]string
|
||||
|
||||
// UnmarshalJSON is needed because the bin field can be a string or an object.
|
||||
func (b *Bin) UnmarshalJSON(data []byte) error {
|
||||
switch data[0] {
|
||||
case '"':
|
||||
var value string
|
||||
if err := json.Unmarshal(data, &value); err != nil {
|
||||
return err
|
||||
}
|
||||
*b = Bin{"": value}
|
||||
case '{':
|
||||
var value map[string]string
|
||||
if err := json.Unmarshal(data, &value); err != nil {
|
||||
return err
|
||||
}
|
||||
*b = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PackageAttachment https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#package
|
||||
type PackageAttachment struct {
|
||||
ContentType string `json:"content_type"`
|
||||
@@ -229,6 +272,11 @@ func ParsePackage(r io.Reader) (*Package, error) {
|
||||
meta.Homepage = ""
|
||||
}
|
||||
|
||||
// A string "bin" means a single executable named after the package.
|
||||
if cmd, ok := meta.Bin[""]; ok && len(meta.Bin) == 1 {
|
||||
meta.Bin = Bin{name: cmd}
|
||||
}
|
||||
|
||||
p := &Package{
|
||||
Name: meta.Name,
|
||||
Version: v.String(),
|
||||
|
||||
@@ -326,4 +326,31 @@ func TestParsePackage(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "MIT", string(p.Metadata.License))
|
||||
})
|
||||
|
||||
t.Run("ValidRepositoryAndBinAsString", func(t *testing.T) {
|
||||
// npm allows "repository" and "bin" to be plain strings, not only objects.
|
||||
packageJSON := `{
|
||||
"versions": {
|
||||
"0.1.1": {
|
||||
"name": "dev-null",
|
||||
"version": "0.1.1",
|
||||
"bin": "./cli.js",
|
||||
"repository": "https://gitea.io/gitea/test.git",
|
||||
"dist": {
|
||||
"integrity": "sha256-"
|
||||
}
|
||||
}
|
||||
},
|
||||
"_attachments": {
|
||||
"foo": {
|
||||
"data": "AAAA"
|
||||
}
|
||||
}
|
||||
}`
|
||||
p, err := ParsePackage(strings.NewReader(packageJSON))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "https://gitea.io/gitea/test.git", p.Metadata.Repository.URL)
|
||||
// a string bin is named after the package
|
||||
require.Equal(t, "./cli.js", p.Metadata.Bin["dev-null"])
|
||||
})
|
||||
}
|
||||
|
||||
@@ -79,13 +79,12 @@ func RemoveLock(ctx context.Context, packageID int64) error {
|
||||
}
|
||||
|
||||
func updateLock(ctx context.Context, refID int64, value string, cond builder.Cond) error {
|
||||
pp := packages_model.PackageProperty{RefType: packages_model.PropertyTypePackage, RefID: refID, Name: LockFile}
|
||||
ok, err := db.GetEngine(ctx).Get(&pp)
|
||||
pp, ok, err := db.Get[packages_model.PackageProperty](ctx, builder.Eq{"ref_type": packages_model.PropertyTypePackage, "ref_id": refID, "`name`": LockFile})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
n, err := db.GetEngine(ctx).Where("ref_type=? AND ref_id=? AND name=?", packages_model.PropertyTypePackage, refID, LockFile).And(cond).Cols("value").Update(&packages_model.PackageProperty{Value: value})
|
||||
n, err := db.GetEngine(ctx).ID(pp.ID).And(cond).Cols("value").Update(&packages_model.PackageProperty{Value: value})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -29,12 +29,14 @@ var (
|
||||
AbandonedJobTimeout time.Duration `ini:"ABANDONED_JOB_TIMEOUT"`
|
||||
SkipWorkflowStrings []string `ini:"SKIP_WORKFLOW_STRINGS"`
|
||||
WorkflowDirs []string `ini:"WORKFLOW_DIRS"`
|
||||
ScopedWorkflowDirs []string `ini:"SCOPED_WORKFLOW_DIRS"`
|
||||
MaxRerunAttempts int64 `ini:"MAX_RERUN_ATTEMPTS"`
|
||||
}{
|
||||
Enabled: true,
|
||||
DefaultActionsURL: defaultActionsURLGitHub,
|
||||
SkipWorkflowStrings: []string{"[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"},
|
||||
WorkflowDirs: []string{".gitea/workflows", ".github/workflows"},
|
||||
ScopedWorkflowDirs: []string{".gitea/scoped_workflows"},
|
||||
MaxRerunAttempts: defaultMaxRerunAttempts,
|
||||
}
|
||||
)
|
||||
@@ -130,20 +132,39 @@ func loadActionsFrom(rootCfg ConfigProvider) error {
|
||||
return fmt.Errorf("invalid [actions] LOG_COMPRESSION: %q", Actions.LogCompression)
|
||||
}
|
||||
|
||||
workflowDirs := make([]string, 0, len(Actions.WorkflowDirs))
|
||||
for _, dir := range Actions.WorkflowDirs {
|
||||
workflowDirs := normalizeWorkflowDirs(Actions.WorkflowDirs)
|
||||
if len(workflowDirs) == 0 {
|
||||
return errors.New("[actions] WORKFLOW_DIRS must contain at least one entry")
|
||||
}
|
||||
Actions.WorkflowDirs = workflowDirs
|
||||
|
||||
// SCOPED_WORKFLOW_DIRS may be empty (feature disabled), but it must not overlap with WORKFLOW_DIRS:
|
||||
// a scoped dir nested in (or equal to) a workflow dir would make the same file run both repo-level and scope-level.
|
||||
Actions.ScopedWorkflowDirs = normalizeWorkflowDirs(Actions.ScopedWorkflowDirs)
|
||||
for _, scopedDir := range Actions.ScopedWorkflowDirs {
|
||||
for _, workflowDir := range Actions.WorkflowDirs {
|
||||
if scopedDir == workflowDir ||
|
||||
strings.HasPrefix(scopedDir, workflowDir+"/") ||
|
||||
strings.HasPrefix(workflowDir, scopedDir+"/") {
|
||||
return fmt.Errorf("[actions] SCOPED_WORKFLOW_DIRS entry %q overlaps with WORKFLOW_DIRS entry %q", scopedDir, workflowDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeWorkflowDirs trims, normalizes separators and drops empty/trailing-slash entries.
|
||||
func normalizeWorkflowDirs(dirs []string) []string {
|
||||
normalized := make([]string, 0, len(dirs))
|
||||
for _, dir := range dirs {
|
||||
dir = strings.TrimSpace(dir)
|
||||
if dir == "" {
|
||||
continue
|
||||
}
|
||||
dir = strings.ReplaceAll(dir, `\`, `/`)
|
||||
dir = strings.TrimRight(dir, "/")
|
||||
workflowDirs = append(workflowDirs, dir)
|
||||
normalized = append(normalized, dir)
|
||||
}
|
||||
if len(workflowDirs) == 0 {
|
||||
return errors.New("[actions] WORKFLOW_DIRS must contain at least one entry")
|
||||
}
|
||||
Actions.WorkflowDirs = workflowDirs
|
||||
|
||||
return nil
|
||||
return normalized
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@ package setting
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -238,3 +241,71 @@ DEFAULT_ACTIONS_URL = gitea
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ScopedWorkflowDirs(t *testing.T) {
|
||||
defer test.MockVariableValue(&Actions)()
|
||||
|
||||
defaultWorkflowDirs := []string{".gitea/workflows", ".github/workflows"}
|
||||
defaultScopedDirs := []string{".gitea/scoped_workflows"}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
iniStr string
|
||||
wantScoped []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "default",
|
||||
iniStr: `[actions]`,
|
||||
wantScoped: defaultScopedDirs,
|
||||
},
|
||||
{
|
||||
name: "custom dir",
|
||||
iniStr: "[actions]\nSCOPED_WORKFLOW_DIRS = .gitea/my-scoped",
|
||||
wantScoped: []string{".gitea/my-scoped"},
|
||||
},
|
||||
{
|
||||
name: "empty disables the feature",
|
||||
iniStr: "[actions]\nSCOPED_WORKFLOW_DIRS = , ,",
|
||||
wantScoped: []string{},
|
||||
},
|
||||
{
|
||||
name: "overlap equal with workflow dir",
|
||||
iniStr: "[actions]\nWORKFLOW_DIRS = .gitea/workflows\nSCOPED_WORKFLOW_DIRS = .gitea/workflows",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "scoped dir nested under workflow dir",
|
||||
iniStr: "[actions]\nWORKFLOW_DIRS = .gitea/workflows\nSCOPED_WORKFLOW_DIRS = .gitea/workflows/scoped",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "workflow dir nested under scoped dir",
|
||||
iniStr: "[actions]\nWORKFLOW_DIRS = .gitea/workflows/ci\nSCOPED_WORKFLOW_DIRS = .gitea/workflows",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no overlap",
|
||||
iniStr: "[actions]\nSCOPED_WORKFLOW_DIRS = .gitea/scoped",
|
||||
wantScoped: []string{".gitea/scoped"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// reset to defaults so MapTo starts clean (absent keys keep the defaults)
|
||||
Actions.WorkflowDirs = slices.Clone(defaultWorkflowDirs)
|
||||
Actions.ScopedWorkflowDirs = slices.Clone(defaultScopedDirs)
|
||||
|
||||
cfg, err := NewConfigProviderFromData(tt.iniStr)
|
||||
require.NoError(t, err)
|
||||
err = loadActionsFrom(cfg)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.wantScoped, Actions.ScopedWorkflowDirs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+372
-202
@@ -5,6 +5,11 @@
|
||||
"_rust": "folder-rust",
|
||||
"-rust": "folder-rust",
|
||||
"__rust__": "folder-rust",
|
||||
"cargo": "folder-rust",
|
||||
".cargo": "folder-rust",
|
||||
"_cargo": "folder-rust",
|
||||
"-cargo": "folder-rust",
|
||||
"__cargo__": "folder-rust",
|
||||
"bot": "folder-robot",
|
||||
".bot": "folder-robot",
|
||||
"_bot": "folder-robot",
|
||||
@@ -350,6 +355,11 @@
|
||||
"_scripting": "folder-scripts",
|
||||
"-scripting": "folder-scripts",
|
||||
"__scripting__": "folder-scripts",
|
||||
"xtask": "folder-scripts",
|
||||
".xtask": "folder-scripts",
|
||||
"_xtask": "folder-scripts",
|
||||
"-xtask": "folder-scripts",
|
||||
"__xtask__": "folder-scripts",
|
||||
"node": "folder-node",
|
||||
".node": "folder-node",
|
||||
"_node": "folder-node",
|
||||
@@ -1020,6 +1030,11 @@
|
||||
"_externals": "folder-lib",
|
||||
"-externals": "folder-lib",
|
||||
"__externals__": "folder-lib",
|
||||
"crates": "folder-lib",
|
||||
".crates": "folder-lib",
|
||||
"_crates": "folder-lib",
|
||||
"-crates": "folder-lib",
|
||||
"__crates__": "folder-lib",
|
||||
"themes": "folder-theme",
|
||||
".themes": "folder-theme",
|
||||
"_themes": "folder-theme",
|
||||
@@ -4414,22 +4429,18 @@
|
||||
"_organism": "folder-organism",
|
||||
"-organism": "folder-organism",
|
||||
"__organism__": "folder-organism",
|
||||
"claude": "folder-claude",
|
||||
".claude": "folder-claude",
|
||||
"..claude": "folder-claude",
|
||||
"_.claude": "folder-claude",
|
||||
"-.claude": "folder-claude",
|
||||
"__.claude__": "folder-claude",
|
||||
"_claude": "folder-claude",
|
||||
"-claude": "folder-claude",
|
||||
"__claude__": "folder-claude",
|
||||
"cursor": "folder-cursor",
|
||||
".cursor": "folder-cursor",
|
||||
"..cursor": "folder-cursor",
|
||||
"_.cursor": "folder-cursor",
|
||||
"-.cursor": "folder-cursor",
|
||||
"__.cursor__": "folder-cursor",
|
||||
".gemini": "folder-gemini-ai",
|
||||
"..gemini": "folder-gemini-ai",
|
||||
"_.gemini": "folder-gemini-ai",
|
||||
"-.gemini": "folder-gemini-ai",
|
||||
"__.gemini__": "folder-gemini-ai",
|
||||
"_cursor": "folder-cursor",
|
||||
"-cursor": "folder-cursor",
|
||||
"__cursor__": "folder-cursor",
|
||||
"gemini": "folder-gemini-ai",
|
||||
".gemini": "folder-gemini-ai",
|
||||
"_gemini": "folder-gemini-ai",
|
||||
"-gemini": "folder-gemini-ai",
|
||||
"__gemini__": "folder-gemini-ai",
|
||||
@@ -4443,6 +4454,11 @@
|
||||
"_geminiai": "folder-gemini-ai",
|
||||
"-geminiai": "folder-gemini-ai",
|
||||
"__geminiai__": "folder-gemini-ai",
|
||||
"opencode": "folder-opencode",
|
||||
".opencode": "folder-opencode",
|
||||
"_opencode": "folder-opencode",
|
||||
"-opencode": "folder-opencode",
|
||||
"__opencode__": "folder-opencode",
|
||||
"input": "folder-input",
|
||||
".input": "folder-input",
|
||||
"_input": "folder-input",
|
||||
@@ -4583,6 +4599,11 @@
|
||||
"_instructions": "folder-instructions",
|
||||
"-instructions": "folder-instructions",
|
||||
"__instructions__": "folder-instructions",
|
||||
"zed": "folder-zed",
|
||||
".zed": "folder-zed",
|
||||
"_zed": "folder-zed",
|
||||
"-zed": "folder-zed",
|
||||
"__zed__": "folder-zed",
|
||||
"appwrite": "folder-appwrite",
|
||||
".appwrite": "folder-appwrite",
|
||||
"_appwrite": "folder-appwrite",
|
||||
@@ -4617,7 +4638,22 @@
|
||||
".kotlin": "folder-kotlin",
|
||||
"_kotlin": "folder-kotlin",
|
||||
"-kotlin": "folder-kotlin",
|
||||
"__kotlin__": "folder-kotlin"
|
||||
"__kotlin__": "folder-kotlin",
|
||||
"redis": "folder-redis",
|
||||
".redis": "folder-redis",
|
||||
"_redis": "folder-redis",
|
||||
"-redis": "folder-redis",
|
||||
"__redis__": "folder-redis",
|
||||
"redis-db": "folder-redis",
|
||||
".redis-db": "folder-redis",
|
||||
"_redis-db": "folder-redis",
|
||||
"-redis-db": "folder-redis",
|
||||
"__redis-db__": "folder-redis",
|
||||
"redislabs": "folder-redis",
|
||||
".redislabs": "folder-redis",
|
||||
"_redislabs": "folder-redis",
|
||||
"-redislabs": "folder-redis",
|
||||
"__redislabs__": "folder-redis"
|
||||
},
|
||||
"folderNamesExpanded": {
|
||||
"rust": "folder-rust-open",
|
||||
@@ -4625,6 +4661,11 @@
|
||||
"_rust": "folder-rust-open",
|
||||
"-rust": "folder-rust-open",
|
||||
"__rust__": "folder-rust-open",
|
||||
"cargo": "folder-rust-open",
|
||||
".cargo": "folder-rust-open",
|
||||
"_cargo": "folder-rust-open",
|
||||
"-cargo": "folder-rust-open",
|
||||
"__cargo__": "folder-rust-open",
|
||||
"bot": "folder-robot-open",
|
||||
".bot": "folder-robot-open",
|
||||
"_bot": "folder-robot-open",
|
||||
@@ -4970,6 +5011,11 @@
|
||||
"_scripting": "folder-scripts-open",
|
||||
"-scripting": "folder-scripts-open",
|
||||
"__scripting__": "folder-scripts-open",
|
||||
"xtask": "folder-scripts-open",
|
||||
".xtask": "folder-scripts-open",
|
||||
"_xtask": "folder-scripts-open",
|
||||
"-xtask": "folder-scripts-open",
|
||||
"__xtask__": "folder-scripts-open",
|
||||
"node": "folder-node-open",
|
||||
".node": "folder-node-open",
|
||||
"_node": "folder-node-open",
|
||||
@@ -5640,6 +5686,11 @@
|
||||
"_externals": "folder-lib-open",
|
||||
"-externals": "folder-lib-open",
|
||||
"__externals__": "folder-lib-open",
|
||||
"crates": "folder-lib-open",
|
||||
".crates": "folder-lib-open",
|
||||
"_crates": "folder-lib-open",
|
||||
"-crates": "folder-lib-open",
|
||||
"__crates__": "folder-lib-open",
|
||||
"themes": "folder-theme-open",
|
||||
".themes": "folder-theme-open",
|
||||
"_themes": "folder-theme-open",
|
||||
@@ -9034,22 +9085,18 @@
|
||||
"_organism": "folder-organism-open",
|
||||
"-organism": "folder-organism-open",
|
||||
"__organism__": "folder-organism-open",
|
||||
"claude": "folder-claude-open",
|
||||
".claude": "folder-claude-open",
|
||||
"..claude": "folder-claude-open",
|
||||
"_.claude": "folder-claude-open",
|
||||
"-.claude": "folder-claude-open",
|
||||
"__.claude__": "folder-claude-open",
|
||||
"_claude": "folder-claude-open",
|
||||
"-claude": "folder-claude-open",
|
||||
"__claude__": "folder-claude-open",
|
||||
"cursor": "folder-cursor-open",
|
||||
".cursor": "folder-cursor-open",
|
||||
"..cursor": "folder-cursor-open",
|
||||
"_.cursor": "folder-cursor-open",
|
||||
"-.cursor": "folder-cursor-open",
|
||||
"__.cursor__": "folder-cursor-open",
|
||||
".gemini": "folder-gemini-ai-open",
|
||||
"..gemini": "folder-gemini-ai-open",
|
||||
"_.gemini": "folder-gemini-ai-open",
|
||||
"-.gemini": "folder-gemini-ai-open",
|
||||
"__.gemini__": "folder-gemini-ai-open",
|
||||
"_cursor": "folder-cursor-open",
|
||||
"-cursor": "folder-cursor-open",
|
||||
"__cursor__": "folder-cursor-open",
|
||||
"gemini": "folder-gemini-ai-open",
|
||||
".gemini": "folder-gemini-ai-open",
|
||||
"_gemini": "folder-gemini-ai-open",
|
||||
"-gemini": "folder-gemini-ai-open",
|
||||
"__gemini__": "folder-gemini-ai-open",
|
||||
@@ -9063,6 +9110,11 @@
|
||||
"_geminiai": "folder-gemini-ai-open",
|
||||
"-geminiai": "folder-gemini-ai-open",
|
||||
"__geminiai__": "folder-gemini-ai-open",
|
||||
"opencode": "folder-opencode-open",
|
||||
".opencode": "folder-opencode-open",
|
||||
"_opencode": "folder-opencode-open",
|
||||
"-opencode": "folder-opencode-open",
|
||||
"__opencode__": "folder-opencode-open",
|
||||
"input": "folder-input-open",
|
||||
".input": "folder-input-open",
|
||||
"_input": "folder-input-open",
|
||||
@@ -9203,6 +9255,11 @@
|
||||
"_instructions": "folder-instructions-open",
|
||||
"-instructions": "folder-instructions-open",
|
||||
"__instructions__": "folder-instructions-open",
|
||||
"zed": "folder-zed-open",
|
||||
".zed": "folder-zed-open",
|
||||
"_zed": "folder-zed-open",
|
||||
"-zed": "folder-zed-open",
|
||||
"__zed__": "folder-zed-open",
|
||||
"appwrite": "folder-appwrite-open",
|
||||
".appwrite": "folder-appwrite-open",
|
||||
"_appwrite": "folder-appwrite-open",
|
||||
@@ -9237,7 +9294,22 @@
|
||||
".kotlin": "folder-kotlin-open",
|
||||
"_kotlin": "folder-kotlin-open",
|
||||
"-kotlin": "folder-kotlin-open",
|
||||
"__kotlin__": "folder-kotlin-open"
|
||||
"__kotlin__": "folder-kotlin-open",
|
||||
"redis": "folder-redis-open",
|
||||
".redis": "folder-redis-open",
|
||||
"_redis": "folder-redis-open",
|
||||
"-redis": "folder-redis-open",
|
||||
"__redis__": "folder-redis-open",
|
||||
"redis-db": "folder-redis-open",
|
||||
".redis-db": "folder-redis-open",
|
||||
"_redis-db": "folder-redis-open",
|
||||
"-redis-db": "folder-redis-open",
|
||||
"__redis-db__": "folder-redis-open",
|
||||
"redislabs": "folder-redis-open",
|
||||
".redislabs": "folder-redis-open",
|
||||
"_redislabs": "folder-redis-open",
|
||||
"-redislabs": "folder-redis-open",
|
||||
"__redislabs__": "folder-redis-open"
|
||||
},
|
||||
"rootFolderNames": {},
|
||||
"rootFolderNamesExpanded": {},
|
||||
@@ -9247,11 +9319,29 @@
|
||||
"xhtml": "html",
|
||||
"html_vm": "html",
|
||||
"asp": "html",
|
||||
"html": "html",
|
||||
"aspx": "html",
|
||||
"jshtm": "html",
|
||||
"rhtml": "html",
|
||||
"shtml": "html",
|
||||
"volt": "html",
|
||||
"xht": "html",
|
||||
"jade": "pug",
|
||||
"pug": "pug",
|
||||
"md": "markdown",
|
||||
"markdown": "markdown",
|
||||
"rst": "markdown",
|
||||
"copilotmd": "markdown",
|
||||
"litcoffee": "markdown",
|
||||
"markdn": "markdown",
|
||||
"mdown": "markdown",
|
||||
"mdtext": "markdown",
|
||||
"mdtxt": "markdown",
|
||||
"mdwn": "markdown",
|
||||
"mkd": "markdown",
|
||||
"mkdn": "markdown",
|
||||
"ronn": "markdown",
|
||||
"workbook": "markdown",
|
||||
"blink": "blink",
|
||||
"css": "css",
|
||||
"scss": "sass",
|
||||
@@ -9263,6 +9353,11 @@
|
||||
"json5": "json",
|
||||
"jsonl": "json",
|
||||
"ndjson": "json",
|
||||
"geojson": "json",
|
||||
"har": "json",
|
||||
"jsonld": "json",
|
||||
"webmanifest": "json",
|
||||
"ts.map": "json",
|
||||
"schema.json": "json_schema",
|
||||
"hjson": "hjson",
|
||||
"jinja": "jinja",
|
||||
@@ -9281,6 +9376,14 @@
|
||||
"yml.dist": "yaml",
|
||||
"yaml.dist": "yaml",
|
||||
"yaml-tmlanguage": "yaml",
|
||||
"yaml": "yaml",
|
||||
"yml": "yaml",
|
||||
"cff": "yaml",
|
||||
"eyaml": "yaml",
|
||||
"eyml": "yaml",
|
||||
"winget": "yaml",
|
||||
"yaml-tmpreferences": "yaml",
|
||||
"yaml-tmtheme": "yaml",
|
||||
"xml": "xml",
|
||||
"plist": "xml",
|
||||
"xsd": "xml",
|
||||
@@ -9298,6 +9401,48 @@
|
||||
"dmn": "xml",
|
||||
"jrxml": "xml",
|
||||
"xmp": "xml",
|
||||
"ascx": "xml",
|
||||
"atom": "xml",
|
||||
"axaml": "xml",
|
||||
"axml": "xml",
|
||||
"bpmn": "xml",
|
||||
"csl": "xml",
|
||||
"csproj.user": "xml",
|
||||
"dita": "xml",
|
||||
"ditamap": "xml",
|
||||
"dtml": "xml",
|
||||
"ent": "xml",
|
||||
"fxml": "xml",
|
||||
"isml": "xml",
|
||||
"jmx": "xml",
|
||||
"launch": "xml",
|
||||
"menu": "xml",
|
||||
"opml": "xml",
|
||||
"owl": "xml",
|
||||
"proj": "xml",
|
||||
"publishsettings": "xml",
|
||||
"pubxml": "xml",
|
||||
"pubxml.user": "xml",
|
||||
"rdf": "xml",
|
||||
"rng": "xml",
|
||||
"rss": "xml",
|
||||
"shproj": "xml",
|
||||
"storyboard": "xml",
|
||||
"targets": "xml",
|
||||
"tld": "xml",
|
||||
"tmx": "xml",
|
||||
"vbproj": "xml",
|
||||
"vbproj.user": "xml",
|
||||
"wsdl": "xml",
|
||||
"wxi": "xml",
|
||||
"wxl": "xml",
|
||||
"wxs": "xml",
|
||||
"xbl": "xml",
|
||||
"xib": "xml",
|
||||
"xliff": "xml",
|
||||
"xoml": "xml",
|
||||
"xpdl": "xml",
|
||||
"xul": "xml",
|
||||
"toml": "toml",
|
||||
"toon": "toon",
|
||||
"png": "image",
|
||||
@@ -9322,6 +9467,7 @@
|
||||
"jbig2": "image",
|
||||
"jb2": "image",
|
||||
"jng": "image",
|
||||
"jxl": "image",
|
||||
"jxr": "image",
|
||||
"pgf": "image",
|
||||
"pic": "image",
|
||||
@@ -9397,6 +9543,10 @@
|
||||
"act": "palette",
|
||||
"esx": "javascript",
|
||||
"mjs": "javascript",
|
||||
"js": "javascript",
|
||||
"cjs": "javascript",
|
||||
"es6": "javascript",
|
||||
"pac": "javascript",
|
||||
"jsx": "react",
|
||||
"tsx": "react_ts",
|
||||
"routing.ts": "routing",
|
||||
@@ -9426,6 +9576,13 @@
|
||||
"cfg": "settings",
|
||||
"cnf": "settings",
|
||||
"tool-versions": "settings",
|
||||
"directory": "settings",
|
||||
"mak": "settings",
|
||||
"npmrc": "settings",
|
||||
"repo": "settings",
|
||||
"ts": "typescript",
|
||||
"cts": "typescript",
|
||||
"mts": "typescript",
|
||||
"d.ts": "typescript-def",
|
||||
"d.cts": "typescript-def",
|
||||
"d.mts": "typescript-def",
|
||||
@@ -9463,6 +9620,9 @@
|
||||
"vcxproj": "visualstudio",
|
||||
"vcxproj.filters": "visualstudio",
|
||||
"wixproj": "visualstudio",
|
||||
"bas": "visualstudio",
|
||||
"brs": "visualstudio",
|
||||
"vba": "visualstudio",
|
||||
"vcl": "varnish",
|
||||
"pdb": "database",
|
||||
"sql": "database",
|
||||
@@ -9502,6 +9662,7 @@
|
||||
"ldf": "database",
|
||||
"frm": "database",
|
||||
"kdbx": "database",
|
||||
"dsql": "database",
|
||||
"kql": "kusto",
|
||||
"cs": "csharp",
|
||||
"csx": "csharp",
|
||||
@@ -9565,6 +9726,7 @@
|
||||
"hex": "hex",
|
||||
"java": "java",
|
||||
"jsp": "java",
|
||||
"jav": "java",
|
||||
"jar": "jar",
|
||||
"class": "javaclass",
|
||||
"c3": "c3",
|
||||
@@ -9572,6 +9734,7 @@
|
||||
"i": "c",
|
||||
"mi": "c",
|
||||
"h": "h",
|
||||
"hip": "hip",
|
||||
"cc": "cpp",
|
||||
"cpp": "cpp",
|
||||
"cxx": "cpp",
|
||||
@@ -9580,6 +9743,15 @@
|
||||
"mii": "cpp",
|
||||
"ii": "cpp",
|
||||
"cppm": "cpp",
|
||||
"c++m": "cpp",
|
||||
"ccm": "cpp",
|
||||
"cxxm": "cpp",
|
||||
"h.in": "cpp",
|
||||
"hpp.in": "cpp",
|
||||
"ipp": "cpp",
|
||||
"ixx": "cpp",
|
||||
"tpp": "cpp",
|
||||
"txx": "cpp",
|
||||
"hh": "hpp",
|
||||
"hpp": "hpp",
|
||||
"hxx": "hpp",
|
||||
@@ -9587,9 +9759,19 @@
|
||||
"hp": "hpp",
|
||||
"tcc": "hpp",
|
||||
"inl": "hpp",
|
||||
"m": "objective-c",
|
||||
"mm": "objective-cpp",
|
||||
"rc": "rc",
|
||||
"go": "go",
|
||||
"py": "python",
|
||||
"cpy": "python",
|
||||
"gyp": "python",
|
||||
"gypi": "python",
|
||||
"ipy": "python",
|
||||
"pyi": "python",
|
||||
"pyt": "python",
|
||||
"pyw": "python",
|
||||
"rpy": "python",
|
||||
"pyc": "python-misc",
|
||||
"whl": "python-misc",
|
||||
"egg": "python-misc",
|
||||
@@ -9607,12 +9789,34 @@
|
||||
"exp": "console",
|
||||
"nu": "console",
|
||||
"xsh": "console",
|
||||
"bash_aliases": "console",
|
||||
"bash_login": "console",
|
||||
"bash_logout": "console",
|
||||
"bash_profile": "console",
|
||||
"bashrc": "console",
|
||||
"cshrc": "console",
|
||||
"ebuild": "console",
|
||||
"eclass": "console",
|
||||
"profile": "console",
|
||||
"tcshrc": "console",
|
||||
"xprofile": "console",
|
||||
"xsession": "console",
|
||||
"xsessionrc": "console",
|
||||
"yash_profile": "console",
|
||||
"yashrc": "console",
|
||||
"zlogin": "console",
|
||||
"zlogout": "console",
|
||||
"zprofile": "console",
|
||||
"zsh-theme": "console",
|
||||
"zshenv": "console",
|
||||
"zshrc": "console",
|
||||
"ps1": "powershell",
|
||||
"psm1": "powershell",
|
||||
"psd1": "powershell",
|
||||
"ps1xml": "powershell",
|
||||
"psc1": "powershell",
|
||||
"pssc": "powershell",
|
||||
"psrc": "powershell",
|
||||
"excalidraw": "excalidraw",
|
||||
"excalidraw.json": "excalidraw",
|
||||
"excalidraw.svg": "excalidraw",
|
||||
@@ -9654,6 +9858,9 @@
|
||||
"lib": "lib",
|
||||
"a": "lib",
|
||||
"bib": "bibliography",
|
||||
"bbl": "bibliography",
|
||||
"bcf": "bibliography",
|
||||
"blg": "bibliography",
|
||||
"bst": "bibtex-style",
|
||||
"dll": "dll",
|
||||
"ilk": "dll",
|
||||
@@ -9661,10 +9868,18 @@
|
||||
"rb": "ruby",
|
||||
"erb": "ruby",
|
||||
"rbs": "ruby",
|
||||
"gemspec": "ruby",
|
||||
"podspec": "ruby",
|
||||
"rake": "ruby",
|
||||
"rbi": "ruby",
|
||||
"rbx": "ruby",
|
||||
"rjs": "ruby",
|
||||
"ru": "ruby",
|
||||
"fs": "fsharp",
|
||||
"fsx": "fsharp",
|
||||
"fsi": "fsharp",
|
||||
"fsproj": "fsharp",
|
||||
"fsscript": "fsharp",
|
||||
"swift": "swift",
|
||||
"xcplayground": "swift",
|
||||
"swiftdeps": "swift",
|
||||
@@ -9680,6 +9895,15 @@
|
||||
"containerfile": "docker",
|
||||
"compose.yaml": "docker",
|
||||
"compose.yml": "docker",
|
||||
"tex": "tex",
|
||||
"ltx": "tex",
|
||||
"cls": "tex",
|
||||
"clo": "tex",
|
||||
"latex": "tex",
|
||||
"aux": "tex",
|
||||
"tikz": "tex",
|
||||
"synctex": "tex",
|
||||
"synctex.gz": "tex",
|
||||
"sty": "sty",
|
||||
"ctx": "context",
|
||||
"dtx": "dtx",
|
||||
@@ -9820,13 +10044,23 @@
|
||||
"mist.tsx": "mist",
|
||||
"otne": "otne",
|
||||
"patch": "git",
|
||||
"diff": "diff",
|
||||
"rej": "diff",
|
||||
"lua": "lua",
|
||||
"clj": "clojure",
|
||||
"cljs": "clojure",
|
||||
"cljc": "clojure",
|
||||
"cljx": "clojure",
|
||||
"clojure": "clojure",
|
||||
"edn": "clojure",
|
||||
"groovy": "groovy",
|
||||
"gvy": "groovy",
|
||||
"nf": "groovy",
|
||||
"r": "r",
|
||||
"rmd": "r",
|
||||
"rhistory": "r",
|
||||
"rprofile": "r",
|
||||
"rt": "r",
|
||||
"dart": "dart",
|
||||
"freezed.dart": "dart_generated",
|
||||
"g.dart": "dart_generated",
|
||||
@@ -9862,8 +10096,13 @@
|
||||
"lock": "lock",
|
||||
"hbs": "handlebars",
|
||||
"mustache": "handlebars",
|
||||
"handlebars": "handlebars",
|
||||
"hjs": "handlebars",
|
||||
"pm": "perl",
|
||||
"raku": "perl",
|
||||
"pod": "perl",
|
||||
"psgi": "perl",
|
||||
"t": "perl",
|
||||
"hx": "haxe",
|
||||
"spec.ts": "test-ts",
|
||||
"spec.cts": "test-ts",
|
||||
@@ -10074,6 +10313,7 @@
|
||||
"prisma": "prisma",
|
||||
"cshtml": "razor",
|
||||
"vbhtml": "razor",
|
||||
"razor": "razor",
|
||||
"abc": "abc",
|
||||
"ad": "asciidoc",
|
||||
"adoc": "asciidoc",
|
||||
@@ -10088,16 +10328,37 @@
|
||||
"stl": "3d",
|
||||
"stp": "3d",
|
||||
"step": "3d",
|
||||
"ste": "3d",
|
||||
"obj": "3d",
|
||||
"o": "3d",
|
||||
"ac": "3d",
|
||||
"dwg": "3d",
|
||||
"dxf": "3d",
|
||||
"fbx": "3d",
|
||||
"mesh": "3d",
|
||||
"3dm": "3d",
|
||||
"3mf": "3d",
|
||||
"catpart": "3d",
|
||||
"catproduct": "3d",
|
||||
"f3d": "3d",
|
||||
"iam": "3d",
|
||||
"ige": "3d",
|
||||
"iges": "3d",
|
||||
"igs": "3d",
|
||||
"ipt": "3d",
|
||||
"jt": "3d",
|
||||
"mqo": "3d",
|
||||
"pmd": "3d",
|
||||
"pmx": "3d",
|
||||
"prt": "3d",
|
||||
"sab": "3d",
|
||||
"sat": "3d",
|
||||
"skp": "3d",
|
||||
"sldasm": "3d",
|
||||
"slddrw": "3d",
|
||||
"sldprt": "3d",
|
||||
"smb": "3d",
|
||||
"smt": "3d",
|
||||
"vac": "3d",
|
||||
"vdp": "3d",
|
||||
"vox": "3d",
|
||||
@@ -10109,6 +10370,10 @@
|
||||
"wrl": "3d",
|
||||
"usd": "3d",
|
||||
"usdz": "3d",
|
||||
"wire": "3d",
|
||||
"x_b": "3d",
|
||||
"x_t": "3d",
|
||||
"123dx": "3d",
|
||||
"svg": "svg",
|
||||
"ai": "adobe-illustrator",
|
||||
"ait": "adobe-illustrator",
|
||||
@@ -10230,6 +10495,11 @@
|
||||
"fen": "chess",
|
||||
"gmi": "gemini",
|
||||
"gemini": "gemini",
|
||||
"php": "php",
|
||||
"php4": "php",
|
||||
"php5": "php",
|
||||
"phtml": "php",
|
||||
"ctp": "php",
|
||||
"tsconfig.json": "tsconfig",
|
||||
"tauri": "tauri",
|
||||
"jsconfig.json": "jsconfig",
|
||||
@@ -10297,6 +10567,13 @@
|
||||
"wgsl": "shader",
|
||||
"spv": "shader",
|
||||
"slang": "shader",
|
||||
"cginc": "shader",
|
||||
"compute": "shader",
|
||||
"fx": "shader",
|
||||
"fxh": "shader",
|
||||
"hlsli": "shader",
|
||||
"psh": "shader",
|
||||
"vsh": "shader",
|
||||
"sy": "siyuan",
|
||||
"ndst.yml": "ndst",
|
||||
"ndst.yaml": "ndst",
|
||||
@@ -10409,65 +10686,22 @@
|
||||
"lean": "lean",
|
||||
"sls": "salt",
|
||||
"m2": "macaulay2",
|
||||
"ua": "uiua",
|
||||
"skill.md": "skill",
|
||||
"skills.md": "skill",
|
||||
"instructions.md": "instructions",
|
||||
"instruction.md": "instructions",
|
||||
"cljx": "clojure",
|
||||
"clojure": "clojure",
|
||||
"edn": "clojure",
|
||||
"ccm": "cpp",
|
||||
"cxxm": "cpp",
|
||||
"c++m": "cpp",
|
||||
"ipp": "cpp",
|
||||
"ixx": "cpp",
|
||||
"tpp": "cpp",
|
||||
"txx": "cpp",
|
||||
"hpp.in": "cpp",
|
||||
"h.in": "cpp",
|
||||
"diff": "diff",
|
||||
"rej": "diff",
|
||||
"fsscript": "fsharp",
|
||||
"mrpack": "mrpack",
|
||||
"gitignore_global": "ignore",
|
||||
"gitignore": "ignore",
|
||||
"git-blame-ignore-revs": "ignore",
|
||||
"gvy": "groovy",
|
||||
"nf": "groovy",
|
||||
"handlebars": "handlebars",
|
||||
"hjs": "handlebars",
|
||||
"hlsli": "hlsl",
|
||||
"fx": "hlsl",
|
||||
"fxh": "hlsl",
|
||||
"vsh": "hlsl",
|
||||
"psh": "hlsl",
|
||||
"cginc": "hlsl",
|
||||
"compute": "hlsl",
|
||||
"html": "html",
|
||||
"shtml": "html",
|
||||
"xht": "html",
|
||||
"aspx": "html",
|
||||
"jshtm": "html",
|
||||
"volt": "html",
|
||||
"rhtml": "html",
|
||||
"directory": "properties",
|
||||
"gitattributes": "properties",
|
||||
"gitconfig": "properties",
|
||||
"gitmodules": "properties",
|
||||
"editorconfig": "properties",
|
||||
"repo": "properties",
|
||||
"jav": "java",
|
||||
"js": "javascript",
|
||||
"es6": "javascript",
|
||||
"cjs": "javascript",
|
||||
"pac": "javascript",
|
||||
"bowerrc": "json",
|
||||
"jscsrc": "json",
|
||||
"webmanifest": "json",
|
||||
"ts.map": "json",
|
||||
"har": "json",
|
||||
"jslintrc": "json",
|
||||
"jsonld": "json",
|
||||
"geojson": "json",
|
||||
"vuerc": "json",
|
||||
"eslintrc": "jsonc",
|
||||
"eslintrc.json": "jsonc",
|
||||
@@ -10476,134 +10710,14 @@
|
||||
"hintrc": "jsonc",
|
||||
"babelrc": "jsonc",
|
||||
"jmd": "juliamarkdown",
|
||||
"cls": "tex",
|
||||
"tex": "latex",
|
||||
"ltx": "latex",
|
||||
"mak": "makefile",
|
||||
"mkd": "markdown",
|
||||
"mdwn": "markdown",
|
||||
"mdown": "markdown",
|
||||
"markdn": "markdown",
|
||||
"mdtxt": "markdown",
|
||||
"mdtext": "markdown",
|
||||
"workbook": "markdown",
|
||||
"npmignore": "ignore",
|
||||
"npmrc": "properties",
|
||||
"m": "objective-c",
|
||||
"mm": "objective-cpp",
|
||||
"pod": "perl",
|
||||
"t": "perl",
|
||||
"psgi": "perl",
|
||||
"rakumod": "raku",
|
||||
"rakutest": "raku",
|
||||
"rakudoc": "raku",
|
||||
"nqp": "raku",
|
||||
"p6": "raku",
|
||||
"pl6": "raku",
|
||||
"pm6": "raku",
|
||||
"php": "php",
|
||||
"php4": "php",
|
||||
"php5": "php",
|
||||
"phtml": "php",
|
||||
"ctp": "php",
|
||||
"psrc": "powershell",
|
||||
"rpy": "python",
|
||||
"pyw": "python",
|
||||
"cpy": "python",
|
||||
"gyp": "python",
|
||||
"gypi": "python",
|
||||
"pyi": "python",
|
||||
"ipy": "python",
|
||||
"pyt": "python",
|
||||
"rhistory": "r",
|
||||
"rprofile": "r",
|
||||
"rt": "r",
|
||||
"razor": "razor",
|
||||
"rbx": "ruby",
|
||||
"rjs": "ruby",
|
||||
"gemspec": "ruby",
|
||||
"rake": "ruby",
|
||||
"ru": "ruby",
|
||||
"podspec": "ruby",
|
||||
"rbi": "ruby",
|
||||
"bashrc": "shellscript",
|
||||
"bash_aliases": "shellscript",
|
||||
"bash_profile": "shellscript",
|
||||
"bash_login": "shellscript",
|
||||
"ebuild": "shellscript",
|
||||
"eclass": "shellscript",
|
||||
"profile": "shellscript",
|
||||
"bash_logout": "shellscript",
|
||||
"xprofile": "shellscript",
|
||||
"xsession": "shellscript",
|
||||
"xsessionrc": "shellscript",
|
||||
"zshrc": "shellscript",
|
||||
"zprofile": "shellscript",
|
||||
"zlogin": "shellscript",
|
||||
"zlogout": "shellscript",
|
||||
"zshenv": "shellscript",
|
||||
"zsh-theme": "shellscript",
|
||||
"cshrc": "shellscript",
|
||||
"tcshrc": "shellscript",
|
||||
"yashrc": "shellscript",
|
||||
"yash_profile": "shellscript",
|
||||
"dsql": "sql",
|
||||
"ts": "typescript",
|
||||
"cts": "typescript",
|
||||
"mts": "typescript",
|
||||
"brs": "vb",
|
||||
"bas": "vb",
|
||||
"vba": "vb",
|
||||
"ascx": "xml",
|
||||
"atom": "xml",
|
||||
"axml": "xml",
|
||||
"axaml": "xml",
|
||||
"bpmn": "xml",
|
||||
"csl": "xml",
|
||||
"csproj.user": "xml",
|
||||
"dita": "xml",
|
||||
"ditamap": "xml",
|
||||
"ent": "xml",
|
||||
"dtml": "xml",
|
||||
"fxml": "xml",
|
||||
"isml": "xml",
|
||||
"jmx": "xml",
|
||||
"launch": "xml",
|
||||
"menu": "xml",
|
||||
"opml": "xml",
|
||||
"owl": "xml",
|
||||
"proj": "xml",
|
||||
"publishsettings": "xml",
|
||||
"pubxml": "xml",
|
||||
"pubxml.user": "xml",
|
||||
"rdf": "xml",
|
||||
"rng": "xml",
|
||||
"rss": "xml",
|
||||
"shproj": "xml",
|
||||
"storyboard": "xml",
|
||||
"targets": "xml",
|
||||
"tld": "xml",
|
||||
"tmx": "xml",
|
||||
"vbproj": "xml",
|
||||
"vbproj.user": "xml",
|
||||
"wsdl": "xml",
|
||||
"wxi": "xml",
|
||||
"wxl": "xml",
|
||||
"wxs": "xml",
|
||||
"xbl": "xml",
|
||||
"xib": "xml",
|
||||
"xliff": "xml",
|
||||
"xpdl": "xml",
|
||||
"xul": "xml",
|
||||
"xoml": "xml",
|
||||
"yaml": "yaml",
|
||||
"yml": "yaml",
|
||||
"eyaml": "yaml",
|
||||
"eyml": "yaml",
|
||||
"cff": "yaml",
|
||||
"yaml-tmpreferences": "yaml",
|
||||
"yaml-tmtheme": "yaml",
|
||||
"winget": "yaml"
|
||||
"pm6": "raku"
|
||||
},
|
||||
"fileNames": {
|
||||
".pug-lintrc": "pug",
|
||||
@@ -10637,6 +10751,7 @@
|
||||
"playwright-ct.config.cts": "playwright",
|
||||
"playwright-ct.config.mts": "playwright",
|
||||
".htaccess": "xml",
|
||||
"jakefile": "javascript",
|
||||
".release-it.json": "rocket",
|
||||
".release-it.ts": "rocket",
|
||||
".release-it.js": "rocket",
|
||||
@@ -10713,6 +10828,17 @@
|
||||
"pre-commit": "console",
|
||||
"pre-push": "console",
|
||||
"post-merge": "console",
|
||||
".envrc": "console",
|
||||
".hushlogin": "console",
|
||||
"apkbuild": "console",
|
||||
"pkgbuild": "console",
|
||||
"bashrc_apple_terminal": "console",
|
||||
"zlogin": "console",
|
||||
"zlogout": "console",
|
||||
"zprofile": "console",
|
||||
"zshenv": "console",
|
||||
"zshrc": "console",
|
||||
"zshrc_apple_terminal": "console",
|
||||
"excalidraw": "excalidraw",
|
||||
"excalidraw.json": "excalidraw",
|
||||
"excalidraw.svg": "excalidraw",
|
||||
@@ -10759,6 +10885,25 @@
|
||||
"keystatic.config.jsx": "keystatic",
|
||||
"keystatic.config.js": "keystatic",
|
||||
".ruby-version": "ruby",
|
||||
"appraisals": "ruby",
|
||||
"berksfile": "ruby",
|
||||
"berksfile.lock": "ruby",
|
||||
"brewfile": "ruby",
|
||||
"capfile": "ruby",
|
||||
"cheffile": "ruby",
|
||||
"dangerfile": "ruby",
|
||||
"deliverfile": "ruby",
|
||||
"guardfile": "ruby",
|
||||
"gymfile": "ruby",
|
||||
"hobofile": "ruby",
|
||||
"matchfile": "ruby",
|
||||
"podfile": "ruby",
|
||||
"puppetfile": "ruby",
|
||||
"rakefile": "ruby",
|
||||
"rantfile": "ruby",
|
||||
"scanfile": "ruby",
|
||||
"snapfile": "ruby",
|
||||
"thorfile": "ruby",
|
||||
"gemfile": "gemfile",
|
||||
".rubocop.yml": "rubocop",
|
||||
".rubocop-todo.yml": "rubocop",
|
||||
@@ -10927,6 +11072,9 @@
|
||||
".git-blame-ignore-revs": "git",
|
||||
".git-for-windows-updater": "git",
|
||||
"git-history": "git",
|
||||
"commit_editmsg": "git",
|
||||
"merge_msg": "git",
|
||||
"git-rebase-todo": "git",
|
||||
".luacheckrc": "lua",
|
||||
".rhistory": "r",
|
||||
".pubignore": "dart",
|
||||
@@ -10936,10 +11084,13 @@
|
||||
"semgrep.yml": "semgrep",
|
||||
".semgrepignore": "semgrep",
|
||||
"vue.config.js": "vue-config",
|
||||
"vue.config.cjs": "vue-config",
|
||||
"vue.config.mjs": "vue-config",
|
||||
"vue.config.ts": "vue-config",
|
||||
"vetur.config.js": "vue-config",
|
||||
"vetur.config.ts": "vue-config",
|
||||
"volar.config.js": "vue-config",
|
||||
".vuerc": "vue-config",
|
||||
"nuxt.config.js": "nuxt",
|
||||
"nuxt.config.ts": "nuxt",
|
||||
".nuxtignore": "nuxt",
|
||||
@@ -10955,6 +11106,7 @@
|
||||
"ng-package.json": "angular",
|
||||
".mjmlconfig": "mjml",
|
||||
"vercel.json": "vercel",
|
||||
"vercel.ts": "vercel",
|
||||
".vercelignore": "vercel",
|
||||
"now.json": "vercel",
|
||||
".nowignore": "vercel",
|
||||
@@ -11715,6 +11867,8 @@
|
||||
".browserslistrc": "browserlist",
|
||||
".snyk": "snyk",
|
||||
".drone.yml": "drone",
|
||||
"opencode.json": "opencode",
|
||||
"opencode.jsonc": "opencode",
|
||||
".sequelizerc": "sequelize",
|
||||
"gatsby-config.js": "gatsby",
|
||||
"gatsby-config.mjs": "gatsby",
|
||||
@@ -12585,6 +12739,7 @@
|
||||
"zeabur.yml": "zeabur",
|
||||
"zeabur.toml": "zeabur",
|
||||
"copilot-instructions.md": "copilot",
|
||||
".copilotignore": "copilot",
|
||||
".pre-commit-config.yaml": "pre-commit",
|
||||
".pre-commit-hooks.yaml": "pre-commit",
|
||||
".lintstagedrc": "lintstaged",
|
||||
@@ -12666,6 +12821,9 @@
|
||||
"metro.config.cjs": "metro",
|
||||
"metro.config.mjs": "metro",
|
||||
"metro.config.json": "metro",
|
||||
"metro.config.ts": "metro",
|
||||
"metro.config.cts": "metro",
|
||||
"metro.config.mts": "metro",
|
||||
"src/bashly.yaml": "bashly",
|
||||
"src/bashly.yml": "bashly",
|
||||
"bashly-settings.yaml": "bashly-settings",
|
||||
@@ -12680,6 +12838,14 @@
|
||||
"skill.md": "skill",
|
||||
"instructions.md": "instructions",
|
||||
"instruction.md": "instructions",
|
||||
"tsdown.config.ts": "tsdown",
|
||||
"tsdown.config.mts": "tsdown",
|
||||
"tsdown.config.cts": "tsdown",
|
||||
"tsdown.config.js": "tsdown",
|
||||
"tsdown.config.mjs": "tsdown",
|
||||
"tsdown.config.cjs": "tsdown",
|
||||
"tsdown.config.json": "tsdown",
|
||||
"tsdown.config": "tsdown",
|
||||
"appwrite.json": "appwrite",
|
||||
"appwrite.js": "appwrite",
|
||||
"appwrite.ts": "appwrite",
|
||||
@@ -12934,6 +13100,7 @@
|
||||
".rubocop-todo.yml": "rubocop_light",
|
||||
".rubocop_todo.yml": "rubocop_light",
|
||||
"vercel.json": "vercel_light",
|
||||
"vercel.ts": "vercel_light",
|
||||
".vercelignore": "vercel_light",
|
||||
"now.json": "vercel_light",
|
||||
".nowignore": "vercel_light",
|
||||
@@ -13001,6 +13168,8 @@
|
||||
"browserslist": "browserlist_light",
|
||||
".browserslistrc": "browserlist_light",
|
||||
".drone.yml": "drone_light",
|
||||
"opencode.json": "opencode_light",
|
||||
"opencode.jsonc": "opencode_light",
|
||||
".wakatime-project": "wakatime_light",
|
||||
"circle.yml": "circleci_light",
|
||||
".releaserc": "semantic-release_light",
|
||||
@@ -13086,6 +13255,7 @@
|
||||
"zeabur.yml": "zeabur_light",
|
||||
"zeabur.toml": "zeabur_light",
|
||||
"copilot-instructions.md": "copilot_light",
|
||||
".copilotignore": "copilot_light",
|
||||
"hosts": "hosts_light",
|
||||
".cursorignore": "cursor_light",
|
||||
".cursorindexingignore": "cursor_light",
|
||||
@@ -13129,11 +13299,11 @@
|
||||
"_idea": "folder-intellij_light",
|
||||
"-idea": "folder-intellij_light",
|
||||
"__idea__": "folder-intellij_light",
|
||||
"cursor": "folder-cursor_light",
|
||||
".cursor": "folder-cursor_light",
|
||||
"..cursor": "folder-cursor_light",
|
||||
"_.cursor": "folder-cursor_light",
|
||||
"-.cursor": "folder-cursor_light",
|
||||
"__.cursor__": "folder-cursor_light"
|
||||
"_cursor": "folder-cursor_light",
|
||||
"-cursor": "folder-cursor_light",
|
||||
"__cursor__": "folder-cursor_light"
|
||||
},
|
||||
"folderNamesExpanded": {
|
||||
"jinja": "folder-jinja-open_light",
|
||||
@@ -13156,11 +13326,11 @@
|
||||
"_idea": "folder-intellij-open_light",
|
||||
"-idea": "folder-intellij-open_light",
|
||||
"__idea__": "folder-intellij-open_light",
|
||||
"cursor": "folder-cursor-open_light",
|
||||
".cursor": "folder-cursor-open_light",
|
||||
"..cursor": "folder-cursor-open_light",
|
||||
"_.cursor": "folder-cursor-open_light",
|
||||
"-.cursor": "folder-cursor-open_light",
|
||||
"__.cursor__": "folder-cursor-open_light"
|
||||
"_cursor": "folder-cursor-open_light",
|
||||
"-cursor": "folder-cursor-open_light",
|
||||
"__cursor__": "folder-cursor-open_light"
|
||||
},
|
||||
"rootFolderNames": {},
|
||||
"rootFolderNamesExpanded": {}
|
||||
|
||||
Generated
+566
-554
File diff suppressed because it is too large
Load Diff
@@ -181,7 +181,6 @@
|
||||
"startpage.platform_desc": "Gitea běží všude, kde <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">Go</a> může kompilovat: Windows, macOS, Linux, ARM, atd. Vyberte si ten, který milujete!",
|
||||
"startpage.lightweight": "Lehká",
|
||||
"startpage.lightweight_desc": "Gitea má minimální požadavky a může běžet na Raspberry Pi. Šetřete energii vašeho stroje!",
|
||||
"startpage.license_desc": "Vše je na <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%[1]s\">%[2]s</a>! Připojte se tím, že <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%[3]s\">přispějete</a> a uděláte tento projekt ještě lepší. Nestyďte se být přispěvatel!",
|
||||
"install.install": "Instalace",
|
||||
"install.title": "Výchozí konfigurace",
|
||||
"install.docker_helper": "Pokud spouštíte Gitea v Dockeru, přečtěte si <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">dokumentaci</a>, než budete měnit jakákoliv nastavení.",
|
||||
@@ -209,7 +208,6 @@
|
||||
"install.lfs_path": "Kořenový adresář Git LFS",
|
||||
"install.lfs_path_helper": "V tomto adresáři budou uloženy soubory, které jsou sledovány Git LFS. Pokud ponecháte prázdné, LFS zakážete.",
|
||||
"install.run_user": "Spustit jako uživatel",
|
||||
"install.run_user_helper": "Zadejte uživatelské jméno, pod kterým Gitea běží v operačním systému. Pozor: tento uživatel musí mít přístup ke kořenovému adresáři repozitářů.",
|
||||
"install.domain": "Doména serveru",
|
||||
"install.domain_helper": "Adresa domény, nebo hostitele serveru.",
|
||||
"install.ssh_port": "Port SSH serveru",
|
||||
@@ -231,12 +229,6 @@
|
||||
"install.register_confirm": "Pro registraci vyžadovat potvrzení e-mailu",
|
||||
"install.mail_notify": "Povolit e-mailová oznámení",
|
||||
"install.server_service_title": "Nastavení serveru a dalších služeb",
|
||||
"install.offline_mode": "Povolit místní režim",
|
||||
"install.offline_mode_popup": "Zakázat sítě pro doručování obsahu a poskytovat veškerý obsah lokálně.",
|
||||
"install.disable_gravatar": "Zakázat Gravatar",
|
||||
"install.disable_gravatar_popup": "Zakážete Gravatar a jiné cizí zdroje avatarů. Pokud uživatel nenahraje avatar, bude použit výchozí.",
|
||||
"install.federated_avatar_lookup": "Povolit avatary z veřejných zdrojů",
|
||||
"install.federated_avatar_lookup_popup": "Povolte vyhledání avatarů z veřejných zdrojů pro využití služeb založených na libravatar.",
|
||||
"install.disable_registration": "Vypnout možnost uživatelské registrace",
|
||||
"install.disable_registration_popup": "Vypnout možnost registrace. Pouze správci budou moci vytvářet účty.",
|
||||
"install.allow_only_external_registration_popup": "Povolit registraci pouze prostřednictvím externích služeb",
|
||||
@@ -256,12 +248,10 @@
|
||||
"install.admin_email": "E-mailová adresa",
|
||||
"install.install_btn_confirm": "Nainstalovat Gitea",
|
||||
"install.test_git_failed": "Chyba při testu příkazu 'git': %v",
|
||||
"install.sqlite3_not_available": "Tato verze Gitea nepodporuje SQLite3. Stáhněte si oficiální binární verzi od %s (nikoli verzi „gobuild“).",
|
||||
"install.invalid_db_setting": "Nastavení databáze je neplatné: %v",
|
||||
"install.invalid_db_table": "Databázová tabulka „%s“ je neplatná: %v",
|
||||
"install.invalid_repo_path": "Kořenový adresář repozitářů není správný: %v",
|
||||
"install.invalid_app_data_path": "Cesta k datům aplikace je neplatná: %v",
|
||||
"install.run_user_not_match": "\"Run as\" uživatelské jméno není aktuální uživatelské jméno: %s -> %s",
|
||||
"install.internal_token_failed": "Nepodařilo se vytvořit interní token: %v",
|
||||
"install.secret_key_failed": "Nepodařilo se vytvořit tajný klíč: %v",
|
||||
"install.save_config_failed": "Uložení konfigurace se nezdařilo: %v",
|
||||
@@ -541,13 +531,6 @@
|
||||
"user.block.unblock.failure": "Nepodařilo se odblokovat uživatele: %s",
|
||||
"user.block.blocked": "Zablokovali jste tohoto uživatele.",
|
||||
"user.block.title": "Zablokovat Uživatele",
|
||||
"user.block.info": "Blokování uživatele brání v interakci s repozitáři, jako je otevírání nebo komentování pull requestů nebo úkolů. Další informace o blokování uživatele.",
|
||||
"user.block.info_1": "Zablokování uživatele zabrání následujícím akcím na vašem účtu a repozirářích:",
|
||||
"user.block.info_2": "sledují váš účet",
|
||||
"user.block.info_3": "pošle vám oznámení pomocí @zmínění vašeho uživatelského jména",
|
||||
"user.block.info_4": "pozváním vás jako spolupracovníka do jejich repozitářů",
|
||||
"user.block.info_5": "oblíbení, rozštěpení nebo sledování repozitářů",
|
||||
"user.block.info_6": "otevření a komentování úkolů nebo pull requestů",
|
||||
"user.block.user_to_block": "Uživatel k blokování",
|
||||
"user.block.note": "Poznámka",
|
||||
"user.block.note.title": "Volitelná poznámka:",
|
||||
@@ -756,7 +739,6 @@
|
||||
"settings.permissions_list": "Oprávnění:",
|
||||
"settings.manage_oauth2_applications": "Spravovat OAuth2 aplikace",
|
||||
"settings.edit_oauth2_application": "Upravit OAuth2 aplikaci",
|
||||
"settings.oauth2_applications_desc": "OAuth2 aplikace umožní aplikacím třetích stran bezpečně ověřit uživatele v této instanci Gitea.",
|
||||
"settings.remove_oauth2_application": "Odstranit OAuth2 aplikaci",
|
||||
"settings.remove_oauth2_application_desc": "Odstraněním OAuth2 aplikace odeberete přístup všem podepsaným přístupovým tokenům. Pokračovat?",
|
||||
"settings.remove_oauth2_application_success": "Aplikace byla odstraněna.",
|
||||
@@ -774,7 +756,6 @@
|
||||
"settings.oauth2_regenerate_secret_hint": "Ztratili jste svůj tajný klíč?",
|
||||
"settings.oauth2_client_secret_hint": "Tajný klíč se znovu nezobrazí po opuštění nebo obnovení této stránky. Ujistěte se, že jste si jej uložili.",
|
||||
"settings.oauth2_application_edit": "Upravit",
|
||||
"settings.oauth2_application_create_description": "OAuth2 aplikace poskytuje přístup aplikacím třetích stran k uživatelským účtům na této instanci.",
|
||||
"settings.oauth2_application_remove_description": "Odebráním OAuth2 aplikace zabrání přístupu ověřeným uživatelům na této instanci. Pokračovat?",
|
||||
"settings.oauth2_application_locked": "Gitea předregistruje některé OAuth2 aplikace při spuštění, pokud je to povoleno v konfiguraci. Aby se zabránilo neočekávanému chování, nelze je upravovat ani odstranit. Více informací naleznete v dokumentaci OAuth2.",
|
||||
"settings.authorized_oauth2_applications": "Autorizovat OAuth2 aplikaci",
|
||||
@@ -845,7 +826,6 @@
|
||||
"repo.visibility_description": "Pouze majitelé nebo členové organizace to budou moci vidět, pokud mají práva.",
|
||||
"repo.visibility_helper": "Nastavit repozitář jako soukromý",
|
||||
"repo.visibility_helper_forced": "Váš administrátor vynutil, že nové repozitáře budou soukromé.",
|
||||
"repo.visibility_fork_helper": "(Změna tohoto ovlivní všechny rozštěpení repozitáře.)",
|
||||
"repo.clone_helper": "Potřebujete pomoci s klonováním? Navštivte <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">nápovědu</a>.",
|
||||
"repo.fork_repo": "Rozštěpení repozitáře",
|
||||
"repo.fork_from": "Rozštěpit z",
|
||||
@@ -931,8 +911,6 @@
|
||||
"repo.transfer.accept_desc": "Převést do „%s“",
|
||||
"repo.transfer.reject": "Odmítnout převod",
|
||||
"repo.transfer.reject_desc": "Zrušit převod do „%s“",
|
||||
"repo.transfer.no_permission_to_accept": "Nemáte oprávnění k přijetí tohoto převodu.",
|
||||
"repo.transfer.no_permission_to_reject": "Nemáte oprávnění k odmítnutí tohoto převodu.",
|
||||
"repo.desc.private": "Soukromý",
|
||||
"repo.desc.public": "Veřejný",
|
||||
"repo.desc.template": "Šablona",
|
||||
@@ -1065,7 +1043,6 @@
|
||||
"repo.ambiguous_runes_description": "Tento soubor obsahuje znaky Unicode, které mohou být zaměněny s jinými znaky. Pokud si myslíte, že je to záměrné, můžete toto varování bezpečně ignorovat. Použijte tlačítko Escape sekvence k jejich zobrazení.",
|
||||
"repo.invisible_runes_line": "Tento řádek má neviditelné znaky Unicode",
|
||||
"repo.ambiguous_runes_line": "Tento řádek má nejednoznačné znaky Unicode",
|
||||
"repo.ambiguous_character": "%[1]c [U+%04[1]X] je zaměnitelný s %[2]c [U+%04[2]X]",
|
||||
"repo.escape_control_characters": "Escape sekvence",
|
||||
"repo.unescape_control_characters": "Bez escape sekvencí",
|
||||
"repo.file_copy_permalink": "Kopírovat trvalý odkaz",
|
||||
@@ -1145,7 +1122,6 @@
|
||||
"repo.editor.upload_file_is_locked": "Soubor „%s“ je uzamčen uživatelem %s.",
|
||||
"repo.editor.upload_files_to_dir": "Nahrát soubory do „%s“",
|
||||
"repo.editor.cannot_commit_to_protected_branch": "Nelze vytvořit commit v chráněné větvi „%s“.",
|
||||
"repo.editor.no_commit_to_branch": "Nelze odevzdat přímo do větve, protože:",
|
||||
"repo.editor.user_no_push_to_branch": "Uživatel nemůže nahrávat do větve",
|
||||
"repo.editor.require_signed_commit": "Větev vyžaduje podepsaný commit",
|
||||
"repo.editor.cherry_pick": "Cherry-pick %s na:",
|
||||
@@ -1153,7 +1129,6 @@
|
||||
"repo.commits.desc": "Procházet historii změn zdrojového kódu.",
|
||||
"repo.commits.commits": "Commity",
|
||||
"repo.commits.no_commits": "Žádné společné commity. „%s“ a „%s“ mají zcela odlišnou historii.",
|
||||
"repo.commits.nothing_to_compare": "Tyto větve jsou stejné.",
|
||||
"repo.commits.search.tooltip": "Můžete předřadit klíčová slova s „author:“, „committer:“, „after:“ nebo „before:“, např. „revert author:Alice before:2019-01-03“.",
|
||||
"repo.commits.search_branch": "Tato větev",
|
||||
"repo.commits.search_all": "Všechny větve",
|
||||
@@ -1224,11 +1199,9 @@
|
||||
"repo.issues.new": "Nový úkol",
|
||||
"repo.issues.new.title_empty": "Název nesmí být prázdný",
|
||||
"repo.issues.new.labels": "Štítky",
|
||||
"repo.issues.new.no_label": "Bez štítku",
|
||||
"repo.issues.new.clear_labels": "Zrušit štítky",
|
||||
"repo.issues.new.projects": "Projekty",
|
||||
"repo.issues.new.clear_projects": "Vymazat projekty",
|
||||
"repo.issues.new.no_projects": "Žádný projekt",
|
||||
"repo.issues.new.open_projects": "Otevřít projekty",
|
||||
"repo.issues.new.closed_projects": "Uzavřené projekty",
|
||||
"repo.issues.new.no_items": "Žádné položky",
|
||||
@@ -1351,7 +1324,6 @@
|
||||
"repo.issues.comment_pull_merged_at": "sloučený commit %[1]s do %[2]s %[3]s",
|
||||
"repo.issues.comment_manually_pull_merged_at": "ručně sloučený commit %[1]s do %[2]s %[3]s",
|
||||
"repo.issues.close_comment_issue": "Okomentovat a zavřít",
|
||||
"repo.issues.reopen_issue": "Znovuotevřít",
|
||||
"repo.issues.reopen_comment_issue": "Znovu otevřít s komentářem",
|
||||
"repo.issues.create_comment": "Okomentovat",
|
||||
"repo.issues.comment.blocked_user": "Nemůžete vytvořit nebo upravovat komentář, protože jste zablokováni zadavatelem příspěvku nebo vlastníkem repozitáře.",
|
||||
@@ -1557,7 +1529,6 @@
|
||||
"repo.pulls.allow_edits_from_maintainers": "Povolit úpravy od správců",
|
||||
"repo.pulls.allow_edits_from_maintainers_desc": "Uživatelé s přístupem k zápisu do základní větve mohou také nahrávat do této větve",
|
||||
"repo.pulls.allow_edits_from_maintainers_err": "Aktualizace se nezdařila",
|
||||
"repo.pulls.compare_changes_desc": "Vyberte větev pro sloučení a větev pro natažení.",
|
||||
"repo.pulls.has_viewed_file": "Zobrazeno",
|
||||
"repo.pulls.has_changed_since_last_review": "Změněno od vašeho posledního posouzení",
|
||||
"repo.pulls.viewed_files_label": "%[1]d / %[2]d souborů zobrazeno",
|
||||
@@ -1574,7 +1545,6 @@
|
||||
"repo.pulls.showing_specified_commit_range": "Zobrazují se pouze změny mezi %[1]s..%[2]s",
|
||||
"repo.pulls.review_only_possible_for_full_diff": "Posouzení je možné pouze při zobrazení plného rozlišení",
|
||||
"repo.pulls.filter_changes_by_commit": "Filtrovat podle commitu",
|
||||
"repo.pulls.nothing_to_compare": "Tyto větve jsou stejné. Není potřeba vytvářet pull request.",
|
||||
"repo.pulls.nothing_to_compare_and_allow_empty_pr": "Tyto větve jsou stejné. Tento pull request bude prázdný.",
|
||||
"repo.pulls.has_pull_request": "Pull request mezi těmito větvemi již existuje: <a href=\"%[1]s\">%[2]s#%[3]d</a>",
|
||||
"repo.pulls.create": "Vytvořit pull request",
|
||||
@@ -1641,7 +1611,6 @@
|
||||
"repo.pulls.status_checking": "Některé kontroly jsou nedořešeny",
|
||||
"repo.pulls.status_checks_success": "Všechny kontroly byly úspěšné",
|
||||
"repo.pulls.status_checks_warning": "Některé kontroly nahlásily varování",
|
||||
"repo.pulls.status_checks_failure": "Některé kontroly se nezdařily",
|
||||
"repo.pulls.status_checks_error": "Některé kontroly nahlásily chyby",
|
||||
"repo.pulls.status_checks_requested": "Požadováno",
|
||||
"repo.pulls.status_checks_details": "Podrobnosti",
|
||||
@@ -1719,7 +1688,6 @@
|
||||
"repo.signing.wont_sign.headsigned": "Sloučení nebude podepsáno, protože hlavní revize není podepsána.",
|
||||
"repo.signing.wont_sign.commitssigned": "Sloučení nebude podepsáno, protože všechny přidružené revize nejsou podepsány.",
|
||||
"repo.signing.wont_sign.approved": "Sloučení nebude podepsáno, protože pull request není schválen.",
|
||||
"repo.signing.wont_sign.not_signed_in": "Nejste přihlášeni.",
|
||||
"repo.ext_wiki": "Přístup k externí Wiki",
|
||||
"repo.ext_wiki.desc": "Odkaz do externí Wiki.",
|
||||
"repo.wiki.welcome": "Vítejte ve Wiki.",
|
||||
@@ -1928,7 +1896,6 @@
|
||||
"repo.settings.transfer_abort_invalid": "Nemůžete zrušit neexistující převod repozitáře.",
|
||||
"repo.settings.transfer_abort_success": "Převod repozitáře do %s byl úspěšně zrušen.",
|
||||
"repo.settings.transfer_desc": "Předat tento repozitář uživateli nebo organizaci, ve které máte administrátorská práva.",
|
||||
"repo.settings.transfer_form_title": "Zadejte jméno repozitáře pro potvrzení:",
|
||||
"repo.settings.transfer_notices_1": "- Ztratíte přístup k repozitáři, pokud jej převedete na uživatele.",
|
||||
"repo.settings.transfer_notices_2": "- Zůstane vám přístup k repozitáři, pokud jej převedete na organizaci kterou (spolu)vlastníte.",
|
||||
"repo.settings.transfer_notices_3": "- Pokud je repozitář soukromý a je předán jednotlivému uživateli, tato akce se ujistí, že uživatel má alespoň oprávnění ke čtení (a v případě potřeby změní oprávnění).",
|
||||
@@ -1994,8 +1961,6 @@
|
||||
"repo.settings.webhook.delivery.success": "Událost byla přidána do fronty doručení. Může to trvat několik sekund, než se zobrazí v historii doručení.",
|
||||
"repo.settings.githooks_desc": "Jelikož háčky Gitu jsou spravovány Gitem samotným, můžete upravit soubory háčků k provádění uživatelských operací.",
|
||||
"repo.settings.githook_edit_desc": "Je-li háček neaktivní, bude zobrazen vzorový obsah. Nebude-li zadán žádný obsah, háček bude vypnut.",
|
||||
"repo.settings.githook_name": "Název háčku",
|
||||
"repo.settings.githook_content": "Obsah háčku",
|
||||
"repo.settings.update_githook": "Aktualizovat háček",
|
||||
"repo.settings.add_webhook_desc": "Gitea odešle dotaz <code>POST</code> s nastaveným Content Type na cílovou URL. Čtěte více v <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">průvodci webovými háčky</a>.",
|
||||
"repo.settings.payload_url": "Cílové URL",
|
||||
@@ -2160,9 +2125,7 @@
|
||||
"repo.settings.block_outdated_branch": "Blokovat sloučení, pokud je pull request zastaralý",
|
||||
"repo.settings.block_outdated_branch_desc": "Slučování nebude možné, pokud je hlavní větev za základní větví.",
|
||||
"repo.settings.block_admin_merge_override": "Pravidla pro ochranu větví se vztahují i na administrátory",
|
||||
"repo.settings.default_branch_desc": "Vybrat výchozí větev repozitáře pro pull requesty a revize kódu:",
|
||||
"repo.settings.merge_style_desc": "Sloučit styly",
|
||||
"repo.settings.default_merge_style_desc": "Výchozí styl sloučení",
|
||||
"repo.settings.choose_branch": "Vyberte větev…",
|
||||
"repo.settings.no_protected_branch": "Nejsou tu žádné chráněné větve.",
|
||||
"repo.settings.edit_protected_branch": "Upravit",
|
||||
@@ -2264,8 +2227,8 @@
|
||||
"repo.diff.too_many_files": "Některé soubory nejsou zobrazny, neboť je v této revizi změněno mnoho souborů",
|
||||
"repo.diff.show_more": "Zobrazit více",
|
||||
"repo.diff.load": "Načíst rozdílové porovnání",
|
||||
"repo.diff.generated": "vygenerováno",
|
||||
"repo.diff.vendored": "vendorováno",
|
||||
"repo.diff.generated": "Generováno",
|
||||
"repo.diff.vendored": "Vendorováno",
|
||||
"repo.diff.comment.add_line_comment": "Přidat jednořádkový komentář",
|
||||
"repo.diff.comment.placeholder": "Zanechat komentář",
|
||||
"repo.diff.comment.add_single_comment": "Přidat jeden komentář",
|
||||
@@ -2281,6 +2244,7 @@
|
||||
"repo.diff.review.reject": "Požadovat změny",
|
||||
"repo.diff.review.self_approve": "Autoři pull requestu nemohou schválit svůj vlastní pull request",
|
||||
"repo.diff.committed_by": "odevzdal",
|
||||
"repo.commits.avatar_stack_and": "a",
|
||||
"repo.diff.protected": "Chráněno",
|
||||
"repo.diff.image.side_by_side": "Vedle sebe",
|
||||
"repo.diff.image.swipe": "Posunout",
|
||||
@@ -2358,7 +2322,6 @@
|
||||
"repo.branch.restore_success": "Větev „%s“ byla obnovena.",
|
||||
"repo.branch.restore_failed": "Nepodařilo se obnovit větev „%s“.",
|
||||
"repo.branch.protected_deletion_failed": "Větev „%s“ je chráněna. Nemůže být smazána.",
|
||||
"repo.branch.default_deletion_failed": "Větev „%s“ je výchozí větev. Nelze ji odstranit.",
|
||||
"repo.branch.restore": "Obnovit větev „%s“",
|
||||
"repo.branch.download": "Stáhnout větev „%s“",
|
||||
"repo.branch.rename": "Přejmenovat větev „%s“",
|
||||
@@ -2374,7 +2337,6 @@
|
||||
"repo.branch.new_branch_from": "Vytvořit novou větev z „%s“",
|
||||
"repo.branch.renamed": "Větev %s byla přejmenována na %s.",
|
||||
"repo.branch.rename_default_or_protected_branch_error": "Pouze administrátoři mohou přejmenovat výchozí nebo chráněné větve.",
|
||||
"repo.branch.rename_protected_branch_failed": "Tato větev je chráněna pravidly ochrany založenými na zástupném vzoru.",
|
||||
"repo.tag.create_tag": "Vytvořit značku %s",
|
||||
"repo.tag.create_tag_operation": "Vytvořit značku",
|
||||
"repo.tag.confirm_create_tag": "Vytvořit značku",
|
||||
@@ -2444,9 +2406,7 @@
|
||||
"org.settings.labels_desc": "Přidejte štítky, které mohou být použity pro úkoly <strong>všech repositářů</strong> v rámci této organizace.",
|
||||
"org.members.membership_visibility": "Viditelnost členství:",
|
||||
"org.members.public": "Viditelný",
|
||||
"org.members.public_helper": "skrýt",
|
||||
"org.members.private": "Skrytý",
|
||||
"org.members.private_helper": "zviditělnit",
|
||||
"org.members.member_role": "Role člena:",
|
||||
"org.members.owner": "Vlastník",
|
||||
"org.members.member": "Člen",
|
||||
@@ -2477,7 +2437,6 @@
|
||||
"org.teams.members": "Členové týmu",
|
||||
"org.teams.update_settings": "Upravit nastavení",
|
||||
"org.teams.delete_team": "Smazat tým",
|
||||
"org.teams.add_team_member": "Přidat člena týmu",
|
||||
"org.teams.invite_team_member": "Pozvat do %s",
|
||||
"org.teams.invite_team_member.list": "Čekající pozvánky",
|
||||
"org.teams.delete_team_title": "Smazat tým",
|
||||
@@ -2503,6 +2462,10 @@
|
||||
"org.teams.all_repositories_read_permission_desc": "Tomuto týmu je udělen přístup pro <strong>Čtení</strong> <strong>všech repozitářů</strong>: členové mohou prohlížet a klonovat repozitáře.",
|
||||
"org.teams.all_repositories_write_permission_desc": "Tomuto týmu je udělen přístup pro <strong>Zápis</strong> do <strong>všech repozitářů</strong>: členové mohou prohlížet a nahrávat do repozitářů.",
|
||||
"org.teams.all_repositories_admin_permission_desc": "Tomuto týmu je udělen <strong>Administrátorský</strong> přístup do <strong>všech repozitářů</strong>: členové mohou prohlížet, nahrávat a přidávat spolupracovníky do repozitářů.",
|
||||
"org.teams.visibility": "Viditelnost",
|
||||
"org.teams.visibility_private": "Soukromý",
|
||||
"org.teams.visibility_limited": "Omezený",
|
||||
"org.teams.visibility_public": "Veřejná",
|
||||
"org.teams.invite.title": "Byli jste pozváni do týmu <strong>%s</strong> v organizaci <strong>%s</strong>.",
|
||||
"org.teams.invite.by": "Pozvání od %s",
|
||||
"org.teams.invite.description": "Pro připojení k týmu klikněte na tlačítko níže.",
|
||||
@@ -2520,6 +2483,7 @@
|
||||
"admin.hooks": "Webové háčky",
|
||||
"admin.integrations": "Integrace",
|
||||
"admin.authentication": "Zdroje ověření",
|
||||
"admin.badges.description": "Popis",
|
||||
"admin.config": "Nastavení",
|
||||
"admin.config_summary": "Souhrn",
|
||||
"admin.config_settings": "Nastavení",
|
||||
@@ -2813,11 +2777,8 @@
|
||||
"admin.config.server_config": "Nastavení serveru",
|
||||
"admin.config.app_name": "Název stránky",
|
||||
"admin.config.app_ver": "Verze Gitea",
|
||||
"admin.config.app_url": "Základní URL Gitea",
|
||||
"admin.config.custom_conf": "Cesta ke konfiguračnímu souboru",
|
||||
"admin.config.custom_file_root_path": "Kořenový adresář vlastních souborů",
|
||||
"admin.config.domain": "Doména serveru",
|
||||
"admin.config.offline_mode": "Lokální režim",
|
||||
"admin.config.disable_router_log": "Vypnout log směrovače",
|
||||
"admin.config.run_user": "Spustit jako uživatel",
|
||||
"admin.config.run_mode": "Režim spouštění",
|
||||
@@ -2894,7 +2855,6 @@
|
||||
"admin.config.cache_config": "Nastavení mezipaměti",
|
||||
"admin.config.cache_adapter": "Adaptér mezipaměti",
|
||||
"admin.config.cache_interval": "Interval mezipaměti",
|
||||
"admin.config.cache_conn": "Připojení mezipaměti",
|
||||
"admin.config.cache_item_ttl": "Čas vypršení položky v mezipaměti",
|
||||
"admin.config.cache_test": "Otestovat cache",
|
||||
"admin.config.cache_test_failed": "Test cache se nezdařil: %v.",
|
||||
@@ -2902,7 +2862,6 @@
|
||||
"admin.config.cache_test_succeeded": "Test mezipaměti byl úspěšný, odpověď za %s.",
|
||||
"admin.config.session_config": "Nastavení relace",
|
||||
"admin.config.session_provider": "Poskytovatel relace",
|
||||
"admin.config.provider_config": "Nastavení poskytovatele",
|
||||
"admin.config.cookie_name": "Název souboru cookie",
|
||||
"admin.config.gc_interval_time": "Čas intervalu GC",
|
||||
"admin.config.session_life_time": "Doba trvání relace",
|
||||
@@ -2910,7 +2869,6 @@
|
||||
"admin.config.cookie_life_time": "Doba života souboru cookie",
|
||||
"admin.config.picture_config": "Nastavení obrázku a avataru",
|
||||
"admin.config.picture_service": "Služba ikon uživatelů",
|
||||
"admin.config.disable_gravatar": "Zakázat službu Gravatar",
|
||||
"admin.config.enable_federated_avatar": "Povolit avatary z veřejných zdrojů",
|
||||
"admin.config.open_with_editor_app_help": "Editory \"Otevřít\" v nabídce klon. Ponecháte-li prázdné, bude použito výchozí. Pro zobrazení výchozího nastavení rozbalte.",
|
||||
"admin.config.git_config": "Konfigurace Gitu",
|
||||
@@ -2921,8 +2879,6 @@
|
||||
"admin.config.git_gc_args": "Parametry GC",
|
||||
"admin.config.git_migrate_timeout": "Časový limit migrace",
|
||||
"admin.config.git_mirror_timeout": "Časový limit aktualizace zrcadla",
|
||||
"admin.config.git_clone_timeout": "Časový limit operace naklonování",
|
||||
"admin.config.git_pull_timeout": "Časový limit operace stažení",
|
||||
"admin.config.git_gc_timeout": "Časový limit operace GC",
|
||||
"admin.config.log_config": "Nastavení logů",
|
||||
"admin.config.disabled_logger": "Zakázané",
|
||||
@@ -3159,6 +3115,10 @@
|
||||
"packages.rubygems.required.rubygems": "Vyžaduje verzi RubyGem",
|
||||
"packages.swift.install": "Přidejte balíček do svého <code>Package.swift</code> souboru:",
|
||||
"packages.swift.install2": "a spustit následující příkaz:",
|
||||
"packages.terraform.install2": "a spustit následující příkaz:",
|
||||
"packages.terraform.lock_status": "Stav zámku",
|
||||
"packages.terraform.lock": "Uzamknout",
|
||||
"packages.terraform.unlock": "Odemknout",
|
||||
"packages.vagrant.install": "Pro přidání Vagrant box spusťte následující příkaz:",
|
||||
"packages.settings.link": "Propojit tento balíček s repozitářem",
|
||||
"packages.settings.link.select": "Vybrat repozitář",
|
||||
@@ -3262,6 +3222,8 @@
|
||||
"actions.runs.actor": "Aktér",
|
||||
"actions.runs.actors_no_select": "Všichni aktéři",
|
||||
"actions.runs.status_no_select": "Všechny stavy",
|
||||
"actions.runs.branch": "Větev",
|
||||
"actions.runs.branches_no_select": "Všechny větve",
|
||||
"actions.runs.no_results": "Nebyly nalezeny žádné výsledky.",
|
||||
"actions.runs.no_workflows": "Zatím neexistují žádné pracovní postupy.",
|
||||
"actions.runs.no_workflows.quick_start": "Nevíte jak začít s Gitea Actions? Podívejte se na <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">průvodce rychlým startem</a>.",
|
||||
@@ -3269,11 +3231,16 @@
|
||||
"actions.runs.no_runs": "Pracovní postup zatím nebyl spuštěn.",
|
||||
"actions.runs.empty_commit_message": "(prázdná zpráva commitu)",
|
||||
"actions.runs.expire_log_message": "Logy byly vyčištěny, protože byly příliš staré.",
|
||||
"actions.runs.summary": "Souhrn",
|
||||
"actions.runs.latest": "Nejnovější",
|
||||
"actions.workflow.disable": "Zakázat pracovní postup",
|
||||
"actions.workflow.disable_success": "Pracovní postup „%s“ byl úspěšně deaktivován.",
|
||||
"actions.workflow.enable": "Povolit pracovní postup",
|
||||
"actions.workflow.enable_success": "Pracovní postup „%s“ byl úspěšně aktivován.",
|
||||
"actions.workflow.disabled": "Pracovní postup je zakázán.",
|
||||
"actions.workflow.scope_owner": "Vlastník",
|
||||
"actions.workflow.scope_global": "Globální",
|
||||
"actions.workflow.required": "Požadováno",
|
||||
"actions.workflow.run": "Spustit pracovní postup",
|
||||
"actions.workflow.not_found": "Pracovní postup „%s“ nebyl nalezen.",
|
||||
"actions.workflow.run_success": "Pracovní postup „%s“ proběhl úspěšně.",
|
||||
@@ -3303,8 +3270,6 @@
|
||||
"projects.type-2.display_name": "Projekt repozitíře",
|
||||
"projects.type-3.display_name": "Projekt organizace",
|
||||
"git.filemode.directory": "Adresář",
|
||||
"git.filemode.normal_file": "Normální soubor",
|
||||
"git.filemode.executable_file": "Spustitelný soubor",
|
||||
"git.filemode.symbolic_link": "Symbolický odkaz",
|
||||
"git.filemode.submodule": "Submodul"
|
||||
"git.filemode.submodule": "Submodul",
|
||||
"actions.general.token_permissions.mode.restricted": "Omezený"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -183,7 +183,6 @@
|
||||
"install.lfs_path": "Ριζική Διαδρομή Git LFS",
|
||||
"install.lfs_path_helper": "Τα αρχεία που παρακολουθούνται από το Git LFS θα αποθηκεύονται σε αυτόν τον φάκελο. Αφήστε κενό για να το απενεργοποιήσετε.",
|
||||
"install.run_user": "Εκτέλεση Σαν Χρήστη",
|
||||
"install.run_user_helper": "Το όνομα του χρήστη του λειτουργικού συστήματος ο οποίος εκτελεί το Gitea. Επισημαίνεται ότι αυτός ο χρήστης πρέπει να έχει πρόσβαση στο ριζικό φάκελο του αποθετηρίου.",
|
||||
"install.domain": "Domain Διακομιστή",
|
||||
"install.domain_helper": "Όνομα domain διακομιστή ή η διεύθυνση του.",
|
||||
"install.ssh_port": "Θύρα της υπηρεσίας SSH",
|
||||
@@ -204,12 +203,6 @@
|
||||
"install.register_confirm": "Απαιτείται Επιβεβαίωση της Διεύθυνσης Εmail για Εγγραφή",
|
||||
"install.mail_notify": "Ενεργοποίηση Ειδοποιήσεων με Email",
|
||||
"install.server_service_title": "Ρυθμίσεις Διακομιστή και Υπηρεσιών Τρίτων",
|
||||
"install.offline_mode": "Ενεργοποίηση Τοπικής Λειτουργίας",
|
||||
"install.offline_mode_popup": "Απενεργοποιήση των δικτύων διανομής περιεχομένου τρίτων και σερβίρετε όλων των πόρων τοπικά.",
|
||||
"install.disable_gravatar": "Απενεργοποίηση Gravatar",
|
||||
"install.disable_gravatar_popup": "Απενεργοποιήση του Gravatar και των εξωτερικών πηγών avatar. Θα χρησιμοποιηθεί ένα προεπιλεγμένο avatar εκτός αν ένας χρήστης ανεβάσει τοπικά ένα avatar.",
|
||||
"install.federated_avatar_lookup": "Ενεργοποίηση Ομόσπονδων Avatars",
|
||||
"install.federated_avatar_lookup_popup": "Ενεργοποίηση ομόσπονδης αναζήτησης avatar χρησιμοποιώντας το Libravatar.",
|
||||
"install.disable_registration": "Απενεργοποίηση Αυτοεγγραφής",
|
||||
"install.disable_registration_popup": "Απενεργοποίηση αυτοεγγραφής χρήστη. Μόνο οι διαχειριστές θα μπορούν να δημιουργήσουν νέους λογαριασμούς χρηστών.",
|
||||
"install.allow_only_external_registration_popup": "Να Επιτρέπεται Η Εγγραφή Μόνο Μέσω Εξωτερικών Υπηρεσιών",
|
||||
@@ -229,12 +222,10 @@
|
||||
"install.admin_email": "Διεύθυνση Email",
|
||||
"install.install_btn_confirm": "Εγκατάσταση Gitea",
|
||||
"install.test_git_failed": "Αδυναμία δοκιμής της εντολής 'git': %v",
|
||||
"install.sqlite3_not_available": "Αυτή η έκδοση Gitea δεν υποστηρίζει την SQLite3. Παρακαλώ κατεβάστε την επίσημη δυαδική έκδοση από το %s (όχι την έκδοση 'gobuild').",
|
||||
"install.invalid_db_setting": "Οι ρυθμίσεις της βάσης δεδομένων δεν είναι έγκυρες: %v",
|
||||
"install.invalid_db_table": "Ο πίνακας βάσης δεδομένων \"%s\" δεν είναι έγκυρος: %v",
|
||||
"install.invalid_repo_path": "Η αρχική διαδρομή των αποθετηρίων δεν είναι έγκυρη: %v",
|
||||
"install.invalid_app_data_path": "Η διαδρομή δεδομένων εφαρμογής (app data) δεν είναι έγκυρη: %v",
|
||||
"install.run_user_not_match": "Το όνομα χρήστη 'εκτέλεση ως' δεν είναι το τρέχον όνομα χρήστη: %s -> %s",
|
||||
"install.internal_token_failed": "Αποτυχία δημιουργίας εσωτερικού διακριτικού: %v",
|
||||
"install.secret_key_failed": "Αποτυχία δημιουργίας μυστικού κλειδιού: %v",
|
||||
"install.save_config_failed": "Αποτυχία αποθήκευσης ρυθμίσεων: %v",
|
||||
@@ -687,7 +678,6 @@
|
||||
"settings.permissions_list": "Δικαιώματα:",
|
||||
"settings.manage_oauth2_applications": "Διαχείριση Εφαρμογών Oauth2",
|
||||
"settings.edit_oauth2_application": "Επεξεργασία Εφαρμογής Oauth2",
|
||||
"settings.oauth2_applications_desc": "Οι εφαρμογές OAuth2 επιτρέπουν στην εξωτερική εφαρμογή σας την ασφαλή ταυτοποίηση των χρηστών σε αυτό το Gitea.",
|
||||
"settings.remove_oauth2_application": "Αφαίρεση Εφαρμογής Oauth2",
|
||||
"settings.remove_oauth2_application_desc": "Η αφαίρεση μιας εφαρμογής OAuth2 θα ανακαλέσει την πρόσβαση σε όλα τα υπογεγραμμένα διακριτικά πρόσβασης. Συνέχεια;",
|
||||
"settings.remove_oauth2_application_success": "Η εφαρμογή έχει διαγραφεί.",
|
||||
@@ -704,7 +694,6 @@
|
||||
"settings.oauth2_regenerate_secret_hint": "Χάσατε το μυστικό σας;",
|
||||
"settings.oauth2_client_secret_hint": "Το μυστικό δε θα εμφανιστεί ξανά αν κλείσετε ή ανανεώσετε αυτή τη σελίδα. Παρακαλώ βεβαιωθείτε ότι το έχετε αποθηκεύσει.",
|
||||
"settings.oauth2_application_edit": "Επεξεργασία",
|
||||
"settings.oauth2_application_create_description": "Οι εφαρμογές OAuth2 δίνει πρόσβαση στην εξωτερική εφαρμογή σας σε λογαριασμούς χρηστών σε αυτή την υπηρεσία.",
|
||||
"settings.oauth2_application_remove_description": "Αφαιρώντας μια εφαρμογή OAuth2 θα αποτραπεί η πρόσβαση αυτής, σε εξουσιοδοτημένους λογαριασμούς χρηστών σε αυτή την υπηρεσία. Συνέχεια;",
|
||||
"settings.oauth2_application_locked": "Το Gitea κάνει προεγγραφή σε μερικές εφαρμογές OAuth2 κατά την εκκίνηση αν είναι ενεργοποιημένες στις ρυθμίσεις. Για την αποφυγή απροσδόκητης συμπεριφοράς, αυτές δεν μπορούν ούτε να επεξεργαστούν ούτε να καταργηθούν. Παρακαλούμε ανατρέξτε στην τεκμηρίωση OAuth2 για περισσότερες πληροφορίες.",
|
||||
"settings.authorized_oauth2_applications": "Εξουσιοδοτημένες Εφαρμογές OAuth2",
|
||||
@@ -769,7 +758,6 @@
|
||||
"repo.visibility_description": "Μόνο ο ιδιοκτήτης ή τα μέλη του οργανισμού εάν έχουν δικαιώματα, θα είναι σε θέση να το δουν.",
|
||||
"repo.visibility_helper": "Αλλάξτε το αποθετήριο σε ιδιωτικό",
|
||||
"repo.visibility_helper_forced": "Ο διαχειριστής σας αναγκάζει τα νέα αποθετήρια να είναι ιδιωτικά.",
|
||||
"repo.visibility_fork_helper": "(Αλλάζοντας αυτό θα επηρεάσει όλα τα forks.)",
|
||||
"repo.clone_helper": "Χρειάζεστε βοήθεια για τη κλωνοποίηση; Επισκεφθείτε τη <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">Βοήθεια</a>.",
|
||||
"repo.fork_repo": "Δημιουργία ενός Fork",
|
||||
"repo.fork_from": "Fork Από Το",
|
||||
@@ -838,8 +826,6 @@
|
||||
"repo.blame.ignore_revs.failed": "Αποτυχία αγνόησης των αναθεωρήσεων στο <a href=\"%s\">.git-blame-ignore-revs</a>.",
|
||||
"repo.transfer.accept": "Αποδοχή Μεταφοράς",
|
||||
"repo.transfer.reject": "Απόρριψη Μεταφοράς",
|
||||
"repo.transfer.no_permission_to_accept": "Δεν έχετε άδεια να αποδεχτείτε αυτή τη μεταφορά.",
|
||||
"repo.transfer.no_permission_to_reject": "Δεν έχετε άδεια να απορρίψετε αυτή τη μεταφορά.",
|
||||
"repo.desc.private": "Ιδιωτικό",
|
||||
"repo.desc.public": "Δημόσιο",
|
||||
"repo.desc.template": "Πρότυπο",
|
||||
@@ -1032,7 +1018,6 @@
|
||||
"repo.editor.upload_file_is_locked": "Το αρχείο \"%s\" είναι κλειδωμένο από %s.",
|
||||
"repo.editor.upload_files_to_dir": "Μεταφόρτωση αρχείων στο \"%s\"",
|
||||
"repo.editor.cannot_commit_to_protected_branch": "Δεν είναι δυνατή η υποβολή στον προστατευόμενο κλάδο \"%s\".",
|
||||
"repo.editor.no_commit_to_branch": "Δεν είναι δυνατή η απευθείας υποβολή στο κλάδο επειδή:",
|
||||
"repo.editor.user_no_push_to_branch": "Ο χρήστης δεν μπορεί να κάνει push στο κλάδο",
|
||||
"repo.editor.require_signed_commit": "Ο κλάδος απαιτεί υπογεγραμμένη υποβολή",
|
||||
"repo.editor.cherry_pick": "Ανθολόγηση (cherry-pic) του %s στο:",
|
||||
@@ -1040,7 +1025,6 @@
|
||||
"repo.commits.desc": "Δείτε το ιστορικό αλλαγών του πηγαίου κώδικα.",
|
||||
"repo.commits.commits": "Υποβολές",
|
||||
"repo.commits.no_commits": "Δεν υπάρχουν κοινές υποβολές. Τα \"%s\" και \"%s\" έχουν εντελώς διαφορετικές ιστορίες.",
|
||||
"repo.commits.nothing_to_compare": "Αυτοί οι κλάδοι είναι όμοιοι.",
|
||||
"repo.commits.search.tooltip": "Μπορείτε να προθέτετε τις λέξεις-κλειδιά με \"author:\", \"committer:\", \"after:\", ή \"before:\", π.χ. \"επαναφορά author:Alice before:2019-01-13\".",
|
||||
"repo.commits.search_all": "Όλοι Οι Κλάδοι",
|
||||
"repo.commits.author": "Συγγραφέας",
|
||||
@@ -1109,11 +1093,9 @@
|
||||
"repo.issues.new": "Νέο Ζήτημα",
|
||||
"repo.issues.new.title_empty": "Ο τίτλος δεν μπορεί να είναι κενός",
|
||||
"repo.issues.new.labels": "Σήματα",
|
||||
"repo.issues.new.no_label": "Χωρίς Σήμα",
|
||||
"repo.issues.new.clear_labels": "Καθαρισμός σημάτων",
|
||||
"repo.issues.new.projects": "Έργα",
|
||||
"repo.issues.new.clear_projects": "Εκκαθάριση έργων",
|
||||
"repo.issues.new.no_projects": "Χωρίς έργα",
|
||||
"repo.issues.new.open_projects": "Ανοιχτά Έργα",
|
||||
"repo.issues.new.closed_projects": "Κλειστά Έργα",
|
||||
"repo.issues.new.no_items": "Δεν υπάρχουν αντικείμενα",
|
||||
@@ -1229,7 +1211,6 @@
|
||||
"repo.issues.close": "Κλείσιμο Ζητήματος",
|
||||
"repo.issues.comment_pull_merged_at": "συγχώνευσε την υποβολή %[1]s στο %[2]s %[3]s",
|
||||
"repo.issues.comment_manually_pull_merged_at": "συγχώνευσε χειροκίνητα την υποβολή %[1]s στο %[2]s %[3]s",
|
||||
"repo.issues.reopen_issue": "Ανοίξτε ξανά",
|
||||
"repo.issues.create_comment": "Προσθήκη Σχολίου",
|
||||
"repo.issues.closed_at": "αυτό το ζήτημα έκλεισε <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>",
|
||||
"repo.issues.reopened_at": "ξανά άνοιξε αυτό το ζήτημα <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>",
|
||||
@@ -1413,7 +1394,6 @@
|
||||
"repo.pulls.allow_edits_from_maintainers": "Επιτρέπεται η επεξεργασία από συντηρητές",
|
||||
"repo.pulls.allow_edits_from_maintainers_desc": "Οι χρήστες με πρόσβαση εγγραφής στον βασικό κλάδο μπορούν επίσης να ωθήσουν και σε αυτό τον κλάδο",
|
||||
"repo.pulls.allow_edits_from_maintainers_err": "Η ενημέρωση απέτυχε",
|
||||
"repo.pulls.compare_changes_desc": "Επιλέξτε τον κλάδο που θα συγχωνευθεί και τον κλάδο από τον οποίο θα τραβηχτεί.",
|
||||
"repo.pulls.has_viewed_file": "Είδαν",
|
||||
"repo.pulls.has_changed_since_last_review": "Άλλαξε από τη τελευταία κριτική",
|
||||
"repo.pulls.viewed_files_label": "%[1]d / %[2]d αρχεία εμφανίστηκαν",
|
||||
@@ -1430,7 +1410,6 @@
|
||||
"repo.pulls.showing_specified_commit_range": "Εμφάνιση μόνο των αλλαγών μεταξύ %[1]s..%[2]s",
|
||||
"repo.pulls.review_only_possible_for_full_diff": "Η αξιολόγηση είναι δυνατή μόνο κατά την προβολή της πλήρης διαφοράς",
|
||||
"repo.pulls.filter_changes_by_commit": "Φιλτράρισμα κατά υποβολή",
|
||||
"repo.pulls.nothing_to_compare": "Αυτοί οι κλάδοι είναι όμοιοι. Δεν υπάρχει ανάγκη να δημιουργήσετε ένα pull request.",
|
||||
"repo.pulls.nothing_to_compare_and_allow_empty_pr": "Αυτοί οι κλάδοι είναι ίσοι. Αυτό το PR θα είναι κενό.",
|
||||
"repo.pulls.has_pull_request": "Υπάρχει ήδη pull request μεταξύ αυτών των κλάδων: <a href=\"%[1]s\">%[2]s#%[3]d</a>",
|
||||
"repo.pulls.create": "Δημιουργία Pull Request",
|
||||
@@ -1493,7 +1472,6 @@
|
||||
"repo.pulls.status_checking": "Μερικοί έλεγχοι εκκρεμούν",
|
||||
"repo.pulls.status_checks_success": "Όλοι οι έλεγχοι ήταν επιτυχείς",
|
||||
"repo.pulls.status_checks_warning": "Ορισμένοι έλεγχοι ανέφεραν προειδοποιήσεις",
|
||||
"repo.pulls.status_checks_failure": "Κάποιοι έλεγχοι απέτυχαν",
|
||||
"repo.pulls.status_checks_error": "Ορισμένοι έλεγχοι ανέφεραν σφάλματα",
|
||||
"repo.pulls.status_checks_requested": "Απαιτείται",
|
||||
"repo.pulls.status_checks_details": "Λεπτομέρειες",
|
||||
@@ -1564,7 +1542,6 @@
|
||||
"repo.signing.wont_sign.headsigned": "Η συγχώνευση δε θα υπογραφεί καθώς δεν έχει υπογραφή η υποβολή της κεφαλής.",
|
||||
"repo.signing.wont_sign.commitssigned": "Η συγχώνευση δε θα υπογραφεί καθώς όλες οι σχετικές υποβολές δεν έχουν υπογραφεί.",
|
||||
"repo.signing.wont_sign.approved": "Η συγχώνευση δε θα υπογραφεί καθώς το PR δεν έχει εγκριθεί.",
|
||||
"repo.signing.wont_sign.not_signed_in": "Δεν είστε συνδεδεμένοι.",
|
||||
"repo.ext_wiki": "Πρόσβαση στο Εξωτερικό Wiki",
|
||||
"repo.ext_wiki.desc": "Σύνδεση σε ένα εξωτερικό wiki.",
|
||||
"repo.wiki.welcome": "Καλώς ήρθατε στο Wiki.",
|
||||
@@ -1752,7 +1729,6 @@
|
||||
"repo.settings.transfer_abort_invalid": "Δεν μπορείτε να ακυρώσετε μια ανύπαρκτη μεταβίβαση αποθετηρίου.",
|
||||
"repo.settings.transfer_abort_success": "Η μεταφορά αποθετηρίου στο %s ακυρώθηκε με επιτυχία.",
|
||||
"repo.settings.transfer_desc": "Μεταβιβάστε αυτό το αποθετήριο σε έναν χρήστη ή σε έναν οργανισμό για τον οποίο έχετε δικαιώματα διαχειριστή.",
|
||||
"repo.settings.transfer_form_title": "Εισάγετε το όνομα του αποθετηρίου ως επιβεβαίωση:",
|
||||
"repo.settings.transfer_notices_1": "- Θα χάσετε την πρόσβαση στο αποθετήριο αν το μεταβιβάσετε σε έναν μεμονωμένο χρήστη.",
|
||||
"repo.settings.transfer_notices_2": "- Θα διατηρήσετε την πρόσβαση στο αποθετήριο αν το μεταβιβάσετε σε έναν οργανισμό που είστε (συν)ιδιοκτήτης.",
|
||||
"repo.settings.transfer_notices_3": "- Εάν το αποθετήριο είναι ιδιωτικό και μεταβιβάζεται σε μεμονωμένο χρήστη, αυτή η ενέργεια εξασφαλίζει ότι ο χρήστης έχει τουλάχιστον άδεια ανάγνωσης (και αλλάζει τα δικαιώματα εάν είναι απαραίτητο).",
|
||||
@@ -1816,8 +1792,6 @@
|
||||
"repo.settings.webhook.delivery.success": "Ένα γεγονός έχει προστεθεί στην ουρά παράδοσης. Μπορεί να χρειαστούν λίγα δευτερόλεπτα μέχρι να εμφανιστεί στο ιστορικό.",
|
||||
"repo.settings.githooks_desc": "Τα Άγκιστρα Git παρέχονται από το ίδιο το Git. Μπορείτε να επεξεργαστείτε τα αρχεία αγκίστρων παρακάτω για να ρυθμίσετε προσαρμοσμένες λειτουργίες.",
|
||||
"repo.settings.githook_edit_desc": "Αν το hook είναι ανενεργό, θα παρουσιαστεί ένα παράδειγμα. Αφήνοντας το περιεχόμενο του hook κενό θα το απενεργοποιήσετε.",
|
||||
"repo.settings.githook_name": "Όνομα Hook",
|
||||
"repo.settings.githook_content": "Περιεχόμενο Hook",
|
||||
"repo.settings.update_githook": "Ενημέρωση Hook",
|
||||
"repo.settings.add_webhook_desc": "Ο Gitea θα στείλει αιτήματα <code>POST</code> με συγκεκριμένο τύπο περιεχομένου στο URL προορισμού. Διαβάστε περισσότερα στον οδηγό <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">webhooks</a>.",
|
||||
"repo.settings.payload_url": "URL Στόχου",
|
||||
@@ -1947,7 +1921,6 @@
|
||||
"repo.settings.block_on_official_review_requests_desc": "Η συγχώνευση δεν θα είναι δυνατή όταν θα έχει αιτήσεις επίσημης αξιολόγησης, ακόμη και αν υπάρχουν αρκετές εγκρίσεις.",
|
||||
"repo.settings.block_outdated_branch": "Φραγή συγχώνευσης αν το pull request είναι ξεπερασμένο",
|
||||
"repo.settings.block_outdated_branch_desc": "Η συγχώνευση δεν θα είναι δυνατή όταν ο κλάδος κεφαλής είναι πίσω από τον βασικό κλάδο.",
|
||||
"repo.settings.default_branch_desc": "Επιλέξτε έναν προεπιλεγμένο κλάδο αποθετηρίου για pull requests και υποβολές κώδικα:",
|
||||
"repo.settings.merge_style_desc": "Συγχώνευση Στυλ",
|
||||
"repo.settings.choose_branch": "Επιλέξτε έναν κλάδο…",
|
||||
"repo.settings.no_protected_branch": "Δεν υπάρχουν προστατευμένοι κλάδοι.",
|
||||
@@ -2037,8 +2010,6 @@
|
||||
"repo.diff.too_many_files": "Ορισμένα αρχεία δεν εμφανίστηκαν επειδή έχουν αλλάξει πάρα πολλά αρχεία σε αυτή τη διαφορά",
|
||||
"repo.diff.show_more": "Εμφάνιση Περισσότερων",
|
||||
"repo.diff.load": "Φόρτωση Διαφορών",
|
||||
"repo.diff.generated": "δημιουργημένο",
|
||||
"repo.diff.vendored": "εξωτερικό",
|
||||
"repo.diff.comment.add_line_comment": "Προσθήκη σχολίου στη γραμμή",
|
||||
"repo.diff.comment.placeholder": "Αφήστε ένα σχόλιο",
|
||||
"repo.diff.comment.add_single_comment": "Προσθέστε ένα σχόλιο",
|
||||
@@ -2054,6 +2025,7 @@
|
||||
"repo.diff.review.reject": "Αίτηση αλλαγών",
|
||||
"repo.diff.review.self_approve": "Οι συγγραφείς pull request δεν μπορούν να εγκρίνουν τα δικά τους pull request",
|
||||
"repo.diff.committed_by": "υποβλήθηκε από",
|
||||
"repo.commits.avatar_stack_and": "και",
|
||||
"repo.diff.protected": "Προστατευμένο",
|
||||
"repo.diff.image.side_by_side": "Δίπλα Δίπλα",
|
||||
"repo.diff.image.swipe": "Σύρσιμο",
|
||||
@@ -2127,7 +2099,6 @@
|
||||
"repo.branch.restore_success": "Ο κλάδος \"%s\" επαναφέρθηκε.",
|
||||
"repo.branch.restore_failed": "Αποτυχία επαναφοράς του κλάδου \"%s\".",
|
||||
"repo.branch.protected_deletion_failed": "Ο κλάδος \"%s\" προστατεύεται. Δεν μπορεί να διαγραφεί.",
|
||||
"repo.branch.default_deletion_failed": "Ο κλάδος \"%s\" είναι ο προεπιλεγμένος κλάδος. Δεν μπορεί να διαγραφεί.",
|
||||
"repo.branch.restore": "Επαναφορά του Κλάδου \"%s\"",
|
||||
"repo.branch.download": "Λήψη του Κλάδου \"%s\"",
|
||||
"repo.branch.rename": "Μετονομασία Κλάδου \"%s\"",
|
||||
@@ -2203,9 +2174,7 @@
|
||||
"org.settings.labels_desc": "Προσθήκη σημάτων που μπορούν να χρησιμοποιηθούν σε ζητήματα για <strong>όλα τα αποθετήρια</strong> κάτω από αυτό τον οργανισμό.",
|
||||
"org.members.membership_visibility": "Ορατότητα Μέλους:",
|
||||
"org.members.public": "Ορατό",
|
||||
"org.members.public_helper": "κάνε το κρυφό",
|
||||
"org.members.private": "Κρυφό",
|
||||
"org.members.private_helper": "κάνε ορατό",
|
||||
"org.members.member_role": "Ρόλος Μέλους:",
|
||||
"org.members.owner": "Ιδιοκτήτης",
|
||||
"org.members.member": "Μέλος",
|
||||
@@ -2235,7 +2204,6 @@
|
||||
"org.teams.members": "Μέλη Ομάδας",
|
||||
"org.teams.update_settings": "Ενημέρωση Ρυθμίσεων",
|
||||
"org.teams.delete_team": "Διαγραφή Ομάδας",
|
||||
"org.teams.add_team_member": "Προσθήκη Μέλους Ομάδας",
|
||||
"org.teams.invite_team_member": "Πρόσκληση στο %s",
|
||||
"org.teams.invite_team_member.list": "Εκκρεμείς Προσκλήσεις",
|
||||
"org.teams.delete_team_title": "Διαγραφή Ομάδας",
|
||||
@@ -2260,6 +2228,10 @@
|
||||
"org.teams.all_repositories_read_permission_desc": "Αυτή η ομάδα χορηγεί πρόσβαση <strong>Ανάγνωσης</strong> σε <strong>όλα τα αποθετήρια</strong>: τα μέλη μπορούν να δουν και να κλωνοποιήσουν αποθετήρια.",
|
||||
"org.teams.all_repositories_write_permission_desc": "Αυτή η ομάδα χορηγεί πρόσβαση <strong>Εγγραφής</strong> σε <strong>όλα τα αποθετήρια</strong>: τα μέλη μπορούν να διαβάσουν και να κάνουν push σε αποθετήρια.",
|
||||
"org.teams.all_repositories_admin_permission_desc": "Αυτή η ομάδα παρέχει πρόσβαση <strong>Διαχείρισης</strong> σε <strong>όλα τα αποθετήρια</strong>: τα μέλη μπορούν να διαβάσουν, να κάνουν push και να προσθέσουν συνεργάτες στα αποθετήρια.",
|
||||
"org.teams.visibility": "Ορατότητα",
|
||||
"org.teams.visibility_private": "Ιδιωτικό",
|
||||
"org.teams.visibility_limited": "Περιορισμένη",
|
||||
"org.teams.visibility_public": "Δημόσιος",
|
||||
"org.teams.invite.title": "Έχετε προσκληθεί να συμμετάσχετε στην ομάδα <strong>%s</strong> του οργανισμού <strong>%s</strong>.",
|
||||
"org.teams.invite.by": "Προσκλήθηκε από %s",
|
||||
"org.teams.invite.description": "Παρακαλώ κάντε κλικ στον παρακάτω σύνδεσμο για συμμετοχή στην ομάδα.",
|
||||
@@ -2271,6 +2243,7 @@
|
||||
"admin.repositories": "Αποθετήρια",
|
||||
"admin.integrations": "Ενσωματώσεις",
|
||||
"admin.authentication": "Πηγές Ταυτοποίησης",
|
||||
"admin.badges.description": "Περιγραφή",
|
||||
"admin.config": "Διαμόρφωση",
|
||||
"admin.config_summary": "Περίληψη",
|
||||
"admin.config_settings": "Ρυθμίσεις",
|
||||
@@ -2537,11 +2510,8 @@
|
||||
"admin.config.server_config": "Ρυθμίσεις Διακομιστή",
|
||||
"admin.config.app_name": "Τίτλος Ιστοτόπου",
|
||||
"admin.config.app_ver": "Έκδοση Gitea",
|
||||
"admin.config.app_url": "Βασικό URL Του Gitea",
|
||||
"admin.config.custom_conf": "Διαδρομή Αρχείου Ρυθμίσεων",
|
||||
"admin.config.custom_file_root_path": "Προσαρμοσμένη Βασική Διαδρομή Αρχείου",
|
||||
"admin.config.domain": "Domain Διακομιστή",
|
||||
"admin.config.offline_mode": "Τοπική Λειτουργία",
|
||||
"admin.config.disable_router_log": "Απενεργοποίηση Καταγραφής Δρομολογητή",
|
||||
"admin.config.run_user": "Εκτέλεση Σαν Χρήστη",
|
||||
"admin.config.run_mode": "Λειτουργία Εκτέλεσης",
|
||||
@@ -2619,11 +2589,9 @@
|
||||
"admin.config.cache_config": "Ρύθμιση Προσωρινής Αποθήκευσης",
|
||||
"admin.config.cache_adapter": "Προσαρμογέας Προσωρινής Αποθήκευσης",
|
||||
"admin.config.cache_interval": "Διάστημα Προσωρινής Αποθήκευσης",
|
||||
"admin.config.cache_conn": "Σύνδεση Προσωρινής Αποθήκευσης",
|
||||
"admin.config.cache_item_ttl": "TTL Στοιχείων Προσωρινής Αποθήκευσης",
|
||||
"admin.config.session_config": "Ρύθμιση Συνεδρίας",
|
||||
"admin.config.session_provider": "Πάροχος Συνεδρίας",
|
||||
"admin.config.provider_config": "Ρυθμίσεις Πάροχου",
|
||||
"admin.config.cookie_name": "Όνομα Cookie",
|
||||
"admin.config.gc_interval_time": "Χρόνος Διαστήματος GC",
|
||||
"admin.config.session_life_time": "Χρόνος Ζωής Συνεδρίας",
|
||||
@@ -2631,7 +2599,6 @@
|
||||
"admin.config.cookie_life_time": "Χρόνος Ζωής Cookie",
|
||||
"admin.config.picture_config": "Ρύθμιση Εικόνας και Avatar",
|
||||
"admin.config.picture_service": "Υπηρεσία Εικόνας",
|
||||
"admin.config.disable_gravatar": "Απενεργοποίηση Gravatar",
|
||||
"admin.config.enable_federated_avatar": "Ενεργοποίηση Ομόσπονδων Avatars",
|
||||
"admin.config.git_config": "Ρύθμιση Git",
|
||||
"admin.config.git_disable_diff_highlight": "Απενεργοποίηση Επισήμανσης Σύνταξης Diff",
|
||||
@@ -2641,8 +2608,6 @@
|
||||
"admin.config.git_gc_args": "Παράμετροι GC",
|
||||
"admin.config.git_migrate_timeout": "Χρονικό Όριο Μεταφοράς",
|
||||
"admin.config.git_mirror_timeout": "Χρονικό Όριο Ενημέρωσης Ειδώλου",
|
||||
"admin.config.git_clone_timeout": "Χρονικό Όριο Κλωνοποίησης",
|
||||
"admin.config.git_pull_timeout": "Χρονικό Όριο Pull",
|
||||
"admin.config.git_gc_timeout": "Χρονικό Όριο Λειτουργίας GC",
|
||||
"admin.config.log_config": "Ρύθμιση Καταγραφών",
|
||||
"admin.config.logger_name_fmt": "Καταγραφέας: %s",
|
||||
@@ -2863,6 +2828,10 @@
|
||||
"packages.rubygems.required.rubygems": "Απαιτεί έκδοση RubyGem",
|
||||
"packages.swift.install": "Προσθέστε το πακέτο στο αρχείο <code>Package.swift</code>:",
|
||||
"packages.swift.install2": "και εκτελέστε την ακόλουθη εντολή:",
|
||||
"packages.terraform.install2": "και εκτελέστε την ακόλουθη εντολή:",
|
||||
"packages.terraform.lock_status": "Κατάσταση Κλειδώματος",
|
||||
"packages.terraform.lock": "Κλείδωμα",
|
||||
"packages.terraform.unlock": "Ξεκλείδωμα",
|
||||
"packages.vagrant.install": "Για προσθήκη ενός κυτίου Vagrant, εκτελέστε την ακόλουθη εντολή:",
|
||||
"packages.settings.link": "Σύνδεση αυτού του πακέτου με ένα αποθετήριο",
|
||||
"packages.settings.link.select": "Επιλογή Αποθετηρίου",
|
||||
@@ -2970,15 +2939,21 @@
|
||||
"actions.runs.status": "Κατάσταση",
|
||||
"actions.runs.actors_no_select": "Όλοι οι φορείς",
|
||||
"actions.runs.status_no_select": "Όλες οι καταστάσεις",
|
||||
"actions.runs.branch": "Κλάδος",
|
||||
"actions.runs.branches_no_select": "Όλοι οι κλάδοι",
|
||||
"actions.runs.no_results": "Δεν βρέθηκαν αποτελέσματα.",
|
||||
"actions.runs.no_workflows": "Δεν υπάρχουν ροές εργασίας ακόμα.",
|
||||
"actions.runs.no_runs": "Η ροή εργασίας δεν έχει τρέξει ακόμα.",
|
||||
"actions.runs.empty_commit_message": "(κενό μήνυμα υποβολής)",
|
||||
"actions.runs.summary": "Περίληψη",
|
||||
"actions.workflow.disable": "Απενεργοποίηση Ροής Εργασιών",
|
||||
"actions.workflow.disable_success": "Η ροή εργασίας '%s' απενεργοποιήθηκε επιτυχώς.",
|
||||
"actions.workflow.enable": "Ενεργοποίηση Ροής Εργασίας",
|
||||
"actions.workflow.enable_success": "Η ροή εργασίας '%s' ενεργοποιήθηκε επιτυχώς.",
|
||||
"actions.workflow.disabled": "Η ροή εργασιών είναι απενεργοποιημένη.",
|
||||
"actions.workflow.scope_owner": "Ιδιοκτήτης",
|
||||
"actions.workflow.scope_global": "Γενικό",
|
||||
"actions.workflow.required": "Απαιτείται",
|
||||
"actions.need_approval_desc": "Πρέπει να εγκριθεί η εκτέλεση ροών εργασίας για pull request από fork.",
|
||||
"actions.variables": "Μεταβλητές",
|
||||
"actions.variables.management": "Διαχείριση Μεταβλητών",
|
||||
@@ -2998,7 +2973,6 @@
|
||||
"projects.type-2.display_name": "Έργο Αποθετηρίου",
|
||||
"projects.type-3.display_name": "Έργο Οργανισμού",
|
||||
"git.filemode.directory": "Φάκελος",
|
||||
"git.filemode.normal_file": "Κανονικό αρχείο",
|
||||
"git.filemode.executable_file": "Εκτελέσιμο αρχείο",
|
||||
"git.filemode.submodule": "Υπομονάδα"
|
||||
"git.filemode.submodule": "Υπομονάδα",
|
||||
"actions.general.token_permissions.mode.restricted": "Περιορισμένος"
|
||||
}
|
||||
|
||||
@@ -3779,6 +3779,7 @@
|
||||
"actions.runs.commit": "Commit",
|
||||
"actions.runs.run_details": "Run Details",
|
||||
"actions.runs.workflow_file": "Workflow file",
|
||||
"actions.runs.workflow_file_no_permission": "No permission to view the workflow file",
|
||||
"actions.runs.scheduled": "Scheduled",
|
||||
"actions.runs.pushed_by": "pushed by",
|
||||
"actions.runs.invalid_workflow_helper": "Workflow config file is invalid. Please check your config file: %s",
|
||||
@@ -3832,7 +3833,36 @@
|
||||
"actions.workflow.enable": "Enable Workflow",
|
||||
"actions.workflow.enable_success": "Workflow '%s' enabled successfully.",
|
||||
"actions.workflow.disabled": "Workflow is disabled.",
|
||||
"actions.workflow.scope_owner": "Owner",
|
||||
"actions.workflow.scope_global": "Global",
|
||||
"actions.workflow.required": "Required",
|
||||
"actions.workflow.scoped_required_cannot_disable": "This scoped workflow is required and cannot be disabled.",
|
||||
"actions.scoped_workflows": "Scoped Workflows",
|
||||
"actions.scoped_workflows.desc_org": "Register repositories as scoped-workflow sources. Workflow files under the scoped workflow directories of a source repository's default branch run on every repository of this organization, in that repository's own context.",
|
||||
"actions.scoped_workflows.desc_user": "Register repositories as scoped-workflow sources. Workflow files under the scoped workflow directories of a source repository's default branch run on every repository you own, in that repository's own context.",
|
||||
"actions.scoped_workflows.desc_global": "Register repositories as scoped-workflow sources. Workflow files under the scoped workflow directories of a source repository's default branch run on every repository on this instance, in that repository's own context. Because instance-level sources are evaluated on every repository's events, registering them can add overhead on large instances.",
|
||||
"actions.scoped_workflows.add_help": "To provide scoped workflows from a repository, commit the workflow files under <code>%s</code> on its default branch, then register the repository as a source below.",
|
||||
"actions.scoped_workflows.security_note": "A source repository's workflow content is executed in every repository it applies to, and its step scripts and their output are written to that repository's Actions logs and readable by anyone who can view the consuming repository's Actions. Registering a private repository as a source therefore discloses its workflow logic through those logs. Only register repositories whose workflow content may be shared with every consuming repository. If a scoped workflow references a reusable workflow from a private repository, make sure every consuming repository can read it, otherwise the workflow will fail there.",
|
||||
"actions.scoped_workflows.source.add": "Add source repository",
|
||||
"actions.scoped_workflows.source.add_success": "Source repository added.",
|
||||
"actions.scoped_workflows.source.remove_success": "Source repository removed.",
|
||||
"actions.scoped_workflows.source.not_found": "Repository not found.",
|
||||
"actions.scoped_workflows.required.update_success": "Required workflows updated.",
|
||||
"actions.scoped_workflows.required.label": "Mark workflows as required (a required workflow cannot be disabled by repositories):",
|
||||
"actions.scoped_workflows.required.patterns": "Required status check patterns",
|
||||
"actions.scoped_workflows.required.patterns_aria": "Required status check patterns for %s",
|
||||
"actions.scoped_workflows.required.patterns_note": "only enforced while the workflow is required",
|
||||
"actions.scoped_workflows.required.patterns_hint": "Mark the workflow as required to configure its status check patterns.",
|
||||
"actions.scoped_workflows.required.patterns_help": "One status check pattern (glob) per line. A consuming pull request can only be merged once a status matching every pattern has passed. This is enforced on any target branch that has a protection rule, even one with its own status checks disabled; a target branch with no protection rule is not gated.",
|
||||
"actions.scoped_workflows.required.patterns_empty": "Each required workflow needs at least one status check pattern.",
|
||||
"actions.scoped_workflows.required.missing_file": "file no longer in source",
|
||||
"actions.scoped_workflows.required.expected_contexts": "Expected status checks (a check that matches a pattern is marked)",
|
||||
"actions.scoped_workflows.required.no_status_contexts": "This workflow posts no status checks, so marking it required would block every consuming pull request from merging. Uncheck Required.",
|
||||
"actions.scoped_workflows.no_files": "No scoped workflow files were found on the default branch.",
|
||||
"actions.workflow.run": "Run Workflow",
|
||||
"actions.workflow.create_status_badge": "Create status badge",
|
||||
"actions.workflow.status_badge": "Status Badge",
|
||||
"actions.workflow.status_badge_url": "Badge URL",
|
||||
"actions.workflow.not_found": "Workflow '%s' not found.",
|
||||
"actions.workflow.run_success": "Workflow '%s' run successfully.",
|
||||
"actions.workflow.from_ref": "Use workflow from",
|
||||
|
||||
@@ -178,7 +178,6 @@
|
||||
"install.lfs_path": "Ruta raíz de Git LFS",
|
||||
"install.lfs_path_helper": "Los archivos almacenados con Git LFS se almacenarán en este directorio. Déjelo vacío para deshabilitarlo.",
|
||||
"install.run_user": "Ejecutar como usuario",
|
||||
"install.run_user_helper": "El nombre de usuario del sistema operativo que ejecuta Gitea. Tenga en cuenta que este usuario debe tener acceso a la ruta raíz del repositorio.",
|
||||
"install.domain": "Dominio del Servidor",
|
||||
"install.domain_helper": "Dominio o dirección de host para el servidor.",
|
||||
"install.ssh_port": "Puerto de servidor SSH",
|
||||
@@ -199,12 +198,6 @@
|
||||
"install.register_confirm": "Requerir confirmación de correo electrónico para registrarse",
|
||||
"install.mail_notify": "Habilitar las notificaciones por correo electrónico",
|
||||
"install.server_service_title": "Configuración del servidor y de servicios de terceros",
|
||||
"install.offline_mode": "Habilitar autenticación Local",
|
||||
"install.offline_mode_popup": "Deshabilitar redes de distribución de contenido de terceros y servir todos los recursos localmente.",
|
||||
"install.disable_gravatar": "Desactivar Gravatar",
|
||||
"install.disable_gravatar_popup": "Desactivar el Gravatar y fuentes de avatares de terceros. Se utilizará un avatar por defecto a menos que un usuario suba un avatar localmente.",
|
||||
"install.federated_avatar_lookup": "Habilitar avatares federados",
|
||||
"install.federated_avatar_lookup_popup": "Habilitar búsqueda de avatares federador para usar el servicio federado de código abierto basado en libravatar.",
|
||||
"install.disable_registration": "Deshabilitar auto-registro",
|
||||
"install.disable_registration_popup": "Deshabilitar auto-registro de usuarios. Sólo los administradores podrán crear nuevas cuentas de usuario.",
|
||||
"install.allow_only_external_registration_popup": "Permitir el registro únicamente a través de servicios externos",
|
||||
@@ -224,12 +217,10 @@
|
||||
"install.admin_email": "Correo electrónico",
|
||||
"install.install_btn_confirm": "Instalar Gitea",
|
||||
"install.test_git_failed": "Fallo al probar el comando 'git': %v",
|
||||
"install.sqlite3_not_available": "Esta versión de Gitea no soporta SQLite3. Por favor, descarga la versión binaria oficial de %s (no la versión 'gobuild').",
|
||||
"install.invalid_db_setting": "La configuración de la base de datos no es válida: %v",
|
||||
"install.invalid_db_table": "La tabla \"%s\" de la base de datos no es válida: %v",
|
||||
"install.invalid_repo_path": "La ruta de la raíz del repositorio no es válida: %v",
|
||||
"install.invalid_app_data_path": "La ruta de datos de la aplicación (APP_DATA_PATH) no es válida: %v",
|
||||
"install.run_user_not_match": "El nombre de usuario 'ejecutar como' no es el nombre actual de usuario: %s -> %s",
|
||||
"install.internal_token_failed": "Fallo al generar el INTERNAL_TOKEN: %v",
|
||||
"install.secret_key_failed": "Fallo al generar el SECRET_KEY: %v",
|
||||
"install.save_config_failed": "Error al guardar la configuración: %v",
|
||||
@@ -678,7 +669,6 @@
|
||||
"settings.permissions_list": "Permisos:",
|
||||
"settings.manage_oauth2_applications": "Administrar aplicaciones OAuth2",
|
||||
"settings.edit_oauth2_application": "Modificar aplicaciones OAuth2",
|
||||
"settings.oauth2_applications_desc": "Las aplicaciones OAuth2 permiten a su aplicación de terceros autenticar de forma segura a los usuarios en esta instancia de Gitea.",
|
||||
"settings.remove_oauth2_application": "Eliminar aplicación OAuth2",
|
||||
"settings.remove_oauth2_application_desc": "Eliminar una aplicación OAuth2 revocará el acceso a todos los tokens de acceso firmados. ¿Continuar?",
|
||||
"settings.remove_oauth2_application_success": "La aplicación ha sido eliminada.",
|
||||
@@ -695,7 +685,6 @@
|
||||
"settings.oauth2_regenerate_secret_hint": "¿Ha perdido su secreto?",
|
||||
"settings.oauth2_client_secret_hint": "El secreto no se mostrará de nuevo después de salir o actualizar esta página. Por favor, asegúrese de haberlo guardado.",
|
||||
"settings.oauth2_application_edit": "Editar",
|
||||
"settings.oauth2_application_create_description": "Las aplicaciones OAuth2 le dan acceso a su aplicación de terceros a cuentas de usuario en esta instancia.",
|
||||
"settings.oauth2_application_remove_description": "Eliminar una aplicación de OAuth2 evitará que acceda a cuentas de usuario autorizadas en esta instancia. ¿Continuar?",
|
||||
"settings.authorized_oauth2_applications": "Aplicaciones OAuth2 autorizadas",
|
||||
"settings.revoke_key": "Revocar",
|
||||
@@ -754,7 +743,6 @@
|
||||
"repo.visibility_description": "Sólo el propietario o los miembros de la organización -si tienen derechos- podrán verlo.",
|
||||
"repo.visibility_helper": "Hacer el repositorio privado",
|
||||
"repo.visibility_helper_forced": "El administrador de su sitio obliga a nuevos repositorios a ser privados.",
|
||||
"repo.visibility_fork_helper": "(Cambiar esto afectará a todos los forks)",
|
||||
"repo.clone_helper": "¿Necesita ayuda para clonar? Visite <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">Ayuda</a>.",
|
||||
"repo.fork_repo": "Hacer fork del repositorio",
|
||||
"repo.fork_from": "Crear un fork desde",
|
||||
@@ -825,8 +813,6 @@
|
||||
"repo.blame.ignore_revs.failed": "No se pudieron ignorar las revisiones en <a href=\"%s\">.git-blame-ignore-revs</a>.",
|
||||
"repo.transfer.accept": "Aceptar transferencia",
|
||||
"repo.transfer.reject": "Rechazar transferencia",
|
||||
"repo.transfer.no_permission_to_accept": "No tienes permiso para aceptar esta transferencia.",
|
||||
"repo.transfer.no_permission_to_reject": "No tienes permiso para rechazar esta transferencia.",
|
||||
"repo.desc.private": "Privado",
|
||||
"repo.desc.public": "Público",
|
||||
"repo.desc.template": "Plantilla",
|
||||
@@ -1017,14 +1003,12 @@
|
||||
"repo.editor.upload_file_is_locked": "El archivo \"%s\" está bloqueado por %s.",
|
||||
"repo.editor.upload_files_to_dir": "Subir archivos a \"%s\"",
|
||||
"repo.editor.cannot_commit_to_protected_branch": "No se puede hacer commit a la rama protegida \"%s\".",
|
||||
"repo.editor.no_commit_to_branch": "No se puede hacer commit directamente a la rama porque:",
|
||||
"repo.editor.user_no_push_to_branch": "El usuario no puede hacer push a la rama",
|
||||
"repo.editor.require_signed_commit": "Esta rama requiere un commit firmado",
|
||||
"repo.editor.cherry_pick": "Hacer Cherry-pick %s en:",
|
||||
"repo.editor.revert": "Revertir %s en:",
|
||||
"repo.commits.desc": "Ver el historial de cambios de código fuente.",
|
||||
"repo.commits.no_commits": "No hay commits en común. \"%s\" y \"%s\" tienen historias totalmente diferentes.",
|
||||
"repo.commits.nothing_to_compare": "Estas ramas son iguales.",
|
||||
"repo.commits.search.tooltip": "Puede prefijar palabras clave con \"author:\", \"committer:\", \"after:\", o \"before:\", p. ej., \"revertir author:Alice before:2019-01-13\".",
|
||||
"repo.commits.search_all": "Todas las Ramas",
|
||||
"repo.commits.author": "Autor",
|
||||
@@ -1092,11 +1076,9 @@
|
||||
"repo.issues.new": "Nueva incidencia",
|
||||
"repo.issues.new.title_empty": "El título no puede estar vacío",
|
||||
"repo.issues.new.labels": "Etiquetas",
|
||||
"repo.issues.new.no_label": "Sin etiquetas",
|
||||
"repo.issues.new.clear_labels": "Limpiar etiquetas",
|
||||
"repo.issues.new.projects": "Proyectos",
|
||||
"repo.issues.new.clear_projects": "Limpiar proyectos",
|
||||
"repo.issues.new.no_projects": "Ningún proyecto",
|
||||
"repo.issues.new.open_projects": "Proyectos Abiertos",
|
||||
"repo.issues.new.closed_projects": "Proyectos cerrados",
|
||||
"repo.issues.new.no_items": "No hay elementos",
|
||||
@@ -1212,7 +1194,6 @@
|
||||
"repo.issues.close": "Cerrar Incidencia",
|
||||
"repo.issues.comment_pull_merged_at": "commit fusionado %[1]s en %[2]s %[3]s",
|
||||
"repo.issues.comment_manually_pull_merged_at": "commit manualmente fusionado %[1]s en %[2]s %[3]s",
|
||||
"repo.issues.reopen_issue": "Reabrir",
|
||||
"repo.issues.create_comment": "Comentar",
|
||||
"repo.issues.closed_at": "cerró esta incidencia <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>",
|
||||
"repo.issues.reopened_at": "reabrió esta incidencia <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>",
|
||||
@@ -1395,7 +1376,6 @@
|
||||
"repo.pulls.allow_edits_from_maintainers": "Permitir ediciones de mantenedores",
|
||||
"repo.pulls.allow_edits_from_maintainers_desc": "Los usuarios con acceso de escritura a la rama base también pueden hacer push a esta rama",
|
||||
"repo.pulls.allow_edits_from_maintainers_err": "Error al Actualizar",
|
||||
"repo.pulls.compare_changes_desc": "Seleccione la rama en la que se fusiona y la rama a recuperar.",
|
||||
"repo.pulls.has_viewed_file": "Visto",
|
||||
"repo.pulls.has_changed_since_last_review": "Cambiado desde tu última revisión",
|
||||
"repo.pulls.viewed_files_label": "%[1]d / %[2]d archivos vistos",
|
||||
@@ -1412,7 +1392,6 @@
|
||||
"repo.pulls.showing_specified_commit_range": "Mostrando solo cambios entre %[1]s..%[2]s",
|
||||
"repo.pulls.review_only_possible_for_full_diff": "La revisión solo es posible cuando se ve la diff completa",
|
||||
"repo.pulls.filter_changes_by_commit": "Filtrar por commit",
|
||||
"repo.pulls.nothing_to_compare": "Estas ramas son iguales. No hay necesidad para crear un pull request.",
|
||||
"repo.pulls.nothing_to_compare_and_allow_empty_pr": "Estas ramas son iguales. Este PR estará vacío.",
|
||||
"repo.pulls.has_pull_request": "Ya existe un pull request entre estas ramas: <a href=\"%[1]s\">%[2]s#%[3]d</a>",
|
||||
"repo.pulls.create": "Crear Pull Request",
|
||||
@@ -1474,7 +1453,6 @@
|
||||
"repo.pulls.status_checking": "Algunas comprobaciones están pendientes",
|
||||
"repo.pulls.status_checks_success": "Todas las comprobaciones han sido exitosas",
|
||||
"repo.pulls.status_checks_warning": "Algunas comprobaciones han reportado advertencias",
|
||||
"repo.pulls.status_checks_failure": "Algunas comprobaciones han fallado",
|
||||
"repo.pulls.status_checks_error": "Algunas comprobaciones reportaron errores",
|
||||
"repo.pulls.status_checks_requested": "Obligatorio",
|
||||
"repo.pulls.status_checks_details": "Detalles",
|
||||
@@ -1539,7 +1517,6 @@
|
||||
"repo.signing.wont_sign.headsigned": "La fusión no se firmará ya que el commit principal no está firmado.",
|
||||
"repo.signing.wont_sign.commitssigned": "La fusión no se firmará ya que todos los commits asociados no están firmados.",
|
||||
"repo.signing.wont_sign.approved": "La fusión no se firmará ya que el PR no está aprobado.",
|
||||
"repo.signing.wont_sign.not_signed_in": "No ha iniciado sesión.",
|
||||
"repo.ext_wiki": "Acceso a la wiki externa",
|
||||
"repo.ext_wiki.desc": "Enlace a una wiki externa.",
|
||||
"repo.wiki.welcome": "¡Bienvenidos a la Wiki!",
|
||||
@@ -1725,7 +1702,6 @@
|
||||
"repo.settings.transfer_abort_invalid": "No puede cancelar una transferencia de repositorio inexistente.",
|
||||
"repo.settings.transfer_abort_success": "La transferencia del repositorio a %s fue cancelada.",
|
||||
"repo.settings.transfer_desc": "Transferir este repositorio a un usuario o una organizacion de la cual disponga de privilegios administrativos.",
|
||||
"repo.settings.transfer_form_title": "Escriba el nombre del repositorio como confirmación:",
|
||||
"repo.settings.transfer_notices_1": "- Perderá el acceso al repositorio si lo transfiere a un usuario individual.",
|
||||
"repo.settings.transfer_notices_2": "- Mantendrá el acceso al repositorio si lo transfiere a una organización que usted (co-)posee.",
|
||||
"repo.settings.transfer_notices_3": "- Si el repositorio es privado y se transfiere a un usuario individual, esta acción se asegura de que el usuario tenga al menos permisos de lectura (y cambie los permisos si es necesario).",
|
||||
@@ -1788,8 +1764,6 @@
|
||||
"repo.settings.webhook.delivery.success": "Se ha añadido un evento a la cola. Puede tardar unos segundos antes de que se muestre en el historial de entrega.",
|
||||
"repo.settings.githooks_desc": "Los Hooks de Git son ejecutados por el propio Git. Puede editar los archivos de hooks a continuación para configurar operaciones personalizadas.",
|
||||
"repo.settings.githook_edit_desc": "Si el hook no está activo, se mostrará contenido de ejemplo. Dejar el contenido vacío deshabilitará este hook.",
|
||||
"repo.settings.githook_name": "Nombre del Hook",
|
||||
"repo.settings.githook_content": "Contenido del Hook",
|
||||
"repo.settings.update_githook": "Actualizar Hook",
|
||||
"repo.settings.add_webhook_desc": "Gitea enviará solicitudes <code>POST</code> con un tipo de contenido especificado a la URL de destino. Leer más en la guía <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">webhooks</a>.",
|
||||
"repo.settings.payload_url": "Url destino",
|
||||
@@ -1916,7 +1890,6 @@
|
||||
"repo.settings.block_on_official_review_requests_desc": "Fusionar no será posible cuando tenga pedidos de revisión oficial, incluso si hay suficientes aprobaciones.",
|
||||
"repo.settings.block_outdated_branch": "Bloquear fusión si la pull request está desactualizada",
|
||||
"repo.settings.block_outdated_branch_desc": "La fusión no será posible cuando la rama principal esté detrás de la rama base.",
|
||||
"repo.settings.default_branch_desc": "Seleccione una rama de repositorio por defecto para los pull request y los commits:",
|
||||
"repo.settings.merge_style_desc": "Estilos de fusión",
|
||||
"repo.settings.choose_branch": "Elija una rama…",
|
||||
"repo.settings.no_protected_branch": "No hay ramas protegidas.",
|
||||
@@ -2006,8 +1979,6 @@
|
||||
"repo.diff.too_many_files": "Algunos archivos no se mostraron porque demasiados archivos han cambiado en esta diferencia",
|
||||
"repo.diff.show_more": "Ver más",
|
||||
"repo.diff.load": "Cargar Diff",
|
||||
"repo.diff.generated": "generado",
|
||||
"repo.diff.vendored": "vendido",
|
||||
"repo.diff.comment.add_line_comment": "Añadir comentario en línea",
|
||||
"repo.diff.comment.placeholder": "Deja un comentario",
|
||||
"repo.diff.comment.add_single_comment": "Añadir solo comentario",
|
||||
@@ -2023,6 +1994,7 @@
|
||||
"repo.diff.review.reject": "Solicitud de cambios",
|
||||
"repo.diff.review.self_approve": "Los autores del pull request no pueden aprobarlo",
|
||||
"repo.diff.committed_by": "cometido por",
|
||||
"repo.commits.avatar_stack_and": "y",
|
||||
"repo.diff.protected": "Protegido",
|
||||
"repo.diff.image.side_by_side": "Lado a lado",
|
||||
"repo.diff.image.swipe": "Deslizar",
|
||||
@@ -2095,7 +2067,6 @@
|
||||
"repo.branch.restore_success": "La rama \"%s\" ha sido restaurada.",
|
||||
"repo.branch.restore_failed": "Error al restaurar la rama \"%s\".",
|
||||
"repo.branch.protected_deletion_failed": "La rama \"%s\" está protegida. No se puede eliminar.",
|
||||
"repo.branch.default_deletion_failed": "La rama \"%s\" es la rama por defecto. No se puede eliminar.",
|
||||
"repo.branch.restore": "Restaurar rama \"%s\"",
|
||||
"repo.branch.download": "Descargar rama \"%s\"",
|
||||
"repo.branch.rename": "Renombrar rama \"%s\"",
|
||||
@@ -2171,9 +2142,7 @@
|
||||
"org.settings.labels_desc": "Añadir etiquetas que pueden ser utilizadas en problemas para <strong>todos los repositorios</strong> bajo esta organización.",
|
||||
"org.members.membership_visibility": "Visibilidad de Membresía:",
|
||||
"org.members.public": "Público",
|
||||
"org.members.public_helper": "hacer oculto",
|
||||
"org.members.private": "Oculto",
|
||||
"org.members.private_helper": "hacer público",
|
||||
"org.members.member_role": "Rol del miembro:",
|
||||
"org.members.owner": "Propietario",
|
||||
"org.members.member": "Miembro",
|
||||
@@ -2203,7 +2172,6 @@
|
||||
"org.teams.members": "Miembros del equipo",
|
||||
"org.teams.update_settings": "Actualizar configuración",
|
||||
"org.teams.delete_team": "Eliminar equipo",
|
||||
"org.teams.add_team_member": "Añadir miembro al equipo",
|
||||
"org.teams.invite_team_member": "Invitar a %s",
|
||||
"org.teams.invite_team_member.list": "Invitaciones pendientes",
|
||||
"org.teams.delete_team_title": "Eliminar equipo",
|
||||
@@ -2228,6 +2196,10 @@
|
||||
"org.teams.all_repositories_read_permission_desc": "Este equipo concede <strong>Leer</strong> a <strong>todos los repositorios</strong>: los miembros pueden ver y clonar repositorios.",
|
||||
"org.teams.all_repositories_write_permission_desc": "Este equipo concede <strong>Escribir</strong> a <strong>todos los repositorios</strong>: los miembros pueden leer y enviar a los repositorios.",
|
||||
"org.teams.all_repositories_admin_permission_desc": "Este equipo concede a <strong>Administrador</strong> acceso a <strong>todos los repositorios</strong>: los miembros pueden leer, enviar y agregar colaboradores a los repositorios.",
|
||||
"org.teams.visibility": "Visibilidad",
|
||||
"org.teams.visibility_private": "Privado",
|
||||
"org.teams.visibility_limited": "Limitado",
|
||||
"org.teams.visibility_public": "Público",
|
||||
"org.teams.invite.title": "Has sido invitado a unirte al equipo <strong>%s</strong> en la organización <strong>%s</strong>.",
|
||||
"org.teams.invite.by": "Invitado por %s",
|
||||
"org.teams.invite.description": "Por favor, haga clic en el botón de abajo para unirse al equipo.",
|
||||
@@ -2239,6 +2211,7 @@
|
||||
"admin.repositories": "Repositorios",
|
||||
"admin.integrations": "Integraciones",
|
||||
"admin.authentication": "Orígenes de autenticación",
|
||||
"admin.badges.description": "Descripción",
|
||||
"admin.config": "Configuración",
|
||||
"admin.config_summary": "Resumen",
|
||||
"admin.config_settings": "Configuración",
|
||||
@@ -2501,11 +2474,8 @@
|
||||
"admin.config.server_config": "Configuración del servidor",
|
||||
"admin.config.app_name": "Título del sitio",
|
||||
"admin.config.app_ver": "Versión de Gitea",
|
||||
"admin.config.app_url": "URL base de Gitea",
|
||||
"admin.config.custom_conf": "Ruta del fichero de configuración",
|
||||
"admin.config.custom_file_root_path": "Ruta raíz de los archivos personalizada",
|
||||
"admin.config.domain": "Dominio del Servidor",
|
||||
"admin.config.offline_mode": "Modo offline",
|
||||
"admin.config.disable_router_log": "Deshabilitar Log del Router",
|
||||
"admin.config.run_user": "Ejecutar como usuario",
|
||||
"admin.config.run_mode": "Modo de ejecución",
|
||||
@@ -2582,11 +2552,9 @@
|
||||
"admin.config.cache_config": "Configuración de la Caché",
|
||||
"admin.config.cache_adapter": "Adaptador de la Caché",
|
||||
"admin.config.cache_interval": "Intervalo de la Caché",
|
||||
"admin.config.cache_conn": "Conexión de la Caché",
|
||||
"admin.config.cache_item_ttl": "Período de vida para elementos de caché",
|
||||
"admin.config.session_config": "Configuración de la Sesión",
|
||||
"admin.config.session_provider": "Proveedor de la Sesión",
|
||||
"admin.config.provider_config": "Configuración del Proveedor",
|
||||
"admin.config.cookie_name": "Nombre de la Cookie",
|
||||
"admin.config.gc_interval_time": "Intervalo de tiempo del GC",
|
||||
"admin.config.session_life_time": "Tiempo de Vida de la Sesión",
|
||||
@@ -2594,7 +2562,6 @@
|
||||
"admin.config.cookie_life_time": "Tiempo de Vida de la Cookie",
|
||||
"admin.config.picture_config": "Configuración de imagen y avatar",
|
||||
"admin.config.picture_service": "Servicio de Imágen",
|
||||
"admin.config.disable_gravatar": "Desactivar Gravatar",
|
||||
"admin.config.enable_federated_avatar": "Habilitar Avatares Federados",
|
||||
"admin.config.git_config": "Configuración de Git",
|
||||
"admin.config.git_disable_diff_highlight": "Desactivar resaltado de sintaxis del Diff",
|
||||
@@ -2604,8 +2571,6 @@
|
||||
"admin.config.git_gc_args": "Argumentos de GC",
|
||||
"admin.config.git_migrate_timeout": "Tiempo de espera de migración",
|
||||
"admin.config.git_mirror_timeout": "Tiempo de espera de actualización de réplicas",
|
||||
"admin.config.git_clone_timeout": "Tiempo de espera de operación de clones",
|
||||
"admin.config.git_pull_timeout": "Tiempo de espera de operación de pull",
|
||||
"admin.config.git_gc_timeout": "Tiempo de espera de operación de GC",
|
||||
"admin.config.log_config": "Configuración del Log",
|
||||
"admin.config.logger_name_fmt": "Registro: %s",
|
||||
@@ -2827,6 +2792,10 @@
|
||||
"packages.rubygems.required.rubygems": "Requiere la versión de RubyGem",
|
||||
"packages.swift.install": "Añade el paquete en tu archivo <code>Package.swift</code>:",
|
||||
"packages.swift.install2": "y ejecuta el siguiente comando:",
|
||||
"packages.terraform.install2": "y ejecuta el siguiente comando:",
|
||||
"packages.terraform.lock_status": "Estado de bloqueo",
|
||||
"packages.terraform.lock": "Bloquear",
|
||||
"packages.terraform.unlock": "Desbloquear",
|
||||
"packages.vagrant.install": "Para añadir un paquete Vagrant, ejecuta el siguiente comando:",
|
||||
"packages.settings.link": "Vincular este paquete a un repositorio",
|
||||
"packages.settings.link.select": "Seleccionar repositorio",
|
||||
@@ -2931,13 +2900,18 @@
|
||||
"actions.runs.status": "Estado",
|
||||
"actions.runs.actors_no_select": "Todos los actores",
|
||||
"actions.runs.status_no_select": "Todo el estado",
|
||||
"actions.runs.branch": "Rama",
|
||||
"actions.runs.branches_no_select": "Todas las ramas",
|
||||
"actions.runs.no_results": "No hay resultados coincidentes.",
|
||||
"actions.runs.no_runs": "El flujo de trabajo no tiene ejecuciones todavía.",
|
||||
"actions.runs.summary": "Resumen",
|
||||
"actions.workflow.disable": "Desactivar fllujo de trabajo",
|
||||
"actions.workflow.disable_success": "Flujo de trabajo '%s' deshabilitado correctamente.",
|
||||
"actions.workflow.enable": "Activar flujo de trabajo",
|
||||
"actions.workflow.enable_success": "Flujo de trabajo '%s' habilitado con éxito.",
|
||||
"actions.workflow.disabled": "El flujo de trabajo está deshabilitado.",
|
||||
"actions.workflow.scope_owner": "Propietario",
|
||||
"actions.workflow.required": "Obligatorio",
|
||||
"actions.need_approval_desc": "Necesita aprobación para ejecutar flujos de trabajo para el pull request del fork.",
|
||||
"actions.variables.management": "Gestión de variables",
|
||||
"actions.variables.creation": "Añadir variable",
|
||||
@@ -2956,8 +2930,6 @@
|
||||
"projects.type-2.display_name": "Proyecto repositorio",
|
||||
"projects.type-3.display_name": "Proyecto de organización",
|
||||
"git.filemode.directory": "Directorio",
|
||||
"git.filemode.normal_file": "Archivo normal",
|
||||
"git.filemode.executable_file": "Archivo ejecutable",
|
||||
"git.filemode.symbolic_link": "Enlace simbólico",
|
||||
"git.filemode.submodule": "Submódulo"
|
||||
"git.filemode.submodule": "Submódulo",
|
||||
"actions.general.token_permissions.mode.restricted": "Restringido"
|
||||
}
|
||||
|
||||
@@ -140,12 +140,6 @@
|
||||
"install.register_confirm": "نیاز به تایید ایمیل ثبت نام",
|
||||
"install.mail_notify": "فعالسازی اعلانهای ایمیل",
|
||||
"install.server_service_title": "تنظیمات سرور و سرویسهای شخص ثالث",
|
||||
"install.offline_mode": "فعال کردن حالت محلی",
|
||||
"install.offline_mode_popup": "غیر فعال کردن شبکه های شخص ثالث تحویل محتوا و استفاده از تمام منابع به صورت محلی.",
|
||||
"install.disable_gravatar": "غیر فعال کردن Gravatar",
|
||||
"install.disable_gravatar_popup": "غیر فعال کردن کلیک و منابع آواتار شخص ثالث. مگر در مواردی که کاربر محلی بارگزاری آواتار پیش فرض استفاده خواهد شد.",
|
||||
"install.federated_avatar_lookup": "فعال سازی آواتار مشترک",
|
||||
"install.federated_avatar_lookup_popup": "مراجعه مشترک آواتار با استفاده از Libravatar را قادر می سازد.",
|
||||
"install.disable_registration": "غیرفعالکردن خود ثبت نامی",
|
||||
"install.disable_registration_popup": "غیرفعال کردن ثبت نام کاربر. تنها مدیر ها قادر خواهند بود حساب کاربری جدید اضافه کنند.",
|
||||
"install.allow_only_external_registration_popup": "اجازه ثبت نام فقط از طریق خدمات خارجی",
|
||||
@@ -164,11 +158,9 @@
|
||||
"install.admin_email": "نشانی رایانامه (ایمیل)",
|
||||
"install.install_btn_confirm": "نصب گیتی",
|
||||
"install.test_git_failed": "عدم توانایی در آزمایش دستور 'git' توضیح بیشتر: %v",
|
||||
"install.sqlite3_not_available": "نسخه مورد استفاده شما از SQLite3 پشتیبانی نمی کند. لطفا نسخه باینری رسمی را از s% دانلود کنید و از ورژن gobuild هم استفاده نکنید.",
|
||||
"install.invalid_db_setting": "تنظیمات پایگاه داده معتبر نیست: %v",
|
||||
"install.invalid_repo_path": "مسیر ریشه مخزن نامعتبر است: %v",
|
||||
"install.invalid_app_data_path": "مسیر داده برنامه نامعتبر است: %v",
|
||||
"install.run_user_not_match": "نام کاربری 'اجرا به عنوان' نام کاربری فعلی نیست: %s -> %s",
|
||||
"install.internal_token_failed": "کد داخلی ایجاد نشد: %v",
|
||||
"install.secret_key_failed": "کلید مخفی ایجاد نشد: %v",
|
||||
"install.save_config_failed": "تنظیمات ذخیره نشد: %v",
|
||||
@@ -533,7 +525,6 @@
|
||||
"settings.permission_read": "خواندن",
|
||||
"settings.manage_oauth2_applications": "مدیریت برنامههای OAuth2",
|
||||
"settings.edit_oauth2_application": "ویرایش برنامه OAuth2",
|
||||
"settings.oauth2_applications_desc": "برنامههای OAuth2 احراز هویت برنامه های شخص ثالث را با بستری امن میسر میکند.",
|
||||
"settings.remove_oauth2_application": "حذف برنامه OAuth2",
|
||||
"settings.remove_oauth2_application_desc": "حذف برنامه OAuth2 دسترسی تمام برنامه های متصل با آن را از بین میبرد. آیا ادامه میدهید؟",
|
||||
"settings.remove_oauth2_application_success": "برنامه حذف شده است.",
|
||||
@@ -546,7 +537,6 @@
|
||||
"settings.oauth2_regenerate_secret": "تولید دوباره کلمه امن",
|
||||
"settings.oauth2_regenerate_secret_hint": "کلمه امن خود را فراموش کرده اید؟",
|
||||
"settings.oauth2_application_edit": "ويرايش",
|
||||
"settings.oauth2_application_create_description": "برنامههای OAuth2 این امکان را به برنامههای شخص ثالث میدهد یک نمونه از دسترسی به حساب کاربری شما داشته باشد.",
|
||||
"settings.authorized_oauth2_applications": "برنامه OAuth2 تایید شد",
|
||||
"settings.revoke_key": "ابطال",
|
||||
"settings.revoke_oauth2_grant": "ابطال دسترسی",
|
||||
@@ -593,7 +583,6 @@
|
||||
"repo.visibility": "پدیداری",
|
||||
"repo.visibility_description": "فقط مالک یا عضو سازمان اگر دسترسی داشته باشند. میتوانند این را مشاهده کنند.",
|
||||
"repo.visibility_helper_forced": "مدیر سایت شما ایجاد مخازن جدید را به صورت خصوصی اجباری کرده است.",
|
||||
"repo.visibility_fork_helper": "(تغییر این مقدار تمام انشعابها را تحت تاثیر می گذارد)",
|
||||
"repo.clone_helper": "کمک برای نسخهبرداری <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">کمک</a>.",
|
||||
"repo.fork_repo": "انشعاب از مخزن",
|
||||
"repo.fork_from": "انشعاب از",
|
||||
@@ -805,12 +794,10 @@
|
||||
"repo.editor.no_changes_to_show": "تغییری برای نمایش وجود ندارد.",
|
||||
"repo.editor.push_rejected_summary": "متن کامل پیام دلیل رد شدن:",
|
||||
"repo.editor.add_subdir": "افزودن پوشه…",
|
||||
"repo.editor.no_commit_to_branch": "نمیتوان به طور مستقیم درمورد شاخه نطر داد زیرا:",
|
||||
"repo.editor.user_no_push_to_branch": "کاربر نمیتواند به شاخه ارسال کند",
|
||||
"repo.editor.require_signed_commit": "شاخه یک کامیت امضا شده لازم دارد",
|
||||
"repo.commits.desc": "تاریخچه تغییرات کد منبع را مرور کنید.",
|
||||
"repo.commits.commits": "کامیتها",
|
||||
"repo.commits.nothing_to_compare": "این شاخه ها برابرند.",
|
||||
"repo.commits.search_all": "همه شاخه ها",
|
||||
"repo.commits.author": "مولف",
|
||||
"repo.commits.message": "پیام",
|
||||
@@ -855,11 +842,9 @@
|
||||
"repo.issues.new": "مسئلهی جدید",
|
||||
"repo.issues.new.title_empty": "عنوان نمی تواند خالی باشد",
|
||||
"repo.issues.new.labels": "برچسبها",
|
||||
"repo.issues.new.no_label": "بدون برچسب",
|
||||
"repo.issues.new.clear_labels": "پاککردن برچسبها",
|
||||
"repo.issues.new.projects": "پروژهها",
|
||||
"repo.issues.new.clear_projects": "پاک کردن پروژهها",
|
||||
"repo.issues.new.no_projects": "هیچ پروژه ثبت نشده",
|
||||
"repo.issues.new.open_projects": "برنامههای باز",
|
||||
"repo.issues.new.closed_projects": "پروژههای بسته شده",
|
||||
"repo.issues.new.no_items": "موردی وجود ندارد",
|
||||
@@ -952,7 +937,6 @@
|
||||
"repo.issues.context.reference_issue": "مرجع در شماره جدید",
|
||||
"repo.issues.context.edit": "ویرایش",
|
||||
"repo.issues.context.delete": "حذف",
|
||||
"repo.issues.reopen_issue": "بازگشایی",
|
||||
"repo.issues.create_comment": "دیدگاه",
|
||||
"repo.issues.closed_at": "<a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a> این موضوع را بست",
|
||||
"repo.issues.reopened_at": "<a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a> این موضوع را دوباره باز کرد",
|
||||
@@ -1096,13 +1080,11 @@
|
||||
"repo.pulls.desc": "نمایش تقاضای واکشی ها و بازبینی های کد.",
|
||||
"repo.pulls.new": "ایجاد تقاضای واکشی",
|
||||
"repo.pulls.compare_changes": "تقاضای واکشی جدید",
|
||||
"repo.pulls.compare_changes_desc": "یک شاخه را برای ادغام با شاخه مورد نظر انتخاب تا از آن واکشی کنید.",
|
||||
"repo.pulls.compare_base": "ادغام با",
|
||||
"repo.pulls.compare_compare": "واکشی از",
|
||||
"repo.pulls.switch_comparison_type": "سوئیچ نوع مقایسه",
|
||||
"repo.pulls.switch_head_and_base": "سر و پایه سوئیچ",
|
||||
"repo.pulls.filter_branch": "صافی شاخه",
|
||||
"repo.pulls.nothing_to_compare": "این شاخهها یکی هستند. نیازی به تقاضای واکشی نیست.",
|
||||
"repo.pulls.nothing_to_compare_and_allow_empty_pr": "این شاخه ها برابر هستند. این PR خالی خواهد بود.",
|
||||
"repo.pulls.has_pull_request": "A درخواست pull بین این شاخه ها از قبل وجود دارد: <a href=\"%[1]s\">%[2]s#%[3]d</a>",
|
||||
"repo.pulls.create": "ایجاد تقاضای واکشی",
|
||||
@@ -1155,7 +1137,6 @@
|
||||
"repo.pulls.status_checking": "برخی از بررسیها در حال تعلیق هستند",
|
||||
"repo.pulls.status_checks_success": "تمامی بررسیها موفق بودند",
|
||||
"repo.pulls.status_checks_warning": "برخی از چک ها هشدارهایی را گزارش کردند",
|
||||
"repo.pulls.status_checks_failure": "برخی از چک ها موفق نبود",
|
||||
"repo.pulls.status_checks_error": "برخی از چک ها خطاهایی را گزارش کردند",
|
||||
"repo.pulls.status_checks_requested": "الزامی",
|
||||
"repo.pulls.status_checks_details": "جزئیات",
|
||||
@@ -1342,7 +1323,6 @@
|
||||
"repo.settings.transfer_abort": "لغو انتقال",
|
||||
"repo.settings.transfer_abort_invalid": "شما نمی توانید یک انتقال انبار موجود را لغو کنید.",
|
||||
"repo.settings.transfer_desc": "انتقال مالکیت این مخزن به کاربر بانی یا سازمانی که شما حق مدیریت در آن دارید.",
|
||||
"repo.settings.transfer_form_title": "نام مخزن را برای تایید عمل خورد اینجا وارد کنید:",
|
||||
"repo.settings.transfer_notices_1": "- شما دسترسی خود را نسبت مخزن را از دست میدهید اگر مالکیت آن را به یک کاربری مفرد انتقال دهید.",
|
||||
"repo.settings.transfer_notices_2": "- شما دسترسی خود را نسبت مخزن را حفظ میکنید. اگر مالکیت آن را به یک سازمانی که در آن مالکیت دارید انتقال دهید.",
|
||||
"repo.settings.transfer_notices_3": "- اگر انبار خصوصی است و به یک کاربر جداگانه منتقل می شود، این عمل مطمئن می شود که کاربر حداقل مجوز خواندن را دارد (و در صورت لزوم مجوزها را تغییر می دهد).",
|
||||
@@ -1396,8 +1376,6 @@
|
||||
"repo.settings.webhook.payload": "محتوا",
|
||||
"repo.settings.webhook.body": "پیکر",
|
||||
"repo.settings.githook_edit_desc": "در صورتیکه hook غیرفعال باشد، محتوای نمونه ای موجود در آن ارائه خواهد شد. برای اینکه به کلی غیر فعال شود، محتوا را پاک کنید تا خالی شود.",
|
||||
"repo.settings.githook_name": "نام hook",
|
||||
"repo.settings.githook_content": "محتوای هوک",
|
||||
"repo.settings.update_githook": "به روزرسانی hook",
|
||||
"repo.settings.add_webhook_desc": "Gitea یک درخواست <code>POST</code> با نوع محتوی مشخص برای نشانی مقصذ ارسال خواهد کرد. برای کسب اطلاعات بیشتر به <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">راهنمای هوک تحت وب </a> مراجعه کنید.",
|
||||
"repo.settings.payload_url": "نشانی هدف",
|
||||
@@ -1502,7 +1480,6 @@
|
||||
"repo.settings.block_on_official_review_requests_desc": "زمانی که درخواستهای بررسی رسمی داشته باشد، ادغام امکانپذیر نخواهد بود، حتی اگر تأییدیههای کافی وجود داشته باشد.",
|
||||
"repo.settings.block_outdated_branch": "اگر درخواست pull قدیمی است، ادغام را مسدود شود",
|
||||
"repo.settings.block_outdated_branch_desc": "وقتی سرشاخه پشت شاخه پایه باشد، ادغام امکان پذیر نخواهد بود.",
|
||||
"repo.settings.default_branch_desc": "شاخه اصلی مخزن را برای تقاضا واکشی و کامیت کد ها انتخاب نمایید:",
|
||||
"repo.settings.choose_branch": "شاخه اصلی مخزن را برای تقاضا واکشی و کامیت کد ها انتخاب نمایید:",
|
||||
"repo.settings.no_protected_branch": "اینجا هیچ شاخه محافظت شده ای وجود ندارد.",
|
||||
"repo.settings.edit_protected_branch": "ویرایش",
|
||||
@@ -1586,8 +1563,6 @@
|
||||
"repo.diff.too_many_files": "برخی از فایل ها نشان داده نشدند زیرا تعداد زیادی فایل در این تفاوت تغییر کرده اند",
|
||||
"repo.diff.show_more": "نمایش بیشتر",
|
||||
"repo.diff.load": "Diff را بارگزاری کن",
|
||||
"repo.diff.generated": "تولید شده",
|
||||
"repo.diff.vendored": "فروخته شده",
|
||||
"repo.diff.comment.placeholder": "اظهار نظر کنید",
|
||||
"repo.diff.comment.add_single_comment": "افزودن یک دیدگاه به تنهایی",
|
||||
"repo.diff.comment.add_review_comment": "افزودن دیدگاه",
|
||||
@@ -1600,6 +1575,7 @@
|
||||
"repo.diff.review.approve": "پذیرفتن",
|
||||
"repo.diff.review.reject": "درخواست تغییر کوکی ها",
|
||||
"repo.diff.committed_by": "کامیت شده توسط",
|
||||
"repo.commits.avatar_stack_and": "و",
|
||||
"repo.diff.protected": "حفاظت شده",
|
||||
"repo.diff.image.side_by_side": "کنار هم",
|
||||
"repo.diff.image.swipe": "با خط کشش",
|
||||
@@ -1706,9 +1682,7 @@
|
||||
"org.settings.labels_desc": "تگ هایی را اضافه کنید که میتوانند برای مشکلات <strong>همه مخازن</strong> تحت این سازمان استفاده شوند.",
|
||||
"org.members.membership_visibility": "قابل مشاهده بودن عضویت:",
|
||||
"org.members.public": "نمایان",
|
||||
"org.members.public_helper": "مخفی کردن",
|
||||
"org.members.private": "مخفی",
|
||||
"org.members.private_helper": "نمایان کردن",
|
||||
"org.members.member_role": "نقش عضو:",
|
||||
"org.members.owner": "مالک",
|
||||
"org.members.member": "عضو",
|
||||
@@ -1735,7 +1709,6 @@
|
||||
"org.teams.members": "اعضای تیم",
|
||||
"org.teams.update_settings": "به روزرسانی تنظیمات",
|
||||
"org.teams.delete_team": "حذف تیم",
|
||||
"org.teams.add_team_member": "افزودن عضو به تیم",
|
||||
"org.teams.delete_team_title": "حذف تیم",
|
||||
"org.teams.delete_team_desc": "حذف یک تیم از مخزن تمامی دسترسی اعضای آن را از بین میبرد. آیا مطمئن هستید؟",
|
||||
"org.teams.delete_team_success": "تیم حذف شد.",
|
||||
@@ -1758,12 +1731,17 @@
|
||||
"org.teams.all_repositories_read_permission_desc": "این تیم دسترسی<strong> خواندن </strong> <strong> مخازن همه</strong> را می بخشد: اعضا می توانند مخازن را مشاهده و همزاد سازی کنند.",
|
||||
"org.teams.all_repositories_write_permission_desc": "این تیم دسترسی<strong> نوشتن </strong> <strong> مخازن همه</strong> را می بخشد: اعضا می توانند مخازن را مشاهده و درج کنند.",
|
||||
"org.teams.all_repositories_admin_permission_desc": "این تیم دسترسی<strong> مدیر </strong> به <strong> مخازن همه</strong> را می بخشد: اعضا می توانند مخازن را بخواند، همکار و مخزن اضافه کنند.",
|
||||
"org.teams.visibility": "پدیداری",
|
||||
"org.teams.visibility_private": "خصوصی",
|
||||
"org.teams.visibility_limited": "محدود شده",
|
||||
"org.teams.visibility_public": "عمومی",
|
||||
"admin.dashboard": "پیشخوان",
|
||||
"admin.users": "حساب کاربران",
|
||||
"admin.organizations": "تشکیلات",
|
||||
"admin.repositories": "مخازن",
|
||||
"admin.hooks": "وب هوک ها",
|
||||
"admin.authentication": "منابع احراز هویت",
|
||||
"admin.badges.description": "توضیحات",
|
||||
"admin.config": "پیکربندی",
|
||||
"admin.config_summary": "چکیده",
|
||||
"admin.config_settings": "تنظيمات",
|
||||
@@ -1993,11 +1971,8 @@
|
||||
"admin.config.server_config": "پیکربندی سرور",
|
||||
"admin.config.app_name": "عنوان سایت",
|
||||
"admin.config.app_ver": "نسخهی GitGo",
|
||||
"admin.config.app_url": "آدرس پایه گیتی",
|
||||
"admin.config.custom_conf": "مسیر پرونده پیکربندی",
|
||||
"admin.config.custom_file_root_path": "مسیر ریشهی پرونده سفارشی",
|
||||
"admin.config.domain": "دامنه سرور",
|
||||
"admin.config.offline_mode": "شیوه محلی",
|
||||
"admin.config.disable_router_log": "غیرفعال کردن گزارش مسیریاب",
|
||||
"admin.config.run_user": "اجرا به عنوان نام کاربری",
|
||||
"admin.config.run_mode": "حالت اجرا",
|
||||
@@ -2066,11 +2041,9 @@
|
||||
"admin.config.cache_config": "پیکربندی حافظه پنهان",
|
||||
"admin.config.cache_adapter": "وفق دهنده حافظه پنهان",
|
||||
"admin.config.cache_interval": "وقفه حافظه نهان",
|
||||
"admin.config.cache_conn": "اتصال حافظه نهان",
|
||||
"admin.config.cache_item_ttl": "مولفه TTL حافظه نهان",
|
||||
"admin.config.session_config": "پیکربندی نشست ها",
|
||||
"admin.config.session_provider": "تامین کننده نشست",
|
||||
"admin.config.provider_config": "پیکربندی تامین کننده",
|
||||
"admin.config.cookie_name": "نام کوکی",
|
||||
"admin.config.gc_interval_time": "فاصله زمانی GC",
|
||||
"admin.config.session_life_time": "طول عمر نشست",
|
||||
@@ -2078,7 +2051,6 @@
|
||||
"admin.config.cookie_life_time": "طول عمر کوکی",
|
||||
"admin.config.picture_config": "پیکربندی عکس و آواتار",
|
||||
"admin.config.picture_service": "سرویس تصویر",
|
||||
"admin.config.disable_gravatar": "غیر فعال کردن Gravatar",
|
||||
"admin.config.enable_federated_avatar": "فعال سازی آواتار مشترک",
|
||||
"admin.config.git_config": "پیکربندی Git",
|
||||
"admin.config.git_disable_diff_highlight": "غیرفعال کردن برجسته سازی در Diff",
|
||||
@@ -2088,8 +2060,6 @@
|
||||
"admin.config.git_gc_args": "آرگومان های GC",
|
||||
"admin.config.git_migrate_timeout": "آستانه ی زمان مهاجرت",
|
||||
"admin.config.git_mirror_timeout": "زمان آستانه در به روز رسانی قرینه",
|
||||
"admin.config.git_clone_timeout": "زمان آستانه ی عملیات Clone",
|
||||
"admin.config.git_pull_timeout": "زمان آستانه ی عملیات واکشی",
|
||||
"admin.config.git_gc_timeout": "زمان آستانه ی عملیات GC",
|
||||
"admin.config.log_config": "پیکربندی ثبت رخداد",
|
||||
"admin.config.disabled_logger": "غیرفعال شده",
|
||||
@@ -2216,6 +2186,8 @@
|
||||
"packages.container.labels": "برچسبها",
|
||||
"packages.npm.dependencies": "وابستگی ها",
|
||||
"packages.npm.details.tag": "برچسب",
|
||||
"packages.terraform.lock": "قفل کردن",
|
||||
"packages.terraform.unlock": "رفع انسداد",
|
||||
"packages.owner.settings.cleanuprules.enabled": "فعال شده",
|
||||
"secrets.creation.description": "شرح",
|
||||
"actions.runners.name": "نام",
|
||||
@@ -2228,5 +2200,8 @@
|
||||
"actions.runners.status.active": "فعال",
|
||||
"actions.runners.version": "نسخه",
|
||||
"actions.runs.commit": "کامیت",
|
||||
"git.filemode.symbolic_link": "پیوند نمادین"
|
||||
"actions.runs.summary": "چکیده",
|
||||
"actions.workflow.scope_owner": "مالک",
|
||||
"actions.workflow.required": "الزامی",
|
||||
"actions.general.token_permissions.mode.restricted": "محصور"
|
||||
}
|
||||
|
||||
@@ -153,12 +153,6 @@
|
||||
"install.register_confirm": "Vaadi sähköpostin vahvistaminen rekisteröintiin",
|
||||
"install.mail_notify": "Ota käyttöön sähköpostiilmoitukset",
|
||||
"install.server_service_title": "Palvelin ja kolmansien osapuolten palveluiden asetukset",
|
||||
"install.offline_mode": "Ota käyttöön lokaali tila",
|
||||
"install.offline_mode_popup": "Poista kolmannen osapuolen sisällöstä jakeluverkot ja tarjoa kaikki resurssit paikallisesti.",
|
||||
"install.disable_gravatar": "Poista Gravatar käytöstä",
|
||||
"install.disable_gravatar_popup": "Poista Gravatar ja kolmannen osapuolen avaratir käytöstä. Oletus-avatar näytetään, ellei käyttäjä ole ladannut omaansa.",
|
||||
"install.federated_avatar_lookup": "Käytä ulkopuolisia profiilikuvia",
|
||||
"install.federated_avatar_lookup_popup": "Enable federated avatars lookup to use federated open source service based on libravatar.",
|
||||
"install.disable_registration": "Poista rekisteröinti käytöstä",
|
||||
"install.disable_registration_popup": "Poista käyttäjän itse-rekisteröinti, vain ylläpito voi luoda tilejä.",
|
||||
"install.allow_only_external_registration_popup": "Salli rekisteröinti vain ulkopuolisista palveluista",
|
||||
@@ -177,7 +171,6 @@
|
||||
"install.admin_email": "Sähköpostiosoite",
|
||||
"install.install_btn_confirm": "Asenna Gitea",
|
||||
"install.test_git_failed": "Epäonnistui testata 'git' komentoa: %v",
|
||||
"install.sqlite3_not_available": "Tämä Gitea versio ei tue SQLite3. Lataa virallinen binääriversio kohteesta %s (ei 'gobuild' versio).",
|
||||
"install.invalid_db_setting": "Tietokanta-asetukset ovat väärin: %v",
|
||||
"install.invalid_repo_path": "Repojen juuri polku on virheellinen: %v",
|
||||
"install.invalid_app_data_path": "Sovelluksen datapolku on virheellinen: %v",
|
||||
@@ -632,7 +625,6 @@
|
||||
"repo.editor.add_subdir": "Lisää hakemisto…",
|
||||
"repo.editor.require_signed_commit": "Haara vaatii vahvistetun commitin",
|
||||
"repo.commits.commits": "Commitit",
|
||||
"repo.commits.nothing_to_compare": "Nämä haarat vastaavat toisiaan.",
|
||||
"repo.commits.search_all": "Kaikki haarat",
|
||||
"repo.commits.author": "Tekijä",
|
||||
"repo.commits.message": "Viesti",
|
||||
@@ -663,7 +655,6 @@
|
||||
"repo.issues.filter_milestones": "Suodata merkkipaalu",
|
||||
"repo.issues.new": "Uusi ongelma",
|
||||
"repo.issues.new.labels": "Tunnisteet",
|
||||
"repo.issues.new.no_label": "Ei tunnistetta",
|
||||
"repo.issues.new.clear_labels": "Tyhjennä tunnisteet",
|
||||
"repo.issues.new.projects": "Projektit",
|
||||
"repo.issues.new.no_items": "Ei kohteita",
|
||||
@@ -736,7 +727,6 @@
|
||||
"repo.issues.context.reference_issue": "Viittaa uudesa ongelmassa",
|
||||
"repo.issues.context.edit": "Muokkaa",
|
||||
"repo.issues.context.delete": "Poista",
|
||||
"repo.issues.reopen_issue": "Avaa uudelleen",
|
||||
"repo.issues.create_comment": "Kommentoi",
|
||||
"repo.issues.closed_at": "sulki tämän ongelman <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>",
|
||||
"repo.issues.reopened_at": "uudelleenavasi tämän ongelman <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>",
|
||||
@@ -814,7 +804,6 @@
|
||||
"repo.pulls.viewed_files_label": "%[1]d / %[2]d tiedostoa katsottu",
|
||||
"repo.pulls.compare_compare": "vedä kohteesta",
|
||||
"repo.pulls.filter_branch": "Suodata branch",
|
||||
"repo.pulls.nothing_to_compare": "Nämä haarat vastaavat toisiaan. Ei ole tarvetta luoda vetopyyntöä.",
|
||||
"repo.pulls.nothing_to_compare_and_allow_empty_pr": "Nämä haarat vastaavat toisiaan. Vetopyyntö tulee olemaan tyhjä.",
|
||||
"repo.pulls.has_pull_request": "Vetopyyntö haarojen välillä on jo olemassa: <a href=\"%[1]s\">%[2]s#%[3]d</a>",
|
||||
"repo.pulls.create": "Luo Pull-pyyntö",
|
||||
@@ -917,7 +906,6 @@
|
||||
"repo.settings.danger_zone": "Vaaravyöhyke",
|
||||
"repo.settings.new_owner_has_same_repo": "Uudella omistajalla on jo samanniminen repo. Ole hyvä ja valitse toinen nimi.",
|
||||
"repo.settings.transfer": "Siirrä omistajuus",
|
||||
"repo.settings.transfer_form_title": "Syötä repon nimi vahvistuksena:",
|
||||
"repo.settings.transfer_notices_3": "- Jos arkisto on yksityinen ja se siirretään yksittäiselle käyttäjälle, tämä toiminto varmistaa, että käyttäjällä on ainakin lukuoikeudet (ja muuttaa käyttöoikeuksia tarvittaessa).",
|
||||
"repo.settings.transfer_owner": "Uusi omistaja",
|
||||
"repo.settings.wiki_delete": "Poista Wiki data",
|
||||
@@ -939,8 +927,6 @@
|
||||
"repo.settings.webhook.payload": "Sisältö",
|
||||
"repo.settings.webhook.body": "Sisältö",
|
||||
"repo.settings.githook_edit_desc": "Jos koukku ei ole käytössä, esitellään esimerkkisisältö. Sisällön jättäminen tyhjäksi arvoksi poistaa tämän koukun käytöstä.",
|
||||
"repo.settings.githook_name": "Koukun nimi",
|
||||
"repo.settings.githook_content": "Koukun sisältö",
|
||||
"repo.settings.update_githook": "Päivitys koukku",
|
||||
"repo.settings.payload_url": "Kohde URL",
|
||||
"repo.settings.http_method": "HTTP-menetelmä",
|
||||
@@ -1060,6 +1046,7 @@
|
||||
"repo.diff.review.comment": "Kommentoi",
|
||||
"repo.diff.review.approve": "Hyväksy",
|
||||
"repo.diff.review.reject": "Pyydä muutoksia",
|
||||
"repo.commits.avatar_stack_and": "ja",
|
||||
"repo.release.releases": "Julkaisut",
|
||||
"repo.release.tags": "Tagit",
|
||||
"repo.release.new_release": "Uusi julkaisu",
|
||||
@@ -1130,9 +1117,7 @@
|
||||
"org.settings.hooks_desc": "Lisää webkoukkuja, jotka suoritetaan <strong>kaikissa repoissa</strong> tässä organisaatiossa.",
|
||||
"org.members.membership_visibility": "Jäsenyyden näkyvyys:",
|
||||
"org.members.public": "Näkyvä",
|
||||
"org.members.public_helper": "tee piilotettu",
|
||||
"org.members.private": "Piilotettu",
|
||||
"org.members.private_helper": "tee näkyvä",
|
||||
"org.members.member_role": "Jäsenen rooli:",
|
||||
"org.members.owner": "Omistaja",
|
||||
"org.members.member": "Jäsen",
|
||||
@@ -1154,7 +1139,6 @@
|
||||
"org.teams.members": "Ryhmän jäsenet",
|
||||
"org.teams.update_settings": "Päivitä asetukset",
|
||||
"org.teams.delete_team": "Poista tiimi",
|
||||
"org.teams.add_team_member": "Lisää tiimin jäsen",
|
||||
"org.teams.delete_team_title": "Tiimin poisto",
|
||||
"org.teams.delete_team_desc": "Tiimin poisto peruuttaa sen jäseniltä oikeuden päästä tiimin varastoihin. Jatketaanko?",
|
||||
"org.teams.delete_team_success": "Tiimi on poistettu.",
|
||||
@@ -1164,12 +1148,17 @@
|
||||
"org.teams.repositories": "Tiimin repot",
|
||||
"org.teams.members.none": "Ei jäseniä tässä tiimissä.",
|
||||
"org.teams.all_repositories": "Kaikki repot",
|
||||
"org.teams.visibility": "Näkyvyys",
|
||||
"org.teams.visibility_private": "Yksityinen",
|
||||
"org.teams.visibility_limited": "Rajattu",
|
||||
"org.teams.visibility_public": "Julkinen",
|
||||
"admin.dashboard": "Kojelauta",
|
||||
"admin.users": "Käyttäjätilit",
|
||||
"admin.organizations": "Organisaatiot",
|
||||
"admin.repositories": "Repot",
|
||||
"admin.hooks": "Webkoukut",
|
||||
"admin.authentication": "Todennuslähteet",
|
||||
"admin.badges.description": "Kuvaus",
|
||||
"admin.config": "Asetukset",
|
||||
"admin.config_summary": "Yhteenveto",
|
||||
"admin.config_settings": "Asetukset",
|
||||
@@ -1313,8 +1302,6 @@
|
||||
"admin.config.server_config": "Palvelin asetukset",
|
||||
"admin.config.app_name": "Sivuston otsikko",
|
||||
"admin.config.app_ver": "Gitea versio",
|
||||
"admin.config.app_url": "Gitean juuriosoite",
|
||||
"admin.config.domain": "Palvelimen verkkotunnus",
|
||||
"admin.config.disable_router_log": "Poista käytöstä reitittimen loki",
|
||||
"admin.config.run_user": "Aja käyttäjänä",
|
||||
"admin.config.run_mode": "Suoritustila",
|
||||
@@ -1367,22 +1354,18 @@
|
||||
"admin.config.cache_config": "Välimuistin asetukset",
|
||||
"admin.config.cache_adapter": "Välimuistin sovitin",
|
||||
"admin.config.cache_interval": "Välimuistin aikaväli",
|
||||
"admin.config.cache_conn": "Välimuistin yhteys merkkijono",
|
||||
"admin.config.session_config": "Istunnon asetukset",
|
||||
"admin.config.session_provider": "Istunnon toimittaja",
|
||||
"admin.config.provider_config": "Toimittajan asetukset",
|
||||
"admin.config.cookie_name": "Evästenimi",
|
||||
"admin.config.gc_interval_time": "GC aikaväli aika",
|
||||
"admin.config.session_life_time": "Istunnon elinikä",
|
||||
"admin.config.https_only": "Vain HTTPS",
|
||||
"admin.config.cookie_life_time": "Evästeen elinikä",
|
||||
"admin.config.picture_service": "Kuva palvelu",
|
||||
"admin.config.disable_gravatar": "Poista käytöstä Gravatar",
|
||||
"admin.config.enable_federated_avatar": "Käytä ulkopuolisia profiilikuvia",
|
||||
"admin.config.git_gc_args": "Roskienkeruun parametrit",
|
||||
"admin.config.git_migrate_timeout": "Migraatio aikakatkaistiin",
|
||||
"admin.config.git_mirror_timeout": "Peilauspäivitys aikakatkaistiin",
|
||||
"admin.config.git_clone_timeout": "Kloonaus aikakatkaistiin",
|
||||
"admin.config.git_gc_timeout": "Roskienkeruu aikakatkaistiin",
|
||||
"admin.config.log_config": "Loki asetukset",
|
||||
"admin.config.disabled_logger": "Pois käytöstä",
|
||||
@@ -1466,6 +1449,9 @@
|
||||
"packages.container.labels": "Tunnisteet",
|
||||
"packages.npm.dependencies": "Riippuvuudet",
|
||||
"packages.npm.details.tag": "Tagi",
|
||||
"packages.terraform.lock_status": "Lukituksen tila",
|
||||
"packages.terraform.lock": "Lukitse",
|
||||
"packages.terraform.unlock": "Avaa",
|
||||
"packages.owner.settings.cleanuprules.enabled": "Käytössä",
|
||||
"secrets.creation.description": "Kuvaus",
|
||||
"actions.runners.name": "Nimi",
|
||||
@@ -1474,5 +1460,9 @@
|
||||
"actions.runners.labels": "Tunnisteet",
|
||||
"actions.runners.task_list.run": "Suorita",
|
||||
"actions.runners.task_list.repository": "Repo",
|
||||
"actions.runners.version": "Versio"
|
||||
"actions.runners.version": "Versio",
|
||||
"actions.runs.branch": "Haara",
|
||||
"actions.runs.summary": "Yhteenveto",
|
||||
"actions.workflow.scope_owner": "Omistaja",
|
||||
"actions.general.token_permissions.mode.restricted": "Rajoitettu"
|
||||
}
|
||||
|
||||
@@ -1107,8 +1107,8 @@
|
||||
"repo.migrate_items_releases": "Publications",
|
||||
"repo.migrate_repo": "Migrer le dépôt",
|
||||
"repo.migrate.clone_address": "Migrer/Cloner depuis une URL",
|
||||
"repo.migrate.clone_address_desc": "L'URL HTTP(S) ou 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.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.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_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.",
|
||||
@@ -2204,10 +2204,8 @@
|
||||
"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.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.desc": "La signature d’une révision est dite « fiable » si elle corresponds à son auteur, autrement elle est « discordante ». Pour les révisions déléguées à Gitea, elles seront signées par Gitea et l’auteur original sera crédité \"Co-authored-by:\" et \"Co-committed-by:\" en pied de révision. Pour cela, la clé configurée par défaut de Gitea doit correspondre à celle d’un utilisateur.",
|
||||
"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.desc": "La signature d’une révision est dite « fiable » si elle correponds à l’auteur collaborant au dépôt. Elle est « dilettante » si elle ne correponds qu’à l'auteur, et autrement « discordante ». Pour les révisions déléguées à Gitea, elles seront signées par Gitea et l’auteur original sera crédité \"Co-authored-by:\" et \"Co-committed-by:\" en pied de révision. Pour cela, la clé configurée par défaut de Gitea doit correspondre à celle d’un utilisateur.",
|
||||
"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_notices_1": "- Ceci supprimera de manière permanente et désactivera le wiki de dépôt pour %s.",
|
||||
@@ -2598,6 +2596,7 @@
|
||||
"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.committed_by": "révisé par",
|
||||
"repo.commits.avatar_stack_and": "et",
|
||||
"repo.diff.protected": "Protégé",
|
||||
"repo.diff.image.side_by_side": "Côte à côte",
|
||||
"repo.diff.image.swipe": "Glisser",
|
||||
@@ -2860,6 +2859,14 @@
|
||||
"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_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.visibility": "Visibilité",
|
||||
"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_limited": "Limité",
|
||||
"org.teams.visibility_limited_helper": "Visible à tous les membres de cette organisation.",
|
||||
"org.teams.visibility_public": "Publique",
|
||||
"org.teams.visibility_public_helper": "Visible à tout utilisateur connecté.",
|
||||
"org.teams.owners_visibility_fixed": "La visibilité des propriétaires ne peut pas être modifiée.",
|
||||
"org.teams.invite.title": "Vous avez été invité à rejoindre l'équipe <strong>%s</strong> dans l'organisation <strong>%s</strong>.",
|
||||
"org.teams.invite.by": "Invité par %s",
|
||||
"org.teams.invite.description": "Veuillez cliquer sur le bouton ci-dessous pour rejoindre l’équipe.",
|
||||
@@ -3796,12 +3803,20 @@
|
||||
"actions.runs.latest": "Dernière",
|
||||
"actions.runs.latest_attempt": "Dernière tentative",
|
||||
"actions.runs.triggered_via": "Déclenché via %s",
|
||||
"actions.runs.total_duration": "Durée totale :",
|
||||
"actions.runs.graph_dependencies_count_1": "%d dépendance",
|
||||
"actions.runs.graph_dependencies_count_n": "%d dépendances",
|
||||
"actions.runs.graph_success_rate": "%s succès",
|
||||
"actions.runs.graph_zoom_in": "Zoomer (Ctrl/⌘ + défilement)",
|
||||
"actions.runs.graph_zoom_max": "Déjà zoomé à 100%",
|
||||
"actions.runs.graph_zoom_out": "Dézoomer (Ctrl/⌘ + défilement)",
|
||||
"actions.workflow.disable": "Désactiver le flux de travail",
|
||||
"actions.workflow.disable_success": "Le flux de travail « %s » a bien été désactivé.",
|
||||
"actions.workflow.enable": "Activer le flux de travail",
|
||||
"actions.workflow.enable_success": "Le flux de travail « %s » a bien été activé.",
|
||||
"actions.workflow.disabled": "Le flux de travail est désactivé.",
|
||||
"actions.workflow.scope_owner": "Propriétaire",
|
||||
"actions.workflow.scope_global": "Global",
|
||||
"actions.workflow.required": "Requis",
|
||||
"actions.workflow.run": "Exécuter le flux de travail",
|
||||
"actions.workflow.not_found": "Flux de travail « %s » introuvable.",
|
||||
"actions.workflow.run_success": "Le flux de travail « %s » s’est correctement exécuté.",
|
||||
|
||||
@@ -3832,6 +3832,9 @@
|
||||
"actions.workflow.enable": "Cumasaigh sreabhadh oibre",
|
||||
"actions.workflow.enable_success": "Cumasaíodh sreabhadh oibre '%s' go rathúil.",
|
||||
"actions.workflow.disabled": "Tá sreabhadh oibre díchumasaithe",
|
||||
"actions.workflow.scope_owner": "Úinéir",
|
||||
"actions.workflow.scope_global": "Domhanda",
|
||||
"actions.workflow.required": "Riachtanach",
|
||||
"actions.workflow.run": "Rith Sreabhadh Oibre",
|
||||
"actions.workflow.not_found": "Níor aimsíodh sreabhadh oibre '%s'.",
|
||||
"actions.workflow.run_success": "Ritheann sreabhadh oibre '%s' go rathúil.",
|
||||
|
||||
@@ -122,12 +122,6 @@
|
||||
"install.register_confirm": "A regisztrációhoz e-mail visszaigazolás szükséges",
|
||||
"install.mail_notify": "Email értesítés engedélyezése",
|
||||
"install.server_service_title": "Szerver és egyéb szolgáltatások beállítása",
|
||||
"install.offline_mode": "Helyi mód bekapcsolása",
|
||||
"install.offline_mode_popup": "Gravatar és egyedi források kikapcsolása, minden avatárt a felhasználók töltenek fel.",
|
||||
"install.disable_gravatar": "Gravatar Kikapcsolása",
|
||||
"install.disable_gravatar_popup": "Gravatar és a harmadik féltől származó avatar források letoltása. Alapértelmezett avatárt fog használni, kivéve, ha a felhasználó helyileg tölt fel avatárt.",
|
||||
"install.federated_avatar_lookup": "Összevont profilkép lekérés engedélyezése",
|
||||
"install.federated_avatar_lookup_popup": "Összevont profilkép lekérés engedélyezése a libravatar használatával.",
|
||||
"install.disable_registration": "Ön-regisztráció kikapcsolása",
|
||||
"install.disable_registration_popup": "Regisztráció kikapcsolása, csak a rendszergazda hozhat létre fiókokat.",
|
||||
"install.allow_only_external_registration_popup": "Regisztráció engedélyezése csak külső forrásokból",
|
||||
@@ -145,10 +139,8 @@
|
||||
"install.admin_email": "E-mail cím",
|
||||
"install.install_btn_confirm": "Gitea telepítése",
|
||||
"install.test_git_failed": "Nem sikerült a 'git' parancs kipróbálása: %v",
|
||||
"install.sqlite3_not_available": "Ez a verzió nem támogatja az SQLite3-at, kérlek töltsd le a hivatalos bináris verziót: %s (NE a gobuild változatot).",
|
||||
"install.invalid_db_setting": "Az adatbázis-beállítások érvénytelenek: %v",
|
||||
"install.invalid_repo_path": "Repository gyökérkönyvtára helytelen: %v",
|
||||
"install.run_user_not_match": "Futtató felhasználó más, mint az aktuális felhasználó: %s -> %s",
|
||||
"install.save_config_failed": "Hiba történt a konfiguráció mentése közben: %v",
|
||||
"install.invalid_admin_setting": "Hibás a rendszergazdai fiók beállítása: %v",
|
||||
"install.invalid_log_root_path": "Naplózás gyökérmappa érvénytelen: %v",
|
||||
@@ -462,7 +454,6 @@
|
||||
"repo.template_helper": "A tároló legyen sablon",
|
||||
"repo.visibility": "Láthatóság",
|
||||
"repo.visibility_description": "Csak a tulajdonos és a megfelelő jogokkal rendelkező tagok láthatják ezt.",
|
||||
"repo.visibility_fork_helper": "(Ennek a megváltoztatása kihat minden másolatra)",
|
||||
"repo.clone_helper": "Segítségre van szüksége a klónozáshoz? Látogassa meg a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">súgót</a>.",
|
||||
"repo.fork_repo": "Tároló lemásolása",
|
||||
"repo.fork_from": "Másolás innen",
|
||||
@@ -608,7 +599,6 @@
|
||||
"repo.issues.new": "Új hibajegy",
|
||||
"repo.issues.new.title_empty": "A cím nem lehet üres",
|
||||
"repo.issues.new.labels": "Címkék",
|
||||
"repo.issues.new.no_label": "Nincs címke",
|
||||
"repo.issues.new.clear_labels": "Címkék kiürítése",
|
||||
"repo.issues.new.projects": "Projektek",
|
||||
"repo.issues.new.no_items": "Nincsenek elemek",
|
||||
@@ -680,7 +670,6 @@
|
||||
"repo.issues.context.quote_reply": "Válasz idézettel",
|
||||
"repo.issues.context.edit": "Szerkesztés",
|
||||
"repo.issues.context.delete": "Törlés",
|
||||
"repo.issues.reopen_issue": "Újranyitás",
|
||||
"repo.issues.create_comment": "Hozzászólás",
|
||||
"repo.issues.commit_ref_at": "hivatkozott erre a hibajegyre egy commit-ból <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>",
|
||||
"repo.issues.author": "Szerző",
|
||||
@@ -767,7 +756,6 @@
|
||||
"repo.pulls.compare_base": "egyesítés ide",
|
||||
"repo.pulls.compare_compare": "egyesítés innen",
|
||||
"repo.pulls.filter_branch": "Ágra szűrés",
|
||||
"repo.pulls.nothing_to_compare": "Ezek az ágak egyenlőek. Nincs szükség egyesítési kérésre.",
|
||||
"repo.pulls.create": "Egyesítési kérés létrehozása",
|
||||
"repo.pulls.title_desc": "egyesíteni szeretné %[1]d változás(oka)t a(z) <code>%[2]s</code>-ból <code id=\"branch_target\">%[3]s</code>-ba",
|
||||
"repo.pulls.merged_title_desc": "egyesítve %[1]d változás(ok) a <code>%[2]s</code>-ból <code>%[3]s</code>-ba %[4]s",
|
||||
@@ -945,6 +933,7 @@
|
||||
"repo.diff.comment.reply": "Válasz",
|
||||
"repo.diff.review": "Értékelés",
|
||||
"repo.diff.review.comment": "Hozzászólás",
|
||||
"repo.commits.avatar_stack_and": "és",
|
||||
"repo.release.releases": "Kiadások",
|
||||
"repo.release.tags": "Címkék",
|
||||
"repo.release.new_release": "Új kiadás",
|
||||
@@ -1032,7 +1021,6 @@
|
||||
"org.teams.members": "Csoporttagok",
|
||||
"org.teams.update_settings": "Beállítások frissítése",
|
||||
"org.teams.delete_team": "Csapat törlése",
|
||||
"org.teams.add_team_member": "Csapattag hozzáadása",
|
||||
"org.teams.delete_team_title": "Csapat törlése",
|
||||
"org.teams.delete_team_desc": "Egy csapat törlése visszavonja a tagjai hozzáférését a tárolókhoz. Folytatható?",
|
||||
"org.teams.delete_team_success": "A csoport törölve lett.",
|
||||
@@ -1047,12 +1035,16 @@
|
||||
"org.teams.members.none": "Ennek a csapatnak nincsenek tagjai.",
|
||||
"org.teams.specific_repositories": "Meghatározott tárolók",
|
||||
"org.teams.all_repositories": "Minden tároló",
|
||||
"org.teams.visibility": "Láthatóság",
|
||||
"org.teams.visibility_private": "Privát",
|
||||
"org.teams.visibility_public": "Nyilvános",
|
||||
"admin.dashboard": "Műszerfal",
|
||||
"admin.users": "Felhasználói fiókok",
|
||||
"admin.organizations": "Szervezetek",
|
||||
"admin.repositories": "Tárolók",
|
||||
"admin.hooks": "Webhook-ok",
|
||||
"admin.authentication": "Hitelesítési források",
|
||||
"admin.badges.description": "Leírás",
|
||||
"admin.config": "Konfiguráció",
|
||||
"admin.config_summary": "Összefoglaló",
|
||||
"admin.config_settings": "Beállítások",
|
||||
@@ -1199,10 +1191,8 @@
|
||||
"admin.config.server_config": "Szerver Konfiguráció",
|
||||
"admin.config.app_name": "Webhely címe",
|
||||
"admin.config.app_ver": "Gitea Verzió",
|
||||
"admin.config.app_url": "A Gitea alapértelmezett címe",
|
||||
"admin.config.custom_conf": "Konfigurációs fájl elérési útja",
|
||||
"admin.config.custom_file_root_path": "Egyedi fájlok gyökérútvonala",
|
||||
"admin.config.offline_mode": "Helyi mód",
|
||||
"admin.config.disable_router_log": "Útválasztás naplózásának letiltása",
|
||||
"admin.config.run_user": "Futtatás mint",
|
||||
"admin.config.run_mode": "Futtatás Módja",
|
||||
@@ -1264,10 +1254,8 @@
|
||||
"admin.config.cache_config": "Gyorsítótár Beállítások",
|
||||
"admin.config.cache_adapter": "Gyorsítótár Adapter",
|
||||
"admin.config.cache_interval": "Gyorsítótár Intervallum",
|
||||
"admin.config.cache_conn": "Gyorsítótár Kapcsolat",
|
||||
"admin.config.session_config": "Munkamenet Beállítások",
|
||||
"admin.config.session_provider": "Munkamenet Szolgáltató",
|
||||
"admin.config.provider_config": "Szolgáltató Beállítás",
|
||||
"admin.config.cookie_name": "Süti Név",
|
||||
"admin.config.gc_interval_time": "GC Intervallum Idő",
|
||||
"admin.config.session_life_time": "Munkamenet Élettartama",
|
||||
@@ -1275,7 +1263,6 @@
|
||||
"admin.config.cookie_life_time": "Süti Élettartam",
|
||||
"admin.config.picture_config": "Kép és Avatár Konfiguráció",
|
||||
"admin.config.picture_service": "Kép Szolgáltatás",
|
||||
"admin.config.disable_gravatar": "Gravatar Kikapcsolása",
|
||||
"admin.config.enable_federated_avatar": "Összevont profilkép lekérés engedélyezése",
|
||||
"admin.config.git_config": "Git Beállítás",
|
||||
"admin.config.git_disable_diff_highlight": "Különbségi nézetben a szintakszis kiemelés kikapcsolása",
|
||||
@@ -1285,8 +1272,6 @@
|
||||
"admin.config.git_gc_args": "Szemétgyűjtő (GC) Argumentumok",
|
||||
"admin.config.git_migrate_timeout": "Migrációs Időtúllépés",
|
||||
"admin.config.git_mirror_timeout": "Tükör Frissítési Időtúllépés",
|
||||
"admin.config.git_clone_timeout": "Klónozási időtúllépés",
|
||||
"admin.config.git_pull_timeout": "Frissítési Időtúllépés",
|
||||
"admin.config.git_gc_timeout": "Szemétgyűjtési (GC) Időtúllépés",
|
||||
"admin.config.log_config": "Naplózási Beállítások",
|
||||
"admin.config.disabled_logger": "Letiltva",
|
||||
@@ -1373,6 +1358,8 @@
|
||||
"packages.container.labels": "Címkék",
|
||||
"packages.npm.dependencies": "Függőségek",
|
||||
"packages.npm.details.tag": "Címke",
|
||||
"packages.terraform.lock": "Lezárás",
|
||||
"packages.terraform.unlock": "Feloldás",
|
||||
"packages.owner.settings.cleanuprules.enabled": "Engedélyezett",
|
||||
"secrets.creation.description": "Leírás",
|
||||
"actions.runners.name": "Név",
|
||||
@@ -1383,5 +1370,7 @@
|
||||
"actions.runners.task_list.repository": "Tároló",
|
||||
"actions.runners.status.active": "Aktív",
|
||||
"actions.runners.version": "Verzió",
|
||||
"git.filemode.symbolic_link": "Szimbolikus hivatkozás"
|
||||
"actions.runs.summary": "Összefoglaló",
|
||||
"actions.workflow.scope_owner": "Tulajdonos",
|
||||
"actions.general.token_permissions.mode.restricted": "Korlátozott"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user