1
Fork 0
mirror of https://codeberg.org/forgejo/forgejo.git synced 2024-10-02 13:29:10 +00:00

Compare commits

...

70 commits

Author SHA1 Message Date
Renovate Bot
f45bc5ad62 Update module google.golang.org/grpc to v1.67.1 2024-10-01 06:18:42 +00:00
Earl Warren
c4d2635839 Merge pull request 'fix: referenced sha256:* container images may be deleted' (#5430) from earl-warren/forgejo:wip-container-cleanup into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5430
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
2024-09-30 16:45:09 +00:00
Earl Warren
08999e8189 Merge pull request 'Lock file maintenance (forgejo)' (#5424) from renovate/forgejo-lock-file-maintenance into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5424
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
2024-09-30 15:31:34 +00:00
Earl Warren
0a5fd7fdb8
fix: referenced sha256:* container images may be deleted
The inventory of the sha256:* images and the manifest index that
reference them is incomplete because it does not take into account any
image older than the expiration limit. As a result some sha256:* will
be considered orphaned although they are referenced from a manifest
index that was created more recently than the expiration limit.

There must not be any filtering based on the creation time when
building the inventory. The expiration limit must only be taken into
account when deleting orphaned images: those that are more recent than
the expiration limit must not be deleted.

This limit is specially important because it protects against a race
between a cleanup task and an ongoing mirroring task. A mirroring
task (such as skopeo sync) will first upload sha256:* images and then
create the corresponding manifest index. If a cleanup races against
it, the sha256:* images that are not yet referenced will be deleted
without skopeo noticing and the published index manifest that happens
at a later time will contain references to non-existent images.
2024-09-30 16:56:21 +02:00
Renovate Bot
e3eaa284bb Update actions/checkout action to v4 (forgejo) (#5427)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5427
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
2024-09-30 13:08:26 +00:00
Renovate Bot
92305933b1 Update actions/setup-node action to v4 (forgejo) (#5428)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5428
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
2024-09-30 12:56:08 +00:00
forgejo-renovate-action
b2aa644859 Merge pull request 'Update renovate to v38.101.1 (forgejo)' (#5422) from renovate/forgejo-renovate into forgejo 2024-09-30 06:25:46 +00:00
Renovate Bot
0c993085e0 Lock file maintenance 2024-09-30 02:03:35 +00:00
Renovate Bot
1b06287fe3 Update renovate to v38.101.1 2024-09-30 00:08:52 +00:00
Otto
c37732d1ae Merge pull request 'ci: use custom action for Go caching' (#5328) from fnetx/custom-go-caching into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5328
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2024-09-29 23:53:33 +00:00
Otto Richter
a56c000f60 Replace setup-go with a custom action
To maximize the caching efficiency, all build jobs are executed as
forgejo user.
Hash is explicitly specified to avoid breaking changes.
2024-09-30 01:16:54 +02:00
Otto
3a38dbac17 Merge pull request 'ci: Move preparations to local actions' (#5410) from fnetx/ci-boilerplate into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5410
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2024-09-29 23:16:13 +00:00
Earl Warren
cf56beb3f9 Merge pull request 'Update citation-js monorepo (forgejo)' (#5395) from renovate/forgejo-citation-js-monorepo into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5395
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
2024-09-29 10:02:41 +00:00
Earl Warren
ffed8ac7b4 Merge pull request 'Update module github.com/minio/minio-go/v7 to v7.0.77 (forgejo)' (#5398) from renovate/forgejo-github.com-minio-minio-go-v7-7.x into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5398
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
2024-09-29 09:26:54 +00:00
forgejo-renovate-action
1a90067754 Merge pull request 'Update dependency vue to v3.5.9 (forgejo)' (#5402) from renovate/forgejo-patch-vue-monorepo into forgejo 2024-09-29 08:52:40 +00:00
Renovate Bot
1d5f8e5488 Update dependency vue to v3.5.10 2024-09-29 08:03:13 +00:00
Earl Warren
76a0dcd372 Merge pull request 'Update dependency webpack to v5.95.0 (forgejo)' (#5411) from renovate/forgejo-webpack-5.x into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5411
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
2024-09-29 06:46:03 +00:00
Otto Richter
1fa09d1621 Split Git package installation 2024-09-28 12:08:30 +02:00
Codeberg Translate
e40554f89b i18n: update of translations from Codeberg Translate (#5355)
Co-authored-by: earl-warren <earl-warren@users.noreply.translate.codeberg.org>
Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Co-authored-by: Zughy <Zughy@users.noreply.translate.codeberg.org>
Co-authored-by: aleksi <aleksi@users.noreply.translate.codeberg.org>
Co-authored-by: Application-Maker <Application-Maker@users.noreply.translate.codeberg.org>
Co-authored-by: Salif Mehmed <mail@salif.eu>
Co-authored-by: 0ko <0ko@users.noreply.translate.codeberg.org>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Panagiotis \"Ivory\" Vasilopoulos <git@n0toose.net>
Co-authored-by: claudep <claudep@users.noreply.translate.codeberg.org>
Co-authored-by: vri <vri@users.noreply.translate.codeberg.org>
Co-authored-by: nicokaiser <nicokaiser@users.noreply.translate.codeberg.org>
Co-authored-by: Outbreak2096 <Outbreak2096@users.noreply.translate.codeberg.org>
Co-authored-by: robines <robines@users.noreply.translate.codeberg.org>
Co-authored-by: nazrin <nazrin@users.noreply.translate.codeberg.org>
Co-authored-by: Kaede Fujisaki <ledyba@users.noreply.translate.codeberg.org>

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5355
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: Codeberg Translate <translate@noreply.codeberg.org>
Co-committed-by: Codeberg Translate <translate@noreply.codeberg.org>
2024-09-28 09:40:29 +00:00
Renovate Bot
735bcf978f Update dependency webpack to v5.95.0 2024-09-28 00:02:39 +00:00
Otto Richter
c0b17a06ef Split backend building boilerplate 2024-09-27 20:47:35 +02:00
Otto Richter
5b84756137 Split env preparation boilerplate 2024-09-27 20:47:35 +02:00
Exploding Dragon
89742c4913 feat: add architecture-specific removal support for arch package (#5351)
- [x] add architecture-specific removal support
- [x] Fix upload competition
- [x] Fix not checking input when downloading

docs: https://codeberg.org/forgejo/docs/pulls/874

### Release notes

- [ ] I do not want this change to show in the release notes.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5351
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Exploding Dragon <explodingfkl@gmail.com>
Co-committed-by: Exploding Dragon <explodingfkl@gmail.com>
2024-09-27 08:21:22 +00:00
Earl Warren
89d9307d56 Merge pull request '[gitea] week 2024-39 cherry pick (gitea/main -> forgejo)' (#5372) from earl-warren/wcp/2024-39 into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5372
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2024-09-27 08:12:43 +00:00
forgejo-renovate-action
40a68aa8f8 Merge pull request 'Update dependency markdownlint-cli to v0.42.0 (forgejo)' (#5405) from renovate/forgejo-markdownlint-cli-0.x into forgejo 2024-09-27 08:09:55 +00:00
forgejo-renovate-action
84b96a7343 Merge pull request 'Update dependency @stylistic/stylelint-plugin to v3.1.0 (forgejo)' (#5403) from renovate/forgejo-stylistic-stylelint-plugin-3.x into forgejo 2024-09-27 08:09:26 +00:00
forgejo-renovate-action
9ec57761f9 Merge pull request 'Update dependency @vitest/eslint-plugin to v1.1.4 (forgejo)' (#5404) from renovate/forgejo-vitest-eslint-plugin-1.x into forgejo 2024-09-27 08:08:53 +00:00
Earl Warren
e3deb88a8d
chore(release-notes): weekly cherry-pick week 2024-39 2024-09-27 08:47:23 +02:00
Lunny Xiao
2ffb08bb88
Use camo.Always instead of camo.Allways (#32097)
Fix #31575

https://gitea.com/gitea/docs/pulls/73
(cherry picked from commit 8e2dd5d3ddfb442937c79f05df88d18b856952cb)
2024-09-27 08:45:55 +02:00
Jamie Schouten
9d34731198
Add bin to Composer Metadata (#32099)
This PR addresses the missing `bin` field in Composer metadata, which
currently causes vendor-provided binaries to not be symlinked to
`vendor/bin` during installation.

In the current implementation, running `composer install` does not
publish the binaries, leading to issues where expected binaries are not
available.

By properly declaring the `bin` field, this PR ensures that binaries are
correctly symlinked upon installation, as described in the [Composer
documentation](https://getcomposer.org/doc/articles/vendor-binaries.md).

(cherry picked from commit d351a42494e71b5e2da63302c2f9b46c78e6dbde)
2024-09-27 08:42:48 +02:00
Lunny Xiao
f709de2403
Fix wrong last modify time (#32102)
(cherry picked from commit a802508f88e546bf18990559e44bf27a09c869ee)
2024-09-27 08:42:48 +02:00
Timon van der Berg
2675a24649
Repo Activity: count new issues that were closed (#31776)
I'm new to go and contributing to gitea, your guidance is much
appreciated.

This is meant to solve https://github.com/go-gitea/gitea/issues/13309

Previously, closed issues would not be shown under new issues in the
activity tab, even if they were newly created.

changes:
* Split out newlyCreatedIssues from issuesForActivityStatement to count
  both currently open and closed issues.
* Use a seperate function to count active issues to prevent
double-counting issues after the above change.

Result is that new issues that have been closed are shown both under
"new" and "closed".

Signed-off-by: Timon van der Berg <tmnvanderberg@gmail.com>
(cherry picked from commit ebfde845294cc681de6b1fe1adcf27e35f61b89b)
2024-09-27 08:42:48 +02:00
KN4CK3R
526054332a
Fix incorrect /tokens api (#32085)
Fixes #32078

- Add missing scopes output.
- Disallow empty scope.

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
(cherry picked from commit 08adbc468f8875fd4763c3656b334203c11adc0a)
2024-09-27 08:42:48 +02:00
Earl Warren
6275d1bc50
Refactor CSRF protector (#32057) (fix forgejo tests)
Fix the tests unique to Forgejo that are impacted by the refactor.
2024-09-27 08:42:48 +02:00
wxiaoguang
1ae3b127fc
Refactor CSRF protector (#32057)
Remove unused CSRF options, decouple "new csrf protector" and "prepare"
logic, do not redirect to home page if CSRF validation falis (it
shouldn't happen in daily usage, if it happens, redirecting to home
doesn't help either but just makes the problem more complex for "fetch")

(cherry picked from commit 1fede04b83288d8a91304a83b7601699bb5cba04)

Conflicts:
	options/locale/locale_en-US.ini
	tests/integration/repo_branch_test.go
  trivial context conflicts
2024-09-27 08:42:48 +02:00
Earl Warren
1bdf334844
feat: add IfZero utility function
(cherry picked from commit 43de021ac1ca017212ec75fd88a8a80a9db27c4c)
2024-09-27 08:42:48 +02:00
Renovate Bot
70014a7bc9 Update dependency markdownlint-cli to v0.42.0 2024-09-27 02:03:21 +00:00
Renovate Bot
757080addd Update dependency @vitest/eslint-plugin to v1.1.4 2024-09-27 02:03:09 +00:00
Renovate Bot
d000993905 Update dependency @stylistic/stylelint-plugin to v3.1.0 2024-09-27 00:04:48 +00:00
Gusted
5e342c6fa7 Merge pull request 'Update module github.com/klauspost/compress to v1.17.10 (forgejo)' (#5397) from renovate/forgejo-github.com-klauspost-compress-1.x into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5397
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2024-09-26 14:55:51 +00:00
Earl Warren
5e06b0c0ec Merge pull request 'Update dependency monaco-editor to v0.51.0 (forgejo)' (#5374) from renovate/forgejo-monaco-editor-0.x into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5374
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
2024-09-26 07:23:14 +00:00
Renovate Bot
76c6b34270 Update module github.com/minio/minio-go/v7 to v7.0.77 2024-09-26 02:03:56 +00:00
Renovate Bot
7fbe6b4dc3 Update module github.com/klauspost/compress to v1.17.10 2024-09-26 02:03:41 +00:00
Renovate Bot
a9f9b94478 Update citation-js monorepo 2024-09-26 00:04:54 +00:00
Earl Warren
cd7f92797b
start of the v10.0 development branch 2024-09-25 11:11:41 +02:00
Renovate Bot
f0dab9cc05 Update module code.forgejo.org/forgejo/act to v1.21.3 (forgejo) (#5333)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5333
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
2024-09-25 09:10:00 +00:00
Renovate Bot
68cade5b1d Update dependency @vitejs/plugin-vue to v5.1.4 (forgejo) (#5350)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5350
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
2024-09-25 09:04:07 +00:00
Renovate Bot
e1566d2a1f Update dependency mermaid to v11.2.1 (forgejo) (#5338)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5338
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
2024-09-25 09:03:29 +00:00
Renovate Bot
e1a9ad600b Update dependency tailwindcss to v3.4.13 (forgejo) (#5345)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5345
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
2024-09-25 09:03:00 +00:00
forgejo-renovate-action
db016e0652 Merge pull request 'Update dependency vue to v3.5.7 (forgejo)' (#5364) from renovate/forgejo-patch-vue-monorepo into forgejo 2024-09-25 08:58:38 +00:00
Michael Kriese
6490fc2b99 Merge pull request 'Lock file maintenance (forgejo)' (#5334) from renovate/forgejo-lock-file-maintenance into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5334
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
2024-09-25 08:53:48 +00:00
Michael Kriese
1e5f7d2116
chore: fix lint error 2024-09-25 10:15:02 +02:00
Renovate Bot
69b01eb911
Lock file maintenance 2024-09-25 10:15:02 +02:00
Renovate Bot
c2797ecfc1 Update dependency vue to v3.5.8 2024-09-25 08:03:04 +00:00
forgejo-renovate-action
b79e465092 Merge pull request 'Replace dependency eslint-plugin-vitest with @vitest/eslint-plugin 1.0.0 (forgejo)' (#5377) from renovate/forgejo-eslint-plugin-vitest-replacement into forgejo 2024-09-25 07:16:17 +00:00
Michael Kriese
a8f141f6d1
chore: migrate eslint config 2024-09-25 08:34:57 +02:00
Renovate Bot
c54ebb8bc7 Replace dependency eslint-plugin-vitest with @vitest/eslint-plugin 1.0.0 2024-09-24 22:02:34 +00:00
Otto
8b62d3d5da Merge pull request 'i18n: UX improvements: Team permissions and issue closing' (#5383) from fnetx/team-permissions-i18n into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5383
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
2024-09-24 21:16:40 +00:00
Earl Warren
1b528a7874 Merge pull request 'feat(ui): add more emoji and code block rendering in issues' (#4541) from bramh/forgejo:consistent-title-formatting into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/4541
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2024-09-24 20:47:26 +00:00
Otto Richter
dc9a268d3c i18n: UX improvements: Team permissions and issue closing
Change word order for issue comment actions
-  An attempt to address https://codeberg.org/forgejo/forgejo/issues/2650

Org team permissions improvements

- consistency: added missing dot
- clarity: explain what external units mean
- use dedicated keys to explain the permissions.
- split in read/write permissions
- use explicit labels for accessibility
- ext_wiki.desc and ext_issues.desc are no longer in use.
2024-09-24 19:03:30 +02:00
Bram Hagens
4a74113dee
feat(ui): add more emoji and code block rendering in issues 2024-09-24 14:20:33 +02:00
forgejo-renovate-action
60bcdc8bc3 Merge pull request 'Update renovate to v38.93.1 (forgejo)' (#5373) from renovate/forgejo-renovate into forgejo 2024-09-23 06:42:04 +00:00
Renovate Bot
15ec27e658 Update renovate to v38.93.2 2024-09-23 02:02:04 +00:00
Renovate Bot
ed6fef9844 Update dependency monaco-editor to v0.51.0 2024-09-23 00:04:43 +00:00
Exploding Dragon
a6508f5b03 Fix: database not updated when using git push --tags --force (#5319)
Closes #4274

link: https://github.com/go-gitea/gitea/pull/32040

### Release notes

- [ ] I do not want this change to show in the release notes.

<!--start release-notes-assistant-->

## Draft release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- Bug fixes
  - [PR](https://codeberg.org/forgejo/forgejo/pulls/5319): <!--number 5319 --><!--line 0 --><!--description Rml4OiBkYXRhYmFzZSBub3QgdXBkYXRlZCB3aGVuIHVzaW5nIGBnaXQgcHVzaCAtLXRhZ3MgLS1mb3JjZWA=-->Fix: database not updated when using `git push --tags --force`<!--description-->
<!--end release-notes-assistant-->

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5319
Reviewed-by: Otto <otto@codeberg.org>
Co-authored-by: Exploding Dragon <explodingfkl@gmail.com>
Co-committed-by: Exploding Dragon <explodingfkl@gmail.com>
2024-09-22 07:35:25 +00:00
hiifong
9d5f409a5a
Lazy load avatar images (#32051)
(cherry picked from commit f38e1014483b84f4541ffb354cd5dfdd7e000e2c)
2024-09-22 09:11:06 +02:00
KN4CK3R
0cafec4c7a
Do not escape relative path in RPM primary index (#32038)
Fixes #32021

Do not escape the relative path.

(cherry picked from commit f528df944bb9436afcb9272add2ee0cccefbdb55)
2024-09-22 09:07:45 +02:00
Zettat123
961766744b
Check if the due_date is nil when editing issues (#32035)
(cherry picked from commit 3a51c37672d2fbad1f222922e75ce704d5a1ac71)
2024-09-22 09:05:15 +02:00
forgejo-renovate-action
565084925a Merge pull request 'Update dependency @stoplight/spectral-cli to v6.13.1 (forgejo)' (#5368) from renovate/forgejo-stoplight-spectral-cli-6.x into forgejo 2024-09-22 06:40:45 +00:00
Renovate Bot
6745603edd Update dependency @stoplight/spectral-cli to v6.13.1 2024-09-22 00:02:33 +00:00
88 changed files with 2802 additions and 2061 deletions

View file

@ -13,6 +13,7 @@ parserOptions:
plugins:
- "@eslint-community/eslint-plugin-eslint-comments"
- "@stylistic/eslint-plugin-js"
- "@vitest"
- eslint-plugin-array-func
- eslint-plugin-github
- eslint-plugin-i
@ -21,7 +22,6 @@ plugins:
- eslint-plugin-regexp
- eslint-plugin-sonarjs
- eslint-plugin-unicorn
- eslint-plugin-vitest
- eslint-plugin-vitest-globals
- eslint-plugin-wc
@ -50,55 +50,55 @@ overrides:
env:
vitest-globals/env: true
rules:
vitest/consistent-test-filename: [0]
vitest/consistent-test-it: [0]
vitest/expect-expect: [0]
vitest/max-expects: [0]
vitest/max-nested-describe: [0]
vitest/no-alias-methods: [0]
vitest/no-commented-out-tests: [0]
vitest/no-conditional-expect: [0]
vitest/no-conditional-in-test: [0]
vitest/no-conditional-tests: [0]
vitest/no-disabled-tests: [0]
vitest/no-done-callback: [0]
vitest/no-duplicate-hooks: [0]
vitest/no-focused-tests: [0]
vitest/no-hooks: [0]
vitest/no-identical-title: [2]
vitest/no-interpolation-in-snapshots: [0]
vitest/no-large-snapshots: [0]
vitest/no-mocks-import: [0]
vitest/no-restricted-matchers: [0]
vitest/no-restricted-vi-methods: [0]
vitest/no-standalone-expect: [0]
vitest/no-test-prefixes: [0]
vitest/no-test-return-statement: [0]
vitest/prefer-called-with: [0]
vitest/prefer-comparison-matcher: [0]
vitest/prefer-each: [0]
vitest/prefer-equality-matcher: [0]
vitest/prefer-expect-resolves: [0]
vitest/prefer-hooks-in-order: [0]
vitest/prefer-hooks-on-top: [2]
vitest/prefer-lowercase-title: [0]
vitest/prefer-mock-promise-shorthand: [0]
vitest/prefer-snapshot-hint: [0]
vitest/prefer-spy-on: [0]
vitest/prefer-strict-equal: [0]
vitest/prefer-to-be: [0]
vitest/prefer-to-be-falsy: [0]
vitest/prefer-to-be-object: [0]
vitest/prefer-to-be-truthy: [0]
vitest/prefer-to-contain: [0]
vitest/prefer-to-have-length: [0]
vitest/prefer-todo: [0]
vitest/require-hook: [0]
vitest/require-to-throw-message: [0]
vitest/require-top-level-describe: [0]
vitest/valid-describe-callback: [2]
vitest/valid-expect: [2]
vitest/valid-title: [2]
"@vitest/consistent-test-filename": [0]
"@vitest/consistent-test-it": [0]
"@vitest/expect-expect": [0]
"@vitest/max-expects": [0]
"@vitest/max-nested-describe": [0]
"@vitest/no-alias-methods": [0]
"@vitest/no-commented-out-tests": [0]
"@vitest/no-conditional-expect": [0]
"@vitest/no-conditional-in-test": [0]
"@vitest/no-conditional-tests": [0]
"@vitest/no-disabled-tests": [0]
"@vitest/no-done-callback": [0]
"@vitest/no-duplicate-hooks": [0]
"@vitest/no-focused-tests": [0]
"@vitest/no-hooks": [0]
"@vitest/no-identical-title": [2]
"@vitest/no-interpolation-in-snapshots": [0]
"@vitest/no-large-snapshots": [0]
"@vitest/no-mocks-import": [0]
"@vitest/no-restricted-matchers": [0]
"@vitest/no-restricted-vi-methods": [0]
"@vitest/no-standalone-expect": [0]
"@vitest/no-test-prefixes": [0]
"@vitest/no-test-return-statement": [0]
"@vitest/prefer-called-with": [0]
"@vitest/prefer-comparison-matcher": [0]
"@vitest/prefer-each": [0]
"@vitest/prefer-equality-matcher": [0]
"@vitest/prefer-expect-resolves": [0]
"@vitest/prefer-hooks-in-order": [0]
"@vitest/prefer-hooks-on-top": [2]
"@vitest/prefer-lowercase-title": [0]
"@vitest/prefer-mock-promise-shorthand": [0]
"@vitest/prefer-snapshot-hint": [0]
"@vitest/prefer-spy-on": [0]
"@vitest/prefer-strict-equal": [0]
"@vitest/prefer-to-be": [0]
"@vitest/prefer-to-be-falsy": [0]
"@vitest/prefer-to-be-object": [0]
"@vitest/prefer-to-be-truthy": [0]
"@vitest/prefer-to-contain": [0]
"@vitest/prefer-to-have-length": [0]
"@vitest/prefer-todo": [0]
"@vitest/require-hook": [0]
"@vitest/require-to-throw-message": [0]
"@vitest/require-top-level-describe": [0]
"@vitest/valid-describe-callback": [2]
"@vitest/valid-expect": [2]
"@vitest/valid-title": [2]
- files: ["web_src/js/modules/fetch.js", "web_src/js/standalone/**/*"]
rules:
no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression]

View file

@ -25,7 +25,7 @@ jobs:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: self-hosted
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- id: forgejo
uses: https://code.forgejo.org/actions/setup-forgejo@v1

View file

@ -27,7 +27,7 @@ jobs:
# root is used for testing, allow it
if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
@ -37,7 +37,7 @@ jobs:
repository="${{ github.repository }}"
echo "value=${repository##*/}" >> "$GITHUB_OUTPUT"
- uses: https://code.forgejo.org/actions/setup-node@v3
- uses: https://code.forgejo.org/actions/setup-node@v4
with:
node-version: 20

View file

@ -0,0 +1,29 @@
inputs:
packages:
description: 'Packages to install'
required: true
release:
description: 'Release to install from'
default: testing
runs:
using: "composite"
steps:
- name: setup apt package source
run: |
export DEBIAN_FRONTEND=noninteractive
echo "deb http://deb.debian.org/debian/ ${RELEASE} main" > "/etc/apt/sources.list.d/${RELEASE}.list"
env:
RELEASE: ${{inputs.release}}
- name: install packages
run: |
apt-get update -qq
apt-get -q install -qq -y ${PACKAGES}
env:
PACKAGES: ${{inputs.packages}}
- name: remove temporary package list to prevent using it in other steps
run: |
rm "/etc/apt/sources.list.d/${RELEASE}.list"
apt-get update -qq
env:
RELEASE: ${{inputs.release}}

View file

@ -0,0 +1,15 @@
runs:
using: "composite"
steps:
- run: |
su forgejo -c 'make deps-backend'
- uses: actions/cache@v4
id: cache-backend
with:
path: '/workspace/forgejo/forgejo/gitea'
key: backend-build-${{ github.sha }}
- if: steps.cache-backend.outputs.cache-hit != 'true'
run: |
su forgejo -c 'make backend'
env:
TAGS: bindata

View file

@ -0,0 +1,11 @@
runs:
using: "composite"
steps:
- name: setup user and permissions
run: |
git config --add safe.directory '*'
adduser --quiet --comment forgejo --disabled-password forgejo
chown -R forgejo:forgejo .
- uses: https://codeberg.org/fnetx/setup-cache-go@b2214eaf6fb44c7e8512c0f462a2c3ec31f86a73
with:
username: forgejo

View file

@ -39,7 +39,7 @@ jobs:
runs-on: self-hosted
if: vars.DOER != '' && vars.FORGEJO != '' && vars.TO_OWNER != '' && vars.FROM_OWNER != '' && secrets.TOKEN != ''
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: copy & sign
uses: https://code.forgejo.org/forgejo/forgejo-build-publish/publish@v5

View file

@ -11,7 +11,7 @@ jobs:
container:
image: 'code.forgejo.org/oci/node:20-bookworm'
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/checkout@v4
- uses: https://code.forgejo.org/actions/setup-go@v4
with:

View file

@ -12,7 +12,7 @@ jobs:
container:
image: 'code.forgejo.org/oci/node:20-bookworm'
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/checkout@v4
- name: event
run: |

View file

@ -23,7 +23,7 @@ jobs:
runs-on: docker
container:
image: code.forgejo.org/forgejo-contrib/renovate:38.80.0
image: code.forgejo.org/forgejo-contrib/renovate:38.101.1
steps:
- name: Load renovate repo cache

View file

@ -19,27 +19,18 @@ jobs:
cat <<'EOF'
${{ toJSON(github) }}
EOF
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4
with:
go-version-file: "go.mod"
- run: make deps-backend deps-tools
- run: make --always-make -j$(nproc) lint-backend tidy-check swagger-check fmt-check swagger-validate # ensure the "go-licenses" make target runs
- run: |
make backend
env:
TAGS: bindata
- uses: actions/cache@v4
with:
path: '/workspace/forgejo/forgejo/gitea'
key: backend-build-${{ github.sha }}
- uses: https://code.forgejo.org/actions/checkout@v4
- uses: ./.forgejo/workflows/composite/setup-env
- run: su forgejo -c 'make deps-backend deps-tools'
- run: su forgejo -c 'make --always-make -j$(nproc) lint-backend tidy-check swagger-check fmt-check swagger-validate' # ensure the "go-licenses" make target runs
- uses: ./.forgejo/workflows/composite/build-backend
frontend-checks:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker
container:
image: 'code.forgejo.org/oci/node:20-bookworm'
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/checkout@v4
- run: make deps-frontend
- run: make lint-frontend
- run: make checks-frontend
@ -66,38 +57,17 @@ jobs:
MINIO_ROOT_USER: 123456
MINIO_ROOT_PASSWORD: 12345678
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4
with:
go-version-file: "go.mod"
- run: |
git config --add safe.directory '*'
adduser --quiet --comment forgejo --disabled-password forgejo
chown -R forgejo:forgejo .
- uses: https://code.forgejo.org/actions/checkout@v4
- uses: ./.forgejo/workflows/composite/setup-env
- name: install git >= 2.42
run: |
export DEBIAN_FRONTEND=noninteractive
echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list
apt-get update -qq
apt-get -q install -qq -y git
rm /etc/apt/sources.list.d/testing.list
apt-get update -qq
uses: ./.forgejo/workflows/composite/apt-install-from
with:
packages: git
- name: test release-notes-assistant.sh
run: |
apt-get -q install -qq -y jq
./release-notes-assistant.sh test_main
- run: |
su forgejo -c 'make deps-backend'
- uses: actions/cache/restore@v4
id: cache-backend
with:
path: '/workspace/forgejo/forgejo/gitea'
key: backend-build-${{ github.sha }}
- if: steps.cache-backend.outputs.cache-hit != 'true'
run: |
su forgejo -c 'make backend'
env:
TAGS: bindata
- uses: ./.forgejo/workflows/composite/build-backend
- run: |
su forgejo -c 'make test-backend test-check'
timeout-minutes: 50
@ -131,34 +101,13 @@ jobs:
image: ${{ matrix.cacher.image }}
options: ${{ matrix.cacher.options }}
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4
with:
go-version-file: "go.mod"
- run: |
git config --add safe.directory '*'
adduser --quiet --comment forgejo --disabled-password forgejo
chown -R forgejo:forgejo .
- uses: https://code.forgejo.org/actions/checkout@v4
- uses: ./.forgejo/workflows/composite/setup-env
- name: install git >= 2.42
run: |
export DEBIAN_FRONTEND=noninteractive
echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list
apt-get update -qq
apt-get -q install -qq -y git
rm /etc/apt/sources.list.d/testing.list
apt-get update -qq
- run: |
su forgejo -c 'make deps-backend'
- uses: actions/cache/restore@v4
id: cache-backend
uses: ./.forgejo/workflows/composite/apt-install-from
with:
path: '/workspace/forgejo/forgejo/gitea'
key: backend-build-${{ github.sha }}
- if: steps.cache-backend.outputs.cache-hit != 'true'
run: |
su forgejo -c 'make backend'
env:
TAGS: bindata
packages: git
- uses: ./.forgejo/workflows/composite/build-backend
- run: |
su forgejo -c 'make test-remote-cacher test-check'
timeout-minutes: 50
@ -183,35 +132,13 @@ jobs:
#
MYSQL_EXTRA_FLAGS: --innodb-adaptive-flushing=OFF --innodb-buffer-pool-size=4G --innodb-log-buffer-size=128M --innodb-flush-log-at-trx-commit=0 --innodb-flush-log-at-timeout=30 --innodb-flush-method=nosync --innodb-fsync-threshold=1000000000
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4
with:
go-version-file: "go.mod"
- uses: https://code.forgejo.org/actions/checkout@v4
- uses: ./.forgejo/workflows/composite/setup-env
- name: install dependencies & git >= 2.42
run: |
export DEBIAN_FRONTEND=noninteractive
echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list
apt-get update -qq
apt-get install --no-install-recommends -qq -y git git-lfs
rm /etc/apt/sources.list.d/testing.list
apt-get update -qq
- name: setup user and permissions
run: |
git config --add safe.directory '*'
adduser --quiet --comment forgejo --disabled-password forgejo
chown -R forgejo:forgejo .
- run: |
su forgejo -c 'make deps-backend'
- uses: actions/cache/restore@v4
id: cache-backend
uses: ./.forgejo/workflows/composite/apt-install-from
with:
path: '/workspace/forgejo/forgejo/gitea'
key: backend-build-${{ github.sha }}
- if: steps.cache-backend.outputs.cache-hit != 'true'
run: |
su forgejo -c 'make backend'
env:
TAGS: bindata
packages: git git-lfs
- uses: ./.forgejo/workflows/composite/build-backend
- run: |
su forgejo -c 'make test-mysql-migration test-mysql'
timeout-minutes: 50
@ -237,35 +164,13 @@ jobs:
POSTGRES_DB: test
POSTGRES_PASSWORD: postgres
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4
with:
go-version-file: "go.mod"
- uses: https://code.forgejo.org/actions/checkout@v4
- uses: ./.forgejo/workflows/composite/setup-env
- name: install dependencies & git >= 2.42
run: |
export DEBIAN_FRONTEND=noninteractive
echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list
apt-get update -qq
apt-get install --no-install-recommends -qq -y git git-lfs
rm /etc/apt/sources.list.d/testing.list
apt-get update -qq
- name: setup user and permissions
run: |
git config --add safe.directory '*'
adduser --quiet --comment forgejo --disabled-password forgejo
chown -R forgejo:forgejo .
- run: |
su forgejo -c 'make deps-backend'
- uses: actions/cache/restore@v4
id: cache-backend
uses: ./.forgejo/workflows/composite/apt-install-from
with:
path: '/workspace/forgejo/forgejo/gitea'
key: backend-build-${{ github.sha }}
- if: steps.cache-backend.outputs.cache-hit != 'true'
run: |
su forgejo -c 'make backend'
env:
TAGS: bindata
packages: git git-lfs
- uses: ./.forgejo/workflows/composite/build-backend
- run: |
su forgejo -c 'make test-pgsql-migration test-pgsql'
timeout-minutes: 50
@ -280,35 +185,13 @@ jobs:
container:
image: 'code.forgejo.org/oci/node:20-bookworm'
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4
with:
go-version-file: "go.mod"
- uses: https://code.forgejo.org/actions/checkout@v4
- uses: ./.forgejo/workflows/composite/setup-env
- name: install dependencies & git >= 2.42
run: |
export DEBIAN_FRONTEND=noninteractive
echo deb http://deb.debian.org/debian/ testing main > /etc/apt/sources.list.d/testing.list
apt-get update -qq
apt-get install --no-install-recommends -qq -y git git-lfs
rm /etc/apt/sources.list.d/testing.list
apt-get update -qq
- name: setup user and permissions
run: |
git config --add safe.directory '*'
adduser --quiet --comment forgejo --disabled-password forgejo
chown -R forgejo:forgejo .
- run: |
su forgejo -c 'make deps-backend'
- uses: actions/cache/restore@v4
id: cache-backend
uses: ./.forgejo/workflows/composite/apt-install-from
with:
path: '/workspace/forgejo/forgejo/gitea'
key: backend-build-${{ github.sha }}
- if: steps.cache-backend.outputs.cache-hit != 'true'
run: |
su forgejo -c 'make backend'
env:
TAGS: bindata sqlite sqlite_unlock_notify
packages: git git-lfs
- uses: ./.forgejo/workflows/composite/build-backend
- run: |
su forgejo -c 'make test-sqlite-migration test-sqlite'
timeout-minutes: 50
@ -329,9 +212,7 @@ jobs:
container:
image: 'code.forgejo.org/oci/node:20-bookworm'
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4
with:
go-version-file: "go.mod"
- run: make deps-backend deps-tools
- run: make security-check
- uses: https://code.forgejo.org/actions/checkout@v4
- uses: ./.forgejo/workflows/composite/setup-env
- run: su forgejo -c 'make deps-backend deps-tools'
- run: su forgejo -c 'make security-check'

View file

@ -39,7 +39,7 @@ GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasour
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.25.0 # renovate: datasource=go
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.16.2 # renovate: datasource=go
RENOVATE_NPM_PACKAGE ?= renovate@38.80.0 # renovate: datasource=docker packageName=code.forgejo.org/forgejo-contrib/renovate
RENOVATE_NPM_PACKAGE ?= renovate@38.101.1 # renovate: datasource=docker packageName=code.forgejo.org/forgejo-contrib/renovate
ifeq ($(HAS_GO), yes)
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766

View file

@ -1,4 +1,3 @@
<div align="center">
<img src="./assets/logo.svg" alt="" width="192" align="center" />
<h1 align="center">Welcome to Forgejo</h1>

View file

@ -529,7 +529,8 @@ INTERNAL_TOKEN =
;; HMAC to encode urls with, it **is required** if camo is enabled.
;HMAC_KEY =
;; Set to true to use camo for https too lese only non https urls are proxyed
;ALLWAYS = false
;; ALLWAYS is deprecated and will be removed in the future
;ALWAYS = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

14
go.mod
View file

@ -66,7 +66,7 @@ require (
github.com/jhillyerd/enmime v1.3.0
github.com/json-iterator/go v1.1.12
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.17.9
github.com/klauspost/compress v1.17.10
github.com/klauspost/cpuid/v2 v2.2.8
github.com/lib/pq v1.10.9
github.com/markbates/goth v1.80.0
@ -75,7 +75,7 @@ require (
github.com/meilisearch/meilisearch-go v0.28.0
github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.27
github.com/minio/minio-go/v7 v7.0.74
github.com/minio/minio-go/v7 v7.0.77
github.com/msteinert/pam v1.2.0
github.com/nektos/act v0.2.52
github.com/niklasfasching/go-org v1.7.0
@ -108,7 +108,7 @@ require (
golang.org/x/sys v0.25.0
golang.org/x/text v0.18.0
golang.org/x/tools v0.25.0
google.golang.org/grpc v1.66.2
google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.34.2
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0
@ -119,7 +119,7 @@ require (
)
require (
cloud.google.com/go/compute/metadata v0.3.0 // indirect
cloud.google.com/go/compute/metadata v0.5.0 // indirect
dario.cat/mergo v1.0.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
@ -250,7 +250,7 @@ require (
github.com/rhysd/actionlint v1.6.27 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
@ -283,7 +283,7 @@ require (
golang.org/x/mod v0.21.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
@ -293,6 +293,6 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.21.2
replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.21.3
replace github.com/mholt/archiver/v3 => code.forgejo.org/forgejo/archiver/v3 v3.5.1

28
go.sum
View file

@ -1,11 +1,11 @@
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
code.forgejo.org/f3/gof3/v3 v3.7.0 h1:ZfuCP8CGm8ZJbWmL+V0pUu3E0X4FCAA7GfRDy/y5/K4=
code.forgejo.org/f3/gof3/v3 v3.7.0/go.mod h1:oNhOeqD4DZYjVcNjQXIOdDX9b/1tqxi9ITLS8H9/Csw=
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU=
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM=
code.forgejo.org/forgejo/act v1.21.2 h1:LERMtDNZDFXOYYYSU7Yduyyz7sN0t/Xnc1wFlupweiE=
code.forgejo.org/forgejo/act v1.21.2/go.mod h1:+PcvJ9iv+NTFeJSh79ra9Jbk9l0vvyA9D9me5/dbxYM=
code.forgejo.org/forgejo/act v1.21.3 h1:EeJbrz0aar2QhIcBlOW5gjK1rjrQxcAvQSPpG/R1h5w=
code.forgejo.org/forgejo/act v1.21.3/go.mod h1:+PcvJ9iv+NTFeJSh79ra9Jbk9l0vvyA9D9me5/dbxYM=
code.forgejo.org/forgejo/archiver/v3 v3.5.1 h1:UmmbA7D5550uf71SQjarmrn6yKwOGxtEjb3jaYYtmSE=
code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=
@ -448,8 +448,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0=
github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
@ -502,8 +502,8 @@ github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.74 h1:fTo/XlPBTSpo3BAMshlwKL5RspXRv9us5UeHEGYCFe0=
github.com/minio/minio-go/v7 v7.0.74/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8=
github.com/minio/minio-go/v7 v7.0.77 h1:GaGghJRg9nwDVlNbwYjSDJT1rqltQkBFDsypWX1v3Bw=
github.com/minio/minio-go/v7 v7.0.77/go.mod h1:AVM3IUN6WwKzmwBxVdjzhH8xq+f57JSbbvzqvUzR6eg=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@ -599,8 +599,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
@ -845,10 +845,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View file

@ -34,6 +34,7 @@ type ActivityStats struct {
OpenedPRAuthorCount int64
MergedPRs issues_model.PullRequestList
MergedPRAuthorCount int64
ActiveIssues issues_model.IssueList
OpenedIssues issues_model.IssueList
OpenedIssueAuthorCount int64
ClosedIssues issues_model.IssueList
@ -172,7 +173,7 @@ func (stats *ActivityStats) MergedPRPerc() int {
// ActiveIssueCount returns total active issue count
func (stats *ActivityStats) ActiveIssueCount() int {
return stats.OpenedIssueCount() + stats.ClosedIssueCount()
return len(stats.ActiveIssues)
}
// OpenedIssueCount returns open issue count
@ -285,13 +286,21 @@ func (stats *ActivityStats) FillIssues(ctx context.Context, repoID int64, fromTi
stats.ClosedIssueAuthorCount = count
// New issues
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false)
sess = newlyCreatedIssues(ctx, repoID, fromTime)
sess.OrderBy("issue.created_unix ASC")
stats.OpenedIssues = make(issues_model.IssueList, 0)
if err = sess.Find(&stats.OpenedIssues); err != nil {
return err
}
// Active issues
sess = activeIssues(ctx, repoID, fromTime)
sess.OrderBy("issue.created_unix ASC")
stats.ActiveIssues = make(issues_model.IssueList, 0)
if err = sess.Find(&stats.ActiveIssues); err != nil {
return err
}
// Opened issue authors
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false)
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil {
@ -317,6 +326,23 @@ func (stats *ActivityStats) FillUnresolvedIssues(ctx context.Context, repoID int
return sess.Find(&stats.UnresolvedIssues)
}
func newlyCreatedIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
And("issue.is_pull = ?", false). // Retain the is_pull check to exclude pull requests
And("issue.created_unix >= ?", fromTime.Unix()) // Include all issues created after fromTime
return sess
}
func activeIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
And("issue.is_pull = ?", false).
And("issue.created_unix >= ?", fromTime.Unix()).
Or("issue.closed_unix >= ?", fromTime.Unix())
return sess
}
func issuesForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time, closed, unresolved bool) *xorm.Session {
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
And("issue.is_closed = ?", closed)

View file

@ -245,6 +245,7 @@ func (u *Type) CanBeDefault() bool {
// Unit is a section of one repository
type Unit struct {
Type Type
Name string
NameKey string
URI string
DescKey string
@ -272,6 +273,7 @@ func (u Unit) MaxPerm() perm.AccessMode {
var (
UnitCode = Unit{
TypeCode,
"code",
"repo.code",
"/",
"repo.code.desc",
@ -281,6 +283,7 @@ var (
UnitIssues = Unit{
TypeIssues,
"issues",
"repo.issues",
"/issues",
"repo.issues.desc",
@ -290,6 +293,7 @@ var (
UnitExternalTracker = Unit{
TypeExternalTracker,
"ext_issues",
"repo.ext_issues",
"/issues",
"repo.ext_issues.desc",
@ -299,6 +303,7 @@ var (
UnitPullRequests = Unit{
TypePullRequests,
"pulls",
"repo.pulls",
"/pulls",
"repo.pulls.desc",
@ -308,6 +313,7 @@ var (
UnitReleases = Unit{
TypeReleases,
"releases",
"repo.releases",
"/releases",
"repo.releases.desc",
@ -317,6 +323,7 @@ var (
UnitWiki = Unit{
TypeWiki,
"wiki",
"repo.wiki",
"/wiki",
"repo.wiki.desc",
@ -326,6 +333,7 @@ var (
UnitExternalWiki = Unit{
TypeExternalWiki,
"ext_wiki",
"repo.ext_wiki",
"/wiki",
"repo.ext_wiki.desc",
@ -335,6 +343,7 @@ var (
UnitProjects = Unit{
TypeProjects,
"projects",
"repo.projects",
"/projects",
"repo.projects.desc",
@ -344,6 +353,7 @@ var (
UnitPackages = Unit{
TypePackages,
"packages",
"repo.packages",
"/packages",
"packages.desc",
@ -353,6 +363,7 @@ var (
UnitActions = Unit{
TypeActions,
"actions",
"repo.actions",
"/actions",
"actions.unit.desc",

View file

@ -76,7 +76,8 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s
w.Header().Set("Etag", etag)
}
if lastModified != nil && !lastModified.IsZero() {
w.Header().Set("Last-Modified", lastModified.Format(http.TimeFormat))
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
w.Header().Set("Last-Modified", lastModified.UTC().Format(http.TimeFormat))
}
if len(etag) > 0 {

View file

@ -79,6 +79,7 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
httpcache.SetCacheControlInHeader(header, duration)
if !opts.LastModified.IsZero() {
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
}
}

View file

@ -38,7 +38,7 @@ func camoHandleLink(link string) string {
if setting.Camo.Enabled {
lnkURL, err := url.Parse(link)
if err == nil && lnkURL.IsAbs() && !strings.HasPrefix(link, setting.AppURL) &&
(setting.Camo.Allways || lnkURL.Scheme != "https") {
(setting.Camo.Always || lnkURL.Scheme != "https") {
return CamoEncode(link)
}
}

View file

@ -28,7 +28,7 @@ func TestCamoHandleLink(t *testing.T) {
"https://image.proxy/eivin43gJwGVIjR9MiYYtFIk0mw/aHR0cDovL3Rlc3RpbWFnZXMub3JnL2ltZy5qcGc",
camoHandleLink("http://testimages.org/img.jpg"))
setting.Camo.Allways = true
setting.Camo.Always = true
assert.Equal(t,
"https://gitea.com/img.jpg",
camoHandleLink("https://gitea.com/img.jpg"))

View file

@ -73,6 +73,8 @@ var (
// EmojiShortCodeRegex find emoji by alias like :smile:
EmojiShortCodeRegex = regexp.MustCompile(`:[-+\w]+:`)
InlineCodeBlockRegex = regexp.MustCompile("`[^`]+`")
)
// CSS class for action keywords (e.g. "closes: #1")
@ -243,6 +245,7 @@ func RenderIssueTitle(
title string,
) (string, error) {
return renderProcessString(ctx, []processor{
inlineCodeBlockProcessor,
issueIndexPatternProcessor,
commitCrossReferencePatternProcessor,
hashCurrentPatternProcessor,
@ -251,6 +254,19 @@ func RenderIssueTitle(
}, title)
}
// RenderRefIssueTitle to process title on places where an issue is referenced
func RenderRefIssueTitle(
ctx *RenderContext,
title string,
) (string, error) {
return renderProcessString(ctx, []processor{
inlineCodeBlockProcessor,
issueIndexPatternProcessor,
emojiShortCodeProcessor,
emojiProcessor,
}, title)
}
func renderProcessString(ctx *RenderContext, procs []processor, content string) (string, error) {
var buf strings.Builder
if err := postProcess(ctx, procs, strings.NewReader(content), &buf); err != nil {
@ -438,6 +454,24 @@ func createKeyword(content string) *html.Node {
return span
}
func createInlineCode(content string) *html.Node {
code := &html.Node{
Type: html.ElementNode,
Data: atom.Code.String(),
Attr: []html.Attribute{},
}
code.Attr = append(code.Attr, html.Attribute{Key: "class", Val: "inline-code-block"})
text := &html.Node{
Type: html.TextNode,
Data: content,
}
code.AppendChild(text)
return code
}
func createEmoji(content, class, name string) *html.Node {
span := &html.Node{
Type: html.ElementNode,
@ -1070,6 +1104,21 @@ func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
}
}
func inlineCodeBlockProcessor(ctx *RenderContext, node *html.Node) {
start := 0
next := node.NextSibling
for node != nil && node != next && start < len(node.Data) {
m := InlineCodeBlockRegex.FindStringSubmatchIndex(node.Data[start:])
if m == nil {
return
}
code := node.Data[m[0]+1 : m[1]-1]
replaceContent(node, m[0], m[1], createInlineCode(code))
node = node.NextSibling.NextSibling
}
}
// emojiShortCodeProcessor for rendering text like :smile: into emoji
func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) {
start := 0

View file

@ -39,8 +39,8 @@ const (
var (
reName = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+$`)
reVer = regexp.MustCompile(`^[a-zA-Z0-9:_.+]+-+[0-9]+$`)
reOptDep = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?[a-zA-Z0-9@._+-]+)?(:.*)?$`)
rePkgVer = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?[a-zA-Z0-9@._+-]+)?$`)
reOptDep = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?([0-9]+:)?[a-zA-Z0-9@._+-]+)?(:.*)?$`)
rePkgVer = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?([0-9]+:)?[a-zA-Z0-9@._+-]+)?$`)
magicZSTD = []byte{0x28, 0xB5, 0x2F, 0xFD}
magicXZ = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A}
@ -71,7 +71,7 @@ type VersionMetadata struct {
Conflicts []string `json:"conflicts,omitempty"`
Replaces []string `json:"replaces,omitempty"`
Backup []string `json:"backup,omitempty"`
Xdata []string `json:"xdata,omitempty"`
XData []string `json:"xdata,omitempty"`
}
// FileMetadata Metadata related to specific package file.
@ -125,7 +125,7 @@ func ParsePackage(r *packages.HashedBuffer) (*Package, error) {
defer tarball.Close()
var pkg *Package
var mtree bool
var mTree bool
for {
f, err := tarball.Read()
@ -135,24 +135,24 @@ func ParsePackage(r *packages.HashedBuffer) (*Package, error) {
if err != nil {
return nil, err
}
defer f.Close()
switch f.Name() {
case ".PKGINFO":
pkg, err = ParsePackageInfo(tarballType, f)
if err != nil {
_ = f.Close()
return nil, err
}
case ".MTREE":
mtree = true
mTree = true
}
_ = f.Close()
}
if pkg == nil {
return nil, util.NewInvalidArgumentErrorf(".PKGINFO file not found")
}
if !mtree {
if !mTree {
return nil, util.NewInvalidArgumentErrorf(".MTREE file not found")
}
@ -220,7 +220,7 @@ func ParsePackageInfo(compressType string, r io.Reader) (*Package, error) {
case "replaces":
p.VersionMetadata.Replaces = append(p.VersionMetadata.Replaces, value)
case "xdata":
p.VersionMetadata.Xdata = append(p.VersionMetadata.Xdata, value)
p.VersionMetadata.XData = append(p.VersionMetadata.XData, value)
case "builddate":
bd, err := strconv.ParseInt(value, 10, 64)
if err != nil {
@ -260,48 +260,43 @@ func ValidatePackageSpec(p *Package) error {
return util.NewInvalidArgumentErrorf("invalid project URL")
}
}
for _, cd := range p.VersionMetadata.CheckDepends {
if !rePkgVer.MatchString(cd) {
return util.NewInvalidArgumentErrorf("invalid check dependency: %s", cd)
for _, checkDepend := range p.VersionMetadata.CheckDepends {
if !rePkgVer.MatchString(checkDepend) {
return util.NewInvalidArgumentErrorf("invalid check dependency: %s", checkDepend)
}
}
for _, d := range p.VersionMetadata.Depends {
if !rePkgVer.MatchString(d) {
return util.NewInvalidArgumentErrorf("invalid dependency: %s", d)
for _, depend := range p.VersionMetadata.Depends {
if !rePkgVer.MatchString(depend) {
return util.NewInvalidArgumentErrorf("invalid dependency: %s", depend)
}
}
for _, md := range p.VersionMetadata.MakeDepends {
if !rePkgVer.MatchString(md) {
return util.NewInvalidArgumentErrorf("invalid make dependency: %s", md)
for _, makeDepend := range p.VersionMetadata.MakeDepends {
if !rePkgVer.MatchString(makeDepend) {
return util.NewInvalidArgumentErrorf("invalid make dependency: %s", makeDepend)
}
}
for _, p := range p.VersionMetadata.Provides {
if !rePkgVer.MatchString(p) {
return util.NewInvalidArgumentErrorf("invalid provides: %s", p)
for _, provide := range p.VersionMetadata.Provides {
if !rePkgVer.MatchString(provide) {
return util.NewInvalidArgumentErrorf("invalid provides: %s", provide)
}
}
for _, p := range p.VersionMetadata.Conflicts {
if !rePkgVer.MatchString(p) {
return util.NewInvalidArgumentErrorf("invalid conflicts: %s", p)
for _, conflict := range p.VersionMetadata.Conflicts {
if !rePkgVer.MatchString(conflict) {
return util.NewInvalidArgumentErrorf("invalid conflicts: %s", conflict)
}
}
for _, p := range p.VersionMetadata.Replaces {
if !rePkgVer.MatchString(p) {
return util.NewInvalidArgumentErrorf("invalid replaces: %s", p)
for _, replace := range p.VersionMetadata.Replaces {
if !rePkgVer.MatchString(replace) {
return util.NewInvalidArgumentErrorf("invalid replaces: %s", replace)
}
}
for _, p := range p.VersionMetadata.Replaces {
if !rePkgVer.MatchString(p) {
return util.NewInvalidArgumentErrorf("invalid xdata: %s", p)
for _, optDepend := range p.VersionMetadata.OptDepends {
if !reOptDep.MatchString(optDepend) {
return util.NewInvalidArgumentErrorf("invalid optional dependency: %s", optDepend)
}
}
for _, od := range p.VersionMetadata.OptDepends {
if !reOptDep.MatchString(od) {
return util.NewInvalidArgumentErrorf("invalid optional dependency: %s", od)
}
}
for _, bf := range p.VersionMetadata.Backup {
if strings.HasPrefix(bf, "/") {
for _, b := range p.VersionMetadata.Backup {
if strings.HasPrefix(b, "/") {
return util.NewInvalidArgumentErrorf("backup file contains leading forward slash")
}
}

View file

@ -48,6 +48,7 @@ type Metadata struct {
Homepage string `json:"homepage,omitempty"`
License Licenses `json:"license,omitempty"`
Authors []Author `json:"authors,omitempty"`
Bin []string `json:"bin,omitempty"`
Autoload map[string]any `json:"autoload,omitempty"`
AutoloadDev map[string]any `json:"autoload-dev,omitempty"`
Extra map[string]any `json:"extra,omitempty"`

View file

@ -3,18 +3,28 @@
package setting
import "code.gitea.io/gitea/modules/log"
import (
"strconv"
"code.gitea.io/gitea/modules/log"
)
var Camo = struct {
Enabled bool
ServerURL string `ini:"SERVER_URL"`
HMACKey string `ini:"HMAC_KEY"`
Allways bool
Always bool
}{}
func loadCamoFrom(rootCfg ConfigProvider) {
mustMapSetting(rootCfg, "camo", &Camo)
if Camo.Enabled {
oldValue := rootCfg.Section("camo").Key("ALLWAYS").MustString("")
if oldValue != "" {
log.Warn("camo.ALLWAYS is deprecated, use camo.ALWAYS instead")
Camo.Always, _ = strconv.ParseBool(oldValue)
}
if Camo.ServerURL == "" || Camo.HMACKey == "" {
log.Fatal(`Camo settings require "SERVER_URL" and HMAC_KEY`)
}

View file

@ -172,11 +172,12 @@ func NewFuncMap() template.FuncMap {
"RenderCommitMessage": RenderCommitMessage,
"RenderCommitMessageLinkSubject": RenderCommitMessageLinkSubject,
"RenderCommitBody": RenderCommitBody,
"RenderCodeBlock": RenderCodeBlock,
"RenderIssueTitle": RenderIssueTitle,
"RenderEmoji": RenderEmoji,
"ReactionToEmoji": ReactionToEmoji,
"RenderCommitBody": RenderCommitBody,
"RenderCodeBlock": RenderCodeBlock,
"RenderIssueTitle": RenderIssueTitle,
"RenderRefIssueTitle": RenderRefIssueTitle,
"RenderEmoji": RenderEmoji,
"ReactionToEmoji": ReactionToEmoji,
"RenderMarkdownToHtml": RenderMarkdownToHtml,
"RenderLabel": RenderLabel,

View file

@ -34,7 +34,7 @@ func AvatarHTML(src string, size int, class, name string) template.HTML {
name = "avatar"
}
return template.HTML(`<img class="` + class + `" src="` + src + `" title="` + html.EscapeString(name) + `" width="` + sizeStr + `" height="` + sizeStr + `"/>`)
return template.HTML(`<img loading="lazy" class="` + class + `" src="` + src + `" title="` + html.EscapeString(name) + `" width="` + sizeStr + `" height="` + sizeStr + `"/>`)
}
// Avatar renders user avatars. args: user, size (int), class (string)

View file

@ -130,6 +130,17 @@ func RenderIssueTitle(ctx context.Context, text string, metas map[string]string)
return template.HTML(renderedText)
}
// RenderRefIssueTitle renders referenced issue/pull title with defined post processors
func RenderRefIssueTitle(ctx context.Context, text string) template.HTML {
renderedText, err := markup.RenderRefIssueTitle(&markup.RenderContext{Ctx: ctx}, template.HTMLEscapeString(text))
if err != nil {
log.Error("RenderRefIssueTitle: %v", err)
return ""
}
return template.HTML(renderedText)
}
// RenderLabel renders a label
// locale is needed due to an import cycle with our context providing the `Tr` function
func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML {

View file

@ -35,8 +35,8 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
mail@domain.com
@mention-user test
#123
space
`
space
` + "`code :+1: #123 code`\n"
var testMetas = map[string]string{
"user": "user13",
@ -115,8 +115,8 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
<a href="mailto:mail@domain.com" class="mailto">mail@domain.com</a>
<a href="/mention-user" class="mention">@mention-user</a> test
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
space`
space
` + "`code <span class=\"emoji\" aria-label=\"thumbs up\">👍</span> <a href=\"/user13/repo11/issues/123\" class=\"ref-issue\">#123</a> code`"
assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas))
}
@ -152,11 +152,38 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
mail@domain.com
@mention-user test
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
space
space
<code class="inline-code-block">code :+1: #123 code</code>
`
assert.EqualValues(t, expected, RenderIssueTitle(context.Background(), testInput, testMetas))
}
func TestRenderRefIssueTitle(t *testing.T) {
expected := ` space @mention-user
/just/a/path.bin
https://example.com/file.bin
[local link](file.bin)
[remote link](https://example.com)
[[local link|file.bin]]
[[remote link|https://example.com]]
![local image](image.jpg)
![remote image](https://example.com/image.jpg)
[[local image|image.jpg]]
[[remote link|https://example.com/image.jpg]]
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
<span class="emoji" aria-label="thumbs up">👍</span>
mail@domain.com
@mention-user test
#123
space
<code class="inline-code-block">code :+1: #123 code</code>
`
assert.EqualValues(t, expected, RenderRefIssueTitle(context.Background(), testInput))
}
func TestRenderMarkdownToHtml(t *testing.T) {
expected := `<p>space <a href="/mention-user" rel="nofollow">@mention-user</a><br/>
/just/a/path.bin
@ -177,7 +204,8 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a>
<a href="/mention-user" rel="nofollow">@mention-user</a> test
#123
space</p>
space
<code>code :+1: #123 code</code></p>
`
assert.EqualValues(t, expected, RenderMarkdownToHtml(context.Background(), testInput))
}

View file

@ -225,6 +225,15 @@ func Iif[T any](condition bool, trueVal, falseVal T) T {
return falseVal
}
// IfZero returns "def" if "v" is a zero value, otherwise "v"
func IfZero[T comparable](v, def T) T {
var zero T
if v == zero {
return def
}
return v
}
func ReserveLineBreakForTextarea(input string) string {
// Since the content is from a form which is a textarea, the line endings are \r\n.
// It's a standard behavior of HTML.

View file

@ -168,6 +168,7 @@ versions.view_all = Вижте всички
dependencies = Зависимости
published_by_in = Публикуван %[1]s от <a href="%[2]s">%[3]s</a> в <a href="%[4]s"><strong>%[5]s</strong></a>
published_by = Публикуван %[1]s от <a href="%[2]s">%[3]s</a>
generic.download = Изтеглете пакета от командния ред:
[tool]
hours = %d часа
@ -1020,7 +1021,7 @@ pulls.title_desc_one = иска да слее %[1]d подаване от <code>
pulls.showing_specified_commit_range = Показани са само промените между %[1]s..%[2]s
pulls.merged_title_desc_one = сля %[1]d подаване от <code>%[2]s</code> в <code>%[3]s</code> %[4]s
pulls.no_merge_access = Не сте упълномощени за сливане на тази заявка за сливане.
activity.navbar.code_frequency = Честота на кода
activity.navbar.code_frequency = Честота на промените
activity.git_stats_pushed_1 = е изтласкал
activity.git_stats_push_to_branch = към %s и
contributors.contribution_type.commits = Подавания
@ -1183,6 +1184,15 @@ diff.hide_file_tree = Скриване на файловото дърво
tag.ahead.target = в %s след този маркер
diff.file_image_width = Широчина
activity.unresolved_conv_label = Отворено
invisible_runes_line = `Този ред съдържа невидими Уникод знаци`
code.desc = Достъп до програмния код, файловете, подаванията и клоновете.
settings.branches.update_default_branch = Обновяване на стандартния клон
settings.default_branch_desc = Изберете стандартен клон за хранилището, за заявки за сливане и подавания на код:
settings.transfer.button = Прехвърляне на притежанието
settings.transfer.modal.title = Прехвърляне на притежанието
ambiguous_runes_line = `Този ред съдържа двусмислени Уникод знаци`
ambiguous_character = `%[1]c [U+%04[1]X] може да бъде объркан с %[2]c [U+%04[2]X]`
invisible_runes_header = `Този файл съдържа невидими Уникод знаци`
[modal]
confirm = Потвърждаване
@ -1279,6 +1289,7 @@ members.member = Участник
members.private_helper = Да е видим
teams.no_desc = Този екип няма описание
settings.delete_org_desc = Тази организация ще бъде изтрита перманентно. Продължаване?
open_dashboard = Отваряне на таблото
[install]
admin_password = Парола
@ -1378,6 +1389,7 @@ followers.title.few = Последователи
followers.title.one = Последовател
following.title.one = Следван
following.title.few = Следвани
public_activity.visibility_hint.self_public = Вашата дейност е видима за всички, с изключение на взаимодействията в частни пространства. <a href="%s">Конфигуриране</a>.
[home]
filter = Други филтри
@ -1544,6 +1556,8 @@ push_tag = изтласка маркер <a href="%[2]s">%[3]s</a> към <a hre
approve_pull_request = `одобри <a href="%[1]s">%[3]s#%[2]s</a>`
reject_pull_request = `предложи промени за <a href="%[1]s">%[3]s#%[2]s</a>`
compare_branch = Сравняване
compare_commits_general = Сравняване на подавания
compare_commits = Сравнете %d подавания
[auth]
tab_openid = OpenID
@ -1572,6 +1586,12 @@ tab_signin = Влизане
tab_signup = Регистриране
password_pwned = Паролата, която сте избрали, е в <a target="_blank" rel="noopener noreferrer" href="%s">списък с откраднати пароли</a>, разкрити преди това при публични пробиви на данни. Моля, опитайте отново с различна парола.
confirmation_mail_sent_prompt = Ново ел. писмо за потвърждение е изпратено до <b>%s</b>. За да завършите процеса на регистрация, моля, проверете входящата си кутия и последвайте предоставената връзка в рамките на следващите %s. Ако адресът за ел. поща е неправилен, можете да влезете и да поискате друго ел. писмо за потвърждение да бъде изпратено на различен адрес.
hint_login = Вече имате акаунт? <a href="%s">Влезте!</a>
hint_register = Нуждаете се от акаунт? <a href="%s">Регистрирайте се.</a>
sign_up_button = Регистрирайте се.
back_to_sign_in = Назад към Вход
sign_in_openid = Продължаване с OpenID
send_reset_mail = Изпращане на ел. писмо за възстановяване
[aria]
footer.software = Относно този софтуер
@ -1582,7 +1602,7 @@ footer = Долен колонтитул
install = Лесен за инсталиране
lightweight = Лек
license = Отворен код
install_desc = Просто <a target="_blank" rel="noopener noreferrer" href="%[1]s">стартирайте двоичния файл</a> за вашата платформа, използвайте <a target="_blank" rel="noopener noreferrer" href="%[2]s">Docker</a>, или го получете <a target="_blank" rel="noopener noreferrer" href="%[3]s">пакетирано</a>.
install_desc = Просто <a target="_blank" rel="noopener noreferrer" href="%[1]s">стартирайте двоичния файл</a> за вашата платформа, използвайте <a target="_blank" rel="noopener noreferrer" href="%[2]s">Docker</a>, или го получете <a target="_blank" rel="noopener noreferrer" href="%[3]s">пакетиран</a>.
app_desc = Безпроблемна Git услуга със самостоятелен хостинг
platform = Междуплатформен
lightweight_desc = Forgejo има ниски минимални изисквания и може да работи на икономичен Raspberry Pi. Спестете енергията на вашата машина!
@ -1670,6 +1690,7 @@ contributors.what = приноси
recent_commits.what = скорошни подавания
component_loading = Зареждане на %s...
component_loading_info = Това може да отнеме известно време…
code_frequency.what = честота на промените
[projects]
type-1.display_name = Индивидуален проект

View file

@ -842,8 +842,8 @@ add_key=Přidat klíč
ssh_desc=Tyto veřejné klíče SSH jsou propojeny s vaším účtem. Odpovídající soukromé klíče umožní plný přístup k vašim repozitářům. Klíče SSH, které byly ověřeny, mohou být použity pro ověření Git commitů podepsaných přes SSH.
principal_desc=Tyto SSH Principal certifikáty jsou přidruženy k vašemu účtu a umožňují plný přístup do vašich repozitářů.
gpg_desc=Tyto veřejné klíče GPG jsou propojeny s vaším účtem a používají se k ověření vašich commitů. Uložte je na bezpečné místo, jelikož umožňují podepsat commity vaší identitou.
ssh_helper=<strong>Potřebujete pomoct?</strong> Podívejte se do příručky GitHubu na to <a href="%s">vytvoření vlastních klíčů SSH</a> nebo vyřešte <a href="%s">běžné problémy</a>, se kterými se můžete potkat při použití SSH.
gpg_helper=<strong>Potřebujete pomoct?</strong> Podívejte se do příručky GitHubu <a href="%s">o GPG</a>.
ssh_helper=<strong>Potřebujete pomoct?</strong> Podívejte se do příručky, jak <a href="%s">vytvořit vlastní klíče SSH</a> nebo vyřešte <a href="%s">běžné problémy</a>, se kterými se můžete potkat při použití SSH.
gpg_helper=<strong>Potřebujete pomoct?</strong> Podívejte se do příručky <a href="%s">o GPG</a>.
add_new_key=Přidat klíč SSH
add_new_gpg_key=Přidat klíč GPG
key_content_ssh_placeholder=Začíná s „ssh-ed25519“, „ssh-rsa“, „ecdsa-sha2-nistp256“, „ecdsa-sha2-nistp384“, „ecdsa-sha2-nistp521“, „sk-ecdsa-sha2-nistp256@openssh.com“ nebo „sk-ssh-ed25519@openssh.com“
@ -1427,7 +1427,7 @@ commitstatus.failure=Chyba
commitstatus.pending=Čekající
commitstatus.success=Úspěch
ext_issues=Přístup k externím problémům
ext_issues=Externí problémy
ext_issues.desc=Odkaz na externí systém problémů.
projects=Projekty
@ -1608,9 +1608,9 @@ issues.no_content=K dispozici není žádný popis.
issues.close=Zavřít problém
issues.comment_pull_merged_at=sloučený commit %[1]s do %[2]s %[3]s
issues.comment_manually_pull_merged_at=ručně sloučený commit %[1]s do %[2]s %[3]s
issues.close_comment_issue=Okomentovat a zavřít
issues.close_comment_issue=Zavřít s komentářem
issues.reopen_issue=Znovu otevřít
issues.reopen_comment_issue=Okomentovat a znovu otevřít
issues.reopen_comment_issue=Znovu otevřít s komentářem
issues.create_comment=Okomentovat
issues.closed_at=`uzavřel/a tento problém <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at=`znovu otevřel/a tento problém <a id="%[1]s" href="#%[1]s">%[2]s</a>`
@ -1995,7 +1995,7 @@ signing.wont_sign.commitssigned=Sloučení nebude podepsáno, protože všechny
signing.wont_sign.approved=Sloučení nebude podepsáno, protože požadavek na natažení není schválen.
signing.wont_sign.not_signed_in=Nejste přihlášeni.
ext_wiki=Přístup k externí Wiki
ext_wiki=Externí wiki
ext_wiki.desc=Odkaz do externí Wiki.
wiki=Wiki
@ -2436,7 +2436,7 @@ settings.protect_branch_name_pattern=Vzor jména chráněné větve
settings.protect_branch_name_pattern_desc=Vzory názvů chráněných větví. Pro vzorovou syntaxi viz <a href="%s">dokumentace</a>. Příklady: main, release/**
settings.protect_patterns=Vzory
settings.protect_protected_file_patterns=Vzory chráněných souborů (oddělené středníkem „;“)
settings.protect_protected_file_patterns_desc=Chráněné soubory, které nemají povoleno být měněny přímo, i když uživatel má právo přidávat, upravovat nebo mazat soubory v této větvi. Více vzorů lze oddělit pomocí středníku („;“). Podívejte se na <a href="%s">github.com/gobwas/glob</a> dokumentaci pro syntaxi vzoru. Příklady: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
settings.protect_protected_file_patterns_desc=Chráněné soubory, které nemají povoleno být měněny přímo, i když uživatel má právo přidávat, upravovat nebo mazat soubory v této větvi. Více vzorů lze oddělit pomocí středníku („;“). Podívejte se na dokumentaci <a href="%[1]s">%[2]s</a> pro syntaxi vzoru. Příklady: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
settings.protect_unprotected_file_patterns=Vzory nechráněných souborů (oddělené středníkem „;“)
settings.protect_unprotected_file_patterns_desc=Nechráněné soubory, které je možné měnit přímo, pokud má uživatel právo zápisu, čímž se obejde omezení push. Více vzorů lze oddělit pomocí středníku („;“). Podívejte se na <a href="%[1]s">%[2]s</a> dokumentaci pro syntaxi vzoru. Příklady: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
settings.add_protected_branch=Zapnout ochranu
@ -3822,7 +3822,7 @@ management=Správa tajných klíčů
[actions]
actions=Akce
unit.desc=Spravovat integrované pipeliny CI/CD pomocí funkce Forgejo Actions
unit.desc=Spravovat integrované pipeliny CI/CD pomocí funkce Forgejo Actions.
status.unknown=Neznámý
status.waiting=Čekání
@ -3987,4 +3987,24 @@ eib = EiB
[translation_meta]
test = diky vsem za pomoc :)
test = diky vsem za pomoc :)
[repo.permissions]
pulls.write = <b>Zapisovat:</b> Zavírat žádosti o sloučení a spravovat metadata jako štítky, milníky, zpracovatele, data dokončení a závislosti.
packages.write = <b>Zapisovat:</b> Zveřejňovat a mazat balíčky připojené k repozitáři.
projects.read = <b>Číst:</b> Přístup k projektovým nástěnkám repozitáře.
code.write = <b>Zapisovat:</b> Odesílat změny do repozitáře, vytvářet větve a značky.
issues.write = <b>Zapisovat:</b> Zavírat problémy a spravovat metadata jako štítky, milníky, zpracovatele, data dokončení a závislosti.
pulls.read = <b>Číst:</b> Číst a vytvářet žádosti o sloučení.
releases.read = <b>Číst:</b> Zobrazovat a stahovat vydání.
releases.write = <b>Zapisovat:</b> Zveřejňovat, upravovat a mazat vydání a jejich soubory.
wiki.read = <b>Číst:</b> Číst integrovanou wiki a její historii.
wiki.write = <b>Zapisovat:</b> Vytvářet, aktualizovat a mazat stránky v integrované wiki.
projects.write = <b>Zapisovat:</b> Vytvářet projekty a sloupce a upravovat je.
packages.read = <b>Číst:</b> Zobrazovat a stahovat balíčky připojené k repozitáři.
actions.read = <b>Číst:</b> Zobrazovat integrované pipeliny CI/CD a jejich protokoly.
actions.write = <b>Zapisovat:</b> Ručně spouštět, restartovat, rušit nebo schvalovat čekající pipeliny CI/CD.
ext_wiki = Přístup k odkazu v externí wiki. Oprávnění jsou spravována externě.
code.read = <b>Číst:</b> Přístup a klonování kódu v repozitáři.
issues.read = <b>Číst:</b> Číst a vytvářet problémy a komentáře.
ext_issues = Přístup k odkazu v externím sledovacím systému problémů. Oprávnění jsou spravována externě.

View file

@ -1420,7 +1420,7 @@ commitstatus.failure=Fehler
commitstatus.pending=Ausstehend
commitstatus.success=Erfolg
ext_issues=Zugriff auf Externe Issues
ext_issues=Externe Issues
ext_issues.desc=Link zu externem Issuetracker.
projects=Projekte
@ -1601,9 +1601,9 @@ issues.no_content=Keine Beschreibung angegeben.
issues.close=Issue schließen
issues.comment_pull_merged_at=hat Commit %[1]s in %[2]s %[3]s zusammengeführt
issues.comment_manually_pull_merged_at=hat Commit %[1]s in %[2]s %[3]s manuell zusammengeführt
issues.close_comment_issue=Kommentieren und schließen
issues.close_comment_issue=Mit Kommentar schließen
issues.reopen_issue=Wieder öffnen
issues.reopen_comment_issue=Kommentieren und wieder öffnen
issues.reopen_comment_issue=Mit Kommentar wieder öffnen
issues.create_comment=Kommentieren
issues.closed_at=`hat diesen Issue <a id="%[1]s" href="#%[1]s">%[2]s</a> geschlossen`
issues.reopened_at=`hat dieses Issue <a id="%[1]s" href="#%[1]s">%[2]s</a> wieder geöffnet`
@ -1984,7 +1984,7 @@ signing.wont_sign.commitssigned=Der Merge-Commit wird nicht signiert werden, da
signing.wont_sign.approved=Der Merge-Commit wird nicht signiert werden, da der Pull-Request nicht genehmigt wurde.
signing.wont_sign.not_signed_in=Du bist nicht eingeloggt.
ext_wiki=Zugriff auf externes Wiki
ext_wiki=Externes Wiki
ext_wiki.desc=Verweis auf externes Wiki.
wiki=Wiki
@ -3792,7 +3792,7 @@ management=Secrets verwalten
[actions]
actions=Actions
unit.desc=Integrierte CI/CD-Pipelines mit Forgejo-Actions verwalten
unit.desc=Integrierte CI/CD-Pipelines mit Forgejo-Actions verwalten.
status.unknown=Unbekannt
status.waiting=Wartend
@ -3964,4 +3964,24 @@ eib = EiB
[translation_meta]
test = ok
test = ok
[repo.permissions]
code.write = <b>Schreiben:</b> In das Repository pushen, Branches und Tags erstellen.
code.read = <b>Lesen:</b> Zugriff auf das Repository und Klonen.
issues.read = <b>Lesen:</b> Issues und Kommentare lesen und erstellen.
issues.write = <b>Schreiben:</b> Issues schließen und Metadaten wie Labels, Meilensteine, Zuweisungen, Fälligkeitsdaten und Abhängigkeiten verwalten.
pulls.read = <b>Lesen:</b> Pull-Requests lesen und erstellen.
releases.read = <b>Lesen:</b> Releases ansehen und herunterladen.
releases.write = <b>Schreiben:</b> Releases und ihre Assets veröffentlichen, bearbeiten und löschen.
wiki.read = <b>Read:</b> Das integrierte Wiki und seine Historie lesen.
wiki.write = <b>Schreiben:</b> Seiten im integrierten Wiki erstellen, aktualisieren und löschen.
projects.read = <b>Lesen:</b> Zugriff auf Projektboards des Repositories.
projects.write = <b>Schreiben:</b> Projekte und Spalten erstellen und bearbeiten.
packages.read = <b>Lesen:</b> Pakete dieses Repositories betrachten und herunterladen.
packages.write = <b>Schreiben:</b> Pakete dieses Repositories veröffentlichen und löschen.
actions.read = <b>Lesen:</b> Integrierte CI/CD-Pipelines und ihre Logs betrachten.
actions.write = <b>Schreiben:</b> Ausstehende CI/CD-Pipelines manuell auslösen, neustarten, abbrechen oder genehmigen.
ext_issues = Zugriff auf den Link zu einem externen Issue-Tracker. Die Berechtigungen werden extern verwaltet.
ext_wiki = Zugriff auf den Link zu einem externen Wiki. Die Berechtigungen werden extern verwaltet.
pulls.write = <b>Schreiben:</b> Pull-Requests schließen und Metadaten wie Labels, Meilensteine, Zuweisungen, Fälligkeitsdaten und Abhängigkeiten verwalten.

View file

@ -51,7 +51,7 @@ webauthn_error_empty=Πρέπει να ορίσετε ένα όνομα για
webauthn_error_timeout=Το χρονικό όριο έφτασε πριν το κλειδί να διαβαστεί. Παρακαλώ ανανεώστε τη σελίδα και προσπαθήστε ξανά.
webauthn_reload=Ανανέωση
repository=Αποθετήριο
repository=Repository
organization=Οργανισμός
mirror=Αντίγραφο
new_repo=Νέο αποθετήριο
@ -129,7 +129,7 @@ archived=Αρχειοθετήθηκε
concept_system_global=Γενικό
concept_user_individual=Ατομικό
concept_code_repository=Αποθετήριο
concept_code_repository=Repository
concept_user_organization=Οργανισμός
show_timestamps=Εμφάνιση χρονοσημάνσεων
@ -160,11 +160,11 @@ invalid_data = Τα δεδομένα δεν είναι έγκυρα: %v
test = Τεστ
copy_generic = Αντιγραφή στο πρόχειρο
error413 = Έχετε εξαντλήσει τους διαθέσιμους πόρους σας.
new_repo.link = Νέο αποθετήριο
new_repo.link = Νέο repository
new_migrate.link = Νέα μεταφορά
new_org.link = Νέος οργανισμός
new_migrate.title = Νέα μεταφορά
new_repo.title = Νέο αποθετήριο
new_repo.title = Νέο repository
new_org.title = Νέος οργανισμός
[aria]
@ -504,8 +504,8 @@ reset_password.text=Εφόσον το αίτημα δημιουργήθηκε α
register_success=Η εγγραφή ολοκληρώθηκε επιτυχώς
issue_assigned.pull=Ο/Η @%[1]s σας έχει αναθέσει στο pull request %[2]s στο αποθετήριο %[3]s.
issue_assigned.issue=Ο/Η @%[1]s σας ανέθεσε το ζήτημα %[2]s στο αποθετήριο %[3]s.
issue_assigned.pull=Ο/Η @%[1]s σας έχει αναθέσει στο pull request %[2]s στο repository %[3]s.
issue_assigned.issue=Ο/Η @%[1]s σας ανέθεσε το ζήτημα %[2]s στο repository %[3]s.
issue.x_mentioned_you=Ο/Η <b>@%s</b> σας ανέφερε:
issue.action.force_push=Ο/Η <b>%[1]s</b> έκανε force-push το <b>%[2]s</b> από %[3]s σε %[4]s.
@ -530,13 +530,13 @@ release.downloads=Λήψεις:
release.download.zip=Πηγαίος Κώδικας (ZIP)
release.download.targz=Πηγαίος Κώδικας (TAR.GZ)
repo.transfer.subject_to=Ο/Η %s θα ήθελε να μεταφέρει το αποθετήριο «%s» σε %s
repo.transfer.subject_to_you=Ο/Η %s θα ήθελε να σας μεταφέρει το αποθετήριο «%s»
repo.transfer.subject_to=Ο/Η %s θα ήθελε να μεταφέρει το repository «%s» στο %s
repo.transfer.subject_to_you=Ο/Η %s θα ήθελε να σας μεταφέρει το repository «%s»
repo.transfer.to_you=εσάς
repo.transfer.body=Για να αποδεχτείτε ή να απορρίψετε το αίτημα αυτό, επισκεφθείτε το %s ή απλά αγνοήστε το.
repo.collaborator.added.subject=Ο/Η %s σας πρόσθεσε στο %s ως συνεργάτη
repo.collaborator.added.text=Είστε πλέον συνεργάτης στο αποθετήριο:
repo.collaborator.added.text=Είστε πλέον συνεργάτης στο repository:
team_invite.subject=Ο/Η %[1]s σας προσκάλεσε να συμμετέχετε στον οργανισμό %[2]s
team_invite.text_1=Ο/Η %[1]s σας προσκάλεσε να συμμετέχετε στην ομάδα %[2]s του οργανισμού %[3]s.
@ -615,10 +615,10 @@ username_change_not_local_user=Δεν επιτρέπεται στους μη τ
username_has_not_been_changed=Το όνομα χρήστη δεν άλλαξε
repo_name_been_taken=Το όνομα του αποθετηρίου χρησιμοποιείται ήδη.
repository_force_private=Η επιλογή Μόνο Ιδιωτικά είναι ενεργοποιημένη: τα ιδιωτικά αποθετήρια δεν μπορούν να δημοσιευθούν.
repository_files_already_exist=Αρχεία υπάρχουν ήδη για αυτό το αποθετήριο. Επικοινωνήστε με το διαχειριστή του συστήματος.
repository_files_already_exist.adopt=Αρχεία υπάρχουν ήδη για αυτό το αποθετήριο και μπορούν να Υιοθετηθούν μόνο.
repository_files_already_exist.delete=Τα αρχεία υπάρχουν ήδη για αυτόν το αποθετήριο. Πρέπει να τα διαγράψετε.
repository_files_already_exist.adopt_or_delete=Τα αρχεία υπάρχουν ήδη για αυτόν το αποθετήριο. Είτε υιοθετήστε τα είτε διαγράψτε τα.
repository_files_already_exist=Αρχεία υπάρχουν ήδη για αυτό το repository. Επικοινωνήστε με το διαχειριστή του συστήματος.
repository_files_already_exist.adopt=Αρχεία υπάρχουν ήδη για αυτό το repository και μπορούν μόνο να υιοθετηθούν.
repository_files_already_exist.delete=Τα αρχεία υπάρχουν ήδη για αυτόν το repository. Πρέπει να τα διαγράψετε.
repository_files_already_exist.adopt_or_delete=Υπάρχουν ήδη τα αρχεία για αυτό το repository. Πρέπει να τα υιοθετήσετε ή να τα διαγράψετε.
visit_rate_limit=Συναντήθηκε το όριο ρυθμού κατά την απομακρυσμένη πρόσβαση.
2fa_auth_required=Απαιτήθηκε ταυτοποίηση δύο παραγόντων κατά την απομακρυσμένη πρόσβαση.
org_name_been_taken=Το όνομα του οργανισμού χρησιμοποιείται ήδη.
@ -925,7 +925,7 @@ access_token_deletion_cancel_action=Άκυρο
access_token_deletion_confirm_action=Διαγραφή
access_token_deletion_desc=Η διαγραφή ενός διακριτικού θα ανακαλέσει οριστικά την πρόσβαση στο λογαριασμό σας για εφαρμογές που το χρησιμοποιούν. Συνέχεια;
delete_token_success=Το διακριτικό έχει διαγραφεί. Οι εφαρμογές που το χρησιμοποιούν δεν έχουν πλέον πρόσβαση στο λογαριασμό σας.
repo_and_org_access=Πρόσβαση στο Αποθετήριο και Οργανισμό
repo_and_org_access=Πρόσβαση στο repository και οργανισμό
permissions_public_only=Δημόσια μόνο
permissions_access_all=Όλα (δημόσια, ιδιωτικά, και περιορισμένα)
select_permissions=Επιλογή δικαιωμάτων
@ -1005,7 +1005,7 @@ remove_account_link_success=Ο συνδεδεμένος λογαριασμός
hooks.desc=Προσθήκη webhooks που θα ενεργοποιούνται για <strong>όλα τα αποθετήρια</strong> που σας ανήκουν.
orgs_none=Δεν είστε μέλος σε κάποιο οργανισμό.
repos_none=Δεν σας ανήκει κανένα κάποιο αποθετήριο.
repos_none=Δεν υπάρχει κάποιο repository που σας ανήκει.
delete_account=Διαγραφή του λογαριασμού σας
delete_prompt=Αυτή η ενέργεια θα διαγράψει μόνιμα το λογαριασμό σας. <strong>ΔΕΝ ΘΑ ΜΠΟΡΕΙ</strong> να επανέλθει.
@ -1047,7 +1047,7 @@ language.localization_project = Βοηθήστε μας να μεταφράσο
language.description = Από εδώ και στο εξής, αυτή η γλώσσα θα χρησιμοποιείται από προεπιλογή για τον λογαριασμό σας.
[repo]
new_repo_helper=Ένα αποθετήριο περιέχει όλα τα αρχεία έργου, συμπεριλαμβανομένου του ιστορικού εκδόσεων. Ήδη φιλοξενείται αλλού; <a href="%s">Μετεγκατάσταση αποθετηρίου.</a>
new_repo_helper=Ένα repository περιέχει όλα τα αρχεία έργου, συμπεριλαμβανομένου του ιστορικού εκδόσεων. Έχετε ήδη ένα που φιλοξενείται κάπου αλλού; <a href="%s">Μεταφορά αποθετηρίου.</a>
owner=Ιδιοκτήτης
owner_helper=Ορισμένοι οργανισμοί ενδέχεται να μην εμφανίζονται στο αναπτυσσόμενο μενού λόγω του μέγιστου αριθμού αποθετηρίων.
repo_name=Όνομα αποθετηρίου
@ -1055,7 +1055,7 @@ repo_name_helper=Τα καλά ονόματα αποθετηρίων χρησι
repo_size=Μέγεθος Αποθετηρίου
template=Πρότυπο
template_select=Επιλέξτε ένα πρότυπο
template_helper=Μετατροπή σε πρότυπο αποθετήριο
template_helper=Μετατροπή σε πρότυπο repository
template_description=Τα πρότυπα αποθετήρια επιτρέπουν στους χρήστες να δημιουργήσουν νέα αποθετήρια με την ίδια δομή, αρχεία και προαιρετικές ρυθμίσεις.
visibility=Ορατότητα
visibility_description=Μόνο ο ιδιοκτήτης ή τα μέλη του οργανισμού εάν έχουν δικαιώματα, θα είναι σε θέση να το δουν.
@ -1070,7 +1070,7 @@ fork_to_different_account=Fork σε διαφορετικό λογαριασμό
fork_visibility_helper=Η ορατότητα ενός fork αποθετηρίου δεν μπορεί να αλλάξει.
fork_branch=Κλάδος που θα κλωνοποιηθεί στο fork
all_branches=Όλοι οι κλάδοι
fork_no_valid_owners=Αυτό το αποθετήριο δεν μπορεί να γίνει fork επειδή δεν υπάρχουν έγκυροι ιδιοκτήτες.
fork_no_valid_owners=Αυτό το repository δεν μπορεί να γίνει fork, επειδή δεν υπάρχουν έγκυροι ιδιοκτήτες.
use_template=Χρήση αυτού του πρότυπου
clone_in_vsc=Κλωνοποίηση στο VS Code
download_zip=Λήψη ZIP
@ -1120,7 +1120,7 @@ mirror_password_blank_placeholder=(Μη ορισμένο)
mirror_password_help=Αλλάξτε το όνομα χρήστη για να διαγράψετε έναν αποθηκευμένο κωδικό πρόσβασης.
watchers=Παρατηρητές
stargazers=Θαυμαστές
stars_remove_warning=Αυτό θα αφαιρέσει όλα τα αστέρια από αυτό το αποθετήριο.
stars_remove_warning=Αυτό θα αφαιρέσει όλα τα αστέρια από αυτό το repository.
forks=Forks
reactions_more=και %d περισσότερα
unit_disabled=Ο διαχειριστής του ιστότοπου έχει απενεργοποιήσει αυτήν την ενότητα αποθετηρίου.
@ -1129,7 +1129,7 @@ adopt_search=Εισάγετε όνομα χρήστη για αναζήτηση
adopt_preexisting_label=Υιοθέτηση αρχείων
adopt_preexisting=Υιοθετήστε τα προϋπάρχοντα αρχεία
adopt_preexisting_content=Δημιουργία αποθετηρίου από %s
adopt_preexisting_success=Υιοθετήθηκαν αρχεία και δημιουργήθηκε το αποθετήριο από %s
adopt_preexisting_success=Υιοθετήθηκαν αρχεία και δημιουργήθηκε το repository από %s
delete_preexisting_label=Διαγραφή
delete_preexisting=Διαγραφή αρχείων που προϋπήρχαν
delete_preexisting_content=Διαγραφή αρχείων στο %s
@ -1159,17 +1159,17 @@ desc.archived=Αρχειοθετημένο
template.items=Αντικείμενα προτύπου
template.git_content=Περιεχόμενο Git (Προεπιλεγμένος κλάδος)
template.git_hooks=Git hooks
template.git_hooks_tooltip=Δεν θα μπορέσετε να αφαιρέσετε ή να τροποποιήσετε τα Git hook αφού τα έχετε προσθέσει. Επιλέξτε την ρύθμιση αυτή μόνο αν εμπιστεύεστε το πρότυπο αποθετήριο.
template.git_hooks_tooltip=Δεν θα μπορέσετε να αφαιρέσετε ή να τροποποιήσετε τα Git hook αφού τα έχετε προσθέσει. Επιλέξτε την ρύθμιση αυτή μόνο αν εμπιστεύεστε το πρότυπο repository.
template.webhooks=Webhooks
template.topics=Θέματα
template.avatar=Εικόνα
template.issue_labels=Ταμπέλες ζητημάτων
template.one_item=Πρέπει να επιλέξετε τουλάχιστον ένα αντικείμενο στο πρότυπο
template.invalid=Πρέπει να επιλέξετε ένα πρότυπο αποθετήριο
template.invalid=Πρέπει να επιλέξετε ένα πρότυπο repository
archive.title=Αυτό το αποθετήρειο αρχειοθετήθηκε. Μπορείτε να προβάλετε αρχεία και να τα κλωνοποιήσετε, αλλά δεν μπορείτε να ωθήσετε ή να ανοίξετε ζητήματα ή pull requests.
archive.title_date=Αυτό το αποθετήριο έχει αρχειοθετηθεί στο %s. Μπορείτε να προβάλετε αρχεία και να κλωνοποιήσετε, αλλά δεν μπορείτε να ωθήσετε ή να ανοίξετε ζητήματα ή pull requests.
archive.issue.nocomment=Αυτό το αποθετήριο αρχειοθετήθηκε. Δεν μπορείτε να σχολιάσετε σε ζητήματα.
archive.title_date=Αυτό το repository αρχειοθετήθηκε στις %s. Μπορείτε να δείτε τα αρχεία του και να το κλωνοποιήσετε, αλλά δεν μπορείτε να κάνετε push, να ανοίξετε ζητήματα ή pull requests.
archive.issue.nocomment=Αυτό το repository έχει αρχειοθετηθεί. Δεν μπορείτε να σχολιάσετε σε ζητήματα.
archive.pull.nocomment=Αυτό το repo αρχειοθετήθηκε. Δεν μπορείτε να σχολιάσετε στα pull requests.
form.reach_limit_of_creation_1=Έχετε ήδη συμπληρώσει το όριο του %d αποθετηρίου.
@ -1180,7 +1180,7 @@ form.name_pattern_not_allowed=Το μοτίβο «%s» δεν επιτρέπετ
need_auth=Εξουσιοδότηση
migrate_options=Ρυθμίσεις μεταφοράς
migrate_service=Υπηρεσία Μεταφοράς
migrate_options_mirror_helper=Αυτό το αποθετήριο θα είναι είδωλο
migrate_options_mirror_helper=Αυτό το repository θα είναι είδωλο
migrate_options_lfs=Μεταφορά αρχείων LFS
migrate_options_lfs_endpoint.label=Άκρο LFS
migrate_options_lfs_endpoint.description=Η μεταφορά θα προσπαθήσει να χρησιμοποιήσει το Git remote για να <a target="_blank" rel="noopener noreferrer" href="%s">καθορίσει τον διακομιστή LFS</a>. Μπορείτε επίσης να καθορίσετε ένα δικό σας endpoint αν τα δεδομένα LFS του αποθετηρίου αποθηκεύονται κάπου αλλού.
@ -1233,10 +1233,10 @@ migrate.cancel_migrating_confirm=Θέλετε να ακυρώσετε αυτή
mirror_from=είδωλο του
forked_from=forked από
generated_from=παραγμένο από
fork_from_self=Δεν μπορείτε να κάνετε fork σε ένα αποθετήριο που κατέχετε.
fork_guest_user=Συνδεθείτε για να κάνετε fork αυτό το αποθετήριο.
watch_guest_user=Συνδεθείτε για να παρακολουθήσετε αυτό το αποθετήριο.
star_guest_user=Συνδεθείτε για να δώσετε ένα αστέρι σε αυτό το αποθετήριο.
fork_from_self=Δεν μπορείτε να κάνετε fork ένα repository που σας ανήκει.
fork_guest_user=Συνδεθείτε για να κάνετε fork αυτό το repository.
watch_guest_user=Συνδεθείτε για να παρακολουθήσετε αυτό το repository.
star_guest_user=Συνδεθείτε για να δώσετε ένα αστέρι σε αυτό το repository.
unwatch=Παύση ακολούθησης
watch=Παρακολούθηση
unstar=Αφαίρεση αστεριού
@ -1248,11 +1248,11 @@ more_operations=Περισσότερες λειτουργίες
no_desc=Χωρίς περιγραφή
quick_guide=Γρήγορος οδηγός
clone_this_repo=Κλωνοποίηση αυτού του αποθετηρίου
cite_this_repo=Αναφορά σε αυτό το αποθετήριο
cite_this_repo=Αναφορά σε αυτό το repository
create_new_repo_command=Δημιουργία νέου αποθετηρίου στη γραμμή εντολών
push_exist_repo=Προώθηση ενός υπάρχοντος αποθετηρίου από τη γραμμή εντολών
empty_message=Αυτό το αποθετήριο δεν έχει περιεχόμενο.
broken_message=Τα δεδομένα Git που διέπουν αυτό το αποθετήριο δεν μπορούν να διαβαστούν. Επικοινωνήστε με το διαχειριστή ή διαγράψτε αυτό το αποθετήριο.
empty_message=Αυτό το repository δεν έχει περιεχόμενο.
broken_message=Τα δεδομένα Git που διέπουν αυτό το αποθετήριο δεν μπορούν να διαβαστούν. Επικοινωνήστε με το διαχειριστή ή διαγράψτε αυτό το repository.
code=Κώδικας
code.desc=Πρόσβαση στον πηγαίο κώδικα, αρχεία, υποβολές και κλάδους.
@ -1330,7 +1330,7 @@ editor.cannot_edit_non_text_files=Τα δυαδικά αρχεία δεν μπο
editor.edit_this_file=Επεξεργασία αρχείου
editor.this_file_locked=Το αρχείο είναι κλειδωμένο
editor.must_be_on_a_branch=Πρέπει να βρίσκεστε σε έναν κλάδο για να κάνετε ή να προτείνετε αλλαγές σε αυτό το αρχείο.
editor.fork_before_edit=Πρέπει να κάνετε fork αυτό το αποθετήριο για να κάνετε ή να προτείνετε αλλαγές σε αυτό το αρχείο.
editor.fork_before_edit=Πρέπει να κάνετε fork αυτό το repository για να κάνετε ή να προτείνετε αλλαγές σε αυτό το αρχείο.
editor.delete_this_file=Διαγραφή αρχείου
editor.must_have_write_access=Πρέπει να έχετε πρόσβαση εγγραφής για να κάνετε ή να προτείνετε αλλαγές σε αυτό το αρχείο.
editor.file_delete_success=Το αρχείο «%s» έχει διαγραφεί.
@ -1359,15 +1359,15 @@ editor.new_branch_name_desc=Όνομα νέου κλάδου…
editor.cancel=Ακύρωση
editor.filename_cannot_be_empty=Το όνομα αρχείου δεν μπορεί να είναι κενό.
editor.filename_is_invalid=Το όνομα αρχείου δεν είναι έγκυρο: "%s".
editor.branch_does_not_exist=Ο κλάδος "%s" δεν υπάρχει σε αυτό το αποθετήριο.
editor.branch_already_exists=Ο κλάδος «%s» υπάρχει ήδη σε αυτό το αποθετήριο.
editor.directory_is_a_file=Το όνομα φακέλου «%s» χρησιμοποιείται ήδη ως όνομα αρχείου σε αυτό το αποθετήριο.
editor.branch_does_not_exist=Ο κλάδος "%s" δεν υπάρχει σε αυτό το repository.
editor.branch_already_exists=Ο κλάδος «%s» υπάρχει ήδη σε αυτό το repository.
editor.directory_is_a_file=Το όνομα φακέλου «%s» χρησιμοποιείται ήδη ως όνομα αρχείου σε αυτό το repository.
editor.file_is_a_symlink=`Το «%s» είναι συμβολικός σύνδεσμος. Οι συμβολικοί σύνδεσμοι δεν μπορούν να επεξεργαστούν στην ενσωματωμένη εφαρμογή`
editor.filename_is_a_directory=Το όνομα αρχείου «%s» χρησιμοποιείται ήδη ως όνομα φακέλου σε αυτό το αποθετήριο.
editor.file_editing_no_longer_exists=Το αρχείο «%s», το οποίο επεξεργάζεστε, δεν υπάρχει πλέον σε αυτό το αποθετήριο.
editor.file_deleting_no_longer_exists=Το αρχείο «%s», το οποίο διαγράφεται, δεν υπάρχει πλέον σε αυτό το αποθετήριο.
editor.filename_is_a_directory=Το όνομα αρχείου «%s» χρησιμοποιείται ήδη ως όνομα φακέλου σε αυτό το repository.
editor.file_editing_no_longer_exists=Το αρχείο «%s», το οποίο επεξεργάζεστε, δεν υπάρχει πλέον σε αυτό το repository.
editor.file_deleting_no_longer_exists=Το αρχείο «%s», το οποίο διαγράφεται, δεν υπάρχει πλέον σε αυτό το repository.
editor.file_changed_while_editing=Προέκυψαν κάποιες αλλαγές στα περιεχόμενα του αρχείου από τότε που ξεκινήσατε να τα επεξεργάζεστε. <a target="_blank" rel="noopener noreferrer" href="%s">Κάντε κλικ εδώ</a> για να τα δείτε ή <strong>ξανακάντε μία υποβολή των αλλαγών σας</strong> για να τις αντικαταστήσετε.
editor.file_already_exists=Υπάρχει ήδη ένα αρχείο με το όνομα «%s» σε αυτό το αποθετήριο.
editor.file_already_exists=Υπάρχει ήδη ένα αρχείο με το όνομα «%s» σε αυτό το repository.
editor.commit_empty_file_header=Υποβολή ενός κενού αρχείου
editor.commit_empty_file_text=Το αρχείο που πρόκειται να υποβληθεί είναι κενό. Συνέχεια;
editor.no_changes_to_show=Δεν υπάρχουν αλλαγές για εμφάνιση.
@ -1620,13 +1620,13 @@ issues.author_helper=Αυτός ο χρήστης είναι ο συγγραφέ
issues.role.owner=Ιδιοκτήτης
issues.role.owner_helper=Αυτός ο χρήστης είναι ο ιδιοκτήτης αυτού του αποθετηρίου.
issues.role.member=Μέλος
issues.role.member_helper=Αυτός ο χρήστης είναι μέλος του οργανισμού, του οποίου ανήκει το αποθετήριο.
issues.role.member_helper=Αυτός ο χρήστης είναι μέλος του οργανισμού, του οποίου ανήκει το repository.
issues.role.collaborator=Συνεργάτης
issues.role.collaborator_helper=Ο χρήστης έλαβε πρόσκληση συνεργασίας στο αποθετήριο.
issues.role.collaborator_helper=Ο χρήστης έλαβε πρόσκληση συνεργασίας στο repository.
issues.role.first_time_contributor=Συντελεστής για πρώτη φορά
issues.role.first_time_contributor_helper=Αυτή είναι η πρώτη συνεισφορά αυτού του χρήστη στο αποθετήριο.
issues.role.first_time_contributor_helper=Αυτή είναι η πρώτη συνεισφορά αυτού του χρήστη στο repository.
issues.role.contributor=Συντελεστής
issues.role.contributor_helper=Αυτός ο χρήστης έχει προηγούμενές υποβολές (commits) στο αποθετήριο.
issues.role.contributor_helper=Αυτός ο χρήστης έχει προηγούμενές υποβολές (commits) στο repository.
issues.re_request_review=Επαναίτηση ανασκόπησης
issues.is_stale=Έχουν υπάρξει αλλαγές σε αυτό το PR από αυτή την αναθεώρηση
issues.remove_request_review=Αφαίρεση αιτήματος αναθεώρησης
@ -1678,7 +1678,7 @@ issues.unlock_comment=: ξεκλείδωσε αυτή τη συνομιλία %s
issues.lock_confirm=Κλείδωμα
issues.unlock_confirm=Ξεκλείδωμα
issues.lock.notice_1=- Άλλοι χρήστες δεν μπορούν να αφήσουν νέα σχόλια σε αυτό το ζήτημα.
issues.lock.notice_2=- Εσείς και άλλοι συνεργάτες που έχουν πρόσβαση στο αποθετήριο θα μπορείτε ακόμα να αφήσετε σχόλια που θα είναι ορατά σε άλλους.
issues.lock.notice_2=- Εσείς, μαζί με τους συνεργάτες σας που έχουν πρόσβαση στο repository, θα μπορείτε ακόμα να αφήσετε σχόλια που θα μπορούν να βλέπουν και άλλοι.
issues.lock.notice_3=- Θα μπορείτε να ξεκλειδώσετε αυτό το ζήτημα πιο μετά.
issues.unlock.notice_1=- Όλοι θα βρίσκονται πάλι σε θέση να αφήσουν σχόλιο σε αυτό το ζήτημα.
issues.unlock.notice_2=- Θα μπορείτε πάντα να ξανακλειδώσετε αυτό το ζήτημα πιο μετά.
@ -1759,7 +1759,7 @@ issues.dependency.add_error_dep_issue_not_exist=Εξαρτώμενο ζήτημ
issues.dependency.add_error_dep_not_exist=Δεν υπάρχει η Εξάρτηση.
issues.dependency.add_error_dep_exists=Η Εξάρτηση υπάρχει ήδη.
issues.dependency.add_error_cannot_create_circular=Δεν μπορείτε να δημιουργήσετε μια εξάρτηση με δύο ζητήματα που μπλοκάρουν το ένα το άλλο.
issues.dependency.add_error_dep_not_same_repo=Και τα δύο ζητήματα πρέπει να βρίσκονται στο ίδιο αποθετήριο.
issues.dependency.add_error_dep_not_same_repo=Και τα δύο ζητήματα πρέπει να βρίσκονται στο ίδιο repository.
issues.review.self.approval=Δεν μπορείτε να εγκρίνετε το δικό σας pull request.
issues.review.self.rejection=Δεν μπορείτε να ζητήσετε αλλαγές στο δικό σας pull request.
issues.review.approve=ενέκρινε τις αλλαγές %s
@ -1922,7 +1922,7 @@ pulls.closed_at=`έκλεισε αυτό το pull request <a id="%[1]s" href="#
pulls.reopened_at=`άνοιξε ξανά αυτό το pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.cmd_instruction_hint=Προβολή οδηγιών γραμμής εντολών
pulls.cmd_instruction_checkout_title=Έλεγχος
pulls.cmd_instruction_checkout_desc=Από το αποθετήριο του έργου σας, ελέγξτε έναν νέο κλάδο και δοκιμάστε τις αλλαγές.
pulls.cmd_instruction_checkout_desc=Από το repository του έργου σας, ελέγξτε έναν νέο κλάδο και δοκιμάστε τις αλλαγές.
pulls.cmd_instruction_merge_title=Συγχώνευση
pulls.cmd_instruction_merge_desc=Συγχώνευση των αλλαγών και ενημέρωση στο Gitea.
pulls.clear_merge_message=Εκκαθάριση μηνύματος συγχώνευσης
@ -2101,8 +2101,8 @@ search.code_no_results=Δεν βρέθηκε πηγαίος κώδικας πο
search.code_search_unavailable=Η αναζήτηση κώδικα δεν είναι διαθέσιμη αυτή τη στιγμή. Παρακαλώ επικοινωνήστε με το διαχειριστή.
settings=Ρυθμίσεις
settings.desc=Στις Ρυθμίσεις μπορείτε να διαχειριστείτε τις ρυθμίσεις για το αποθετήριο
settings.options=Αποθετήριο
settings.desc=Στις Ρυθμίσεις μπορείτε να διαχειριστείτε τις ρυθμίσεις για το repository
settings.options=Repository
settings.collaboration=Συνεργάτες
settings.collaboration.admin=Διαχειριστής
settings.collaboration.write=Εγγραφή
@ -2113,18 +2113,18 @@ settings.hooks=Webhooks
settings.githooks=Git hooks
settings.basic_settings=Βασικές ρυθμίσεις
settings.mirror_settings=Ρυθμίσεις ειδώλου
settings.mirror_settings.docs=Ρυθμίστε τον αυτόματο συγχρονισμό των υποβολών, ετικετών και κλάδων του αποθετηρίου σας σε ένα άλλο αποθετήριο.
settings.mirror_settings.docs.disabled_pull_mirror.instructions=Ρυθμίστε τον αυτόματο συγχρονισμό των υποβολών, ετικετών και κλάδων του έργου σας με ένα άλλο αποθετήριο. Τα είδωλα τύπου λήψης έχουν απενεργοποιηθεί από τον διαχειριστή σας.
settings.mirror_settings.docs.disabled_push_mirror.instructions=Ρυθμίστε την αυτόματη λήψη υποβολών, ετικετών και κλάδων από ένα άλλο αποθετήριο.
settings.mirror_settings.docs=Ρυθμίστε τον αυτόματο συγχρονισμό των commit, ετικετών και κλάδων του αποθετηρίου σας σε ένα άλλο repository.
settings.mirror_settings.docs.disabled_pull_mirror.instructions=Ρυθμίστε τον αυτόματο συγχρονισμό των commit, ετικετών και κλάδων του έργου σας με ένα άλλο repository. Τα είδωλα τύπου λήψης έχουν απενεργοποιηθεί από τον διαχειριστή σας.
settings.mirror_settings.docs.disabled_push_mirror.instructions=Ρυθμίστε την αυτόματη λήψη υποβολών, ετικετών και κλάδων από ένα άλλο repository.
settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=Αυτή τη στιγμή, αυτό μπορεί να γίνει μόνο στο μενού "Νέα Μεταφορά". Για περισσότερες πληροφορίες, συμβουλευτείτε το:
settings.mirror_settings.docs.disabled_push_mirror.info=Τα είδωλα ώθησης έχουν απενεργοποιηθεί από το διαχειριστή σας.
settings.mirror_settings.docs.no_new_mirrors=Το αποθετήριο σας αντιγράφει τις αλλαγές προς ή από ένα άλλο αποθετήριο. Λάβετε υπόψη ότι δεν μπορείτε να δημιουργήσετε νέα είδωλα αυτή τη στιγμή.
settings.mirror_settings.docs.no_new_mirrors=Το repository σας αντιγράφει τις αλλαγές προς ή από ένα άλλο repository. Λάβετε υπόψη ότι δεν μπορείτε να δημιουργήσετε νέα είδωλα αυτή τη στιγμή.
settings.mirror_settings.docs.can_still_use=Αν και δεν μπορείτε να τροποποιήσετε τα υπάρχοντα είδωλα ή να δημιουργήσετε νέα, μπορείτε να χρησιμοποιείται ακόμα το υπάρχων είδωλο.
settings.mirror_settings.docs.pull_mirror_instructions=Για να ορίσετε έναν είδωλο έλξης, παρακαλούμε συμβουλευθείτε:
settings.mirror_settings.docs.more_information_if_disabled=Μπορείτε να μάθετε περισσότερα για τα είδωλα ώθησης και έλξης εδώ:
settings.mirror_settings.docs.doc_link_title=Πώς μπορώ να αντιγράψω αποθετήρια;
settings.mirror_settings.docs.doc_link_pull_section=το κεφάλαιο "Pulling from a remote repository" της τεκμηρίωσης.
settings.mirror_settings.docs.pulling_remote_title=Έλξη από ένα απομακρυσμένο αποθετήριο
settings.mirror_settings.docs.pulling_remote_title=Pull από ένα απομακρυσμένο repository
settings.mirror_settings.mirrored_repository=Είδωλο αποθετηρίου
settings.mirror_settings.direction=Κατεύθυνση
settings.mirror_settings.direction.pull=Pull
@ -2188,33 +2188,33 @@ settings.reindex_button=Προσθήκη στην ουρά Reindex
settings.reindex_requested=Αιτήθηκε reindex
settings.admin_enable_close_issues_via_commit_in_any_branch=Κλείσιμο ενός ζητήματος μέσω μιας υποβολής που έγινε σε έναν μη προεπιλεγμένο κλάδο
settings.danger_zone=Ζώνη κινδύνου
settings.new_owner_has_same_repo=Ο νέος ιδιοκτήτης έχει ήδη ένα αποθετήριο με το ίδιο όνομα. Παρακαλώ επιλέξτε ένα άλλο όνομα.
settings.convert=Μετατροπή σε κανονικό αποθετήριο
settings.convert_desc=Μπορείτε να μετατρέψετε αυτόν το είδωλο σε κανονικό αποθετήριο. Αυτό δεν μπορεί να αναιρεθεί.
settings.convert_notices_1=Αυτή η λειτουργία θα μετατρέψει το είδωλο σε ένα κανονικό αποθετήριο και δεν μπορεί να αναιρεθεί.
settings.new_owner_has_same_repo=Ο νέος ιδιοκτήτης έχει ήδη ένα repository με το ίδιο όνομα. Παρακαλώ επιλέξτε ένα άλλο όνομα.
settings.convert=Μετατροπή σε κανονικό repository
settings.convert_desc=Μπορείτε να μετατρέψετε αυτόν το είδωλο σε κανονικό repository. Αυτό δεν μπορεί να αναιρεθεί.
settings.convert_notices_1=Αυτή η λειτουργία θα μετατρέψει το είδωλο σε ένα κανονικό repository και δεν μπορεί να αναιρεθεί.
settings.convert_confirm=Μετατροπή αποθετηρίου
settings.convert_succeed=Το είδωλο έχει μετατραπεί σε κανονικό αποθετήριο.
settings.convert_fork=Μετατροπή σε κανονικό αποθετήριο
settings.convert_fork_desc=Μπορείτε να μετατρέψετε αυτό το fork σε κανονικό αποθετήριο. Αυτό δεν μπορεί να αναιρεθεί.
settings.convert_fork_notices_1=Αυτή η λειτουργία θα μετατρέψει το fork σε ένα κανονικό αποθετήριο και δεν μπορεί να αναιρεθεί.
settings.convert_succeed=Το είδωλο έχει μετατραπεί σε κανονικό repository.
settings.convert_fork=Μετατροπή σε κανονικό repository
settings.convert_fork_desc=Μπορείτε να μετατρέψετε αυτό το fork σε κανονικό repository. Αυτό δεν μπορεί να αναιρεθεί.
settings.convert_fork_notices_1=Αυτή η λειτουργία θα μετατρέψει το fork σε ένα κανονικό repository και δεν μπορεί να αναιρεθεί.
settings.convert_fork_confirm=Μετατροπή αποθετηρίου
settings.convert_fork_succeed=Το fork έχει μετατραπεί σε κανονικό αποθετήριο.
settings.convert_fork_succeed=Το fork έχει μετατραπεί σε κανονικό repository.
settings.transfer.title=Μεταβίβαση ιδιοκτησίας
settings.transfer.rejected=Η μεταβίβαση του αποθετηρίου απορρίφθηκε.
settings.transfer.success=Η μεταβίβαση του αποθετηρίου ήταν επιτυχής.
settings.transfer_abort=Ακύρωση μεταβίβασης
settings.transfer_abort_invalid=Δεν μπορείτε να ακυρώσετε μια ανύπαρκτη μεταβίβαση αποθετηρίου.
settings.transfer_abort_success=Η μεταφορά αποθετηρίου στο %s ακυρώθηκε με επιτυχία.
settings.transfer_desc=Μεταβιβάστε αυτό το αποθετήριο σε έναν χρήστη ή σε έναν οργανισμό για τον οποίο έχετε δικαιώματα διαχειριστή.
settings.transfer_desc=Μεταβιβάστε αυτό το repository σε έναν χρήστη ή σε έναν οργανισμό για τον οποίο έχετε δικαιώματα διαχειριστή.
settings.transfer_form_title=Εισάγετε το όνομα του αποθετηρίου ως επιβεβαίωση:
settings.transfer_in_progress=Αυτή τη στιγμή υπάρχει μια εν εξελίξει μεταβίβαση. Παρακαλούμε ακυρώστε την αν θέλετε να μεταβιβάσετε αυτό το αποθετήριο σε άλλο χρήστη.
settings.transfer_notices_1=- Θα χάσετε την πρόσβαση στο αποθετήριο αν το μεταβιβάσετε σε έναν μεμονωμένο χρήστη.
settings.transfer_notices_2=- Θα διατηρήσετε την πρόσβαση στο αποθετήριο αν το μεταβιβάσετε σε έναν οργανισμό που είστε (συν)ιδιοκτήτης.
settings.transfer_notices_3=- Εάν το αποθετήριο είναι ιδιωτικό και μεταβιβάζεται σε μεμονωμένο χρήστη, αυτή η ενέργεια εξασφαλίζει ότι ο χρήστης έχει τουλάχιστον άδεια ανάγνωσης (και αλλάζει τα δικαιώματα εάν είναι απαραίτητο).
settings.transfer_in_progress=Αυτή τη στιγμή υπάρχει μια εν εξελίξει μεταβίβαση. Παρακαλούμε ακυρώστε την αν θέλετε να μεταβιβάσετε αυτό το repository σε άλλο χρήστη.
settings.transfer_notices_1=- Θα χάσετε την πρόσβαση στο repository αν το μεταβιβάσετε σε έναν μεμονωμένο χρήστη.
settings.transfer_notices_2=- Θα διατηρήσετε την πρόσβαση στο repository αν το μεταβιβάσετε σε έναν οργανισμό που είστε (συν)ιδιοκτήτης.
settings.transfer_notices_3=- Εάν το repository είναι ιδιωτικό και μεταβιβάζεται σε μεμονωμένο χρήστη, αυτή η ενέργεια εξασφαλίζει ότι ο χρήστης έχει τουλάχιστον άδεια ανάγνωσης (και αλλάζει τα δικαιώματα εάν είναι απαραίτητο).
settings.transfer_owner=Νέος ιδιοκτήτης
settings.transfer_perform=Εκτέλεση μεταφοράς
settings.transfer_started=`Αυτό το αποθετήριο έχει επισημανθεί για μεταφορά και αναμένει επιβεβαίωση από το "%s"`
settings.transfer_succeed=Το αποθετήριο έχει μεταφερθεί.
settings.transfer_started=`Αυτό το repository έχει επισημανθεί για μεταφορά και αναμένει επιβεβαίωση από το "%s"`
settings.transfer_succeed=Το repository έχει μεταφερθεί.
settings.signing_settings=Ρυθμίσεις επαλήθευσης υπογραφών
settings.trust_model=Μοντέλο εμπιστοσύνης υπογραφών
settings.trust_model.default=Προεπιλεγμένο μοντέλο εμπιστοσύνης
@ -2236,33 +2236,33 @@ settings.wiki_deletion_success=Τα δεδομένα wiki του αποθετη
settings.delete=Διαγραφή αυτόυ του αποθετηρίου
settings.delete_desc=Η διαγραφή ενός αποθετηρίου είναι μόνιμη και δεν μπορεί να αναιρεθεί.
settings.delete_notices_1=- Αυτή η ενέργεια <strong>ΔΕΝ ΜΠΟΡΕΙ</strong> να αναιρεθεί.
settings.delete_notices_2=- Αυτή η ενέργεια θα διαγράψει μόνιμα το αποθετήριο <strong>%s</strong> μαζί με τον κώδικα, τα ζητημάτα, τα σχόλια, τα δεδομένα των wiki και τις ρυθμίσεις συνεργατών που βρίσκονται μέσα σε αυτό.
settings.delete_notices_2=- Αυτή η ενέργεια θα διαγράψει μόνιμα το repository <strong>%s</strong> μαζί με τον κώδικα, τα ζητημάτα, τα σχόλια, τα δεδομένα των wiki και τις ρυθμίσεις συνεργατών που βρίσκονται μέσα σε αυτό.
settings.delete_notices_fork_1=- Τα Forks αυτού του αποθετηρίου θα γίνουν ανεξάρτητα μετά τη διαγραφή.
settings.deletion_success=Το αποθετήριο έχει διαγραφεί.
settings.deletion_success=Το repository έχει διαγραφεί.
settings.update_settings_success=Οι ρυθμίσεις του αποθετηρίου έχουν ενημερωθεί.
settings.update_settings_no_unit=Το αποθετήριο θα πρέπει να επιτρέπει τουλάχιστον κάποιο είδος αλληλεπίδρασης.
settings.update_settings_no_unit=Το repository θα πρέπει να επιτρέπει τουλάχιστον κάποιο είδος αλληλεπίδρασης.
settings.confirm_delete=Διαγραφή αποθετηρίου
settings.add_collaborator=Προσθήκη συνεργάτη
settings.add_collaborator_success=Ο συνεργάτης προστέθηκε.
settings.add_collaborator_inactive_user=Δεν είναι δυνατή η προσθήκη ενός ανενεργού χρήστη ως συνεργάτη.
settings.add_collaborator_owner=Δεν είναι δυνατή η προσθήκη ενός ιδιοκτήτη σαν συνεργάτη.
settings.add_collaborator_duplicate=Ο συνεργάτης έχει ήδη προστεθεί σε αυτό το αποθετήριο.
settings.add_collaborator_duplicate=Ο συνεργάτης έχει ήδη προστεθεί σε αυτό το repository.
settings.delete_collaborator=Κατάργηση
settings.collaborator_deletion=Κατάργηση συνεργάτη
settings.collaborator_deletion_desc=Η κατάργηση ενός συνεργάτη θα αφαιρέσει και την πρόσβασή του στο αποθετήριο. Είστε βέβαιοι;
settings.collaborator_deletion_desc=Η κατάργηση ενός συνεργάτη θα αφαιρέσει και την πρόσβασή του στο repository. Είστε βέβαιοι;
settings.remove_collaborator_success=Ο συνεργάτης έχει καταργηθεί.
settings.search_user_placeholder=Αναζήτηση χρήστη…
settings.org_not_allowed_to_be_collaborator=Δεν μπορείτε να προσθέσετε έναν οργανισμό ως συνεργάτη.
settings.change_team_access_not_allowed=Η αλλαγή της πρόσβασης ομάδας για το αποθετήριο έχει περιοριστεί στον ιδιοκτήτη του οργανισμού
settings.team_not_in_organization=Η ομάδα δεν είναι στον ίδιο οργανισμό με το αποθετήριο
settings.change_team_access_not_allowed=Η αλλαγή της πρόσβασης ομάδας για το repository έχει περιοριστεί στον ιδιοκτήτη του οργανισμού
settings.team_not_in_organization=Η ομάδα δεν είναι στον ίδιο οργανισμό με το repository
settings.teams=Ομάδες
settings.add_team=Προσθήκη ομάδας
settings.add_team_duplicate=Η ομάδα έχει ήδη το αποθετήριο
settings.add_team_success=Η ομάδα έχει πλέον πρόσβαση στο αποθετήριο.
settings.add_team_duplicate=Η ομάδα έχει ήδη το repository
settings.add_team_success=Η ομάδα έχει πλέον πρόσβαση στο repository.
settings.search_team=Αναζήτηση Ομάδας…
settings.change_team_permission_tip=Τα δικαιώματα της ομάδας έχουν οριστεί στη σελίδα ρυθμίσεων της ομάδας και δεν μπορούν να αλλάξουν ανά αποθετήριο
settings.change_team_permission_tip=Τα δικαιώματα της ομάδας έχουν οριστεί στη σελίδα ρυθμίσεων της ομάδας και δεν μπορούν να αλλάξουν ανά repository
settings.delete_team_tip=Αυτή η ομάδα έχει πρόσβαση σε όλα τα αποθετήρια και δεν μπορεί να αφαιρεθεί
settings.remove_team_success=Έχει αφαιρεθεί η πρόσβαση της ομάδας στο αποθετήριο.
settings.remove_team_success=Έχει αφαιρεθεί η πρόσβαση της ομάδας στο repository.
settings.add_webhook=Προσθήκη webhook
settings.add_webhook.invalid_channel_name=Το όνομα του καναλιού Webhook δεν μπορεί να είναι κενό και δεν μπορεί να περιέχει μόνο έναν χαρακτήρα #.
settings.hooks_desc=Τα Webhooks κάνουν αυτόματα αιτήσεις HTTP POST σε ένα διακομιστή όταν ενεργοποιούνται ορισμένα γεγονότα στο Forgejo. Διαβάστε περισσότερα στον <a target="_blank" rel="noopener noreferrer" href="%s">οδηγό webhooks</a>.
@ -2305,15 +2305,15 @@ settings.event_create_desc=Ο κλάδος ή η ετικέτα δημιουργ
settings.event_delete=Διαγραφή
settings.event_delete_desc=Ο κλάδος ή η ετικέτα διαγράφηκε.
settings.event_fork=Fork
settings.event_fork_desc=Το αποθετήριο έγινε fork.
settings.event_fork_desc=Το repository έγινε fork.
settings.event_wiki=Wiki
settings.event_wiki_desc=Η σελίδα Wiki δημιουργήθηκε, μετονομάστηκε, επεξεργάστηκε ή διαγράφηκε.
settings.event_release=Κυκλοφορία
settings.event_release_desc=Η έκδοση δημοσιεύτηκε, ενημερώθηκε ή διαγράφηκε από ένα αποθετήριο.
settings.event_push=Push
settings.event_push_desc=Git push σε ένα αποθετήριο.
settings.event_repository=Αποθετήριο
settings.event_repository_desc=Το αποθετήριο δημιουργήθηκε ή διαγράφηκε.
settings.event_push_desc=Git push σε ένα repository.
settings.event_repository=Repository
settings.event_repository_desc=Το repository δημιουργήθηκε ή διαγράφηκε.
settings.event_header_issue=Συμβάντα ζητημάτων
settings.event_issues=Ζητήματα
settings.event_issues_desc=Το ζήτημα άνοιξε, έκλεισε, ανοίχθηκε εκ νέου ή επεξεργάστηκε.
@ -2345,7 +2345,7 @@ settings.event_pull_request_review_request_desc=Ζητήθηκε η αξιολό
settings.event_pull_request_approvals=Εγκρίσεις pull request
settings.event_pull_request_merge=Συγχώνευση pull request
settings.event_package=Πακέτο
settings.event_package_desc=Το πακέτο δημιουργήθηκε ή διαγράφηκε σε ένα αποθετήριο.
settings.event_package_desc=Το πακέτο δημιουργήθηκε ή διαγράφηκε σε ένα repository.
settings.branch_filter=Φίλτρο κλάδου
settings.branch_filter_desc=Λίστα επιτρεπόμενων κλάδων για ωθήσεις, δημιουργία κλάδων και γεγονότα διαγραφής κλάδων, που ορίζονται ως μοτίβο glob. Εάν είναι κενό ή <code>*</code>, αναφέρονται συμβάντα για όλους τους κλάδους. Δείτε τη τεκμηρίωση<a href="%[1]s">%[2]s</a> για σύνταξη. Παραδείγματα: <code>master</code>, <code>{master,release*}</code>.
settings.authorization_header=Κεφαλίδα authorization
@ -2361,7 +2361,7 @@ settings.hook_type=Είδος hook
settings.slack_token=Διακριτικό
settings.slack_domain=Domain
settings.slack_channel=Κανάλι
settings.add_web_hook_desc=Ενσωμάτωσε το <a target="_blank" rel="noreferrer" href="%s">%s</a> στο αποθετήριο σας.
settings.add_web_hook_desc=Ενσωμάτωσε το <a target="_blank" rel="noreferrer" href="%s">%s</a> στο repository σας.
settings.web_hook_name_gitea=Gitea
settings.web_hook_name_forgejo = Forgejo
settings.web_hook_name_gogs=Gogs
@ -2381,9 +2381,9 @@ settings.packagist_api_token=Διακριτικό API
settings.packagist_package_url=URL πακέτων Packagist
settings.deploy_keys=Κλειδιά διάθεσης
settings.add_deploy_key=Προσθήκη κλειδιού διάθεσης
settings.deploy_key_desc=Τα κλειδιά διάθεσης έχουν πρόσβαση μόνο-ανάγνωσης στο αποθετήριο.
settings.deploy_key_desc=Τα κλειδιά διάθεσης έχουν πρόσβαση μόνο-ανάγνωσης στο repository.
settings.is_writable=Ενεργοποίηση πρόσβασης εγγραφής
settings.is_writable_info=Επιτρέψτε σε αυτό το κλειδί διάθεσης να <strong>ωθήσει</strong> στο αποθετήριο.
settings.is_writable_info=Επιτρέψτε σε αυτό το κλειδί διάθεσης να <strong>ωθήσει</strong> στο repository.
settings.no_deploy_keys=Δεν υπάρχουν ακόμα κλειδιά διάθεσης.
settings.title=Τίτλος
settings.deploy_key_content=Περιεχόμενο
@ -2391,7 +2391,7 @@ settings.key_been_used=Ένα κλειδί διάθεσης με το ίδιο
settings.key_name_used=Ένα κλειδί διάθεσης με το ίδιο όνομα υπάρχει ήδη.
settings.add_key_success=Το κλειδί διάθεσης «%s» προστέθηκε.
settings.deploy_key_deletion=Αφαίρεση κλειδιού διάθεσης
settings.deploy_key_deletion_desc=Η κατάργηση ενός κλειδί διάθεσης θα ανακαλέσει την πρόσβασή του σε αυτό το αποθετήριο. Συνέχεια;
settings.deploy_key_deletion_desc=Η κατάργηση ενός κλειδί διάθεσης θα ανακαλέσει την πρόσβασή του σε αυτό το repository. Συνέχεια;
settings.deploy_key_deletion_success=Το κλειδί διάθεσης έχει αφαιρεθεί.
settings.branches=Κλάδοι
settings.protected_branch=Προστασία κλάδου
@ -2424,7 +2424,7 @@ settings.protect_check_status_contexts=Ενεργοποίηση ελέγχου
settings.protect_status_check_patterns=Μοτίβα ελέγχου κατάστασης:
settings.protect_status_check_patterns_desc=Ορίστε μοτίβα για να καθορίσετε ποιοι έλεγχοι κατάστασης πρέπει να περάσουν πριν οι κλάδοι να μπορούν να συγχωνευτούν σε έναν κλάδο που ταιριάζει με αυτόν τον κανόνα. Κάθε γραμμή καθορίζει ένα μοτίβο. Τα μοτίβα δεν μπορούν να είναι κενά.
settings.protect_check_status_contexts_desc=Απαιτείται έλεγχος κατάστασης για να περάσει το pull request πριν από τη συγχώνευση. Επιλέξτε ποιοι έλεγχοι κατάστασης πρέπει να περάσουν πριν κλάδοι μπορούν να συγχωνευτούν σε έναν κλάδο που ταιριάζει με αυτόν τον κανόνα. Όταν είναι ενεργοποιημένο, οι υποβολές πρέπει πρώτα να γίνονται push σε άλλο κλάδο, στη συνέχεια, να συγχωνεύονται ή γίνονται push απευθείας σε ένα κλάδο που ταιριάζει με αυτόν τον κανόνα, αφού έχουν ολοκληρωθεί οι έλεγχοι κατάστασης. Αν δεν επιλεχθεί κανένα πλαίσιο, η τελευταία υποβολή πρέπει να είναι επιτυχής ανεξάρτητα από το πλαίσιο.
settings.protect_check_status_contexts_list=Έλεγχοι κατάστασης που βρέθηκαν την τελευταία εβδομάδα για αυτό το αποθετήριο
settings.protect_check_status_contexts_list=Έλεγχοι κατάστασης που βρέθηκαν την τελευταία εβδομάδα για αυτό το repository
settings.protect_status_check_matched=Ταιριάζει
settings.protect_invalid_status_check_pattern=Μη έγκυρο μοτίβο ελέγχου κατάστασης: "%s".
settings.protect_no_valid_status_check_patterns=Μη έγκυρα μοτίβα ελέγχου κατάστασης.
@ -2486,20 +2486,20 @@ settings.matrix.message_type=Είδος μηνύματος
settings.archive.button=Αρχειοθέτηση αποθετηρίου
settings.archive.header=Αρχειοθέτηση αποθετηρίου
settings.archive.text=Η αρχειοθέτηση του αποθετηρίου θα το αλλάξει σε μόνο για ανάγνωση. Δε θα φαίνεται στον αρχικό πίνακα. Κανείς (ακόμα και εσείς!) δε θα μπορεί να κάνει νέες υποβολές, ή να ανοίξει ζητήματα ή pull request.
settings.archive.success=Το αποθετήριο αρχειοθετήθηκε με επιτυχία.
settings.archive.success=Το repository αρχειοθετήθηκε με επιτυχία.
settings.archive.error=Παρουσιάστηκε σφάλμα κατά την προσπάθεια αρχειοθέτησης του αποθετηρίου. Δείτε το αρχείο καταγραφής για περισσότερες λεπτομέρειες.
settings.archive.error_ismirror=Δε μπορείτε να αρχειοθετήσετε ένα είδωλο αποθετηρίου.
settings.archive.branchsettings_unavailable=Οι ρυθμίσεις του κλάδου δεν είναι διαθέσιμες αν το αποθετήριο είναι αρχειοθετημένο.
settings.archive.tagsettings_unavailable=Οι ρυθμίσεις της ετικέτας δεν είναι διαθέσιμες αν το αποθετήριο είναι αρχειοθετημένο.
settings.archive.branchsettings_unavailable=Οι ρυθμίσεις του κλάδου δεν είναι διαθέσιμες αν το repository είναι αρχειοθετημένο.
settings.archive.tagsettings_unavailable=Οι ρυθμίσεις της ετικέτας δεν είναι διαθέσιμες αν το repository είναι αρχειοθετημένο.
settings.unarchive.button=Αναίρεση αρχειοθέτησης αποθετηρίου
settings.unarchive.header=Απο-Αρχειοθέτηση του αποθετηρίου
settings.unarchive.text=Η απο-αρχειοθέτηση του αποθετηρίου θα αποκαταστήσει την ικανότητά του να λαμβάνει υποβολές και ωθήσεις, καθώς και νέα ζητήματα και pull-requests.
settings.unarchive.success=Το αποθετήριο απο-αρχειοθετήθηκε με επιτυχία.
settings.unarchive.success=Το repository απο-αρχειοθετήθηκε με επιτυχία.
settings.unarchive.error=Παρουσιάστηκε σφάλμα κατά την προσπάθεια απο-αρχειοθέτησης του αποθετηρίου. Δείτε τις καταγραφές για περισσότερες λεπτομέρειες.
settings.update_avatar_success=Η εικόνα του αποθετηρίου έχει ενημερωθεί.
settings.lfs=LFS
settings.lfs_filelist=Αρχεία LFS σε αυτό το αποθετήριο
settings.lfs_no_lfs_files=Δεν υπάρχουν αρχεία LFS σε αυτό το αποθετήριο
settings.lfs_filelist=Αρχεία LFS σε αυτό το repository
settings.lfs_no_lfs_files=Δεν υπάρχουν αρχεία LFS σε αυτό το repository
settings.lfs_findcommits=Εύρεση υποβολών
settings.lfs_lfs_file_no_commits=Δεν βρέθηκαν υποβολές για αυτό το αρχείο LFS
settings.lfs_noattribute=Αυτή η διαδρομή δεν έχει λειτουργία κλειδώματος στον προεπιλεγμένο κλάδο
@ -2518,7 +2518,7 @@ settings.lfs_force_unlock=Εξαγκαναστικό ξεκλείδωμα
settings.lfs_pointers.found=Βρέθηκαν %d δείκτης(ες) blob - %d συσχετίστηκαν, %d δεν συσχετίστηκαν (%d λείπουν από το χώρο αποθήκευσης)
settings.lfs_pointers.sha=Blob hash
settings.lfs_pointers.oid=OID
settings.lfs_pointers.inRepo=Στο αποθετήριο
settings.lfs_pointers.inRepo=Στο repository
settings.lfs_pointers.exists=Υπάρχει στο χώρο αποθήκευσης
settings.lfs_pointers.accessible=Προσβάσιμο στον χρήστη
settings.lfs_pointers.associateAccessible=Συσχετισμός προσιτών %d OID
@ -2621,7 +2621,7 @@ release.delete_tag=Διαγραφή ετικέτας
release.deletion=Διαγραφή κυκλοφορίας
release.deletion_desc=Διαγράφοντας μια κυκλοφορία, αυτή αφαιρείται μόνο από το Gitea. Δε θα επηρεάσει την ετικέτα Git, τα περιεχόμενα του αποθετηρίου σας ή το ιστορικό της. Συνέχεια;
release.deletion_success=Η κυκλοφορία έχει διαγραφεί.
release.deletion_tag_desc=Θα διαγράψει αυτή την ετικέτα από το αποθετήριο. Τα περιεχόμενα του αποθετηρίου και το ιστορικό παραμένουν αμετάβλητα. Συνέχεια;
release.deletion_tag_desc=Θα διαγράψει αυτή την ετικέτα από το repository. Τα περιεχόμενα του repository και το ιστορικό δεν θα πειραχτούν. Να γίνει συνέχεια;
release.deletion_tag_success=Η ετικέτα έχει διαγραφεί.
release.tag_name_already_exist=Υπάρχει ήδη μια έκδοση με αυτό το όνομα ετικέτας.
release.tag_name_invalid=Το όνομα της ετικέτας δεν είναι έγκυρο.
@ -2646,7 +2646,7 @@ branch.delete_branch_has_new_commits=Ο κλάδος «%s» δεν μπορεί
branch.create_branch=Δημιουργία κλάδου %s
branch.create_from=`από το «%s»`
branch.create_success=Ο κλάδος «%s» δημιουργήθηκε.
branch.branch_already_exists=Ο κλάδος «%s» υπάρχει ήδη σε αυτό το αποθετήριο.
branch.branch_already_exists=Ο κλάδος «%s» υπάρχει ήδη σε αυτό το repository.
branch.branch_name_conflict=Το όνομα του κλάδου «%s» συγκρούεται με το ήδη υπάρχον κλάδο «%s».
branch.tag_collision=Ο κλάδος «%s» δεν μπορεί να δημιουργηθεί επειδή μια ετικέτα με το ίδιο όνομα υπάρχει ήδη στο αποθετήριο.
branch.deleted_by=Διαγράφηκε από %s
@ -2691,7 +2691,7 @@ error.csv.invalid_field_count=Δεν είναι δυνατή η απόδοση
commits.renamed_from = Μετονομάστηκε από %σ
settings.wiki_rename_branch_main_desc = Ο κλάδος, ο οποίος χρησιμοποιείται εσωτερικά από το wiki, θα μετονομαστεί (μόνιμα και μη αναστράψιμα) σε «%s».
issues.comment.blocked_by_user = Δεν μπορείτε να αφήσετε σχόλιο σε αυτό το ζήτημα, επειδή ο κάτοχος του αποθετηρίου ή το άτομο που δημιούργησε το ζήτημα σας έχει αποκλείσει.
pulls.blocked_by_user = Δεν μπορείτε να δημιουργήσετε pull request σε αυτό το αποθετήριο, επειδή ο κάτοχος του αποθετηρίου σας έχει αποκλείσει.
pulls.blocked_by_user = Δεν μπορείτε να δημιουργήσετε pull request σε αυτό το repository, επειδή ο κάτοχος του repository σας έχει αποκλείσει.
pulls.made_using_agit = AGit
wiki.cancel = Ακύρωση
settings.units.add_more = Προσθήκη μονάδων...
@ -2710,13 +2710,13 @@ rss.must_be_on_branch = Για να αποκτήσετε ένα RSS feed, πρέ
clone_in_vscodium = Κλωνοποίηση στο VSCodium
editor.invalid_commit_mail = Αυτή η διεύθυνση email δεν είναι έγκυρη για την δημιουργία μίας υποβολής.
pulls.nothing_to_compare_have_tag = Ο επιλεγμένος κλάδος/tag είναι όμοιος.
issues.blocked_by_user = Δεν μπορείτε να δημιουργήσετε ζητήματα σε αυτό το αποθετήριο, επειδή ο κάτοχος του αποθετηρίου σας έχει αποκλείσει.
issues.blocked_by_user = Δεν μπορείτε να δημιουργήσετε ζητήματα σε αυτό το repository, επειδή ο κάτοχος του repository σας έχει αποκλείσει.
pulls.agit_explanation = Δημιουργημένο μέσω του AGit. Το AGit επιτρέπει σε συνεισφέροντες να προτείνουν αλλαγές χρησιμοποιώντας την εντολή «git push», χωρίς την δημιουργία fork ή έναν νέο κλάδο.
activity.navbar.recent_commits = Πρόσφατες υποβολές
settings.wiki_globally_editable = Να επιτρέπεται η επεξεργασία του wiki σε όλους
admin.manage_flags = Διαχείριση σημάνσεων
admin.enabled_flags = Το αποθετήριο έχει τις εξής σημάνσεις:
settings.mirror_settings.pushed_repository = Προοριζόμενο αποθετήριο
admin.enabled_flags = Το repository έχει τις εξής σημάνσεις:
settings.mirror_settings.pushed_repository = Προοριζόμενο repository
admin.flags_replaced = Οι σημάνσεις του αποθετηρίου αντικαταστάθηκαν
activity.navbar.code_frequency = Συχνότητα κώδικα
settings.wiki_branch_rename_success = Το όνομα κλάδου wiki του αποθετηρίου κανονικοποιήθηκε επιτυχώς.
@ -2767,7 +2767,7 @@ editor.commit_id_not_matching = Το αρχείο άλλαξε όσο το επ
settings.sourcehut_builds.visibility = Ορατότητα εργασιών
object_format = Μορφή αντικειμένων («object format»)
settings.ignore_stale_approvals_desc = Οι εγκρίσεις, οι οποίες αναφέρονται σε παλαιότερες υποβολές, δεν θα προσμετρούνται στο σύνολο των απαιτούμενων εγκρίσεων του pull request. Εφόσον αυτές οι εγκρίσεις έχουν ήδη ανακληθεί, τότε αυτή η ρύθμιση δεν θα παίξει κάποιον ρόλο.
settings.archive.mirrors_unavailable = Οι λειτουργίες ειδώλου δεν είναι διαθέσιμες εφόσον το αποθετήριο έχει αρχειοθετηθεί.
settings.archive.mirrors_unavailable = Οι λειτουργίες ειδώλου δεν είναι διαθέσιμες εφόσον το repository έχει αρχειοθετηθεί.
settings.web_hook_name_sourcehut_builds = SourceHut Builds
settings.enforce_on_admins = Εφαρμογή κανόνα σε διαχειριστές του αποθετηρίου
object_format_helper = Η μορφή αντικειμένων («object format») του αποθετηρίου. Δεν θα μπορείτε να το αλλάξετε μεταγενέστερα. Η πιο συμβατή μορφή είναι η SHA1.
@ -2805,7 +2805,7 @@ release.type_attachment = Συνημμένο
activity.published_prerelease_label = Προδημοσίευση
activity.published_tag_label = Ετικέτα
settings.pull_mirror_sync_quota_exceeded = Έχετε υπερβεί τους διαθέσιμους πόρους σας, για αυτό δεν θα γίνει λήψη των πιο πρόσφατων αλλαγών.
settings.transfer_quota_exceeded = Ο νέος ιδιοκτήτης (%s) έχει υπερβεί τους διαθέσιμους πόρους του. Το αποθετήριο δεν μπορεί να μεταφερθεί.
settings.transfer_quota_exceeded = Ο νέος ιδιοκτήτης (%s) έχει υπερβεί τους διαθέσιμους πόρους του. Το repository δεν μπορεί να μεταφερθεί.
release.asset_name = Όνομα αρχείου
release.asset_external_url = Εξωτερικό URL
release.invalid_external_url = Μη έγκυρο εξωτερικό URL: «%s»
@ -2901,7 +2901,7 @@ teams.join=Συμμετοχή
teams.leave=Αποχώρηση
teams.leave.detail=Σίγουρα θέλετε να αποχωρήσετε από την ομάδα %s;
teams.can_create_org_repo=Δημιουργία αποθετηρίων
teams.can_create_org_repo_helper=Τα μέλη μπορούν να δημιουργήσουν νέα αποθετήρια στον οργανισμό. Ο δημιουργός θα αποκτήσει πρόσβαση διαχειριστή στο νέο αποθετήριο.
teams.can_create_org_repo_helper=Τα μέλη μπορούν να δημιουργήσουν νέα αποθετήρια στον οργανισμό. Ο δημιουργός θα αποκτήσει πρόσβαση διαχειριστή στο νέο repository.
teams.none_access=Καμία πρόσβαση
teams.none_access_helper=Τα μέλη δεν μπορούν να δουν ή να κάνουν οποιαδήποτε άλλη ενέργεια σε αυτή τη μονάδα.
teams.general_access=Γενική πρόσβαση
@ -2922,7 +2922,7 @@ teams.add_team_member=Προσθήκη μέλους ομάδας
teams.invite_team_member=Πρόσκληση στην ομάδα %s
teams.invite_team_member.list=Εκκρεμείς προσκλήσεις
teams.delete_team_title=Διαγραφή ομάδας
teams.delete_team_desc=Η διαγραφή μιας ομάδας ανακαλεί τη πρόσβαση στο αποθετήριο από τα μέλη της. Συνέχεια;
teams.delete_team_desc=Η διαγραφή μιας ομάδας ανακαλεί τη πρόσβαση στο repository από τα μέλη της. Συνέχεια;
teams.delete_team_success=Η ομάδα έχει διαγραφεί.
teams.read_permission_desc=Αυτή η ομάδα χορηγεί πρόσβαση <strong>Ανάγνωσης</strong>: τα μέλη μπορούν να δουν και να κλωνοποιήσουν τα αποθετήρια της ομάδας.
teams.write_permission_desc=Αυτή η ομάδα χορηγεί πρόσβαση <strong>Εγγραφής</strong>: τα μέλη μπορούν να διαβάσουν και να κάνουν push στα αποθετήρια της ομάδας.
@ -2934,9 +2934,9 @@ teams.remove_all_repos_title=Αφαίρεση όλων των αποθετηρί
teams.remove_all_repos_desc=Αυτό θα αφαιρέσει όλα τα αποθετήρια από την ομάδα.
teams.add_all_repos_title=Προσθήκη όλων των αποθετηρίων
teams.add_all_repos_desc=Αυτό θα προσθέσει όλα τα αποθετήρια του οργανισμού στην ομάδα.
teams.add_nonexistent_repo=Το αποθετήριο που προσπαθείτε να προσθέσετε δεν υπάρχει, παρακαλώ δημιουργήστε το πρώτα.
teams.add_nonexistent_repo=Το repository που προσπαθείτε να προσθέσετε δεν υπάρχει, παρακαλώ δημιουργήστε το πρώτα.
teams.add_duplicate_users=Ο χρήστης είναι ήδη μέλος της ομάδας.
teams.repos.none=Αυτή η ομάδα δεν έχει πρόσβαση σε κανένα αποθετήριο.
teams.repos.none=Αυτή η ομάδα δεν έχει πρόσβαση σε κανένα repository.
teams.members.none=Δεν υπάρχουν μέλη σε αυτήν την ομάδα.
teams.specific_repositories=Συγκεκριμένα αποθετήρια
teams.specific_repositories_helper=Τα μέλη θα έχουν πρόσβαση μόνο σε αποθετήρια που προστίθενται ρητά στην ομάδα. Επιλέγοντας το <strong>δεν θα</strong> θα αφαιρεθούν αυτόματα τα αποθετήρια που έχουν ήδη προστεθεί με το <i>Όλα τα αποθετήρια</i>.
@ -3154,7 +3154,7 @@ packages.creator=Δημιουργός
packages.name=Όνομα
packages.version=Έκδοση
packages.type=Τύπος
packages.repository=Αποθετήριο
packages.repository=Repository
packages.size=Μέγεθος
packages.published=Δημοσιευμένα
@ -3473,7 +3473,7 @@ notices.inverse_selection=Αντιστροφή επιλογής
notices.delete_selected=Διαγραφή επιλεγμένων
notices.delete_all=Διαγραφή όλων των ειδοποιήσεων
notices.type=Τύπος
notices.type_1=Αποθετήριο
notices.type_1=Repository
notices.type_2=Εργασία
notices.desc=Περιγραφή
notices.op=Λειτ.
@ -3511,7 +3511,7 @@ users.organization_creation.description = Να επιτρέπεται η δημ
[action]
create_repo=δημιούργησε το αποθετήριο <a href="%s">%s</a>
rename_repo=μετονόμασε το αποθετήριο από <code>%[1]s</code> σε <a href="%[2]s">%[3]s</a>
rename_repo=μετονόμασε το repository από <code>%[1]s</code> σε <a href="%[2]s">%[3]s</a>
commit_repo=έκανε push στο <a href="%[2]s">%[3]s</a> του <a href="%[1]s">%[4]s</a>
create_issue=`άνοιξε το ζήτημα <a href="%[1]s">%[3]s#%[2]s</a>`
close_issue=`έκλεισε το ζήτημα <a href="%[1]s">%[3]s#%[2]s</a>`
@ -3523,7 +3523,7 @@ comment_issue=`άφησε σχόλιο στο ζήτημα <a href="%[1]s">%[3]s
comment_pull=`σχολίασε στο pull request <a href="%[1]s">%[3]s#%[2]s</a>`
merge_pull_request=`συγχώνευσε το pull request <a href="%[1]s">%[3]s#%[2]s</a>`
auto_merge_pull_request=`αυτόματη συγχώνευση του pull request <a href="%[1]s">%[3]s#%[2]s</a>`
transfer_repo=μετέφερε το αποθετήριο <code>%s</code> σε <a href="%s">%s</a>
transfer_repo=μετέφερε το repository <code>%s</code> σε <a href="%s">%s</a>
push_tag=ώθησε την ετικέτα <a href="%[2]s">%[3]s</a> σε <a href="%[1]s">%[4]s</a>
delete_tag=διέγραψε την ετικέτα %[2]s από <a href="%[1]s">%[3]s</a>
delete_branch=διέγραψε το κλάδο %[2]s από <a href="%[1]s">%[3]s</a>
@ -3603,7 +3603,7 @@ title=Πακέτα
desc=Διαχείριση πακέτων μητρώου.
empty=Δεν υπάρχουν πακέτα ακόμα.
empty.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο πακέτων, συμβουλευτείτε <a target="_blank" rel="noopener noreferrer" href="%s">τον οδηγό</a>.
empty.repo=Μήπως ανεβάσατε ένα πακέτο, αλλά δεν εμφανίζεται εδώ; Πηγαίνετε στις <a href="%[1]s">ρυθμίσεις πακέτων</a> και συνδέστε το σε αυτό το αποθετήριο.
empty.repo=Μήπως ανεβάσατε ένα πακέτο, αλλά δεν εμφανίζεται εδώ; Πηγαίνετε στις <a href="%[1]s">ρυθμίσεις πακέτων</a> και συνδέστε το σε αυτό το repository.
registry.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο %s, συμβουλευτείτε τον <a target="_blank" rel="noopener noreferrer" href="%s">οδηγό</a>.
filter.type=Τύπος
filter.type.all=Όλα
@ -3644,10 +3644,10 @@ composer.registry=Ρυθμίστε αυτό το μητρώο στο αρχεί
composer.install=Για να εγκαταστήσετε το πακέτο χρησιμοποιώντας το Composer, εκτελέστε την ακόλουθη εντολή:
composer.dependencies=Εξαρτήσεις
composer.dependencies.development=Εξαρτήσεις Ανάπτυξης
conan.details.repository=Αποθετήριο
conan.details.repository=Repository
conan.registry=Ρυθμίστε αυτό το μητρώο από τη γραμμή εντολών:
conan.install=Για να εγκαταστήσετε το πακέτο χρησιμοποιώντας το Conan, εκτελέστε την ακόλουθη εντολή:
conda.registry=Ρυθμίστε αυτό το μητρώο ως αποθετήριο Conda στο αρχείο <code>.condarc</code>:
conda.registry=Ρυθμίστε αυτό το μητρώο ως repository Conda στο αρχείο <code>.condarc</code>:
conda.install=Για να εγκαταστήσετε το πακέτο χρησιμοποιώντας το Conda, εκτελέστε την ακόλουθη εντολή:
container.details.type=Τύπος Εικόνας
container.details.platform=Πλατφόρμα
@ -3705,8 +3705,8 @@ swift.registry=Ρυθμίστε αυτό το μητρώο από τη γραμ
swift.install=Προσθέστε το πακέτο στο αρχείο <code>Package.swift</code>:
swift.install2=και εκτελέστε την ακόλουθη εντολή:
vagrant.install=Για προσθήκη ενός κυτίου Vagrant, εκτελέστε την ακόλουθη εντολή:
settings.link=Σύνδεση αυτού του πακέτου με ένα αποθετήριο
settings.link.description=Εάν συνδέσετε ένα πακέτο με ένα αποθετήριο, το πακέτο περιλαμβάνεται στη λίστα πακέτων του αποθετηρίου.
settings.link=Σύνδεση αυτού του πακέτου με ένα repository
settings.link.description=Εάν συνδέσετε ένα πακέτο με ένα repository, το πακέτο περιλαμβάνεται στη λίστα πακέτων του repository.
settings.link.select=Επιλογή Αποθετηρίου
settings.link.button=Ενημέρωση Συνδέσμου Αποθετηρίου
settings.link.success=Ο σύνδεσμος αποθετηρίου ενημερώθηκε επιτυχώς.
@ -3718,7 +3718,7 @@ settings.delete.success=Το πακέτο έχει διαγραφεί.
settings.delete.error=Αποτυχία διαγραφής του πακέτου.
owner.settings.cargo.title=Ευρετήριο μητρώου Cargo
owner.settings.cargo.initialize=Αρχικοποίηση ευρετηρίου
owner.settings.cargo.initialize.description=Απαιτείται ένα ειδικό αποθετήριο ευρετηρίου Git για τη χρήση του μητρώου Cargo. Χρησιμοποιώντας αυτή την επιλογή θα δημιουργηθεί ξανά το αποθετήριο και θα ρυθμιστεί αυτόματα.
owner.settings.cargo.initialize.description=Απαιτείται ένα ειδικό repository ευρετηρίου Git για τη χρήση του μητρώου Cargo. Χρησιμοποιώντας αυτή την επιλογή θα δημιουργηθεί ξανά το repository και θα ρυθμιστεί αυτόματα.
owner.settings.cargo.initialize.error=Αποτυχία αρχικοποίησης ευρετηρίου Cargo: %v
owner.settings.cargo.initialize.success=Ο ευρετήριο Cargo δημιουργήθηκε με επιτυχία.
owner.settings.cargo.rebuild=Ανανέωση ευρετηρίου
@ -3811,7 +3811,7 @@ runners.task_list=Πρόσφατες εργασίες στον εκτελεστ
runners.task_list.no_tasks=Δεν υπάρχει καμία εργασία ακόμα.
runners.task_list.run=Εκτέλεση
runners.task_list.status=Κατάσταση
runners.task_list.repository=Αποθετήριο
runners.task_list.repository=Repository
runners.task_list.commit=Υποβολή
runners.task_list.done_at=Ολοκλήρωσε Στις
runners.edit_runner=Επεξεργασία Εκτελεστή

View file

@ -231,7 +231,6 @@ string.desc = Z - A
[error]
occurred = An error occurred
report_message = If you believe that this is a Forgejo bug, please search for issues on <a href="%s" target="_blank">Codeberg</a> or open a new issue if necessary.
invalid_csrf = Bad Request: invalid CSRF token
not_found = The target couldn't be found.
network_error = Network error
server_internal = Internal server error
@ -1441,8 +1440,7 @@ commitstatus.failure = Failure
commitstatus.pending = Pending
commitstatus.success = Success
ext_issues = Access to external issues
ext_issues.desc = Link to an external issue tracker.
ext_issues = External issues
projects = Projects
projects.desc = Manage issues and pulls in project boards.
@ -1619,9 +1617,9 @@ issues.no_content = No description provided.
issues.close = Close issue
issues.comment_pull_merged_at = merged commit %[1]s into %[2]s %[3]s
issues.comment_manually_pull_merged_at = manually merged commit %[1]s into %[2]s %[3]s
issues.close_comment_issue = Comment and close
issues.close_comment_issue = Close with comment
issues.reopen_issue = Reopen
issues.reopen_comment_issue = Comment and reopen
issues.reopen_comment_issue = Reopen with comment
issues.create_comment = Comment
issues.closed_at = `closed this issue <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at = `reopened this issue <a id="%[1]s" href="#%[1]s">%[2]s</a>`
@ -2025,8 +2023,7 @@ signing.wont_sign.commitssigned = The merge will not be signed as all the associ
signing.wont_sign.approved = The merge will not be signed as the PR is not approved.
signing.wont_sign.not_signed_in = You are not signed in.
ext_wiki = Access to external Wiki
ext_wiki.desc = Link to an external wiki.
ext_wiki = External Wiki
wiki = Wiki
wiki.welcome = Welcome to the Wiki.
@ -2766,6 +2763,26 @@ error.csv.unexpected = Can't render this file because it contains an unexpected
error.csv.invalid_field_count = Can't render this file because it has a wrong number of fields in line %d.
error.broken_git_hook = Git hooks of this repository seem to be broken. Please follow the <a target="_blank" rel="noreferrer" href="%s">documentation</a> to fix them, then push some commits to refresh the status.
[repo.permissions]
code.read = <b>Read:</b> Access and clone the code of the repository.
code.write = <b>Write:</b> Push to the repository, create branches and tags.
issues.read = <b>Read:</b> Read and create issues and comments.
issues.write = <b>Write:</b> Close issues and manage metadata like labels, milestones, assignees, due dates and dependencies.
pulls.read = <b>Read:</b> Reading and create pull requests.
pulls.write = <b>Write:</b> Close pull requests and manage metadata like labels, milestones, assignees, due dates and dependencies.
releases.read = <b>Read:</b> View and download releases.
releases.write = <b>Write:</b> Publish, edit and delete releases and their assets.
wiki.read = <b>Read:</b> Read the integrated wiki and it's history.
wiki.write = <b>Write:</b> Create, update and delete pages in the integrated wiki.
projects.read = <b>Read:</b> Access repository project boards.
projects.write = <b>Write:</b> Create projects and columns and edit them.
packages.read = <b>Read:</b> View and download packages assigned to the repository.
packages.write = <b>Write:</b> Publish and delete packages assigned to the repository.
actions.read = <b>Read:</b> View integrated CI/CD pipelines and their logs.
actions.write = <b>Write:</b> Manually trigger, restart, cancel or approve pending CI/CD pipelines.
ext_issues = Access the link to an external issue tracker. The permissions are managed externally.
ext_wiki = Access the link to an external wiki. The permissions are managed externally.
[graphs]
component_loading = Loading %s...
component_loading_failed = Could not load %s
@ -3733,7 +3750,7 @@ management = Manage secrets
[actions]
actions = Actions
unit.desc = Manage integrated CI/CD pipelines with Forgejo Actions
unit.desc = Manage integrated CI/CD pipelines with Forgejo Actions.
status.unknown = Unknown
status.waiting = Waiting

View file

@ -147,6 +147,11 @@ value = Arvo
rerun = Suorita uudelleen
filter.clear = Tyhjennä suodattimet
invalid_data = Virheellistä dataa: %v
new_repo.title = Uusi repositorio
new_org.title = Uusi organisaatio
new_org.link = Uusi organisaatio
new_repo.link = Uusi repositorio
new_migrate.link = Uusi siirto
[aria]
footer.links = Linkit

View file

@ -207,7 +207,7 @@ string.desc=Z - A
[error]
occurred=Une erreur sest produite
report_message=Si vous pensez qu'il s'agit d'un bug Forgejo, veuillez consulter notre board <a href="%s" target="_blank">Codeberg</a> ou ouvrir un nouveau ticket si nécessaire.
report_message=Si vous pensez qu'il s'agit d'un bug Forgejo, veuillez consulter les tickets de <a href="%s" target="_blank">Codeberg</a> ou ouvrir un nouveau ticket si nécessaire.
missing_csrf=Requête incorrecte: aucun jeton CSRF présent
invalid_csrf=Requête incorrecte : jeton CSRF invalide
not_found=La cible n'a pu être trouvée.
@ -223,7 +223,7 @@ platform_desc=Forgejo est confirmé fonctionner sur des systèmes d'exploitation
lightweight=Léger
lightweight_desc=Forgejo utilise peu de ressources. Il peut même tourner sur un Raspberry Pi très bon marché. Économisez l'énergie de vos serveurs !
license=Open Source
license_desc=Toutes les sources sont sur <a target="_blank" rel="noopener noreferrer" href="%[1]s">Forgejo</a>! Rejoignez-nous et <a target="_blank" rel="noopener noreferrer" href="https://codeberg.org/forgejo/forgejo">contribuez</a> à rendre ce projet encore meilleur!
license_desc=Toutes les sources sont sur <a target="_blank" rel="noopener noreferrer" href="%[1]s">Forgejo</a>! Rejoignez-nous et <a target="_blank" rel="noopener noreferrer" href="https://codeberg.org/forgejo/forgejo">contribuez</a> à rendre ce projet encore meilleur. Ne craignez pas de devenir un·e contributeur·trice !
[install]
install=Installation
@ -558,6 +558,11 @@ removed_security_key.subject = Une clé de sécurité a été supprimée
totp_disabled.subject = TOTP a été désactivé
removed_security_key.no_2fa = Il n'y a plus de méthodes 2FA configurées ce qui signifie qu'il n'est plus nécessaire d'utiliser 2FA pour se connecter à votre compte.
account_security_caution.text_1 = Si vous êtes à lorigine de cette action, vous pouvez ignorer ce courriel.
totp_enrolled.text_1.no_webauthn = Vous venez d'activer TOTP pour votre compte. Cela signifie que pour toutes les prochaines connexions à votre compte, vous devrez utiliser TOTP comme méthode 2FA.
totp_enrolled.subject = Vous avez activé TOTP comme méthode 2FA
totp_enrolled.text_1.has_webauthn = Vous venez d'activer TOTP pour votre compte. Cela signifie que pour toutes les prochaines connexions à votre compte, vous pouvez utiliser TOTP comme méthode 2FA ou l'une de vos clés de sécurité.
removed_security_key.text_1 = La clé de sécurité « %[1]s » vient d'être supprimée de votre compte.
account_security_caution.text_2 = S'il ne s'agissait pas de vous, votre compte est compromis. Veuillez contacter les administrateurs du site.
[modal]
yes=Oui
@ -823,7 +828,7 @@ add_new_email=Ajouter une nouvelle adresse e-mail
add_new_openid=Ajouter une nouvelle URI OpenID
add_email=Ajouter une adresse courriel
add_openid=Ajouter une URI OpenID
add_email_confirmation_sent=Un e-mail de confirmation a été envoyé à "%s". Veuillez vérifier votre boîte de réception dans les %s suivants pour confirmer votre adresse e-mail.
add_email_confirmation_sent=Un courriel de confirmation a été envoyé à « %s ». Pour confirmer votre adresse de courriel, veuillez vérifier votre boîte de réception et suivre le lien indiqué dans les prochains %s.
add_email_success=La nouvelle adresse e-mail a été ajoutée.
email_preference_set_success=L'e-mail de préférence a été défini avec succès.
add_openid_success=La nouvelle adresse OpenID a été ajoutée.
@ -1042,6 +1047,8 @@ pronouns = Pronoms
pronouns_unspecified = Non spécifiés
language.title = Langue par défaut
keep_activity_private.description = Vous seul pourrez voir votre <a href="%s">activité publique</a>, ainsi que les administrateurs de l'instance.
language.localization_project = Aidez-nous à traduire Forgejo dans votre langue ! <a href="%s">En savoir plus</a>.
language.description = Cette langue sera enregistrée dans votre compte et utilisée comme langue par défaut après votre connexion.
[repo]
new_repo_helper=Un dépôt contient tous les fichiers dun projet, ainsi que lhistorique de leurs modifications. Vous avez déjà ça ailleurs ? <a href="%s">Migrez-le ici.</a>
@ -1421,7 +1428,7 @@ commitstatus.failure=Échec
commitstatus.pending=En attente
commitstatus.success=Succès
ext_issues=Accès aux tickets externes
ext_issues=Tickets externes
ext_issues.desc=Lien vers un gestionnaire de tickets externe.
projects=Projets
@ -1602,9 +1609,9 @@ issues.no_content=Sans contenu.
issues.close=Fermer le ticket
issues.comment_pull_merged_at=a fusionné la révision %[1]s dans %[2]s %[3]s
issues.comment_manually_pull_merged_at=a fusionné manuellement la révision %[1]s dans %[2]s %[3]s
issues.close_comment_issue=Commenter et fermer
issues.close_comment_issue=Fermer avec le commentaire
issues.reopen_issue=Rouvrir
issues.reopen_comment_issue=Commenter et réouvrir
issues.reopen_comment_issue=Réouvrir avec le commentaire
issues.create_comment=Commenter
issues.closed_at=`a fermé ce ticket <a id="%[1]s" href="#%[1]s">%[2]s</a>.`
issues.reopened_at=`a réouvert ce ticket <a id="%[1]s" href="#%[1]s">%[2]s</a>.`
@ -1993,7 +2000,7 @@ signing.wont_sign.commitssigned=La fusion ne sera pas signée car ses révisions
signing.wont_sign.approved=La fusion ne sera pas signée car la demande d'ajout n'a pas été approuvée.
signing.wont_sign.not_signed_in=Vous n'êtes pas connecté.
ext_wiki=Accès au wiki externe
ext_wiki=Wiki externe
ext_wiki.desc=Lier un wiki externe.
wiki=Wiki
@ -2418,14 +2425,14 @@ settings.protect_enable_merge_desc=Toute personne ayant un accès en écriture s
settings.protect_whitelist_committers=Liste blanche des soumissions (push)
settings.protect_whitelist_committers_desc=Seuls les utilisateurs ou les équipes autorisés pourront soumettre sur cette branche (sans forcer).
settings.protect_whitelist_deploy_keys=Mettez les clés de déploiement sur liste blanche avec accès en écriture pour soumettre.
settings.protect_whitelist_users=Utilisateurs sur liste blanche :
settings.protect_whitelist_users=Utilisateurs sur liste blanche pour pousser
settings.protect_whitelist_search_users=Rechercher des utilisateurs…
settings.protect_whitelist_teams=Équipes sur liste blanche :
settings.protect_whitelist_teams=Équipes sur liste blanche pour pousser
settings.protect_whitelist_search_teams=Rechercher des équipes…
settings.protect_merge_whitelist_committers=Activer la liste blanche pour la fusion
settings.protect_merge_whitelist_committers_desc=N'autoriser que les utilisateurs et les équipes en liste blanche d'appliquer les demandes de fusion sur cette branche.
settings.protect_merge_whitelist_users=Utilisateurs en liste blanche de fusion :
settings.protect_merge_whitelist_teams=Équipes en liste blanche de fusion :
settings.protect_merge_whitelist_users=Utilisateurs en liste blanche pour fusionner
settings.protect_merge_whitelist_teams=Équipes en liste blanche pour fusionner
settings.protect_check_status_contexts=Activer le contrôle de status
settings.protect_status_check_patterns=Motifs de vérification des statuts :
settings.protect_status_check_patterns_desc=Entrez des motifs pour spécifier quelles vérifications doivent réussir avant que des branches puissent être fusionnées. Un motif par ligne. Un motif ne peut être vide.
@ -2434,12 +2441,12 @@ settings.protect_check_status_contexts_list=Contrôles qualité trouvés au cour
settings.protect_status_check_matched=Correspondant
settings.protect_invalid_status_check_pattern=Motif de vérification des statuts incorrect : « %s ».
settings.protect_no_valid_status_check_patterns=Aucun motif de vérification des statuts valide.
settings.protect_required_approvals=Minimum d'approbations requis :
settings.protect_required_approvals=Approbations requises
settings.protect_required_approvals_desc=Permet de fusionner les demandes dajout lorsque suffisamment dévaluation sont positives.
settings.protect_approvals_whitelist_enabled=Restreindre les approbations aux utilisateurs ou aux équipes en liste blanche
settings.protect_approvals_whitelist_enabled_desc=Seuls les évaluations des utilisateurs ou des équipes suivantes compteront dans les approbations requises. Si laissé vide, les évaluations de toute personne ayant un accès en écriture seront comptabilisées à la place.
settings.protect_approvals_whitelist_users=Évaluateurs autorisés :
settings.protect_approvals_whitelist_teams=Équipes dévaluateurs autorisés :
settings.protect_approvals_whitelist_users=Évaluateurs autorisés
settings.protect_approvals_whitelist_teams=Équipes dévaluateurs autorisés
settings.dismiss_stale_approvals=Révoquer automatiquement les approbations périmées
settings.dismiss_stale_approvals_desc=Lorsque des nouvelles révisions changent le contenu de la demande dajout, les approbations existantes sont révoquées.
settings.ignore_stale_approvals=Ignorer les approbations obsolètes
@ -2449,9 +2456,9 @@ settings.require_signed_commits_desc=Rejeter les soumissions sur cette branche l
settings.protect_branch_name_pattern=Motif de nom de branche protégé
settings.protect_branch_name_pattern_desc=Motifs de nom de branche protégé. Consultez la <a href="%s">documentation</a> pour la syntaxe du motif. Exemples : main, release/**
settings.protect_patterns=Motifs
settings.protect_protected_file_patterns=Liste des fichiers et motifs protégés (séparés par un point virgule ";") :
settings.protect_protected_file_patterns_desc=Les fichiers protégés ne peuvent être modifiés, même si l'utilisateur a le droit d'ajouter, éditer ou supprimer des fichiers dans cette branche. Plusieurs motifs peuvent être séparés par un point-virgule (";"). Veuillez voir <a href="%s">github.com/gobwas/glob</a> la documentation pour la syntaxe des motifs. Exemples : <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
settings.protect_unprotected_file_patterns=Liste des fichiers et motifs exclus (séparés par un point virgule ";") :
settings.protect_protected_file_patterns=Motifs de fichiers protégés (séparés par un point virgule ";")
settings.protect_protected_file_patterns_desc=Les fichiers protégés ne peuvent être modifiés, même si l'utilisateur a le droit d'ajouter, éditer ou supprimer des fichiers dans cette branche. Plusieurs motifs peuvent être séparés par un point-virgule (";"). Veuillez voir <a href="%[1]s">%[2]s</a> la documentation pour la syntaxe des motifs. Exemples : <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
settings.protect_unprotected_file_patterns=Motifs de fichiers non protégés (séparés par un point virgule ";")
settings.protect_unprotected_file_patterns_desc=Les fichiers non-protégés qui peuvent être modifiés si l'utilisateur a le droit d'écriture, prenant le pas sur les restrictions de push. Plusieurs motifs peuvent être séparés par un point-virgule (";"). Veuillez voir <a href="%[1]s">%[2]s</a> la documentation pour la syntaxe des motifs. Exemples : <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
settings.add_protected_branch=Activer la protection
settings.delete_protected_branch=Désactiver la protection
@ -2811,6 +2818,23 @@ settings.federation_following_repos = Les URL des dépôts suivis séparés par
settings.federation_not_enabled = La fédération n'est pas activée pour votre instance.
comments.edit.already_changed = Impossible de sauvegarder les changements du commentaire car son contenu a déjà été modifié par un autre utilisateur. Veuillez recharger la page et essayer de l'éditer à nouveau pour éviter d'écraser ses changements
settings.federation_apapiurl = URL de fédération de ce dépôt. A copier-coller dans les paramètres de fédérations d'un autre dépôt comme URL d'un dépôt à suivre.
mirror_denied_combination = Il n'est pas possible de combiner une authentification par clé publique et par mot de passe.
mirror_public_key = Clé SSH publique
mirror_use_ssh.text = Utiliser l'authentification SSH
mirror_use_ssh.helper = Forgejo va créer un miroir du dépôt via Git sur SSH et créer une paire de clés pour vous lorsque vous sélectionnez cette option. Vous devez vous assurer que la clé publique générée est autorisée à pousser dans le dépôt de destination. Il n'est pas possible d'utiliser l'autorisation basée sur un mot de passe si vous choisissez cette option.
no_eol.text = Pas d'EOL
mirror_use_ssh.not_available = L'authentification par SSH n'est pas disponible.
no_eol.tooltip = Ce fichier ne contient pas de caractère final de fin de ligne.
release.type_attachment = Pièce jointe
settings.transfer_quota_exceeded = Le nouvel utilisateur (%s) a dépassé son quota. Le dépôt n'a pas été transféré.
settings.pull_mirror_sync_quota_exceeded = Quota dépassé, les modifications ne sont pas tirées.
activity.commit = Activité de commit
settings.mirror_settings.push_mirror.copy_public_key = Copier la clé publique
release.asset_external_url = URL externe
release.invalid_external_url = URL externe non valable : « %s »
milestones.filter_sort.name = Nom
settings.mirror_settings.push_mirror.none_ssh = Aucun
settings.protect_new_rule = Créer une nouvelle règle de protection de branche
[graphs]
component_loading=Chargement de %s…

View file

@ -3,7 +3,7 @@ home=Forsíða
dashboard=Stjórnborð
explore=Vafra
help=Hjálp
sign_in=Skrá Inn
sign_in=Skrá inn
sign_in_or=eða
sign_out=Skrá Út
sign_up=Nýskráning
@ -15,9 +15,9 @@ page=Síða
template=Sniðmát
language=Tungumál
notifications=Tilkynningar
active_stopwatch=Virk Tímamæling
active_stopwatch=Virk tímamæling
create_new=Skapa…
user_profile_and_more=Notandasíða og Stillingar…
user_profile_and_more=Notandasíða og stillingar…
signed_in_as=Skráð(ur) inn sem
toc=Efnisyfirlit
licenses=Hugbúnaðarleyfi
@ -111,6 +111,8 @@ concept_code_repository=Hugbúnaðarsafn
name=Heiti
value=Gildi
sign_in_with_provider = Skrá inn með %s
enable_javascript = Þessi síða krefst JavaScript.
[aria]

View file

@ -471,6 +471,11 @@ openid_signin_desc = Inserisci il tuo URI OpenID. Per esempio: alice.openid.exam
password_pwned = La password che hai scelto è in un <a target="_blank" rel="noopener noreferrer" href="%s">elenco di password rubate</a> precedentemente esposte a violazioni di dati pubblici. Riprova con una password diversa e valuta di modificare questa password anche altrove.
tab_signup = Registrati
tab_signin = Accedi
back_to_sign_in = Torna alla schermata d'accesso
sign_in_openid = Procedi con OpenID
hint_login = Hai già un'utenza? <a href="%s">Accedi!</a>
hint_register = Non hai un'utenza? <a href="%s">Registrati ora.</a>
sign_up_button = Registrati ora.
[mail]
view_it_on=Visualizza su %s
@ -539,6 +544,21 @@ activate_email.title = %s, verifica il tuo indirizzo email
admin.new_user.text = <a href="%s">Clicca qui</a> per gestire questo utente dal pannello di amministrazione.
team_invite.text_1 = %[1]s ti ha invitato a far parte del team %[2]s nell'organizzazione %[3]s.
team_invite.text_3 = Nota: Questo invito è destinato a %[1]s. Se non ti aspettavi questo invito, puoi ignorare questa email.
primary_mail_change.subject = La tua mail principale è stata cambiata
removed_security_key.no_2fa = Non ci sono più altri metodi di autenticazione a due fattori configurati, ergo non c'è più bisogno di accedere alla tua utenza tramite tale autenticazione.
primary_mail_change.text_1 = La mail principale della tua utenza è appena stata cambiata in %[1]s. Ciò significa che questo indirizzo di posta elettronica non riceverà più notifiche mail da quest'utenza.
totp_disabled.subject = La TOTP è stata disabilitata
totp_disabled.no_2fa = Non ci sono più altri metodi d'autenticazione a due fattori configurati, ergo non c'è più bisogno di accedere alla tua utenza con tale autenticazione.
removed_security_key.subject = È stata rimossa una chiave di sicurezza
removed_security_key.text_1 = La chiave di sicurezza "%[1]s" è appena stata rimossa dalla tua utenza.
totp_disabled.text_1 = La password a tempo usa e getta (TOTP) della tua utenza è appena stata disabilitata.
totp_enrolled.subject = Hai attivato la TOTP come metodo d'autenticazione a due fattori
totp_enrolled.text_1.no_webauthn = Hai appena attivato la TOTP per la tua utenza. Ciò significa che dovrai usarla come metodo d'autenticazione a due fattori per tutti i tuoi accessi futuri.
totp_enrolled.text_1.has_webauthn = Hai appena attivato la TOTP per la tua utenza. Ciò significa che dovrai usare come metodo d'autenticazione a due fattori per i tuoi accessi futuri tale TOTP o una delle tue chiavi di sicurezza.
password_change.subject = La tua password è stata modificata
password_change.text_1 = La password della tua utenza è appena stata modificata.
account_security_caution.text_1 = Se sei statə tu, puoi ignorare questa mail.
account_security_caution.text_2 = Se non sei statə tu, la tua utenza è compromessa. Contatta l'amministrazione del sito.
[modal]
@ -1022,6 +1042,8 @@ pronouns = Pronomi
pronouns_custom = Personalizzato
pronouns_unspecified = Non specificato
language.title = Lingua predefinita
language.description = Questa lingua verrà salvata nella tua utenza e verrà usata come predefinita ogni volta che farai l'accesso.
language.localization_project = Aiutaci a tradurre Forgejo nella tua lingua! <a href="%s">Più informazioni</a>.
[repo]
owner=Proprietario
@ -3863,6 +3885,7 @@ exact_tooltip = Includi solo i risultati che corrispondono esattamente al termin
issue_kind = Cerca segnalazioni...
pull_kind = Cerca richieste...
exact = Esatto
milestone_kind = Ricerca tappe...
[munits.data]
gib = GiB

View file

@ -158,6 +158,13 @@ filter.not_template = テンプレートではない
invalid_data = 無効なデータ: %v
more_items = さらに表示
copy_generic = クリップボードへコピー
new_repo.title = 新しいリポジトリ
new_migrate.title = 新しいマイグレーション
new_org.title = 新しい組織
new_repo.link = 新しいリポジトリ
new_migrate.link = 新しいマイグレーション
new_org.link = 新しい組織
test = テスト
[aria]
navbar=ナビゲーションバー

View file

@ -159,6 +159,18 @@ fuzzy_tooltip = Įtraukti rezultatus, kurie taip pat labai atitinka paieškos te
repo_kind = Ieškoti saugyklų...
code_search_unavailable = Kodų paieška šiuo metu nepasiekiama. Kreipkis į svetainės administratorių.
org_kind = Ieškoti organizacijų...
union = Bendrinis
code_search_by_git_grep = Dabartiniai kodo paieškos rezultatai pateikiami atliekant „git grep“. Rezultatai gali būti geresni, jei svetainės administratorius įjungs kodo indeksuotoją.
package_kind = Ieškoti paketų...
project_kind = Ieškoti projektų...
commit_kind = Ieškoti įsipareigojimų...
runner_kind = Ieškoti vykdyklių...
no_results = Nerasta atitinkamų rezultatų.
issue_kind = Ieškoti problemų...
branch_kind = Ieškoti šakų...
milestone_kind = Ieškoti gairių...
pull_kind = Ieškoti sujungimų...
keyword_search_unavailable = Ieškoti pagal raktažodį šiuo metu nepasiekiamas. Susisiekite su svetainės administratoriumi.
[actions]
workflow.disable = Išjungti darbo eigą
@ -171,6 +183,9 @@ runs.empty_commit_message = (tuščias įsipareigojimo pranešimas)
submodule = Pomodulis
changed_filemode = %[1]s → %[2]s
symbolic_link = Virtualusis aplankas
directory = Katalogas
executable_file = Vykdomasis failas
normal_file = Įprastas failas
[projects]
deleted.display_name = Ištrintas projektas
@ -182,4 +197,64 @@ type-3.display_name = Organizacijos projektas
filepreview.truncated = Peržiūra buvo sutrumpinta
[mail]
reset_password.text = Jei tai buvote jūs, spustelėkite toliau esančią nuorodą, kad atkurtumėte savo paskyrą per <b>%s</b>:
reset_password.text = Jei tai buvote jūs, spustelėkite toliau esančią nuorodą, kad atkurtumėte savo paskyrą per <b>%s</b>:
[heatmap]
contributions_one = įnašas
contributions_few = įnašai
less = Mažiau
more = Daugiau
number_of_contributions_in_the_last_12_months = %s įnašų per pastaruosius 12 mėnesių
contributions_zero = Įnašų nėra
contributions_format = {contributions} {year} {month} {day}
[aria]
navbar = Naršymo juosta
footer = Puslapinė poraštė
footer.software = Apie šią programinę įrangą
footer.links = Nuorodos
[editor]
buttons.quote.tooltip = Cituoti tekstą
buttons.code.tooltip = Pridėti kodą
buttons.link.tooltip = Pridėti nuorodą
buttons.heading.tooltip = Pridėti antraštę
buttons.bold.tooltip = Pridėti pusjuodį tekstą
buttons.italic.tooltip = Pridėti kursyvinį tekstą
[error]
network_error = Tinklo klaida
server_internal = Vidinio serverio klaida
[startpage]
app_desc = Nesudėtinga, savarankiškai teikiama „Git“ paslauga
install = Lengva įdiegti
[install]
path = Kelias
err_admin_name_is_reserved = Administratoriaus naudotojo vardas netinkamas. Naudotojo vardas yra rezervuotas.
enable_update_checker = Įjungti naujinimų tikrintuvą
env_config_keys = Aplinkos konfigūracija
db_title = Duomenų bazės nustatymai
db_type = Duomenų bazės tipas
user = Naudotojo vardas
password = Slaptažodis
db_name = Duomenų bazės pavadinimas
db_schema = Schema
ssl_mode = SSL
host = Pagrindinis komputeris
general_title = Bendrieji nustatymai
email_title = El. pašto nustatymai
federated_avatar_lookup.description = Ieškokite pseudoportretų naudojant „Libravatar“.
db_schema_helper = Palikite tuščią, jei tai numatytoji duomenų bazė („public“).
err_empty_admin_password = Administratoriaus slaptažodis negali būti tuščias.
err_empty_admin_email = Administratoriaus el. paštas negali būti tuščias.
[explore]
go_to = Eiti į
code = Kodas
[auth]
remember_me = Prisiminti šį įrenginį
forgot_password_title = Pamirštas slaptažodis
forgot_password = Pamiršote slaptažodį?

View file

@ -23,4 +23,115 @@ language = Språk
notifications = Varslinger
create_new = Opprett…
user_profile_and_more = Profil og innstillinger…
signed_in_as = Logget inn som
signed_in_as = Logget inn som
confirm_delete_selected = Bekreft sletting av alle valgte elementer?
dashboard = Dashbord
download_logs = Last ned logger
copy_hash = Kopier hash
more_items = Flere elementer
passcode = Adgangskode
webauthn_insert_key = Skriv inn din sikkerhetsnøkkel
webauthn_use_twofa = Bruk tofaktorkode fra din mobil
organization = Organisasjon
mirror = Speil
new_mirror = Ny speiling
repository = Repositorium
new_project = Nytt prosjekt
new_project_column = Ny kolonne
webauthn_error = Klarte ikke lese sikkerhetsnøkkelen.
webauthn_unsupported_browser = Nettleseren din støtter ikke WebAuthn.
webauthn_error_unknown = En ukjent feil oppstod. Vennligst prøv igjen.
webauthn_error_insecure = WebAuhn støtter kun sikre forbindelser. For testing over HTTP kan du bruke verten "localhost" eller "127.0.0.1"
admin_panel = Nettsideadministrasjon
settings = Innstillinger
your_profile = Profil
your_starred = Stjernemerket
your_settings = Innstillinger
new_repo.title = Nytt repositorium
new_migrate.title = Ny migrasjon
new_org.title = Ny organisasjon
new_repo.link = Nytt repositorium
new_migrate.link = Ny migrasjon
new_org.link = Ny organisasjon
all = Alle
sources = Kilder
mirrors = Speilinger
activities = Aktiviteter
rss_feed = RSS feed
retry = Prøv igjen
rerun = Kjør på nytt
rerun_all = Kjør alle jobber på nytt
save = Lagre
cancel = Avbryt
forks = Forks
milestones = Milepæler
ok = OK
test = Test
loading = Laster inn…
error = Feil
go_back = Gå tilbake
never = Aldri
invalid_data = Ugyldig data: %v
unknown = Ukjent
pin = Pin
artifacts = Artefakter
archived = Arkivert
concept_system_global = Global
add = Legg til
add_all = Legg til alle
remove = Fjern
remove_all = Fjern alle
remove_label_str = Fjern element "%s"
edit = Rediger
view = Vis
enabled = Aktivert
disabled = Deaktivert
locked = Låst
copy = Kopier
copy_generic = Kopier til utklippstavlen
copy_url = Kopier URL
copy_content = Kopier innhold
copy_success = Kopiert!
copy_error = Kopiering mislyktes
copy_type_unsupported = Denne filtypen kan ikke kopieres
write = Skriv
preview = Forhåndsvis
concept_user_individual = Individuell
concept_code_repository = Repositorium
concept_user_organization = Organisasjon
show_timestamps = Vis tidsstempler
show_log_seconds = Vis sekunder
show_full_screen = Vis fullskjerm
name = Navn
value = Verdi
filter = Filter
filter.clear = Tøm filtre
filter.is_archived = Arkivert
filter.not_archived = Ikke arkivert
filter.is_mirror = Speilinger
filter.not_mirror = Ikke speilinger
filter.is_template = Maler
filter.not_template = Ikke maler
filter.public = Offentlig
filter.private = Privat
explore = Utforsk
active_stopwatch = Aktiv tidsregistrering
home = Hjem
help = Hjelp
logo = Logo
sign_in = Logg inn
sign_in_with_provider = Logg inn med %s
sign_in_or = eller
sign_out = Logg ut
sign_up = Opprett konto
confirm_delete_artifact = Er du sikker på at du vil slette artefakten "%s" ?
[search]
search = Søk...
type_tooltip = Søketype
fuzzy = Fuzzy
union = Union
[auth]
verify = Bekreft
sign_up_button = Opprett konto nå.

View file

@ -1406,7 +1406,7 @@ commitstatus.failure=Неудача
commitstatus.pending=Ожидание
commitstatus.success=Успешно
ext_issues=Доступ ко внешним задачам
ext_issues=Внешние задачи
ext_issues.desc=Ссылка на внешнюю систему отслеживания задач.
projects=Проекты
@ -1587,9 +1587,9 @@ issues.no_content=Описание отсутствует.
issues.close=Закрыть задачу
issues.comment_pull_merged_at=коммит %[1]s был добавлен в %[2]s %[3]s
issues.comment_manually_pull_merged_at=коммит %[1]s был вручную добавлен в %[2]s %[3]s
issues.close_comment_issue=Прокомментировать и закрыть
issues.close_comment_issue=Закрыть комментарием
issues.reopen_issue=Открыть снова
issues.reopen_comment_issue=Прокомментировать и открыть снова
issues.reopen_comment_issue=Открыть снова комментарием
issues.create_comment=Комментировать
issues.closed_at=`задача была закрыта <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at=`задача была открыта снова <a id="%[1]s" href="#%[1]s">%[2]s</a>`
@ -1964,7 +1964,7 @@ signing.wont_sign.commitssigned=Слияние не будет подписан
signing.wont_sign.approved=Слияние не будет подписано, так как запрос на слияние не одобрен.
signing.wont_sign.not_signed_in=Вы не вошли в систему.
ext_wiki=Доступ ко внешней вики
ext_wiki=Внешняя вики
ext_wiki.desc=Ссылка на внешнюю вики.
wiki=Вики
@ -3332,7 +3332,7 @@ config.allow_only_external_registration=Регистрация только че
config.enable_openid_signup=Саморегистрация через OpenID
config.enable_openid_signin=Вход через OpenID
config.show_registration_button=Кнопка регистрации
config.require_sign_in_view=Для просмотра содержимого необходима авторизация
config.require_sign_in_view=Требовать авторизацию для просмотра содержимого
config.mail_notify=Уведомления по эл. почте
config.enable_captcha=CAPTCHA
config.active_code_lives=Срок действия кода активации учётной записи
@ -3963,4 +3963,8 @@ filepreview.lines = Строки с %[1]d по %[2]d в %[3]s
filepreview.truncated = Предпросмотр был обрезан
[translation_meta]
test = хи-хи!
test = хи-хи!
[repo.permissions]
code.write = <b>Запись:</b> отправка изменений в репозиторий, создание веток и тегов.
code.read = <b>Чтение:</b> доступ к исходному коду репозитория и клонированию.

View file

@ -1427,7 +1427,7 @@ commitstatus.failure=失败
commitstatus.pending=待定
commitstatus.success=成功
ext_issues=访问外部工单
ext_issues=外部工单
ext_issues.desc=链接到外部工单跟踪系统。
projects=项目
@ -1608,9 +1608,9 @@ issues.no_content=没有提供说明。
issues.close=关闭工单
issues.comment_pull_merged_at=已合并提交 %[1]s 到 %[2]s %[3]s
issues.comment_manually_pull_merged_at=手动合并提交 %[1]s 到 %[2]s %[3]s
issues.close_comment_issue=评论并关闭
issues.close_comment_issue=关闭评论
issues.reopen_issue=重新开启
issues.reopen_comment_issue=评论并重新开启
issues.reopen_comment_issue=重新打开评论
issues.create_comment=评论
issues.closed_at=`于 <a id="%[1]s" href="#%[1]s">%[2]s</a> 关闭此工单`
issues.reopened_at=`重新打开此问题 <a id="%[1]s" href="#%[1]s">%[2]s</a>`
@ -1999,7 +1999,7 @@ signing.wont_sign.commitssigned=合并将不会被签名,因为所有相关的
signing.wont_sign.approved=合并将不会被签名,因为合并请求未被批准。
signing.wont_sign.not_signed_in=您还没有登录。
ext_wiki=访问外部百科
ext_wiki=外部百科
ext_wiki.desc=链接到外部 wiki。
wiki=百科
@ -2330,7 +2330,7 @@ settings.event_repository_desc=创建或删除仓库
settings.event_header_issue=工单事件
settings.event_issues=工单
settings.event_issues_desc=工单已打开、已关闭、已重新打开或已编辑。
settings.event_issue_assign=工单已分配
settings.event_issue_assign=工单已指派
settings.event_issue_assign_desc=工单已被指派或取消指派。
settings.event_issue_label=工单已分类
settings.event_issue_label_desc=工单标签被更新或清除。
@ -3818,7 +3818,7 @@ management=密钥管理
[actions]
actions=Actions
unit.desc=使用 Forgejo Actions 管理集成的 CI/CD 管道
unit.desc=使用 Forgejo Actions 管理集成的 CI/CD 管道
status.unknown=未知
status.waiting=等待中
@ -3980,4 +3980,24 @@ filepreview.lines = %[3]s 中的第 %[1]d 到 %[2]d 行
filepreview.truncated = 预览已被截断
[translation_meta]
test = 好的
test = 好的
[repo.permissions]
code.write = <b>写入:</b>推送到仓库,创建分支和标签。
code.read = <b>读取:</b>访问并克隆仓库的代码。
actions.read = <b>读取:</b>查看集成的 CI/CD 管道及其日志。
issues.write = <b>写入:</b>关闭工单并管理元数据,如标签、里程碑、指派成员、截止日期和依赖。
releases.write = <b>写入:</b>发布、编辑和删除版本发布及其资产。
issues.read = <b>读取:</b>阅读并创建工单和评论。
pulls.read = <b>读取:</b>阅读并创建合并请求。
releases.read = <b>读取:</b>查看并下载版本发布。
wiki.read = <b>读取:</b>阅读集成的百科及其历史。
wiki.write = <b>写入:</b>在集成的百科中创建、更新和删除页面。
projects.read = <b>读取:</b>访问仓库项目看板。
packages.read = <b>读取:</b>查看并下载指派给仓库的软件包。
packages.write = <b>写入:</b>发布并删除指派给仓库的软件包。
actions.write = <b>写入:</b>手动触发、重启、取消或批准待处理的 CI/CD 管道。
ext_issues = 访问外部工单系统的链接。权限由外部管理。
ext_wiki = 访问外部百科的链接。权限由外部管理。
projects.write = <b>写入:</b>创建项目和列并进行编辑。
pulls.write = <b>写入:</b>关闭合并请求并管理元数据,如标签、里程碑、指派成员、截止日期和依赖。

2048
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,8 +4,8 @@
"node": ">= 18.0.0"
},
"dependencies": {
"@citation-js/core": "0.7.11",
"@citation-js/plugin-bibtex": "0.7.11",
"@citation-js/core": "0.7.14",
"@citation-js/plugin-bibtex": "0.7.16",
"@citation-js/plugin-software-formats": "0.6.1",
"@github/markdown-toolbar-element": "2.2.3",
"@github/relative-time-element": "4.4.3",
@ -29,10 +29,10 @@
"idiomorph": "0.3.0",
"jquery": "3.7.1",
"katex": "0.16.11",
"mermaid": "11.2.0",
"mermaid": "11.2.1",
"mini-css-extract-plugin": "2.9.1",
"minimatch": "10.0.1",
"monaco-editor": "0.50.0",
"monaco-editor": "0.51.0",
"monaco-editor-webpack-plugin": "7.1.0",
"pdfobject": "2.3.0",
"postcss": "8.4.47",
@ -41,7 +41,7 @@
"pretty-ms": "9.0.0",
"sortablejs": "1.15.3",
"swagger-ui-dist": "5.17.14",
"tailwindcss": "3.4.11",
"tailwindcss": "3.4.13",
"temporal-polyfill": "0.2.4",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0",
@ -50,11 +50,11 @@
"tributejs": "5.1.3",
"uint8-to-base64": "0.2.0",
"vanilla-colorful": "0.7.2",
"vue": "3.5.6",
"vue": "3.5.10",
"vue-chartjs": "5.3.1",
"vue-loader": "17.4.2",
"vue3-calendar-heatmap": "2.0.5",
"webpack": "5.94.0",
"webpack": "5.95.0",
"webpack-cli": "5.1.4",
"wrap-ansi": "9.0.0"
},
@ -62,11 +62,12 @@
"@axe-core/playwright": "4.10.0",
"@eslint-community/eslint-plugin-eslint-comments": "4.4.0",
"@playwright/test": "1.47.2",
"@stoplight/spectral-cli": "6.13.0",
"@stoplight/spectral-cli": "6.13.1",
"@stylistic/eslint-plugin-js": "2.8.0",
"@stylistic/stylelint-plugin": "3.0.1",
"@vitejs/plugin-vue": "5.1.3",
"@stylistic/stylelint-plugin": "3.1.0",
"@vitejs/plugin-vue": "5.1.4",
"@vitest/coverage-v8": "2.1.1",
"@vitest/eslint-plugin": "1.1.4",
"@vue/test-utils": "2.4.6",
"eslint": "8.57.1",
"eslint-plugin-array-func": "4.0.0",
@ -78,14 +79,13 @@
"eslint-plugin-regexp": "2.6.0",
"eslint-plugin-sonarjs": "2.0.2",
"eslint-plugin-unicorn": "55.0.0",
"eslint-plugin-vitest": "0.5.4",
"eslint-plugin-vitest-globals": "1.5.0",
"eslint-plugin-vue": "9.28.0",
"eslint-plugin-vue-scoped-css": "2.8.1",
"eslint-plugin-wc": "2.1.1",
"happy-dom": "15.7.4",
"license-checker-rseidelsohn": "4.4.2",
"markdownlint-cli": "0.41.0",
"markdownlint-cli": "0.42.0",
"postcss-html": "1.7.0",
"stylelint": "16.9.0",
"stylelint-declaration-block-no-ignored-properties": "2.8.0",

191
poetry.lock generated
View file

@ -214,104 +214,105 @@ files = [
[[package]]
name = "regex"
version = "2023.12.25"
version = "2024.9.11"
description = "Alternative regular expression module, to replace re."
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"},
{file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"},
{file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"},
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"},
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"},
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"},
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"},
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"},
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"},
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"},
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"},
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"},
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"},
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"},
{file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"},
{file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"},
{file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"},
{file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"},
{file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"},
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"},
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"},
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"},
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"},
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"},
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"},
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"},
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"},
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"},
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"},
{file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"},
{file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"},
{file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"},
{file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"},
{file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"},
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"},
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"},
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"},
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"},
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"},
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"},
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"},
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"},
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"},
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"},
{file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"},
{file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"},
{file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"},
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"},
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"},
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"},
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"},
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"},
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"},
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"},
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"},
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"},
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"},
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"},
{file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"},
{file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"},
{file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"},
{file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"},
{file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"},
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"},
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"},
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"},
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"},
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"},
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"},
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"},
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"},
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"},
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"},
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"},
{file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"},
{file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"},
{file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"},
{file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"},
{file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"},
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"},
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"},
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"},
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"},
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"},
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"},
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"},
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"},
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"},
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"},
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"},
{file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"},
{file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"},
{file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"},
{file = "regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408"},
{file = "regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d"},
{file = "regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5"},
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c"},
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8"},
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35"},
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71"},
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8"},
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a"},
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d"},
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137"},
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6"},
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca"},
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a"},
{file = "regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0"},
{file = "regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623"},
{file = "regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df"},
{file = "regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268"},
{file = "regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad"},
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679"},
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4"},
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664"},
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50"},
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199"},
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4"},
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd"},
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f"},
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96"},
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1"},
{file = "regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9"},
{file = "regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf"},
{file = "regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7"},
{file = "regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231"},
{file = "regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d"},
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64"},
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42"},
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766"},
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a"},
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9"},
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d"},
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822"},
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0"},
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a"},
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a"},
{file = "regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776"},
{file = "regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009"},
{file = "regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784"},
{file = "regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36"},
{file = "regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92"},
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86"},
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85"},
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963"},
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6"},
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802"},
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29"},
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8"},
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84"},
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554"},
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8"},
{file = "regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8"},
{file = "regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f"},
{file = "regex-2024.9.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:35f4a6f96aa6cb3f2f7247027b07b15a374f0d5b912c0001418d1d55024d5cb4"},
{file = "regex-2024.9.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b96e7ce3a69a8449a66984c268062fbaa0d8ae437b285428e12797baefce7e"},
{file = "regex-2024.9.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb130fccd1a37ed894824b8c046321540263013da72745d755f2d35114b81a60"},
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:323c1f04be6b2968944d730e5c2091c8c89767903ecaa135203eec4565ed2b2b"},
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be1c8ed48c4c4065ecb19d882a0ce1afe0745dfad8ce48c49586b90a55f02366"},
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5b029322e6e7b94fff16cd120ab35a253236a5f99a79fb04fda7ae71ca20ae8"},
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6fff13ef6b5f29221d6904aa816c34701462956aa72a77f1f151a8ec4f56aeb"},
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d4af3979376652010e400accc30404e6c16b7df574048ab1f581af82065e4"},
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:079400a8269544b955ffa9e31f186f01d96829110a3bf79dc338e9910f794fca"},
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f9268774428ec173654985ce55fc6caf4c6d11ade0f6f914d48ef4719eb05ebb"},
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:23f9985c8784e544d53fc2930fc1ac1a7319f5d5332d228437acc9f418f2f168"},
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2941333154baff9838e88aa71c1d84f4438189ecc6021a12c7573728b5838e"},
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e93f1c331ca8e86fe877a48ad64e77882c0c4da0097f2212873a69bbfea95d0c"},
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:846bc79ee753acf93aef4184c040d709940c9d001029ceb7b7a52747b80ed2dd"},
{file = "regex-2024.9.11-cp38-cp38-win32.whl", hash = "sha256:c94bb0a9f1db10a1d16c00880bdebd5f9faf267273b8f5bd1878126e0fbde771"},
{file = "regex-2024.9.11-cp38-cp38-win_amd64.whl", hash = "sha256:2b08fce89fbd45664d3df6ad93e554b6c16933ffa9d55cb7e01182baaf971508"},
{file = "regex-2024.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066"},
{file = "regex-2024.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62"},
{file = "regex-2024.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16"},
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3"},
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199"},
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8"},
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca"},
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9"},
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a"},
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39"},
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba"},
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664"},
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89"},
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35"},
{file = "regex-2024.9.11-cp39-cp39-win32.whl", hash = "sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142"},
{file = "regex-2024.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919"},
{file = "regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd"},
]
[[package]]

5
release-notes/5372.md Normal file
View file

@ -0,0 +1,5 @@
feat: [commit](https://codeberg.org/forgejo/forgejo/commit/9d3473119893ffde0ab36d98e7a0e41c5d0ba9a3) Add bin to Composer Metadata.
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/f709de24039ab7e605d3e09e3b61240836381603) Fix wrong last modify time.
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/2675a24649af2fff34f5c7e416d6ff78591d8d9c) Repo Activity: count new issues that were closed.
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/526054332acb221e061d3900bba2dc6e012da52d) Fix incorrect /tokens api.
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/0cafec4c7a2faf810953e9d522faf5dc019e1522) Do not escape relative path in RPM primary index.

View file

@ -175,18 +175,20 @@ func CommonRoutes() *web.Route {
arch.PushPackage(ctx)
return
} else if isDelete {
if groupLen < 2 {
if groupLen < 3 {
ctx.Status(http.StatusBadRequest)
return
}
if groupLen == 2 {
if groupLen == 3 {
ctx.SetParams("group", "")
ctx.SetParams("package", pathGroups[0])
ctx.SetParams("version", pathGroups[1])
ctx.SetParams("arch", pathGroups[2])
} else {
ctx.SetParams("group", strings.Join(pathGroups[:groupLen-2], "/"))
ctx.SetParams("package", pathGroups[groupLen-2])
ctx.SetParams("version", pathGroups[groupLen-1])
ctx.SetParams("group", strings.Join(pathGroups[:groupLen-3], "/"))
ctx.SetParams("package", pathGroups[groupLen-3])
ctx.SetParams("version", pathGroups[groupLen-2])
ctx.SetParams("arch", pathGroups[groupLen-1])
}
reqPackageAccess(perm.AccessModeWrite)(ctx)
if ctx.Written() {

View file

@ -9,12 +9,14 @@ import (
"fmt"
"io"
"net/http"
"path/filepath"
"regexp"
"strings"
packages_model "code.gitea.io/gitea/models/packages"
packages_module "code.gitea.io/gitea/modules/packages"
arch_module "code.gitea.io/gitea/modules/packages/arch"
"code.gitea.io/gitea/modules/sync"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
@ -25,6 +27,8 @@ import (
var (
archPkgOrSig = regexp.MustCompile(`^.*\.pkg\.tar\.\w+(\.sig)*$`)
archDBOrSig = regexp.MustCompile(`^.*.db(\.tar\.gz)*(\.sig)*$`)
locker = sync.NewExclusivePool()
)
func apiError(ctx *context.Context, status int, obj any) {
@ -33,6 +37,14 @@ func apiError(ctx *context.Context, status int, obj any) {
})
}
func refreshLocker(ctx *context.Context, group string) func() {
key := fmt.Sprintf("pkg_%d_arch_pkg_%s", ctx.Package.Owner.ID, group)
locker.CheckIn(key)
return func() {
locker.CheckOut(key)
}
}
func GetRepositoryKey(ctx *context.Context) {
_, pub, err := arch_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID)
if err != nil {
@ -48,7 +60,8 @@ func GetRepositoryKey(ctx *context.Context) {
func PushPackage(ctx *context.Context) {
group := ctx.Params("group")
releaser := refreshLocker(ctx, group)
defer releaser()
upload, needToClose, err := ctx.UploadStream()
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@ -154,6 +167,7 @@ func PushPackage(ctx *context.Context) {
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
if err = arch_service.BuildPacmanDB(ctx, ctx.Package.Owner.ID, group, p.FileMetadata.Arch); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@ -169,7 +183,7 @@ func GetPackageOrDB(ctx *context.Context) {
arch = ctx.Params("arch")
)
if archPkgOrSig.MatchString(file) {
pkg, err := arch_service.GetPackageFile(ctx, group, file, ctx.Package.Owner.ID)
pkg, u, pf, err := arch_service.GetPackageFile(ctx, group, file, ctx.Package.Owner.ID)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
@ -178,15 +192,12 @@ func GetPackageOrDB(ctx *context.Context) {
}
return
}
ctx.ServeContent(pkg, &context.ServeHeaderOptions{
Filename: file,
})
helper.ServePackageFile(ctx, pkg, u, pf)
return
}
if archDBOrSig.MatchString(file) {
pkg, err := arch_service.GetPackageDBFile(ctx, group, arch, ctx.Package.Owner.ID,
pkg, u, pf, err := arch_service.GetPackageDBFile(ctx, group, arch, ctx.Package.Owner.ID,
strings.HasSuffix(file, ".sig"))
if err != nil {
if errors.Is(err, util.ErrNotExist) {
@ -196,9 +207,7 @@ func GetPackageOrDB(ctx *context.Context) {
}
return
}
ctx.ServeContent(pkg, &context.ServeHeaderOptions{
Filename: file,
})
helper.ServePackageFile(ctx, pkg, u, pf)
return
}
@ -207,10 +216,13 @@ func GetPackageOrDB(ctx *context.Context) {
func RemovePackage(ctx *context.Context) {
var (
group = ctx.Params("group")
pkg = ctx.Params("package")
ver = ctx.Params("version")
group = ctx.Params("group")
pkg = ctx.Params("package")
ver = ctx.Params("version")
pkgArch = ctx.Params("arch")
)
releaser := refreshLocker(ctx, group)
defer releaser()
pv, err := packages_model.GetVersionByNameAndVersion(
ctx, ctx.Package.Owner.ID, packages_model.TypeArch, pkg, ver,
)
@ -229,7 +241,13 @@ func RemovePackage(ctx *context.Context) {
}
deleted := false
for _, file := range files {
if file.CompositeKey == group {
extName := fmt.Sprintf("-%s.pkg.tar%s", pkgArch, filepath.Ext(file.LowerName))
if strings.HasSuffix(file.LowerName, ".sig") {
extName = fmt.Sprintf("-%s.pkg.tar%s.sig", pkgArch,
filepath.Ext(strings.TrimSuffix(file.LowerName, filepath.Ext(file.LowerName))))
}
if file.CompositeKey == group &&
strings.HasSuffix(file.LowerName, extName) {
deleted = true
err := packages_service.RemovePackageFileAndVersionIfUnreferenced(ctx, ctx.ContextUser, file)
if err != nil {
@ -242,6 +260,7 @@ func RemovePackage(ctx *context.Context) {
err = arch_service.BuildCustomRepositoryFiles(ctx, ctx.Package.Owner.ID, group)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
ctx.Status(http.StatusNoContent)
} else {

View file

@ -117,7 +117,9 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
xmlMetadataWithHeader := append([]byte(xml.Header), xmlMetadata...)
latest := pds[len(pds)-1]
ctx.Resp.Header().Set("Last-Modified", latest.Version.CreatedUnix.Format(http.TimeFormat))
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
lastModifed := latest.Version.CreatedUnix.AsTime().UTC().Format(http.TimeFormat)
ctx.Resp.Header().Set("Last-Modified", lastModifed)
ext := strings.ToLower(filepath.Ext(params.Filename))
if isChecksumExtension(ext) {

View file

@ -839,10 +839,16 @@ func EditIssue(ctx *context.APIContext) {
if (form.Deadline != nil || form.RemoveDeadline != nil) && canWrite {
var deadlineUnix timeutil.TimeStamp
if (form.RemoveDeadline == nil || !*form.RemoveDeadline) && !form.Deadline.IsZero() {
deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
23, 59, 59, 0, form.Deadline.Location())
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
if form.RemoveDeadline == nil || !*form.RemoveDeadline {
if form.Deadline == nil {
ctx.Error(http.StatusBadRequest, "", "The due_date cannot be empty")
return
}
if !form.Deadline.IsZero() {
deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
23, 59, 59, 0, form.Deadline.Location())
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
}
}
if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {

View file

@ -118,6 +118,10 @@ func CreateAccessToken(ctx *context.APIContext) {
ctx.Error(http.StatusBadRequest, "AccessTokenScope.Normalize", fmt.Errorf("invalid access token scope provided: %w", err))
return
}
if scope == "" {
ctx.Error(http.StatusBadRequest, "AccessTokenScope", "access token must have a scope")
return
}
t.Scope = scope
if err := auth_model.NewAccessToken(ctx, t); err != nil {
@ -129,6 +133,7 @@ func CreateAccessToken(ctx *context.APIContext) {
Token: t.Token,
ID: t.ID,
TokenLastEight: t.TokenLastEight,
Scopes: t.Scope.StringSlice(),
})
}

View file

@ -395,7 +395,8 @@ func (h *serviceHandler) sendFile(ctx *context.Context, contentType, file string
ctx.Resp.Header().Set("Content-Type", contentType)
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
ctx.Resp.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
ctx.Resp.Header().Set("Last-Modified", fi.ModTime().UTC().Format(http.TimeFormat))
http.ServeFile(ctx.Resp, ctx.Req, reqFile)
}

View file

@ -132,6 +132,8 @@ func webAuth(authMethod auth_service.Method) func(*context.Context) {
// ensure the session uid is deleted
_ = ctx.Session.Delete("uid")
}
ctx.Csrf.PrepareForSessionUser(ctx)
}
}

View file

@ -127,10 +127,8 @@ func Contexter() func(next http.Handler) http.Handler {
csrfOpts := CsrfOptions{
Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()),
Cookie: setting.CSRFCookieName,
SetCookie: true,
Secure: setting.SessionConfig.Secure,
CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
Header: "X-Csrf-Token",
CookieDomain: setting.SessionConfig.Domain,
CookiePath: setting.SessionConfig.CookiePath,
SameSite: setting.SessionConfig.SameSite,
@ -156,7 +154,7 @@ func Contexter() func(next http.Handler) http.Handler {
ctx.Base.AppendContextValue(WebContextKey, ctx)
ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
ctx.Csrf = PrepareCSRFProtector(csrfOpts, ctx)
ctx.Csrf = NewCSRFProtector(csrfOpts)
// Get the last flash message from cookie
lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash)
@ -193,8 +191,6 @@ func Contexter() func(next http.Handler) http.Handler {
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
ctx.Data["SystemConfig"] = setting.Config()
ctx.Data["CsrfToken"] = ctx.Csrf.GetToken()
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations

View file

@ -20,64 +20,43 @@
package context
import (
"encoding/base32"
"fmt"
"html/template"
"net/http"
"strconv"
"time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/middleware"
)
const (
CsrfHeaderName = "X-Csrf-Token"
CsrfFormName = "_csrf"
CsrfErrorString = "Invalid CSRF token."
)
// CSRFProtector represents a CSRF protector and is used to get the current token and validate the token.
type CSRFProtector interface {
// GetHeaderName returns HTTP header to search for token.
GetHeaderName() string
// GetFormName returns form value to search for token.
GetFormName() string
// GetToken returns the token.
GetToken() string
// Validate validates the token in http context.
// PrepareForSessionUser prepares the csrf protector for the current session user.
PrepareForSessionUser(ctx *Context)
// Validate validates the csrf token in http context.
Validate(ctx *Context)
// DeleteCookie deletes the cookie
// DeleteCookie deletes the csrf cookie
DeleteCookie(ctx *Context)
}
type csrfProtector struct {
opt CsrfOptions
// Token generated to pass via header, cookie, or hidden form value.
Token string
// This value must be unique per user.
ID string
}
// GetHeaderName returns the name of the HTTP header for csrf token.
func (c *csrfProtector) GetHeaderName() string {
return c.opt.Header
}
// GetFormName returns the name of the form value for csrf token.
func (c *csrfProtector) GetFormName() string {
return c.opt.Form
}
// GetToken returns the current token. This is typically used
// to populate a hidden form in an HTML template.
func (c *csrfProtector) GetToken() string {
return c.Token
// id must be unique per user.
id string
// token is the valid one which wil be used by end user and passed via header, cookie, or hidden form value.
token string
}
// CsrfOptions maintains options to manage behavior of Generate.
type CsrfOptions struct {
// The global secret value used to generate Tokens.
Secret string
// HTTP header used to set and get token.
Header string
// Form value used to set and get token.
Form string
// Cookie value used to set and get token.
Cookie string
// Cookie domain.
@ -87,103 +66,64 @@ type CsrfOptions struct {
CookieHTTPOnly bool
// SameSite set the cookie SameSite type
SameSite http.SameSite
// Key used for getting the unique ID per user.
SessionKey string
// oldSessionKey saves old value corresponding to SessionKey.
oldSessionKey string
// If true, send token via X-Csrf-Token header.
SetHeader bool
// If true, send token via _csrf cookie.
SetCookie bool
// Set the Secure flag to true on the cookie.
Secure bool
// Disallow Origin appear in request header.
Origin bool
// Cookie lifetime. Default is 0
CookieLifeTime int
// sessionKey is the key used for getting the unique ID per user.
sessionKey string
// oldSessionKey saves old value corresponding to sessionKey.
oldSessionKey string
}
func prepareDefaultCsrfOptions(opt CsrfOptions) CsrfOptions {
if opt.Secret == "" {
randBytes, err := util.CryptoRandomBytes(8)
if err != nil {
// this panic can be handled by the recover() in http handlers
panic(fmt.Errorf("failed to generate random bytes: %w", err))
}
opt.Secret = base32.StdEncoding.EncodeToString(randBytes)
}
if opt.Header == "" {
opt.Header = "X-Csrf-Token"
}
if opt.Form == "" {
opt.Form = "_csrf"
}
if opt.Cookie == "" {
opt.Cookie = "_csrf"
}
if opt.CookiePath == "" {
opt.CookiePath = "/"
}
if opt.SessionKey == "" {
opt.SessionKey = "uid"
}
if opt.CookieLifeTime == 0 {
opt.CookieLifeTime = int(CsrfTokenTimeout.Seconds())
}
opt.oldSessionKey = "_old_" + opt.SessionKey
return opt
}
func newCsrfCookie(c *csrfProtector, value string) *http.Cookie {
func newCsrfCookie(opt *CsrfOptions, value string) *http.Cookie {
return &http.Cookie{
Name: c.opt.Cookie,
Name: opt.Cookie,
Value: value,
Path: c.opt.CookiePath,
Domain: c.opt.CookieDomain,
MaxAge: c.opt.CookieLifeTime,
Secure: c.opt.Secure,
HttpOnly: c.opt.CookieHTTPOnly,
SameSite: c.opt.SameSite,
Path: opt.CookiePath,
Domain: opt.CookieDomain,
MaxAge: int(CsrfTokenTimeout.Seconds()),
Secure: opt.Secure,
HttpOnly: opt.CookieHTTPOnly,
SameSite: opt.SameSite,
}
}
// PrepareCSRFProtector returns a CSRFProtector to be used for every request.
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
func PrepareCSRFProtector(opt CsrfOptions, ctx *Context) CSRFProtector {
opt = prepareDefaultCsrfOptions(opt)
x := &csrfProtector{opt: opt}
if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
return x
func NewCSRFProtector(opt CsrfOptions) CSRFProtector {
if opt.Secret == "" {
panic("CSRF secret is empty but it must be set") // it shouldn't happen because it is always set in code
}
opt.Cookie = util.IfZero(opt.Cookie, "_csrf")
opt.CookiePath = util.IfZero(opt.CookiePath, "/")
opt.sessionKey = "uid"
opt.oldSessionKey = "_old_" + opt.sessionKey
return &csrfProtector{opt: opt}
}
x.ID = "0"
uidAny := ctx.Session.Get(opt.SessionKey)
if uidAny != nil {
func (c *csrfProtector) PrepareForSessionUser(ctx *Context) {
c.id = "0"
if uidAny := ctx.Session.Get(c.opt.sessionKey); uidAny != nil {
switch uidVal := uidAny.(type) {
case string:
x.ID = uidVal
c.id = uidVal
case int64:
x.ID = strconv.FormatInt(uidVal, 10)
c.id = strconv.FormatInt(uidVal, 10)
default:
log.Error("invalid uid type in session: %T", uidAny)
}
}
oldUID := ctx.Session.Get(opt.oldSessionKey)
uidChanged := oldUID == nil || oldUID.(string) != x.ID
cookieToken := ctx.GetSiteCookie(opt.Cookie)
oldUID := ctx.Session.Get(c.opt.oldSessionKey)
uidChanged := oldUID == nil || oldUID.(string) != c.id
cookieToken := ctx.GetSiteCookie(c.opt.Cookie)
needsNew := true
if uidChanged {
_ = ctx.Session.Set(opt.oldSessionKey, x.ID)
_ = ctx.Session.Set(c.opt.oldSessionKey, c.id)
} else if cookieToken != "" {
// If cookie token presents, reuse existing unexpired token, else generate a new one.
if issueTime, ok := ParseCsrfToken(cookieToken); ok {
dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time.
if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval {
x.Token = cookieToken
c.token = cookieToken
needsNew = false
}
}
@ -191,42 +131,33 @@ func PrepareCSRFProtector(opt CsrfOptions, ctx *Context) CSRFProtector {
if needsNew {
// FIXME: actionId.
x.Token = GenerateCsrfToken(x.opt.Secret, x.ID, "POST", time.Now())
if opt.SetCookie {
cookie := newCsrfCookie(x, x.Token)
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
}
c.token = GenerateCsrfToken(c.opt.Secret, c.id, "POST", time.Now())
cookie := newCsrfCookie(&c.opt, c.token)
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
}
if opt.SetHeader {
ctx.Resp.Header().Add(opt.Header, x.Token)
}
return x
ctx.Data["CsrfToken"] = c.token
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + template.HTMLEscapeString(c.token) + `">`)
}
func (c *csrfProtector) validateToken(ctx *Context, token string) {
if !ValidCsrfToken(token, c.opt.Secret, c.ID, "POST", time.Now()) {
if !ValidCsrfToken(token, c.opt.Secret, c.id, "POST", time.Now()) {
c.DeleteCookie(ctx)
if middleware.IsAPIPath(ctx.Req) {
// currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
http.Error(ctx.Resp, "Invalid CSRF token.", http.StatusBadRequest)
} else {
ctx.Flash.Error(ctx.Tr("error.invalid_csrf"))
ctx.Redirect(setting.AppSubURL + "/")
}
// currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
// FIXME: distinguish what the response is for: HTML (web page) or JSON (fetch)
http.Error(ctx.Resp, CsrfErrorString, http.StatusBadRequest)
}
}
// Validate should be used as a per route middleware. It attempts to get a token from an "X-Csrf-Token"
// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated.
// If this validation fails, custom Error is sent in the reply.
// If neither a header nor form value is found, http.StatusBadRequest is sent.
// If this validation fails, http.StatusBadRequest is sent.
func (c *csrfProtector) Validate(ctx *Context) {
if token := ctx.Req.Header.Get(c.GetHeaderName()); token != "" {
if token := ctx.Req.Header.Get(CsrfHeaderName); token != "" {
c.validateToken(ctx, token)
return
}
if token := ctx.Req.FormValue(c.GetFormName()); token != "" {
if token := ctx.Req.FormValue(CsrfFormName); token != "" {
c.validateToken(ctx, token)
return
}
@ -234,9 +165,7 @@ func (c *csrfProtector) Validate(ctx *Context) {
}
func (c *csrfProtector) DeleteCookie(ctx *Context) {
if c.opt.SetCookie {
cookie := newCsrfCookie(c, "")
cookie.MaxAge = -1
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
}
cookie := newCsrfCookie(&c.opt, "")
cookie.MaxAge = -1
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
}

View file

@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"io"
"net/url"
"os"
"path/filepath"
"sort"
@ -20,6 +21,7 @@ import (
packages_module "code.gitea.io/gitea/modules/packages"
arch_module "code.gitea.io/gitea/modules/packages/arch"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sync"
"code.gitea.io/gitea/modules/util"
packages_service "code.gitea.io/gitea/services/packages"
@ -28,6 +30,8 @@ import (
"github.com/ProtonMail/go-crypto/openpgp/packet"
)
var locker = sync.NewExclusivePool()
func GetOrCreateRepositoryVersion(ctx context.Context, ownerID int64) (*packages_model.PackageVersion, error) {
return packages_service.GetOrCreateInternalPackageVersion(ctx, ownerID, packages_model.TypeArch, arch_module.RepositoryPackage, arch_module.RepositoryVersion)
}
@ -101,6 +105,9 @@ func NewFileSign(ctx context.Context, ownerID int64, input io.Reader) (*packages
// BuildPacmanDB Create db signature cache
func BuildPacmanDB(ctx context.Context, ownerID int64, group, arch string) error {
key := fmt.Sprintf("pkg_%d_arch_db_%s", ownerID, group)
locker.CheckIn(key)
defer locker.CheckOut(key)
pv, err := GetOrCreateRepositoryVersion(ctx, ownerID)
if err != nil {
return err
@ -173,15 +180,18 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages
if err != nil {
return nil, err
}
defer db.Close()
gw := gzip.NewWriter(db)
defer gw.Close()
tw := tar.NewWriter(gw)
defer tw.Close()
count := 0
for _, pkg := range pkgs {
versions, err := packages_model.GetVersionsByPackageName(
ctx, ownerID, packages_model.TypeArch, pkg.Name,
)
if err != nil {
return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err)
return nil, err
}
sort.Slice(versions, func(i, j int) bool {
return versions[i].CreatedUnix > versions[j].CreatedUnix
@ -190,7 +200,7 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages
for _, ver := range versions {
files, err := packages_model.GetFilesByVersionID(ctx, ver.ID)
if err != nil {
return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err)
return nil, err
}
var pf *packages_model.PackageFile
for _, file := range files {
@ -213,7 +223,7 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages
ctx, packages_model.PropertyTypeFile, pf.ID, arch_module.PropertyDescription,
)
if err != nil {
return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err)
return nil, err
}
if len(pps) >= 1 {
meta := []byte(pps[0].Value)
@ -223,60 +233,50 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages
Mode: int64(os.ModePerm),
}
if err = tw.WriteHeader(header); err != nil {
return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err)
return nil, err
}
if _, err := tw.Write(meta); err != nil {
return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err)
return nil, err
}
count++
break
}
}
}
defer gw.Close()
defer tw.Close()
if count == 0 {
return nil, errors.Join(db.Close(), io.EOF)
return nil, io.EOF
}
return db, nil
}
// GetPackageFile Get data related to provided filename and distribution, for package files
// update download counter.
func GetPackageFile(ctx context.Context, group, file string, ownerID int64) (io.ReadSeekCloser, error) {
pf, err := getPackageFile(ctx, group, file, ownerID)
if err != nil {
return nil, err
func GetPackageFile(ctx context.Context, group, file string, ownerID int64) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
fileSplit := strings.Split(file, "-")
if len(fileSplit) <= 3 {
return nil, nil, nil, errors.New("invalid file format, need <name>-<version>-<release>-<arch>.pkg.<archive>")
}
filestream, _, _, err := packages_service.GetPackageFileStream(ctx, pf)
return filestream, err
}
// Ejects parameters required to get package file property from file name.
func getPackageFile(ctx context.Context, group, file string, ownerID int64) (*packages_model.PackageFile, error) {
var (
splt = strings.Split(file, "-")
pkgname = strings.Join(splt[0:len(splt)-3], "-")
vername = splt[len(splt)-3] + "-" + splt[len(splt)-2]
pkgName = strings.Join(fileSplit[0:len(fileSplit)-3], "-")
pkgVer = fileSplit[len(fileSplit)-3] + "-" + fileSplit[len(fileSplit)-2]
)
version, err := packages_model.GetVersionByNameAndVersion(ctx, ownerID, packages_model.TypeArch, pkgname, vername)
version, err := packages_model.GetVersionByNameAndVersion(ctx, ownerID, packages_model.TypeArch, pkgName, pkgVer)
if err != nil {
return nil, err
return nil, nil, nil, err
}
pkgfile, err := packages_model.GetFileForVersionByName(ctx, version.ID, file, group)
pkgFile, err := packages_model.GetFileForVersionByName(ctx, version.ID, file, group)
if err != nil {
return nil, err
return nil, nil, nil, err
}
return pkgfile, nil
return packages_service.GetPackageFileStream(ctx, pkgFile)
}
func GetPackageDBFile(ctx context.Context, group, arch string, ownerID int64, signFile bool) (io.ReadSeekCloser, error) {
func GetPackageDBFile(ctx context.Context, group, arch string, ownerID int64, signFile bool) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
pv, err := GetOrCreateRepositoryVersion(ctx, ownerID)
if err != nil {
return nil, err
return nil, nil, nil, err
}
fileName := fmt.Sprintf("%s.db", arch)
if signFile {
@ -284,10 +284,9 @@ func GetPackageDBFile(ctx context.Context, group, arch string, ownerID int64, si
}
file, err := packages_model.GetFileForVersionByName(ctx, pv.ID, fileName, group)
if err != nil {
return nil, err
return nil, nil, nil, err
}
filestream, _, _, err := packages_service.GetPackageFileStream(ctx, file)
return filestream, err
return packages_service.GetPackageFileStream(ctx, file)
}
// GetOrCreateKeyPair gets or creates the PGP keys used to sign repository metadata files

View file

@ -0,0 +1,116 @@
// Copyright 2024 The Forgejo Authors.
// SPDX-License-Identifier: GPL-3.0-or-later
package container
import (
"testing"
"time"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
container_module "code.gitea.io/gitea/modules/packages/container"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/timeutil"
container_service "code.gitea.io/gitea/services/packages/container"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCleanupSHA256(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
defer test.MockVariableValue(&container_service.SHA256BatchSize, 1)()
ctx := db.DefaultContext
createContainer := func(t *testing.T, name, version, digest string, created timeutil.TimeStamp) {
t.Helper()
ownerID := int64(2001)
p := packages.Package{
OwnerID: ownerID,
LowerName: name,
Type: packages.TypeContainer,
}
_, err := db.GetEngine(ctx).Insert(&p)
// package_version").Where("version = ?", multiTag).Update(&packages_model.PackageVersion{MetadataJSON: `corrupted "manifests":[{ bad`})
require.NoError(t, err)
var metadata string
if digest != "" {
m := container_module.Metadata{
Manifests: []*container_module.Manifest{
{
Digest: digest,
},
},
}
mt, err := json.Marshal(m)
require.NoError(t, err)
metadata = string(mt)
}
v := packages.PackageVersion{
PackageID: p.ID,
LowerVersion: version,
MetadataJSON: metadata,
CreatedUnix: created,
}
_, err = db.GetEngine(ctx).NoAutoTime().Insert(&v)
require.NoError(t, err)
}
cleanupAndCheckLogs := func(t *testing.T, olderThan time.Duration, expected ...string) {
t.Helper()
logChecker, cleanup := test.NewLogChecker(log.DEFAULT, log.TRACE)
logChecker.Filter(expected...)
logChecker.StopMark(container_service.SHA256LogFinish)
defer cleanup()
require.NoError(t, CleanupExpiredData(ctx, olderThan))
logFiltered, logStopped := logChecker.Check(5 * time.Second)
assert.True(t, logStopped)
filtered := make([]bool, 0, len(expected))
for range expected {
filtered = append(filtered, true)
}
assert.EqualValues(t, filtered, logFiltered, expected)
}
ancient := 1 * time.Hour
t.Run("no packages, cleanup nothing", func(t *testing.T) {
cleanupAndCheckLogs(t, ancient, "Nothing to cleanup")
})
orphan := "orphan"
createdLongAgo := timeutil.TimeStamp(time.Now().Add(-(ancient * 2)).Unix())
createdRecently := timeutil.TimeStamp(time.Now().Add(-(ancient / 2)).Unix())
t.Run("an orphaned package created a long time ago is removed", func(t *testing.T) {
createContainer(t, orphan, "sha256:"+orphan, "", createdLongAgo)
cleanupAndCheckLogs(t, ancient, "Removing 1 entries from `package_version`")
cleanupAndCheckLogs(t, ancient, "Nothing to cleanup")
})
t.Run("a newly created orphaned package is not cleaned up", func(t *testing.T) {
createContainer(t, orphan, "sha256:"+orphan, "", createdRecently)
cleanupAndCheckLogs(t, ancient, "1 out of 1 container image(s) are not deleted because they were created less than")
cleanupAndCheckLogs(t, 0, "Removing 1 entries from `package_version`")
cleanupAndCheckLogs(t, 0, "Nothing to cleanup")
})
t.Run("a referenced package is not removed", func(t *testing.T) {
referenced := "referenced"
digest := "sha256:" + referenced
createContainer(t, referenced, digest, "", createdRecently)
index := "index"
createContainer(t, index, index, digest, createdRecently)
cleanupAndCheckLogs(t, ancient, "Nothing to cleanup")
})
}

View file

@ -0,0 +1,14 @@
// Copyright 2024 The Forgejo Authors.
// SPDX-License-Identifier: GPL-3.0-or-later
package container
import (
"testing"
"code.gitea.io/gitea/models/unittest"
)
func TestMain(m *testing.M) {
unittest.MainTest(m)
}

View file

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
container_module "code.gitea.io/gitea/modules/packages/container"
"code.gitea.io/gitea/modules/timeutil"
)
var (
@ -37,18 +38,24 @@ func cleanupSHA256(outerCtx context.Context, olderThan time.Duration) error {
defer committer.Close()
foundAtLeastOneSHA256 := false
shaToVersionID := make(map[string]int64, 100)
type packageVersion struct {
id int64
created timeutil.TimeStamp
}
shaToPackageVersion := make(map[string]packageVersion, 100)
knownSHA := make(map[string]any, 100)
// compute before making the inventory to not race against ongoing
// image creations
old := timeutil.TimeStamp(time.Now().Add(-olderThan).Unix())
log.Debug("Look for all package_version.version that start with sha256:")
old := time.Now().Add(-olderThan).Unix()
// Iterate over all container versions in ascending order and store
// in shaToVersionID all versions with a sha256: prefix. If an index
// in shaToPackageVersion all versions with a sha256: prefix. If an index
// manifest is found, the sha256: digest it references are removed
// from shaToVersionID. If the sha256: digest found in an index
// manifest is not already in shaToVersionID, it is stored in
// from shaToPackageVersion. If the sha256: digest found in an index
// manifest is not already in shaToPackageVersion, it is stored in
// knownSHA to be dealt with later.
//
// Although it is theoretically possible that a sha256: is uploaded
@ -56,16 +63,16 @@ func cleanupSHA256(outerCtx context.Context, olderThan time.Duration) error {
// normal order of operations. First the sha256: version is uploaded
// and then the index manifest. When the iteration completes,
// knownSHA will therefore be empty most of the time and
// shaToVersionID will only contain unreferenced sha256: versions.
// shaToPackageVersion will only contain unreferenced sha256: versions.
if err := db.GetEngine(ctx).
Select("`package_version`.`id`, `package_version`.`lower_version`, `package_version`.`metadata_json`").
Select("`package_version`.`id`, `package_version`.`created_unix`, `package_version`.`lower_version`, `package_version`.`metadata_json`").
Join("INNER", "`package`", "`package`.`id` = `package_version`.`package_id`").
Where("`package`.`type` = ? AND `package_version`.`created_unix` < ?", packages.TypeContainer, old).
Where("`package`.`type` = ?", packages.TypeContainer).
OrderBy("`package_version`.`id` ASC").
Iterate(new(packages.PackageVersion), func(_ int, bean any) error {
v := bean.(*packages.PackageVersion)
if strings.HasPrefix(v.LowerVersion, "sha256:") {
shaToVersionID[v.LowerVersion] = v.ID
shaToPackageVersion[v.LowerVersion] = packageVersion{id: v.ID, created: v.CreatedUnix}
foundAtLeastOneSHA256 = true
} else if strings.Contains(v.MetadataJSON, `"manifests":[{`) {
var metadata container_module.Metadata
@ -74,8 +81,8 @@ func cleanupSHA256(outerCtx context.Context, olderThan time.Duration) error {
return nil
}
for _, manifest := range metadata.Manifests {
if _, ok := shaToVersionID[manifest.Digest]; ok {
delete(shaToVersionID, manifest.Digest)
if _, ok := shaToPackageVersion[manifest.Digest]; ok {
delete(shaToPackageVersion, manifest.Digest)
} else {
knownSHA[manifest.Digest] = true
}
@ -87,10 +94,10 @@ func cleanupSHA256(outerCtx context.Context, olderThan time.Duration) error {
}
for sha := range knownSHA {
delete(shaToVersionID, sha)
delete(shaToPackageVersion, sha)
}
if len(shaToVersionID) == 0 {
if len(shaToPackageVersion) == 0 {
if foundAtLeastOneSHA256 {
log.Debug("All container images with a version matching sha256:* are referenced by an index manifest")
} else {
@ -100,15 +107,24 @@ func cleanupSHA256(outerCtx context.Context, olderThan time.Duration) error {
return nil
}
found := len(shaToVersionID)
found := len(shaToPackageVersion)
log.Warn("%d container image(s) with a version matching sha256:* are not referenced by an index manifest", found)
log.Debug("Deleting unreferenced image versions from `package_version`, `package_file` and `package_property` (%d at a time)", SHA256BatchSize)
packageVersionIDs := make([]int64, 0, SHA256BatchSize)
for _, id := range shaToVersionID {
packageVersionIDs = append(packageVersionIDs, id)
tooYoung := 0
for _, p := range shaToPackageVersion {
if p.created < old {
packageVersionIDs = append(packageVersionIDs, p.id)
} else {
tooYoung++
}
}
if tooYoung > 0 {
log.Warn("%d out of %d container image(s) are not deleted because they were created less than %v ago", tooYoung, found, olderThan)
}
for len(packageVersionIDs) > 0 {

View file

@ -13,7 +13,6 @@ import (
"errors"
"fmt"
"io"
"net/url"
"strings"
"time"
@ -440,7 +439,7 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []
Archive: pd.FileMetadata.ArchiveSize,
},
Location: Location{
Href: fmt.Sprintf("package/%s/%s/%s/%s", url.PathEscape(pd.Package.Name), url.PathEscape(packageVersion), url.PathEscape(pd.FileMetadata.Architecture), url.PathEscape(fmt.Sprintf("%s-%s.%s.rpm", pd.Package.Name, packageVersion, pd.FileMetadata.Architecture))),
Href: fmt.Sprintf("package/%s/%s/%s/%s-%s.%s.rpm", pd.Package.Name, packageVersion, pd.FileMetadata.Architecture, pd.Package.Name, packageVersion, pd.FileMetadata.Architecture),
},
Format: Format{
License: pd.VersionMetadata.License,

View file

@ -309,8 +309,9 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
}
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
RepoID: repo.ID,
TagNames: tags,
RepoID: repo.ID,
TagNames: tags,
IncludeTags: true,
})
if err != nil {
return fmt.Errorf("db.Find[repo_model.Release]: %w", err)
@ -369,14 +370,13 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
return fmt.Errorf("CommitsCount: %w", err)
}
rel, has := relMap[lowerTag]
parts := strings.SplitN(tag.Message, "\n", 2)
note := ""
if len(parts) > 1 {
note = parts[1]
}
if !has {
parts := strings.SplitN(tag.Message, "\n", 2)
note := ""
if len(parts) > 1 {
note = parts[1]
}
if rel, has := relMap[lowerTag]; !has {
rel = &repo_model.Release{
RepoID: repo.ID,
Title: parts[0],
@ -394,13 +394,13 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
if author != nil {
rel.PublisherID = author.ID
}
newReleases = append(newReleases, rel)
} else {
rel.Title = parts[0]
rel.Note = note
rel.Sha1 = commit.ID.String()
rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix())
rel.NumCommits = commitsCount
rel.IsDraft = false
if rel.IsTag && author != nil {
rel.PublisherID = author.ID
}

View file

@ -65,8 +65,8 @@
<tr>
<th>{{ctx.Locale.Tr "units.unit"}}</th>
<th id="access_none">{{ctx.Locale.Tr "org.teams.none_access"}}</th>
<th id="access_read">{{ctx.Locale.Tr "org.teams.read_access"}}</th>
<th id="access_write">{{ctx.Locale.Tr "org.teams.write_access"}}</th>
<th>{{ctx.Locale.Tr "org.teams.read_access"}}</th>
<th>{{ctx.Locale.Tr "org.teams.write_access"}}</th>
</tr>
</thead>
<tbody>
@ -75,25 +75,26 @@
<tr>
<td>
<label {{if $unit.Type.UnitGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
{{ctx.Locale.Tr $unit.NameKey}}{{if $unit.Type.UnitGlobalDisabled}} {{ctx.Locale.Tr "org.team_unit_disabled"}}{{end}}
<span class="help">{{ctx.Locale.Tr $unit.DescKey}}</span>
<span id="help_{{$unit.Type.Value}}_name">{{ctx.Locale.Tr $unit.NameKey}}{{if $unit.Type.UnitGlobalDisabled}} {{ctx.Locale.Tr "org.team_unit_disabled"}}{{end}}</span>
<span class="help" id="help_{{$unit.Type.Value}}_r">{{ctx.Locale.Tr (print "repo.permissions." $unit.Name ".read")}}</span>
<span class="help" id="help_{{$unit.Type.Value}}_w">{{ctx.Locale.Tr (print "repo.permissions." $unit.Name ".write")}}</span>
</label>
</td>
<td>
<label>
<input aria-labelledby="access_none" type="radio" name="unit_{{$unit.Type.Value}}" value="0"{{if or ($unit.Type.UnitGlobalDisabled) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 0)}} checked{{end}}>
<input aria-labelledby="help_{{$unit.Type.Value}}_name access_none" type="radio" name="unit_{{$unit.Type.Value}}" value="0"{{if or ($unit.Type.UnitGlobalDisabled) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 0)}} checked{{end}}>
<span class="only-mobile">{{ctx.Locale.Tr "org.teams.none_access"}}</span>
</label>
</td>
<td>
<label>
<input aria-labelledby="access_read" type="radio" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}>
<input aria-labelledby="help_{{$unit.Type.Value}}_name help_{{$unit.Type.Value}}_r" type="radio" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}>
<span class="only-mobile">{{ctx.Locale.Tr "org.teams.read_access"}}</span>
</label>
</td>
<td>
<label>
<input aria-labelledby="access_write" type="radio" name="unit_{{$unit.Type.Value}}" value="2"{{if (ge ($.Team.UnitAccessMode $.Context $unit.Type) 2)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}>
<input aria-labelledby="help_{{$unit.Type.Value}}_name help_{{$unit.Type.Value}}_w" type="radio" name="unit_{{$unit.Type.Value}}" value="2"{{if (ge ($.Team.UnitAccessMode $.Context $unit.Type) 2)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}>
<span class="only-mobile">{{ctx.Locale.Tr "org.teams.write_access"}}</span>
</label>
</td>
@ -108,7 +109,7 @@
<label {{if $unit.Type.UnitGlobalDisabled}}data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
<input type="checkbox" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}>
{{ctx.Locale.Tr $unit.NameKey}}{{if $unit.Type.UnitGlobalDisabled}} {{ctx.Locale.Tr "org.team_unit_disabled"}}{{end}}
<span class="help">{{ctx.Locale.Tr $unit.DescKey}}</span>
<span class="help">{{ctx.Locale.Tr (print "repo.permissions." $unit.Name)}}</span>
</label>
{{end}}
{{end}}

View file

@ -16,11 +16,11 @@ pacman-key --lsign-key '{{$.SignMail}}'</code></pre>
<pre
class="code-block"><code>
{{- if gt (len $.Groups) 1 -}}
# {{ctx.Locale.Tr "packages.arch.pacman.repo.multi" $.PackageDescriptor.Package.LowerName}}
# {{ctx.Locale.Tr "packages.arch.pacman.repo.multi" $.PackageDescriptor.Package.LowerName}}
{{end -}}
{{- $GroupSize := (len .Groups) -}}
{{- range $i,$v := .Groups -}}
{{- range $i,$v := .Groups -}}
{{- if gt $i 0}}
{{end -}}{{- if gt $GroupSize 1 -}}
# {{ctx.Locale.Tr "packages.arch.pacman.repo.multi.item" .}}

View file

@ -1,4 +1,4 @@
{{if eq .PackageDescriptor.Package.Type "arch"}}
{{range .PackageDescriptor.Metadata.License}}<div class="item" title="{{$.locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "gt-mr-3"}} {{.}}</div>{{end}}
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
{{range .PackageDescriptor.Metadata.License}}<div class="item" title="{{$.locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.}}</div>{{end}}
{{end}}

View file

@ -14,7 +14,7 @@
<div class="issue-card-icon">
{{template "shared/issueicon" .}}
</div>
<a class="issue-card-title muted issue-title tw-break-anywhere" href="{{.Link}}">{{.Title | RenderEmoji ctx | RenderCodeBlock}}</a>
<a class="issue-card-title muted issue-title tw-break-anywhere" href="{{.Link}}">{{RenderRefIssueTitle $.Context .Title}}</a>
{{if and $.isPinnedIssueCard $.Page.IsRepoAdmin}}
<a role="button" class="issue-card-unpin muted tw-flex tw-items-center" data-tooltip-content={{ctx.Locale.Tr "repo.issues.unpin_issue"}} data-issue-id="{{.ID}}" data-unpin-url="{{$.Page.Link}}/unpin/{{.Index}}">
{{svg "octicon-x" 16}}

View file

@ -149,7 +149,7 @@
{{if eq .RefAction 3}}</del>{{end}}
<div class="detail flex-text-block">
<span class="text grey muted-links"><a href="{{.RefIssueLink ctx}}"><b>{{.RefIssueTitle ctx}}</b> {{.RefIssueIdent ctx}}</a></span>
<span class="text grey muted-links"><a href="{{.RefIssueLink ctx}}"><b>{{.RefIssueTitle ctx | RenderEmoji $.Context | RenderCodeBlock}}</b> {{.RefIssueIdent ctx}}</a></span>
</div>
</div>
{{else if eq .Type 4}}
@ -226,7 +226,7 @@
{{template "shared/user/avatarlink" dict "user" .Poster}}
<span class="text grey muted-links">
{{template "shared/user/authorlink" .Poster}}
{{ctx.Locale.Tr "repo.issues.change_title_at" (.OldTitle|RenderEmoji $.Context) (.NewTitle|RenderEmoji $.Context) $createdStr}}
{{ctx.Locale.Tr "repo.issues.change_title_at" (RenderRefIssueTitle $.Context .OldTitle) (RenderRefIssueTitle $.Context .NewTitle) $createdStr}}
</span>
</div>
{{else if eq .Type 11}}
@ -339,10 +339,11 @@
{{svg "octicon-plus"}}
<span class="text grey muted-links">
<a href="{{.DependentIssue.Link}}">
{{$strTitle := RenderRefIssueTitle $.Context .DependentIssue.Title}}
{{if eq .DependentIssue.RepoID .Issue.RepoID}}
#{{.DependentIssue.Index}} {{.DependentIssue.Title}}
#{{.DependentIssue.Index}} {{$strTitle}}
{{else}}
{{.DependentIssue.Repo.FullName}}#{{.DependentIssue.Index}} - {{.DependentIssue.Title}}
{{.DependentIssue.Repo.FullName}}#{{.DependentIssue.Index}} - {{$strTitle}}
{{end}}
</a>
</span>
@ -362,10 +363,11 @@
{{svg "octicon-trash"}}
<span class="text grey muted-links">
<a href="{{.DependentIssue.Link}}">
{{$strTitle := RenderRefIssueTitle $.Context .DependentIssue.Title}}
{{if eq .DependentIssue.RepoID .Issue.RepoID}}
#{{.DependentIssue.Index}} {{.DependentIssue.Title}}
#{{.DependentIssue.Index}} {{$strTitle}}
{{else}}
{{.DependentIssue.Repo.FullName}}#{{.DependentIssue.Index}} - {{.DependentIssue.Title}}
{{.DependentIssue.Repo.FullName}}#{{.DependentIssue.Index}} - {{$strTitle}}
{{end}}
</a>
</span>

View file

@ -19,8 +19,8 @@
{{range .BlockingDependencies}}
<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
<a class="title muted" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}">
#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}
<a class="title muted" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{RenderRefIssueTitle $.Context .Issue.Title}}}">
#{{.Issue.Index}} {{RenderRefIssueTitle $.Context .Issue.Title}}}
</a>
<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
{{.Repository.OwnerName}}/{{.Repository.Name}}
@ -51,8 +51,9 @@
{{range .BlockedByDependencies}}
<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
<a class="title muted" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}">
#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}
{{$title := RenderRefIssueTitle $.Context .Issue.Title}}
<a class="title muted" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{RenderRefIssueTitle $.Context .Issue.Title}}">
#{{.Issue.Index}} {{RenderRefIssueTitle $.Context .Issue.Title}}
</a>
<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
{{.Repository.OwnerName}}/{{.Repository.Name}}
@ -73,8 +74,8 @@
<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
<div class="gt-ellipsis">
<span data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.no_permission.can_remove"}}">{{svg "octicon-lock" 16}}</span>
<span class="title" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}">
#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}
<span class="title" data-tooltip-content="#{{.Issue.Index}} {{RenderRefIssueTitle $.Context .DependentIssue.Title}}">
#{{.Issue.Index}} {{RenderRefIssueTitle $.Context .DependentIssue.Title}}
</span>
</div>
<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">

View file

@ -7,8 +7,7 @@
{{$canEditIssueTitle := and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}}
<div class="issue-title" id="issue-title-display">
<h1 class="tw-break-anywhere">
{{RenderIssueTitle $.Context .Issue.Title ($.Repository.ComposeMetas ctx) | RenderCodeBlock}}
<span class="index">#{{.Issue.Index}}</span>
{{RenderIssueTitle $.Context .Issue.Title ($.Repository.ComposeMetas ctx)}} <span class="index">#{{.Issue.Index}}</span>
</h1>
<div class="button-row">
{{if $canEditIssueTitle}}

View file

@ -153,7 +153,7 @@
{{range .Activity.MergedPRs}}
<p class="desc">
<span class="ui purple label">{{ctx.Locale.Tr "repo.activity.merged_prs_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{RenderRefIssueTitle $.Context .Issue.Title}}</a>
{{TimeSinceUnix .MergedUnix ctx.Locale}}
</p>
{{end}}
@ -172,7 +172,7 @@
{{range .Activity.OpenedPRs}}
<p class="desc">
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.opened_prs_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{RenderRefIssueTitle $.Context .Issue.Title}}</a>
{{TimeSinceUnix .Issue.CreatedUnix ctx.Locale}}
</p>
{{end}}
@ -191,7 +191,7 @@
{{range .Activity.ClosedIssues}}
<p class="desc">
<span class="ui red label">{{ctx.Locale.Tr "repo.activity.closed_issue_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{RenderRefIssueTitle $.Context .Title}}</a>
{{TimeSinceUnix .ClosedUnix ctx.Locale}}
</p>
{{end}}
@ -210,7 +210,7 @@
{{range .Activity.OpenedIssues}}
<p class="desc">
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.new_issue_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{RenderRefIssueTitle $.Context .Title}}</a>
{{TimeSinceUnix .CreatedUnix ctx.Locale}}
</p>
{{end}}
@ -228,9 +228,9 @@
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.unresolved_conv_label"}}</span>
#{{.Index}}
{{if .IsPull}}
<a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
<a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{RenderRefIssueTitle $.Context .Title}}</a>
{{else}}
<a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
<a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{RenderRefIssueTitle $.Context .Title}}</a>
{{end}}
{{TimeSinceUnix .UpdatedUnix ctx.Locale}}
</p>

View file

@ -58,7 +58,7 @@
<div class="notifications-bottom-row tw-text-16 tw-py-0.5">
<span class="issue-title tw-break-anywhere">
{{if .Issue}}
{{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}}
{{RenderRefIssueTitle $.Context .Issue.Title}}
{{else}}
{{.Repository.FullName}}
{{end}}

View file

@ -1,5 +1,5 @@
import {expect} from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
import {AxeBuilder} from '@axe-core/playwright';
export async function validate_form({page}, scope) {
scope ??= 'form';
@ -21,10 +21,24 @@ export async function validate_form({page}, scope) {
await expect(b).toHaveCSS('margin-top', '0px');
await expect(b).toHaveCSS('vertical-align', 'baseline');
}
// assert no (trailing) colon is used in labels
// might be necessary to adjust in case colons are strictly necessary in help text
for (const l of await page.locator('label').all()) {
const str = await l.textContent();
await expect(str.split('\n')[0]).not.toContain(':');
}
// check that multiple help text are correctly aligned to each other
// used for example to separate read/write permissions in team permission matrix
for (const l of await page.locator('label:has(.help + .help)').all()) {
const helpLabels = await l.locator('.help').all();
const boxes = await Promise.all(helpLabels.map((help) => help.boundingBox()));
for (let i = 1; i < boxes.length; i++) {
// help texts vertically aligned on top of each other
await expect(boxes[i].x).toBe(boxes[0].x);
// help texts don't horizontally intersect each other
await expect(boxes[i].y + boxes[i].height).toBeGreaterThanOrEqual(boxes[i - 1].y + boxes[i - 1].height);
}
}
}

View file

@ -14,6 +14,7 @@ import (
"io"
"net/http"
"strings"
"sync"
"testing"
"testing/fstest"
@ -258,11 +259,15 @@ HMhNSS1IzUsBcpJAPFAwwUXSM0u4BjoaR8EoGAWjgGQAAILFeyQADAAA
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
req = NewRequestWithBody(t, "DELETE", rootURL+"/base/notfound/1.0.0-1", nil).
req = NewRequestWithBody(t, "DELETE", rootURL+"/base/notfound/1.0.0-1/any", nil).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNotFound)
req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1", nil).
req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1/x86_64", nil).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNoContent)
req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1/any", nil).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNoContent)
@ -270,12 +275,22 @@ HMhNSS1IzUsBcpJAPFAwwUXSM0u4BjoaR8EoGAWjgGQAAILFeyQADAAA
respPkg := MakeRequest(t, req, http.StatusOK)
files, err := listTarGzFiles(respPkg.Body.Bytes())
require.NoError(t, err)
require.Len(t, files, 1) // other pkg in L225
require.Len(t, files, 1)
req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1", nil).
req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1/any", nil).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNoContent)
req = NewRequest(t, "GET", groupURL+"/x86_64/base.db")
req = NewRequest(t, "GET", groupURL+"/x86_64/base.db").
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNotFound)
req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1/aarch64", nil).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNoContent)
req = NewRequest(t, "GET", groupURL+"/aarch64/base.db").
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNotFound)
})
@ -294,12 +309,33 @@ HMhNSS1IzUsBcpJAPFAwwUXSM0u4BjoaR8EoGAWjgGQAAILFeyQADAAA
resp := MakeRequest(t, req, http.StatusOK)
require.Equal(t, pkgs[key], resp.Body.Bytes())
req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1", nil).
req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1/any", nil).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNoContent)
})
}
}
t.Run("Concurrent Upload", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
var wg sync.WaitGroup
targets := []string{"any", "aarch64", "x86_64"}
for _, tag := range targets {
wg.Add(1)
go func(i string) {
defer wg.Done()
req := NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgs[i])).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
}(tag)
}
wg.Wait()
for _, target := range targets {
req := NewRequestWithBody(t, "DELETE", rootURL+"/test/1.0.0-1/"+target, nil).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNoContent)
}
})
}
func getProperty(data, key string) string {
@ -318,10 +354,10 @@ func getProperty(data, key string) string {
func listTarGzFiles(data []byte) (fstest.MapFS, error) {
reader, err := gzip.NewReader(bytes.NewBuffer(data))
defer reader.Close()
if err != nil {
return nil, err
}
defer reader.Close()
tarRead := tar.NewReader(reader)
files := make(fstest.MapFS)
for {

View file

@ -37,6 +37,7 @@ func TestPackageComposer(t *testing.T) {
packageType := "composer-plugin"
packageAuthor := "Gitea Authors"
packageLicense := "MIT"
packageBin := "./bin/script"
var buf bytes.Buffer
archive := zip.NewWriter(&buf)
@ -50,6 +51,9 @@ func TestPackageComposer(t *testing.T) {
{
"name": "` + packageAuthor + `"
}
],
"bin": [
"` + packageBin + `"
]
}`))
archive.Close()
@ -211,6 +215,8 @@ func TestPackageComposer(t *testing.T) {
assert.Len(t, pkgs[0].Authors, 1)
assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name)
assert.Equal(t, "zip", pkgs[0].Dist.Type)
assert.Equal(t, "7b40bfd6da811b2b78deec1e944f156dbb2c747b", pkgs[0].Dist.Checksum)
assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum)
assert.Len(t, pkgs[0].Bin, 1)
assert.Equal(t, packageBin, pkgs[0].Bin[0])
})
}

View file

@ -23,10 +23,10 @@ func TestAPICreateAndDeleteToken(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, nil)
newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
deleteAPIAccessToken(t, newAccessToken, user)
newAccessToken = createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user, nil)
newAccessToken = createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
deleteAPIAccessToken(t, newAccessToken, user)
}
@ -72,19 +72,19 @@ func TestAPIDeleteTokensPermission(t *testing.T) {
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
// admin can delete tokens for other users
createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user2, nil)
createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
req := NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-1").
AddBasicAuth(admin.Name)
MakeRequest(t, req, http.StatusNoContent)
// non-admin can delete tokens for himself
createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user2, nil)
createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-2").
AddBasicAuth(user2.Name)
MakeRequest(t, req, http.StatusNoContent)
// non-admin can't delete tokens for other users
createAPIAccessTokenWithoutCleanUp(t, "test-key-3", user2, nil)
createAPIAccessTokenWithoutCleanUp(t, "test-key-3", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-3").
AddBasicAuth(user4.Name)
MakeRequest(t, req, http.StatusForbidden)
@ -520,7 +520,7 @@ func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model
unauthorizedScopes = append(unauthorizedScopes, cateogoryUnauthorizedScopes...)
}
accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-token", user, &unauthorizedScopes)
accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-token", user, unauthorizedScopes)
defer deleteAPIAccessToken(t, accessToken, user)
// Request the endpoint. Verify that permission is denied.
@ -532,20 +532,12 @@ func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model
// createAPIAccessTokenWithoutCleanUp Create an API access token and assert that
// creation succeeded. The caller is responsible for deleting the token.
func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *user_model.User, scopes *[]auth_model.AccessTokenScope) api.AccessToken {
func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *user_model.User, scopes []auth_model.AccessTokenScope) api.AccessToken {
payload := map[string]any{
"name": tokenName,
}
if scopes != nil {
for _, scope := range *scopes {
scopes, scopesExists := payload["scopes"].([]string)
if !scopesExists {
scopes = make([]string, 0)
}
scopes = append(scopes, string(scope))
payload["scopes"] = scopes
}
"name": tokenName,
"scopes": scopes,
}
log.Debug("Requesting creation of token with scopes: %v", scopes)
req := NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", payload).
AddBasicAuth(user.Name)
@ -563,8 +555,7 @@ func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *us
return newAccessToken
}
// createAPIAccessTokenWithoutCleanUp Delete an API access token and assert that
// deletion succeeded.
// deleteAPIAccessToken deletes an API access token and assert that deletion succeeded.
func deleteAPIAccessToken(t *testing.T, accessToken api.AccessToken, user *user_model.User) {
req := NewRequestf(t, "DELETE", "/api/v1/users/"+user.LoginName+"/tokens/%d", accessToken.ID).
AddBasicAuth(user.Name)

View file

@ -60,7 +60,8 @@ func createAttachment(t *testing.T, session *TestSession, repoURL, filename stri
func TestCreateAnonymousAttachment(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := emptyTestSession(t)
createAttachment(t, session, "user2/repo1", "image.png", generateImg(), http.StatusSeeOther)
// this test is not right because it just doesn't pass the CSRF validation
createAttachment(t, session, "user2/repo1", "image.png", generateImg(), http.StatusBadRequest)
}
func TestCreateIssueAttachment(t *testing.T) {

View file

@ -5,12 +5,10 @@ package integration
import (
"net/http"
"strings"
"testing"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
@ -25,28 +23,12 @@ func TestCsrfProtection(t *testing.T) {
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"_csrf": "fake_csrf",
})
session.MakeRequest(t, req, http.StatusSeeOther)
resp := session.MakeRequest(t, req, http.StatusSeeOther)
loc := resp.Header().Get("Location")
assert.Equal(t, setting.AppSubURL+"/", loc)
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
assert.Equal(t, "Bad Request: invalid CSRF token",
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
)
resp := session.MakeRequest(t, req, http.StatusBadRequest)
assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
// test web form csrf via header. TODO: should use an UI api to test
req = NewRequest(t, "POST", "/user/settings")
req.Header.Add("X-Csrf-Token", "fake_csrf")
session.MakeRequest(t, req, http.StatusSeeOther)
resp = session.MakeRequest(t, req, http.StatusSeeOther)
loc = resp.Header().Get("Location")
assert.Equal(t, setting.AppSubURL+"/", loc)
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
assert.Equal(t, "Bad Request: invalid CSRF token",
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
)
resp = session.MakeRequest(t, req, http.StatusBadRequest)
assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
}

View file

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
forgejo_context "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
@ -190,11 +191,6 @@ func TestRedirectsWebhooks(t *testing.T) {
{from: "/user/settings/hooks/" + kind + "/new", to: "/user/login", verb: "GET"},
{from: "/admin/system-hooks/" + kind + "/new", to: "/user/login", verb: "GET"},
{from: "/admin/default-hooks/" + kind + "/new", to: "/user/login", verb: "GET"},
{from: "/user2/repo1/settings/hooks/" + kind + "/new", to: "/", verb: "POST"},
{from: "/admin/system-hooks/" + kind + "/new", to: "/", verb: "POST"},
{from: "/admin/default-hooks/" + kind + "/new", to: "/", verb: "POST"},
{from: "/user2/repo1/settings/hooks/1", to: "/", verb: "POST"},
{from: "/admin/hooks/1", to: "/", verb: "POST"},
}
for _, info := range redirects {
req := NewRequest(t, info.verb, info.from)
@ -202,6 +198,24 @@ func TestRedirectsWebhooks(t *testing.T) {
assert.EqualValues(t, path.Join(setting.AppSubURL, info.to), test.RedirectURL(resp), info.from)
}
}
for _, kind := range []string{"forgejo", "gitea"} {
csrf := []struct {
from string
verb string
}{
{from: "/user2/repo1/settings/hooks/" + kind + "/new", verb: "POST"},
{from: "/admin/hooks/1", verb: "POST"},
{from: "/admin/system-hooks/" + kind + "/new", verb: "POST"},
{from: "/admin/default-hooks/" + kind + "/new", verb: "POST"},
{from: "/user2/repo1/settings/hooks/1", verb: "POST"},
}
for _, info := range csrf {
req := NewRequest(t, info.verb, info.from)
resp := MakeRequest(t, req, http.StatusBadRequest)
assert.Contains(t, resp.Body.String(), forgejo_context.CsrfErrorString)
}
}
}
func TestRepoLinks(t *testing.T) {

View file

@ -11,6 +11,7 @@ import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
@ -24,6 +25,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/routers/web/auth"
forgejo_context "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/tests"
"github.com/markbates/goth"
@ -803,6 +805,16 @@ func TestOAuthIntrospection(t *testing.T) {
})
}
func requireCookieCSRF(t *testing.T, resp http.ResponseWriter) string {
for _, c := range resp.(*httptest.ResponseRecorder).Result().Cookies() {
if c.Name == "_csrf" {
return c.Value
}
}
require.True(t, false, "_csrf not found in cookies")
return ""
}
func TestOAuth_GrantScopesReadUser(t *testing.T) {
defer tests.PrepareTestEnv(t)()
@ -840,19 +852,18 @@ func TestOAuth_GrantScopesReadUser(t *testing.T) {
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&amp")[0]
htmlDoc := NewHTMLParser(t, authorizeResp.Body)
grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"_csrf": requireCookieCSRF(t, authorizeResp),
"client_id": app.ClientID,
"redirect_uri": "a",
"state": "thestate",
"granted": "true",
})
grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther)
htmlDocGrant := NewHTMLParser(t, grantResp.Body)
grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest)
assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString)
accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
"_csrf": htmlDocGrant.GetCSRF(),
"_csrf": requireCookieCSRF(t, authorizeResp),
"grant_type": "authorization_code",
"client_id": app.ClientID,
"client_secret": app.ClientSecret,
@ -921,19 +932,18 @@ func TestOAuth_GrantScopesFailReadRepository(t *testing.T) {
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&amp")[0]
htmlDoc := NewHTMLParser(t, authorizeResp.Body)
grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"_csrf": requireCookieCSRF(t, authorizeResp),
"client_id": app.ClientID,
"redirect_uri": "a",
"state": "thestate",
"granted": "true",
})
grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther)
htmlDocGrant := NewHTMLParser(t, grantResp.Body)
grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest)
assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString)
accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
"_csrf": htmlDocGrant.GetCSRF(),
"_csrf": requireCookieCSRF(t, authorizeResp),
"grant_type": "authorization_code",
"client_id": app.ClientID,
"client_secret": app.ClientSecret,
@ -1000,19 +1010,18 @@ func TestOAuth_GrantScopesReadRepository(t *testing.T) {
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&amp")[0]
htmlDoc := NewHTMLParser(t, authorizeResp.Body)
grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"_csrf": requireCookieCSRF(t, authorizeResp),
"client_id": app.ClientID,
"redirect_uri": "a",
"state": "thestate",
"granted": "true",
})
grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther)
htmlDocGrant := NewHTMLParser(t, grantResp.Body)
grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest)
assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString)
accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
"_csrf": htmlDocGrant.GetCSRF(),
"_csrf": requireCookieCSRF(t, authorizeResp),
"grant_type": "authorization_code",
"client_id": app.ClientID,
"client_secret": app.ClientSecret,
@ -1082,19 +1091,18 @@ func TestOAuth_GrantScopesReadPrivateGroups(t *testing.T) {
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&amp")[0]
htmlDoc := NewHTMLParser(t, authorizeResp.Body)
grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"_csrf": requireCookieCSRF(t, authorizeResp),
"client_id": app.ClientID,
"redirect_uri": "a",
"state": "thestate",
"granted": "true",
})
grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther)
htmlDocGrant := NewHTMLParser(t, grantResp.Body)
grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest)
assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString)
accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
"_csrf": htmlDocGrant.GetCSRF(),
"_csrf": requireCookieCSRF(t, authorizeResp),
"grant_type": "authorization_code",
"client_id": app.ClientID,
"client_secret": app.ClientSecret,
@ -1164,19 +1172,18 @@ func TestOAuth_GrantScopesReadOnlyPublicGroups(t *testing.T) {
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&amp")[0]
htmlDoc := NewHTMLParser(t, authorizeResp.Body)
grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"_csrf": requireCookieCSRF(t, authorizeResp),
"client_id": app.ClientID,
"redirect_uri": "a",
"state": "thestate",
"granted": "true",
})
grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther)
htmlDocGrant := NewHTMLParser(t, grantResp.Body)
grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest)
assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString)
accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
"_csrf": htmlDocGrant.GetCSRF(),
"_csrf": requireCookieCSRF(t, authorizeResp),
"grant_type": "authorization_code",
"client_id": app.ClientID,
"client_secret": app.ClientSecret,
@ -1260,19 +1267,18 @@ func TestOAuth_GrantScopesReadPublicGroupsWithTheReadScope(t *testing.T) {
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&amp")[0]
htmlDoc := NewHTMLParser(t, authorizeResp.Body)
grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"_csrf": requireCookieCSRF(t, authorizeResp),
"client_id": app.ClientID,
"redirect_uri": "a",
"state": "thestate",
"granted": "true",
})
grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther)
htmlDocGrant := NewHTMLParser(t, grantResp.Body)
grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest)
assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString)
accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
"_csrf": htmlDocGrant.GetCSRF(),
"_csrf": requireCookieCSRF(t, authorizeResp),
"grant_type": "authorization_code",
"client_id": app.ClientID,
"client_secret": app.ClientSecret,

View file

@ -18,7 +18,6 @@ import (
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation"
repo_service "code.gitea.io/gitea/services/repository"
@ -157,15 +156,8 @@ func TestCreateBranchInvalidCSRF(t *testing.T) {
"_csrf": "fake_csrf",
"new_branch_name": "test",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
loc := resp.Header().Get("Location")
assert.Equal(t, setting.AppSubURL+"/", loc)
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
assert.Equal(t,
"Bad Request: invalid CSRF token",
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
)
resp := session.MakeRequest(t, req, http.StatusBadRequest)
assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
}
func TestDatabaseMissingABranch(t *testing.T) {

View file

@ -0,0 +1,162 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package integration
import (
"net/http"
"net/url"
"strings"
"testing"
"time"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
files_service "code.gitea.io/gitea/services/repository/files"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIssueTitles(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
repo, _, f := tests.CreateDeclarativeRepo(t, user, "issue-titles", nil, nil, nil)
defer f()
session := loginUser(t, user.LoginName)
title := "Title :+1: `code`"
issue1 := createIssue(t, user, repo, title, "Test issue")
issue2 := createIssue(t, user, repo, title, "Ref #1")
titleHTML := []string{
"Title",
`<span class="emoji" aria-label="thumbs up">👍</span>`,
`<code class="inline-code-block">code</code>`,
}
t.Run("Main issue title", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
html := extractHTML(t, session, issue1, "div.issue-title-header > * > h1")
assertContainsAll(t, titleHTML, html)
})
t.Run("Referenced issue comment", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
html := extractHTML(t, session, issue1, "div.timeline > div.timeline-item:nth-child(3) > div.detail > * > a")
assertContainsAll(t, titleHTML, html)
})
t.Run("Dependent issue comment", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
err := issues_model.CreateIssueDependency(db.DefaultContext, user, issue1, issue2)
require.NoError(t, err)
html := extractHTML(t, session, issue1, "div.timeline > div:nth-child(3) > div.detail > * > a")
assertContainsAll(t, titleHTML, html)
})
t.Run("Dependent issue sidebar", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
html := extractHTML(t, session, issue1, "div.item.dependency > * > a.title")
assertContainsAll(t, titleHTML, html)
})
t.Run("Referenced pull comment", func(t *testing.T) {
_, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user, &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "update",
TreePath: "README.md",
ContentReader: strings.NewReader("Update README"),
},
},
Message: "Update README",
OldBranch: "main",
NewBranch: "branch",
Author: &files_service.IdentityOptions{
Name: user.Name,
Email: user.Email,
},
Committer: &files_service.IdentityOptions{
Name: user.Name,
Email: user.Email,
},
Dates: &files_service.CommitDateOptions{
Author: time.Now(),
Committer: time.Now(),
},
})
require.NoError(t, err)
pullIssue := &issues_model.Issue{
RepoID: repo.ID,
Title: title,
Content: "Closes #1",
PosterID: user.ID,
Poster: user,
IsPull: true,
}
pullRequest := &issues_model.PullRequest{
HeadRepoID: repo.ID,
BaseRepoID: repo.ID,
HeadBranch: "branch",
BaseBranch: "main",
HeadRepo: repo,
BaseRepo: repo,
Type: issues_model.PullRequestGitea,
}
err = pull_service.NewPullRequest(git.DefaultContext, repo, pullIssue, nil, nil, pullRequest, nil)
require.NoError(t, err)
html := extractHTML(t, session, issue1, "div.timeline > div:nth-child(4) > div.detail > * > a")
assertContainsAll(t, titleHTML, html)
})
})
}
func createIssue(t *testing.T, user *user_model.User, repo *repo_model.Repository, title, content string) *issues_model.Issue {
issue := &issues_model.Issue{
RepoID: repo.ID,
Title: title,
Content: content,
PosterID: user.ID,
Poster: user,
}
err := issue_service.NewIssue(db.DefaultContext, repo, issue, nil, nil, nil)
require.NoError(t, err)
return issue
}
func extractHTML(t *testing.T, session *TestSession, issue *issues_model.Issue, query string) string {
req := NewRequest(t, "GET", issue.HTMLURL())
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
res, err := doc.doc.Find(query).Html()
require.NoError(t, err)
return res
}
func assertContainsAll(t *testing.T, expected []string, actual string) {
for i := range expected {
assert.Contains(t, actual, expected[i])
}
}

View file

@ -108,6 +108,40 @@ func TestCreateNewTagProtected(t *testing.T) {
})
})
t.Run("GitTagForce", func(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
httpContext := NewAPITestContext(t, owner.Name, repo.Name)
dstPath := t.TempDir()
u.Path = httpContext.GitPath()
u.User = url.UserPassword(owner.Name, userPassword)
doGitClone(dstPath, u)(t)
_, _, err := git.NewCommand(git.DefaultContext, "tag", "v-1.1", "-m", "force update", "--force").RunStdString(&git.RunOpts{Dir: dstPath})
require.NoError(t, err)
_, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath})
require.NoError(t, err)
_, _, err = git.NewCommand(git.DefaultContext, "tag", "v-1.1", "-m", "force update v2", "--force").RunStdString(&git.RunOpts{Dir: dstPath})
require.NoError(t, err)
_, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath})
require.Error(t, err)
assert.Contains(t, err.Error(), "the tag already exists in the remote")
_, _, err = git.NewCommand(git.DefaultContext, "push", "--tags", "--force").RunStdString(&git.RunOpts{Dir: dstPath})
require.NoError(t, err)
req := NewRequestf(t, "GET", "/%s/releases/tag/v-1.1", repo.FullName())
resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
tagsTab := htmlDoc.Find(".release-list-title")
assert.Contains(t, tagsTab.Text(), "force update v2")
})
})
// Cleanup
releases, err := db.Find[repo_model.Release](db.DefaultContext, repo_model.FindReleasesOptions{
IncludeTags: true,

View file

@ -1,6 +1,7 @@
import {defineConfig} from 'vitest/config';
import vuePlugin from '@vitejs/plugin-vue';
import {stringPlugin} from 'vite-string-plugin';
import {resolve} from 'node:path';
export default defineConfig({
test: {
@ -13,6 +14,9 @@ export default defineConfig({
passWithNoTests: true,
globals: true,
watch: false,
alias: {
'monaco-editor': resolve(import.meta.dirname, '/node_modules/monaco-editor/esm/vs/editor/editor.api'),
},
},
plugins: [
stringPlugin(),

View file

@ -44,9 +44,9 @@
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"license": "MIT",
"engines": {
"node": ">=12"
@ -198,9 +198,9 @@
}
},
"node_modules/@octokit/core/node_modules/@octokit/request-error": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.4.tgz",
"integrity": "sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg==",
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
"license": "MIT",
"peer": true,
"dependencies": {
@ -211,9 +211,9 @@
}
},
"node_modules/@octokit/core/node_modules/@octokit/types": {
"version": "13.5.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz",
"integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==",
"version": "13.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.0.tgz",
"integrity": "sha512-CrooV/vKCXqwLa+osmHLIMUb87brpgUqlqkPGc6iE2wCkUvTrHiXFMhAKoDDaAAYJrtKtrFTgSQTg5nObBEaew==",
"license": "MIT",
"peer": true,
"dependencies": {
@ -304,9 +304,9 @@
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/request-error": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.4.tgz",
"integrity": "sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg==",
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
"license": "MIT",
"peer": true,
"dependencies": {
@ -317,9 +317,9 @@
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/types": {
"version": "13.5.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz",
"integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==",
"version": "13.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.0.tgz",
"integrity": "sha512-CrooV/vKCXqwLa+osmHLIMUb87brpgUqlqkPGc6iE2wCkUvTrHiXFMhAKoDDaAAYJrtKtrFTgSQTg5nObBEaew==",
"license": "MIT",
"peer": true,
"dependencies": {
@ -492,9 +492,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.4.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.4.0.tgz",
"integrity": "sha512-49AbMDwYUz7EXxKU/r7mXOsxwFr4BYbvB7tWYxVuLdb2ibd30ijjXINSMAHiEEZk5PCRBmW1gUeisn2VMKt3cQ==",
"version": "22.7.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
"integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
@ -1115,9 +1115,9 @@
}
},
"node_modules/browserslist": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
"integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz",
"integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==",
"funding": [
{
"type": "opencollective",
@ -1134,8 +1134,8 @@
],
"license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001646",
"electron-to-chromium": "^1.5.4",
"caniuse-lite": "^1.0.30001663",
"electron-to-chromium": "^1.5.28",
"node-releases": "^2.0.18",
"update-browserslist-db": "^1.1.0"
},
@ -1219,9 +1219,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001651",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
"integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==",
"version": "1.0.30001664",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz",
"integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==",
"funding": [
{
"type": "opencollective",
@ -1277,7 +1277,6 @@
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
"integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
"deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies",
"license": "MIT",
"dependencies": {
"anymatch": "^2.0.0",
@ -1962,9 +1961,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.11",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.11.tgz",
"integrity": "sha512-R1CccCDYqndR25CaXFd6hp/u9RaaMcftMkphmvuepXr5b1vfLkRml6aWVeBhXJ7rbevHkKEMJtz8XqPf7ffmew==",
"version": "1.5.29",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz",
"integrity": "sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==",
"license": "ISC"
},
"node_modules/emoji-regex": {
@ -2078,9 +2077,9 @@
}
},
"node_modules/escalade": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"license": "MIT",
"engines": {
"node": ">=6"
@ -2619,7 +2618,7 @@
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2",
"deprecated": "Upgrade to fsevents v2 to mitigate potential security issues",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@ -2700,9 +2699,9 @@
}
},
"node_modules/get-stream/node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
@ -4105,9 +4104,9 @@
}
},
"node_modules/gulp-uglify/node_modules/uglify-js": {
"version": "3.19.2",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.2.tgz",
"integrity": "sha512-S8KA6DDI47nQXJSi2ctQ629YzwOVs+bQML6DAtvy0wgNdpi+0ySpQK0g2pxBq2xfF2z3YCscu7NNA8nXT9PlIQ==",
"version": "3.19.3",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
"integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
"license": "BSD-2-Clause",
"bin": {
"uglifyjs": "bin/uglifyjs"
@ -4620,9 +4619,9 @@
"license": "MIT"
},
"node_modules/is-core-module": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz",
"integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==",
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
@ -5836,9 +5835,9 @@
}
},
"node_modules/native-request": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/native-request/-/native-request-1.1.0.tgz",
"integrity": "sha512-uZ5rQaeRn15XmpgE0xoPL8YWqcX90VtCFglYwAgkvKM5e8fog+vePLAhHxuuv/gRkrQxIeh5U3q9sMNUrENqWw==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/native-request/-/native-request-1.1.2.tgz",
"integrity": "sha512-/etjwrK0J4Ebbcnt35VMWnfiUX/B04uwGJxyJInagxDqf2z5drSt/lsOvEMWGYunz1kaLZAFrV4NDAbOoDKvAQ==",
"license": "MIT",
"optional": true
},
@ -6240,9 +6239,9 @@
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
"node_modules/parse-filepath": {
@ -7415,9 +7414,9 @@
}
},
"node_modules/spdx-license-ids": {
"version": "3.0.18",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz",
"integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==",
"version": "3.0.20",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz",
"integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==",
"license": "CC0-1.0"
},
"node_modules/split-string": {
@ -8132,9 +8131,9 @@
}
},
"node_modules/undici-types": {
"version": "6.19.6",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.6.tgz",
"integrity": "sha512-e/vggGopEfTKSvj4ihnOLTsqhrKRN3LeO6qSN/GxohhuRv8qH9bNQ4B8W7e/vFL+0XTnmHPB4/kegunZGA4Org==",
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"license": "MIT"
},
"node_modules/union-value": {
@ -8230,9 +8229,9 @@
}
},
"node_modules/update-browserslist-db": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
"integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
"integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
"funding": [
{
"type": "opencollective",
@ -8249,8 +8248,8 @@
],
"license": "MIT",
"dependencies": {
"escalade": "^3.1.2",
"picocolors": "^1.0.1"
"escalade": "^3.2.0",
"picocolors": "^1.1.0"
},
"bin": {
"update-browserslist-db": "cli.js"
@ -8260,9 +8259,9 @@
}
},
"node_modules/update-browserslist-db/node_modules/picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
"license": "ISC"
},
"node_modules/urix": {

View file

@ -8,6 +8,7 @@ import {toAbsoluteUrl} from '../utils.js';
import {initDropzone} from './common-global.js';
import {POST, GET} from '../modules/fetch.js';
import {showErrorToast} from '../modules/toast.js';
import {emojiHTML} from './emoji.js';
const {appSubUrl} = window.config;
@ -124,7 +125,7 @@ export function initRepoIssueSidebarList() {
return;
}
filteredResponse.results.push({
name: `#${issue.number} ${htmlEscape(issue.title)
name: `#${issue.number} ${issueTitleHTML(htmlEscape(issue.title))
}<div class="text small tw-break-anywhere">${htmlEscape(issue.repository.full_name)}</div>`,
value: issue.id,
});
@ -731,3 +732,9 @@ export function initArchivedLabelHandler() {
toggleElem(label, label.classList.contains('checked'));
}
}
// Render the issue's title. It converts emojis and code blocks syntax into their respective HTML equivalent.
export function issueTitleHTML(title) {
return title.replaceAll(/:[-+\w]+:/g, (emoji) => emojiHTML(emoji.substring(1, emoji.length - 1)))
.replaceAll(/`[^`]+`/g, (code) => `<code class="inline-code-block">${code.substring(1, code.length - 1)}</code>`);
}

View file

@ -0,0 +1,24 @@
import {vi} from 'vitest';
import {issueTitleHTML} from './repo-issue.js';
// monaco-editor does not have any exports fields, which trips up vitest
vi.mock('./comp/ComboMarkdownEditor.js', () => ({}));
// jQuery is missing
vi.mock('./common-global.js', () => ({}));
test('Convert issue title to html', () => {
expect(issueTitleHTML('')).toEqual('');
expect(issueTitleHTML('issue title')).toEqual('issue title');
const expected_thumbs_up = `<span class="emoji" title=":+1:">👍</span>`;
expect(issueTitleHTML(':+1:')).toEqual(expected_thumbs_up);
expect(issueTitleHTML(':invalid emoji:')).toEqual(':invalid emoji:');
const expected_code_block = `<code class="inline-code-block">code</code>`;
expect(issueTitleHTML('`code`')).toEqual(expected_code_block);
expect(issueTitleHTML('`invalid code')).toEqual('`invalid code');
expect(issueTitleHTML('invalid code`')).toEqual('invalid code`');
expect(issueTitleHTML('issue title :+1: `code`')).toEqual(`issue title ${expected_thumbs_up} ${expected_code_block}`);
});