Compare commits

...

70 commits

Author SHA1 Message Date
Gusted
71e63f541d [BUG] Make delay writer actually work
- Reading the code of this delay writer implemenation, it looks like
that it should only actually write content to the `io.Writer` if x
amount of time has passed by. However in practice it was always printing
the buffer even if the X amount of time didn't pass yet. This is in line
with what was being said in the issue that this was to help with
https://github.com/go-gitea/gitea/issues/9610.
- This was caused by the extra `Close()` calls which in turn caused that
when the second `Close` is called (which is done in a defer already) it
would've printed the buffer anyway. So remove the extra calls to `Close()`.
- Add unit test.

(cherry picked from commit 9320ffd2b5)
2024-04-03 07:34:49 +00:00
Earl Warren
c0fb79b436 Merge pull request '[v7.0/forgejo] Adjust the signed tag verification line' (#2981) from bp-v7.0/forgejo-d58143e into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2981
Reviewed-by: Gergely Nagy <algernon@noreply.codeberg.org>
2024-04-03 06:32:36 +00:00
Gergely Nagy
6051722460 Adjust the signed tag verification line
Move the signed tag verification line above the release notes, don't
disable the bottom margin, and make sure the verification line's box is
properly rounded like other boxes.

Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
2024-04-02 22:03:30 +00:00
Earl Warren
f3ce65a3fc Merge pull request '[v7.0/forgejo] Allow custom repo size format' (#2980) from bp-v7.0/forgejo-2f38c22 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2980
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
2024-04-02 18:39:09 +00:00
Earl Warren
96b2ec888a Merge pull request '[v7.0/forgejo] Make pprof labels conformant with prometheus spec' (#2955) from bp-v7.0/forgejo-c8f515d into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2955
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2024-04-02 18:38:41 +00:00
0ko
f3b6759ab7 [I18N] Allow custom repo size format
Following https://codeberg.org/forgejo/forgejo/pulls/2528#issuecomment-1721846

- simplify the code
- allow to have custom format in translations
- provide proper Russian translation because test depends on it
2024-04-02 18:04:32 +00:00
Earl Warren
2b8d95c8c2 Merge pull request '[v7.0/forgejo] Data size unit localization' (#2979) from bp-v7.0/forgejo-c2d137d-dae95f4-bba1884-075e0de-29cc80d-7b9c346 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2979
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
2024-04-02 18:02:15 +00:00
0ko
470886bf52 Rename ByteSize to ReadableSize 2024-04-02 17:29:32 +00:00
0ko
d2cd1342bf Deprecate usage of FileSize in templates 2024-04-02 17:29:32 +00:00
0ko
e5212c8c96 Add integration test for TrSize 2024-04-02 17:29:32 +00:00
0ko
24552ee9ee Add unit test for TrSize 2024-04-02 17:29:32 +00:00
0ko
7b24b669ed Provide a way to translate data units 2024-04-02 17:29:32 +00:00
0ko
482658a4d0 Add data unit translations 2024-04-02 17:29:32 +00:00
Earl Warren
a89e146cb0 Merge pull request '[v7.0/forgejo] [FEAT] Mark database errors in tests as failure' (#2978) from bp-v7.0/forgejo-2dabd20 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2978
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2024-04-02 15:53:23 +00:00
Earl Warren
7befc34e68 Merge pull request '[v7.0/forgejo] [BUG] Use correct template for commitmail error' (#2977) from bp-v7.0/forgejo-27f3904 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2977
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2024-04-02 15:51:31 +00:00
Earl Warren
69d9d66dda Merge pull request '[v7.0/forgejo] [TESTS] fix flacky git check-attr subtest' (#2975) from bp-v7.0/forgejo-cef39b2 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2975
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2024-04-02 15:09:14 +00:00
Gusted
11feddc21d [FEAT] Mark database errors in tests as failure
- If the database returns a error in integration tests, it should be
marked as a failure of the test.
- Ref: https://codeberg.org/forgejo/forgejo/issues/2962 (this should
help with logging the SQL that is resulting in the error).
2024-04-02 15:00:58 +00:00
Gusted
b4f566fdf5 [BUG] Use correct template for commitmail error
- Use the correct template that was given when there's an error in
retrieving which git identity should be used.
- Remove the error from the exception list.
2024-04-02 14:59:34 +00:00
Earl Warren
5a23ce083d Merge pull request '[v7.0/forgejo] Provide plural support for issue participants' (#2971) from bp-v7.0/forgejo-39b53ef into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2971
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
2024-04-02 14:49:01 +00:00
Earl Warren
ba4f17b1d9 Merge pull request '[v7.0/forgejo] [I18N] Translations update from Weblate (#2937)' (#2970) from earl-warren/forgejo:wip-v7.0-translations into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2970
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
2024-04-02 14:48:12 +00:00
Earl Warren
f25d2ce223 Merge pull request '[v7.0/forgejo] Go and JS dependencies' (#2969) from earl-warren/forgejo:wip-v7.0-dependencies into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2969
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2024-04-02 14:44:07 +00:00
57b19874b8 [TESTS] fix flack git check-attr flacky subtest 2024-04-02 14:27:34 +00:00
0ko
46eeb884b4 Provide plural support for issue participants 2024-04-02 11:22:34 +00:00
Codeberg Translate
0abf358c94
[I18N] Translations update from Weblate (#2937)
Translations update from [Weblate](https://translate.codeberg.org) for [Forgejo/forgejo](https://translate.codeberg.org/projects/forgejo/forgejo/).

Current translation status:

![Weblate translation status](https://translate.codeberg.org/widget/forgejo/forgejo/horizontal-auto.svg)

Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Wuzzy <Wuzzy@users.noreply.translate.codeberg.org>
Co-authored-by: Salif Mehmed <mail@salif.eu>
Co-authored-by: Dirk <Dirk@users.noreply.translate.codeberg.org>
Co-authored-by: earl-warren <earl-warren@users.noreply.translate.codeberg.org>
Co-authored-by: 0ko <0ko@users.noreply.translate.codeberg.org>
Co-authored-by: yeziruo <yeziruo@users.noreply.translate.codeberg.org>
Co-authored-by: Mormegil <Mormegil@users.noreply.translate.codeberg.org>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-authored-by: hankskyjames777 <hankskyjames777@users.noreply.translate.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2937
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Codeberg Translate <translate@noreply.codeberg.org>
Co-committed-by: Codeberg Translate <translate@noreply.codeberg.org>
(cherry picked from commit fc715fb31e)
2024-04-02 12:51:56 +02:00
2937333e2d
[v7.0/forgejo] Go and JS dependencies 2024-04-02 12:27:01 +02:00
Gusted
7b97ea7154 Merge pull request '[v7.0/forgejo] fix: respond with JSON Resource Descriptor Content-Type per RFC7033' (#2968) from bp-v7.0/forgejo-2c2f146 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2968
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2024-04-02 10:22:01 +00:00
Earl Warren
59e42fee1c Merge pull request '[v7.0/forgejo] Render inline file permalinks' (#2951) from earl-warren/forgejo:wip-v7.0-inline-render into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2951
2024-04-02 09:57:35 +00:00
e13854c305 fix: respond with JSON Resource Descriptor Content-Type per RFC7033 2024-04-02 09:41:57 +00:00
Earl Warren
71153ef8b4 Merge pull request '[v7.0/forgejo] [BUG] Use correct translation on closed milestones' (#2960) from bp-v7.0/forgejo-095d845 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2960
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2024-04-02 07:17:46 +00:00
Earl Warren
7db4e374ca Merge pull request '[v7.0/forgejo] [FEAT] Configure if protected branch rule should apply to admins' (#2956) from bp-v7.0/forgejo-79b7089 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2956
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2024-04-02 05:51:43 +00:00
Gusted
2e5aa42f20 [BUG] Use correct translation on closed milestones
- Uh self explanatory? Fix a typo.
- Regression of 365bb77a54
- Resolves https://codeberg.org/Codeberg/Community/issues/1526
2024-04-02 05:33:45 +00:00
Gusted
7a783c3132 Merge pull request '[v7.0/forgejo] [TESTS] prevent overriding testlogger when calling mainApp' (#2952) from earl-warren/forgejo:wip-v7.0-non_failing_test into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2952
Reviewed-by: oliverpool <oliverpool@noreply.codeberg.org>
2024-04-01 20:33:17 +00:00
Gusted
029bcd361a [FEAT] Configure if protected branch rule should apply to admins
- Currently protected branch rules do not apply to admins, however in
some cases (like in the case of Forgejo project) you might also want to
apply these rules to admins to avoid accidental merges.
- Add new option to configure this on a per-rule basis.
- Adds integration tests.
- Resolves #65
2024-04-01 19:31:43 +00:00
1a0c9df87f [FIX] make pprof labels conformant with prometheus spec 2024-04-01 18:22:11 +00:00
ce74e66b95
[TESTS] disable test failure on log.Error for now
(cherry picked from commit 62148859b9)
2024-04-01 20:19:15 +02:00
cf460b8b5f
[TESTS] do not include line numbers in the error whitelist
They are bound to change. The worst that can happen is that the same
error happens somewhere else and is ignored although it should
not. Which is not worse than the previous situation which was to
ignore all errors anyway.

Also be more liberal about what is ignored. Some error messages are
very long and may contain elements with some variance. It is enough to
have an ignored that is specific.

(cherry picked from commit a60b34a451)
2024-04-01 20:19:03 +02:00
4706b644f8
[TESTS] deliver webhooks on localhost
(cherry picked from commit bd97bd875d)
2024-04-01 16:36:04 +02:00
b6dccc0fd4
populate testlogger ignoreList
(cherry picked from commit 0532a91b1a)
2024-04-01 16:36:04 +02:00
966faddee4
[TESTS] prevent overriding testlogger when calling mainApp
(cherry picked from commit 5785ae72c7)
2024-04-01 16:36:04 +02:00
Earl Warren
c01935e9d0 Merge pull request '[v7.0/forgejo] Highlight signed tags like signed commits' (#2950) from bp-v7.0/forgejo-bc08007-432ff7d-cd19564 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2950
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2024-04-01 14:29:24 +00:00
Mai-Lapyst
22aedc6c96
[v7.0/forgejo] Render inline file permalinks
Backport: https://codeberg.org/forgejo/forgejo/pulls/2669

(cherry picked from commit 1d3240887c)
(cherry picked from commit 781a37fbe1)
(cherry picked from commit 8309f008c2)
(cherry picked from commit fae8d9f70d)
(cherry picked from commit 6721cba75b)
(cherry picked from commit 562e5cdf32)
(cherry picked from commit d789d33229)
(cherry picked from commit 8218e80bfc)
(cherry picked from commit 10bca456a9)
(cherry picked from commit db6f6281fc)
(cherry picked from commit ed8e8a792e)
(cherry picked from commit d6428f92ce)
(cherry picked from commit 069d87b80f)
(cherry picked from commit 2b6546adc9)
(cherry picked from commit 4c7cb0a5d2)
(cherry picked from commit 7e0014dd13)
(cherry picked from commit 16a8658878)
(cherry picked from commit 6e98bacbbd)
2024-04-01 16:15:58 +02:00
Gergely Nagy
4dd475dfe5 models/asymkey: Implement Tag verification
This is, in large part, a refactoring: we rename `CommitVerification` to
`ObjectVerification`, and adjust `ParseObjectWithSignature` (previously
`ParseCommitWithSignature`) to work on an object, rather than a commit.

This in turn, lets us implement `ParseTagWithSignature` on top of it, so
commit & tag signature verification will share most of the code.

Work sponsored by @glts.

Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
2024-04-01 13:42:11 +00:00
Gergely Nagy
923035e418 Highlight signed tags like signed commits
This makes signed tags show a badge in the tag list similar to signed
commits in the commit list, and a more verbose block when viewing a
single tag. Works for both GPG and SSH signed tags.

Fixes #1316.

Work sponsored by @glts.

Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
2024-04-01 13:42:11 +00:00
Gergely Nagy
9ecd041975 An integration test for SSH signed tags
Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
2024-04-01 13:42:11 +00:00
forgejo-backport-action
9f80081795 [v7.0/forgejo] [REFACTOR] git attribute: test proper cancellation and unify nul-byte reader (#2939)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/2906

Following #2763 (refactor of git check-attr)
and #2866 (wrong log.Error format in check-attr)

- refactors the `nul-byte` reader to be used in both the streaming and one-off cases.
- add test for some failure cases
- don't log the error returned by `cmd.Run`, but return it to the `CheckPath` caller (which can then decide what to do with it).

This should solve the following flaky `log.Error` (or at least move it to the caller, instead of being inside a random goroutine):

https://codeberg.org/forgejo/forgejo/actions/runs/9541/jobs/5#jobstep-7-839

> FATAL ERROR: log.Error has been called: 2024/03/28 14:30:33 ...it/repo_attribute.go:313:func2() [E] Unable to open checker for 3fa2f829675543ecfc16b2891aebe8bf0608a8f4. Error: failed to run attr-check. Error: exit status 128
        Stderr: fatal: this operation must be run in a work tree

Co-authored-by: oliverpool <git@olivier.pfad.fr>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2939
Reviewed-by: oliverpool <oliverpool@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2024-04-01 06:44:46 +00:00
Gusted
4cb3f331a2 Merge pull request '[v7.0/forgejo] [BUG] Consistent styling for Sort filter' (#2938) from bp-v7.0/forgejo-b7d55de into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2938
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2024-03-31 21:30:51 +00:00
Gusted
fd8f51f2b6 [BUG] Consistent styling for Sort filter
- The dropdowns that contain filters for issues and pull requests are
currently not styled with the `small` class, which causes a smaller font
size to be set. Remove it for the `Sort` filter to make it consistent
and make it _more_ readable.
- Resolves #2914
2024-03-31 20:59:35 +00:00
Earl Warren
e628d0e54b Merge pull request '[v7.0/forgejo] Fix New issue button position consistency' (#2930) from bp-v7.0/forgejo-892fc82 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2930
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2024-03-31 09:45:48 +00:00
Earl Warren
ece5c97931 Merge pull request '[v7.0/forgejo] English improvements' (#2929) from bp-v7.0/forgejo-d183d32 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2929
2024-03-31 09:11:47 +00:00
0ko
b9dbd93ebc Fix New issue button consistency
Fixes https://codeberg.org/forgejo/forgejo/issues/2613
Based on https://codeberg.org/forgejo/forgejo/issues/2613#issuecomment-1654709

- add new class `list-header-issues`;
- add rules that fix button position;
- use variable to keep the vertical offset constant;
- fix gap for edit button.

Co-authored-by: Gusted <gusted@noreply.codeberg.org>
2024-03-31 08:20:16 +00:00
0ko
66d1cd89d1 [I18N] English improvements 2024-03-31 08:17:16 +00:00
Earl Warren
45f39ce839 Merge pull request '[v7.0/forgejo] Fix accessibility and translatability of repo explore counters' (#2919) from bp-v7.0/forgejo-cbd067e into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2919
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2024-03-31 07:57:10 +00:00
Earl Warren
f0a2da40ff Merge pull request '[v7.0/forgejo] [CI] backport: show event information for debug purposes' (#2927) from bp-v7.0/forgejo-7aa686d into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2927
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2024-03-31 07:56:28 +00:00
Earl Warren
60a82b0890 Merge pull request '[v7.0/forgejo] [BUG] Fix selector inner radius' (#2921) from bp-v7.0/forgejo-177d8e7 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2921
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2024-03-31 07:56:02 +00:00
Earl Warren
6b560544fb Merge pull request '[v7.0/forgejo] [REFACTOR] add Icon to webhook.Interface' (#2928) from bp-v7.0/forgejo-69115ba-120fa61 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2928
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2024-03-31 07:55:11 +00:00
Earl Warren
4a4bd75989 Merge pull request '[v7.0/forgejo] [RELEASE] GITEA_VERSION is a fallback for FORGEJO_VERSION' (#2922) from bp-v7.0/forgejo-869795a into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2922
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2024-03-31 07:24:29 +00:00
84eeab59af [REFACTOR] add Icon to webhook.Interface 2024-03-31 07:20:19 +00:00
7f03fdf9f9 [REFACTOR] use Icon from interface in webhook list 2024-03-31 07:20:18 +00:00
Earl Warren
6b2d02528f Merge pull request '[v7.0/fogejo] [I18N] Translations update from Weblate (#2841)' (#2926) from earl-warren/forgejo:wip-i18n-backport into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2926
2024-03-31 07:09:28 +00:00
92acbb0a8e [CI] backport: show event information for debug purposes
It will help figure out why two runs of testing happen when a backport
PR is open.

Refs: https://codeberg.org/forgejo/forgejo/pulls/2922
Refs: https://codeberg.org/forgejo/forgejo/issues/2009
2024-03-31 07:01:34 +00:00
Codeberg Translate
5503db386e
[I18N] Translations update from Weblate (#2841)
Translations update from [Weblate](https://translate.codeberg.org) for [Forgejo/forgejo](https://translate.codeberg.org/projects/forgejo/forgejo/).

Current translation status:

![Weblate translation status](https://translate.codeberg.org/widget/forgejo/forgejo/horizontal-auto.svg)

Co-authored-by: Wuzzy <Wuzzy@users.noreply.translate.codeberg.org>
Co-authored-by: kita <kita@users.noreply.translate.codeberg.org>
Co-authored-by: Fjuro <Fjuro@users.noreply.translate.codeberg.org>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-authored-by: yeziruo <yeziruo@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: Dirk <Dirk@users.noreply.translate.codeberg.org>
Co-authored-by: Kaede Fujisaki <ledyba@users.noreply.translate.codeberg.org>
Co-authored-by: cherryb <cherryb@users.noreply.translate.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2841
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Codeberg Translate <translate@noreply.codeberg.org>
Co-committed-by: Codeberg Translate <translate@noreply.codeberg.org>
(cherry picked from commit 03cac08b1d)
2024-03-31 08:34:41 +02:00
005a9c7850 [RELEASE] GITEA_VERSION is a fallback for FORGEJO_VERSION
Existing Forgejo packages may rely on setting GITEA_VERSION to specify
the version to build if:

* they do not build from the git repository with the proper tag
* they build from a source tarbal that does not have a VERSION file

With 7.0 the logic of setting the version was modified in the
`[RELEASE] Gitea version is for interoperability only` commit and
ignores this variable which creates an unecessary breaking change.

If GITEA_VERSION is set, the versions will be set on 7.0 exactly as
they would have with version before and included 1.21.

* If GITEA_VERSION is not set, all versions are the same
* If GITEA_VERSION is set, there is a distinction between the version
  set in the binary are returned by the Gitea API and the
  version returned by the Forgejo API which includes metadata.

Before:

$ make GITEA_VERSION=7.0.0 show-version-full
7.0.0-dev-1809-cd6fa771ab+gitea-1.22.0
$ make GITEA_VERSION=7.0.0 show-version-api
7.0.0-dev-1809-cd6fa771ab+gitea-1.22.0

After:

$ make GITEA_VERSION=7.0.0 show-version-full
7.0.0
$ make GITEA_VERSION=7.0.0 show-version-api
7.0.0+gitea-1.22.0
2024-03-30 22:39:37 +00:00
Earl Warren
d0dccaec66 Merge pull request '[v7.0/forgejo] [BUG] Don't color dot literal color names' (#2918) from bp-v7.0/forgejo-80f22ab into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2918
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2024-03-30 22:38:16 +00:00
0ko
ea0e8caa6f Fix selector inner radius
- Fixes https://codeberg.org/forgejo/forgejo/issues/2835
2024-03-30 22:35:38 +00:00
0ko
9b4d32446c Fix accessibility and translatability of repo explore counters
Progression of: 9e69ef9c51
Regression of: 65e190ae8b (diff-8d94e33cfe70fa6443d059b9c34e3f8064514816)
2024-03-30 22:27:38 +00:00
Earl Warren
ce5f1b942f Merge pull request '[v7.0/forgejo] [CI] allow backports to be launched on merged pull requests' (#2904) from bp-v7.0/forgejo-d8ab364 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2904
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2024-03-30 22:23:02 +00:00
Gusted
e428231b38 [BUG] Don't color dot literal color names
- Colordots are generated for colors in inline code, such as `red`,
`rgb(255, 0, 124)`, `#ffaabb` and `hsl(124, 52%, 50%)`. However this
shouldn't be doon for literal color names as these can be too common
assiocated with non-color related stuff _and matches the behavior of
some other forge_.
- Move the regexes from bluemonday to Forgejo and do the checking
ourselves.
- Adds unit tests.
- Resolves https://codeberg.org/Codeberg/Community/issues/1510
2024-03-30 22:12:40 +00:00
Gusted
03df59ec95 Merge pull request '[v7.0/forgejo] [BUG] Render emojis in labels in issue info popup' (#2907) from bp-v7.0/forgejo-ddc2417 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2907
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2024-03-30 16:07:12 +00:00
Gusted
a1bce73f5c [BUG] Render emojis in labels in issue info popup
- Currently emojis that are part of the label's name aren't rendered
when shown in the popup that you get when you hover over issue
references.
- This patch fixes that by rendering the emoji.
- Adds CSS to not make the emoji big in the label.
- Resolves #1531
2024-03-30 14:31:06 +00:00
19f7eb657b [CI] allow backports to be launched on merged pull requests
The intention was good initially but the expression was wrong for two
reasons:

* When a pull_request event is received for a labeled action, the
  match should be github.event.action == 'label_updated' and not
  'labeled'
* The event does not have a github.event.label field and
  contains(github.event.label.name, 'backport/v') will always be
  false.

Since the expression is only evaluated in the context of a merged pull
request, either because it was just closed or because it was labeled
after the fact, the only verification that is needed is to assert that
there is at least one `backport/v*` label.
2024-03-30 13:19:01 +00:00
165 changed files with 3863 additions and 1375 deletions

View file

@ -295,6 +295,7 @@ package "code.gitea.io/gitea/modules/translation"
func (MockLocale).TrString
func (MockLocale).Tr
func (MockLocale).TrN
func (MockLocale).TrSize
func (MockLocale).PrettyNumber
package "code.gitea.io/gitea/modules/util/filebuffer"
@ -341,5 +342,4 @@ package "code.gitea.io/gitea/services/repository/files"
package "code.gitea.io/gitea/services/webhook"
func NewNotifier
func List

View file

@ -33,22 +33,18 @@ jobs:
if: >
!startsWith(vars.ROLE, 'forgejo-') && (
github.event.pull_request.merged
&& (
(
github.event.action == 'closed' &&
contains(toJSON(github.event.pull_request.labels), 'backport/v')
)
||
(
github.event.action == 'labeled' &&
contains(github.event.label.name, 'backport/v')
)
)
&&
contains(toJSON(github.event.pull_request.labels), 'backport/v')
)
runs-on: docker
container:
image: 'docker.io/node:20-bookworm'
steps:
- name: event info
run: |
cat <<'EOF'
${{ toJSON(github) }}
EOF
- name: Fetch labels
id: fetch-labels
shell: bash

View file

@ -14,6 +14,11 @@ jobs:
container:
image: 'docker.io/node:20-bookworm'
steps:
- name: event info
run: |
cat <<'EOF'
${{ toJSON(github) }}
EOF
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4
with:

View file

@ -88,8 +88,13 @@ STORED_VERSION=$(shell cat $(STORED_VERSION_FILE) 2>/dev/null)
ifneq ($(STORED_VERSION),)
FORGEJO_VERSION ?= $(STORED_VERSION)
else
# drop the "g" prefix prepended by git describe to the commit hash
FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY}
ifneq ($(GITEA_VERSION),)
FORGEJO_VERSION ?= $(GITEA_VERSION)
FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY}
else
# drop the "g" prefix prepended by git describe to the commit hash
FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY}
endif
endif
FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//')
FORGEJO_VERSION_MINOR=$(shell echo $(FORGEJO_VERSION) | sed -E -e 's/^([0-9]+\.[0-9]+).*/\1/')
@ -106,7 +111,12 @@ show-version-minor:
RELEASE_VERSION ?= ${FORGEJO_VERSION}
VERSION ?= ${RELEASE_VERSION}
LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)"
FORGEJO_VERSION_API ?= ${FORGEJO_VERSION}
show-version-api:
@echo ${FORGEJO_VERSION_API}
LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION_API)"
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64

View file

@ -347,11 +347,10 @@ Forgejo or set your environment appropriately.`, "")
}
var out io.Writer
var dWriter *delayWriter
out = &nilWriter{}
if setting.Git.VerbosePush {
if setting.Git.VerbosePushDelay > 0 {
dWriter = newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
dWriter := newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
defer dWriter.Close()
out = dWriter
} else {
@ -414,7 +413,6 @@ Forgejo or set your environment appropriately.`, "")
hookOptions.RefFullNames = refFullNames
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
if extra.HasError() {
_ = dWriter.Close()
hookPrintResults(results)
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error)
}
@ -434,7 +432,6 @@ Forgejo or set your environment appropriately.`, "")
}
fmt.Fprintf(out, "Processed %d references in total\n", total)
_ = dWriter.Close()
hookPrintResults(results)
return nil
}
@ -447,7 +444,6 @@ Forgejo or set your environment appropriately.`, "")
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
if resp == nil {
_ = dWriter.Close()
hookPrintResults(results)
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error)
}
@ -463,9 +459,8 @@ Forgejo or set your environment appropriately.`, "")
return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error)
}
}
_ = dWriter.Close()
hookPrintResults(results)
hookPrintResults(results)
return nil
}

View file

@ -7,10 +7,20 @@ import (
"bufio"
"bytes"
"context"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
)
func TestPktLine(t *testing.T) {
@ -83,3 +93,72 @@ func TestPktLine(t *testing.T) {
assert.Empty(t, w.Bytes())
})
}
func TestDelayWriter(t *testing.T) {
// Setup the environment.
defer test.MockVariableValue(&setting.InternalToken, "Random")()
defer test.MockVariableValue(&setting.InstallLock, true)()
defer test.MockVariableValue(&setting.Git.VerbosePush, true)()
require.NoError(t, os.Setenv("SSH_ORIGINAL_COMMAND", "true"))
// Setup the Stdin.
f, err := os.OpenFile(t.TempDir()+"/stdin", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666)
require.NoError(t, err)
_, err = f.Write([]byte("00000000000000000000 00000000000000000001 refs/head/main\n"))
require.NoError(t, err)
_, err = f.Seek(0, 0)
require.NoError(t, err)
defer test.MockVariableValue(os.Stdin, *f)()
// Setup the server that processes the hooks.
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Millisecond * 600)
}))
defer ts.Close()
defer test.MockVariableValue(&setting.LocalURL, ts.URL+"/")()
app := cli.NewApp()
app.Commands = []*cli.Command{subcmdHookPreReceive}
// Capture what's being written into stdout
captureStdout := func(t *testing.T) (finish func() (output string)) {
t.Helper()
r, w, err := os.Pipe()
require.NoError(t, err)
resetStdout := test.MockVariableValue(os.Stdout, *w)
return func() (output string) {
w.Close()
resetStdout()
out, err := io.ReadAll(r)
require.NoError(t, err)
return string(out)
}
}
t.Run("Should delay", func(t *testing.T) {
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Millisecond*500)()
finish := captureStdout(t)
err = app.Run([]string{"./forgejo", "pre-receive"})
require.NoError(t, err)
out := finish()
require.Contains(t, out, "* Checking 1 references")
require.Contains(t, out, "Checked 1 references in total")
})
t.Run("Shouldn't delay", func(t *testing.T) {
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Second*5)()
finish := captureStdout(t)
err = app.Run([]string{"./forgejo", "pre-receive"})
require.NoError(t, err)
out := finish()
require.NoError(t, err)
require.Empty(t, out)
})
}

View file

@ -2338,6 +2338,8 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
;MERMAID_MAX_SOURCE_CHARACTERS = 5000
;; Set the maximum number of lines allowed for a filepreview. (Set to -1 to disable limits; set to 0 to disable the feature)
;FILEPREVIEW_MAX_LINES = 50
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

8
go.mod
View file

@ -31,7 +31,7 @@ require (
github.com/editorconfig/editorconfig-core-go/v2 v2.6.1
github.com/emersion/go-imap v1.2.1
github.com/emirpasic/gods v1.18.1
github.com/felixge/fgprof v0.9.3
github.com/felixge/fgprof v0.9.4
github.com/fsnotify/fsnotify v1.7.0
github.com/gliderlabs/ssh v0.3.7
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
@ -44,7 +44,7 @@ require (
github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.11.0
github.com/go-ldap/ldap/v3 v3.4.6
github.com/go-sql-driver/mysql v1.8.0
github.com/go-sql-driver/mysql v1.8.1
github.com/go-swagger/go-swagger v0.30.5
github.com/go-testfixtures/testfixtures/v3 v3.10.0
github.com/go-webauthn/webauthn v0.10.0
@ -53,7 +53,7 @@ require (
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/google/go-github/v57 v57.0.0
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7
github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.1.2
github.com/gorilla/sessions v1.2.2
@ -74,7 +74,7 @@ require (
github.com/meilisearch/meilisearch-go v0.26.1
github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.26
github.com/minio/minio-go/v7 v7.0.66
github.com/minio/minio-go/v7 v7.0.69
github.com/msteinert/pam v1.2.0
github.com/nektos/act v0.2.52
github.com/niklasfasching/go-org v1.7.0

32
go.sum
View file

@ -186,9 +186,15 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ=
github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0=
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
@ -256,8 +262,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/felixge/fgprof v0.9.4 h1:ocDNwMFlnA0NU0zSB3I52xkO4sFXk80VK9lXjLClu88=
github.com/felixge/fgprof v0.9.4/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
@ -336,8 +342,8 @@ github.com/go-openapi/validate v0.22.6 h1:+NhuwcEYpWdO5Nm4bmvhGLW0rt1Fcc532Mu3wp
github.com/go-openapi/validate v0.22.6/go.mod h1:eaddXSqKeTg5XpSmj1dYyFTK/95n/XHwcOY+BMxKMyM=
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-swagger/go-swagger v0.30.5 h1:SQ2+xSonWjjoEMOV5tcOnZJVlfyUfCBhGQGArS1b9+U=
github.com/go-swagger/go-swagger v0.30.5/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewetCdvEXqgPvbc/Q=
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0=
@ -353,6 +359,9 @@ github.com/go-webauthn/x v0.1.6 h1:QNAX+AWeqRt9loE8mULeWJCqhVG5D/jvdmJ47fIWCkQ=
github.com/go-webauthn/x v0.1.6/go.mod h1:W8dFVZ79o4f+nY1eOUICy/uq5dhrRl7mxQkYhXTo0FA=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
@ -441,9 +450,8 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8=
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -494,7 +502,7 @@ github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
@ -567,6 +575,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
@ -614,8 +623,8 @@ github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
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.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw=
github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs=
github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
@ -670,6 +679,7 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
@ -1034,9 +1044,9 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View file

@ -5,18 +5,10 @@ package asymkey
import (
"context"
"fmt"
"hash"
"strings"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/keybase/go-crypto/openpgp/packet"
)
// __________________ ________ ____ __.
@ -40,45 +32,22 @@ import (
// This file provides functions relating commit verification
// CommitVerification represents a commit validation of signature
type CommitVerification struct {
Verified bool
Warning bool
Reason string
SigningUser *user_model.User
CommittingUser *user_model.User
SigningEmail string
SigningKey *GPGKey
SigningSSHKey *PublicKey
TrustStatus string
}
// SignCommit represents a commit with validation of signature.
type SignCommit struct {
Verification *CommitVerification
Verification *ObjectVerification
*user_model.UserCommit
}
const (
// BadSignature is used as the reason when the signature has a KeyID that is in the db
// but no key that has that ID verifies the signature. This is a suspicious failure.
BadSignature = "gpg.error.probable_bad_signature"
// BadDefaultSignature is used as the reason when the signature has a KeyID that matches the
// default Key but is not verified by the default key. This is a suspicious failure.
BadDefaultSignature = "gpg.error.probable_bad_default_signature"
// NoKeyFound is used as the reason when no key can be found to verify the signature.
NoKeyFound = "gpg.error.no_gpg_keys_found"
)
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) []*SignCommit {
newCommits := make([]*SignCommit, 0, len(oldCommits))
keyMap := map[string]bool{}
for _, c := range oldCommits {
o := commitToGitObject(c.Commit)
signCommit := &SignCommit{
UserCommit: c,
Verification: ParseCommitWithSignature(ctx, c.Commit),
Verification: ParseObjectWithSignature(ctx, &o),
}
_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap)
@ -88,456 +57,7 @@ func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.Use
return newCommits
}
// ParseCommitWithSignature check if signature is good against keystore.
func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerification {
var committer *user_model.User
if c.Committer != nil {
var err error
// Find Committer account
committer, err = user_model.GetUserByEmail(ctx, c.Committer.Email) // This finds the user by primary email or activated email so commit will not be valid if email is not
if err != nil { // Skipping not user for committer
committer = &user_model.User{
Name: c.Committer.Name,
Email: c.Committer.Email,
}
// We can expect this to often be an ErrUserNotExist. in the case
// it is not, however, it is important to log it.
if !user_model.IsErrUserNotExist(err) {
log.Error("GetUserByEmail: %v", err)
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.no_committer_account",
}
}
}
}
// If no signature just report the committer
if c.Signature == nil {
return &CommitVerification{
CommittingUser: committer,
Verified: false, // Default value
Reason: "gpg.error.not_signed_commit", // Default value
}
}
// If this a SSH signature handle it differently
if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") {
return ParseCommitWithSSHSignature(ctx, c, committer)
}
// Parsing signature
sig, err := extractSignature(c.Signature.Signature)
if err != nil { // Skipping failed to extract sign
log.Error("SignatureRead err: %v", err)
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.extract_sign",
}
}
keyID := ""
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
keyID = fmt.Sprintf("%X", *sig.IssuerKeyId)
}
if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
}
defaultReason := NoKeyFound
// First check if the sig has a keyID and if so just look at that
if commitVerification := hashAndVerifyForKeyID(
ctx,
sig,
c.Signature.Payload,
committer,
keyID,
setting.AppName,
""); commitVerification != nil {
if commitVerification.Reason == BadSignature {
defaultReason = BadSignature
} else {
return commitVerification
}
}
// Now try to associate the signature with the committer, if present
if committer.ID != 0 {
keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
OwnerID: committer.ID,
})
if err != nil { // Skipping failed to get gpg keys of user
log.Error("ListGPGKeys: %v", err)
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys",
}
}
if err := GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
log.Error("LoadSubKeys: %v", err)
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys",
}
}
committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID)
activated := false
for _, e := range committerEmailAddresses {
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
activated = true
break
}
}
for _, k := range keys {
// Pre-check (& optimization) that emails attached to key can be attached to the committer email and can validate
canValidate := false
email := ""
if k.Verified && activated {
canValidate = true
email = c.Committer.Email
}
if !canValidate {
for _, e := range k.Emails {
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
canValidate = true
email = e.Email
break
}
}
}
if !canValidate {
continue // Skip this key
}
commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, c.Signature.Payload, k, committer, committer, email)
if commitVerification != nil {
return commitVerification
}
}
}
if setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
// OK we should try the default key
gpgSettings := git.GPGSettings{
Sign: true,
KeyID: setting.Repository.Signing.SigningKey,
Name: setting.Repository.Signing.SigningName,
Email: setting.Repository.Signing.SigningEmail,
}
if err := gpgSettings.LoadPublicKeyContent(); err != nil {
log.Error("Error getting default signing key: %s %v", gpgSettings.KeyID, err)
} else if commitVerification := verifyWithGPGSettings(ctx, &gpgSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
if commitVerification.Reason == BadSignature {
defaultReason = BadSignature
} else {
return commitVerification
}
}
}
defaultGPGSettings, err := c.GetRepositoryDefaultPublicGPGKey(false)
if err != nil {
log.Error("Error getting default public gpg key: %v", err)
} else if defaultGPGSettings == nil {
log.Warn("Unable to get defaultGPGSettings for unattached commit: %s", c.ID.String())
} else if defaultGPGSettings.Sign {
if commitVerification := verifyWithGPGSettings(ctx, defaultGPGSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
if commitVerification.Reason == BadSignature {
defaultReason = BadSignature
} else {
return commitVerification
}
}
}
return &CommitVerification{ // Default at this stage
CommittingUser: committer,
Verified: false,
Warning: defaultReason != NoKeyFound,
Reason: defaultReason,
SigningKey: &GPGKey{
KeyID: keyID,
},
}
}
func verifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, sig *packet.Signature, payload string, committer *user_model.User, keyID string) *CommitVerification {
// First try to find the key in the db
if commitVerification := hashAndVerifyForKeyID(ctx, sig, payload, committer, gpgSettings.KeyID, gpgSettings.Name, gpgSettings.Email); commitVerification != nil {
return commitVerification
}
// Otherwise we have to parse the key
ekeys, err := checkArmoredGPGKeyString(gpgSettings.PublicKeyContent)
if err != nil {
log.Error("Unable to get default signing key: %v", err)
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.generate_hash",
}
}
for _, ekey := range ekeys {
pubkey := ekey.PrimaryKey
content, err := base64EncPubKey(pubkey)
if err != nil {
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.generate_hash",
}
}
k := &GPGKey{
Content: content,
CanSign: pubkey.CanSign(),
KeyID: pubkey.KeyIdString(),
}
for _, subKey := range ekey.Subkeys {
content, err := base64EncPubKey(subKey.PublicKey)
if err != nil {
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.generate_hash",
}
}
k.SubsKey = append(k.SubsKey, &GPGKey{
Content: content,
CanSign: subKey.PublicKey.CanSign(),
KeyID: subKey.PublicKey.KeyIdString(),
})
}
if commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, payload, k, committer, &user_model.User{
Name: gpgSettings.Name,
Email: gpgSettings.Email,
}, gpgSettings.Email); commitVerification != nil {
return commitVerification
}
if keyID == k.KeyID {
// This is a bad situation ... We have a key id that matches our default key but the signature doesn't match.
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Warning: true,
Reason: BadSignature,
}
}
}
return nil
}
func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error {
// Check if key can sign
if !k.CanSign {
return fmt.Errorf("key can not sign")
}
// Decode key
pkey, err := base64DecPubKey(k.Content)
if err != nil {
return err
}
return pkey.VerifySignature(h, s)
}
func hashAndVerify(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
// Generating hash of commit
hash, err := populateHash(sig.Hash, []byte(payload))
if err != nil { // Skipping as failed to generate hash
log.Error("PopulateHash: %v", err)
return nil, err
}
// We will ignore errors in verification as they don't need to be propagated up
err = verifySign(sig, hash, k)
if err != nil {
return nil, nil
}
return k, nil
}
func hashAndVerifyWithSubKeys(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
verified, err := hashAndVerify(sig, payload, k)
if err != nil || verified != nil {
return verified, err
}
for _, sk := range k.SubsKey {
verified, err := hashAndVerify(sig, payload, sk)
if err != nil || verified != nil {
return verified, err
}
}
return nil, nil
}
func hashAndVerifyWithSubKeysCommitVerification(sig *packet.Signature, payload string, k *GPGKey, committer, signer *user_model.User, email string) *CommitVerification {
key, err := hashAndVerifyWithSubKeys(sig, payload, k)
if err != nil { // Skipping failed to generate hash
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.generate_hash",
}
}
if key != nil {
return &CommitVerification{ // Everything is ok
CommittingUser: committer,
Verified: true,
Reason: fmt.Sprintf("%s / %s", signer.Name, key.KeyID),
SigningUser: signer,
SigningKey: key,
SigningEmail: email,
}
}
return nil
}
func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload string, committer *user_model.User, keyID, name, email string) *CommitVerification {
if keyID == "" {
return nil
}
keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
KeyID: keyID,
IncludeSubKeys: true,
})
if err != nil {
log.Error("GetGPGKeysByKeyID: %v", err)
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys",
}
}
if len(keys) == 0 {
return nil
}
for _, key := range keys {
var primaryKeys []*GPGKey
if key.PrimaryKeyID != "" {
primaryKeys, err = db.Find[GPGKey](ctx, FindGPGKeyOptions{
KeyID: key.PrimaryKeyID,
IncludeSubKeys: true,
})
if err != nil {
log.Error("GetGPGKeysByKeyID: %v", err)
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys",
}
}
}
activated, email := checkKeyEmails(ctx, email, append([]*GPGKey{key}, primaryKeys...)...)
if !activated {
continue
}
signer := &user_model.User{
Name: name,
Email: email,
}
if key.OwnerID != 0 {
owner, err := user_model.GetUserByID(ctx, key.OwnerID)
if err == nil {
signer = owner
} else if !user_model.IsErrUserNotExist(err) {
log.Error("Failed to user_model.GetUserByID: %d for key ID: %d (%s) %v", key.OwnerID, key.ID, key.KeyID, err)
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.no_committer_account",
}
}
}
commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, payload, key, committer, signer, email)
if commitVerification != nil {
return commitVerification
}
}
// This is a bad situation ... We have a key id that is in our database but the signature doesn't match.
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Warning: true,
Reason: BadSignature,
}
}
// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
// There are several trust models in Gitea
func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) error {
if !verification.Verified {
return nil
}
// In the Committer trust model a signature is trusted if it matches the committer
// - it doesn't matter if they're a collaborator, the owner, Gitea or Github
// NB: This model is commit verification only
if repoTrustModel == repo_model.CommitterTrustModel {
// default to "unmatched"
verification.TrustStatus = "unmatched"
// We can only verify against users in our database but the default key will match
// against by email if it is not in the db.
if (verification.SigningUser.ID != 0 &&
verification.CommittingUser.ID == verification.SigningUser.ID) ||
(verification.SigningUser.ID == 0 && verification.CommittingUser.ID == 0 &&
verification.SigningUser.Email == verification.CommittingUser.Email) {
verification.TrustStatus = "trusted"
}
return nil
}
// Now we drop to the more nuanced trust models...
verification.TrustStatus = "trusted"
if verification.SigningUser.ID == 0 {
// This commit is signed by the default key - but this key is not assigned to a user in the DB.
// However in the repo_model.CollaboratorCommitterTrustModel we cannot mark this as trusted
// unless the default key matches the email of a non-user.
if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && (verification.CommittingUser.ID != 0 ||
verification.SigningUser.Email != verification.CommittingUser.Email) {
verification.TrustStatus = "untrusted"
}
return nil
}
// Check we actually have a GPG SigningKey
var err error
if verification.SigningKey != nil {
var isMember bool
if keyMap != nil {
var has bool
isMember, has = (*keyMap)[verification.SigningKey.KeyID]
if !has {
isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
(*keyMap)[verification.SigningKey.KeyID] = isMember
}
} else {
isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
}
if !isMember {
verification.TrustStatus = "untrusted"
if verification.CommittingUser.ID != verification.SigningUser.ID {
// The committing user and the signing user are not the same
// This should be marked as questionable unless the signing user is a collaborator/team member etc.
verification.TrustStatus = "unmatched"
}
} else if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && verification.CommittingUser.ID != verification.SigningUser.ID {
// The committing user and the signing user are not the same and our trustmodel states that they must match
verification.TrustStatus = "unmatched"
}
}
return err
func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *ObjectVerification {
o := commitToGitObject(c)
return ParseObjectWithSignature(ctx, &o)
}

View file

@ -0,0 +1,527 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
// SPDX-License-Identifier: MIT
package asymkey
import (
"context"
"fmt"
"hash"
"strings"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/keybase/go-crypto/openpgp/packet"
)
// This file provides functions related to object (commit, tag) verification
// ObjectVerification represents a commit validation of signature
type ObjectVerification struct {
Verified bool
Warning bool
Reason string
SigningUser *user_model.User
CommittingUser *user_model.User
SigningEmail string
SigningKey *GPGKey
SigningSSHKey *PublicKey
TrustStatus string
}
const (
// BadSignature is used as the reason when the signature has a KeyID that is in the db
// but no key that has that ID verifies the signature. This is a suspicious failure.
BadSignature = "gpg.error.probable_bad_signature"
// BadDefaultSignature is used as the reason when the signature has a KeyID that matches the
// default Key but is not verified by the default key. This is a suspicious failure.
BadDefaultSignature = "gpg.error.probable_bad_default_signature"
// NoKeyFound is used as the reason when no key can be found to verify the signature.
NoKeyFound = "gpg.error.no_gpg_keys_found"
)
type GitObject struct {
ID git.ObjectID
Committer *git.Signature
Signature *git.ObjectSignature
Commit *git.Commit
}
func commitToGitObject(c *git.Commit) GitObject {
return GitObject{
ID: c.ID,
Committer: c.Committer,
Signature: c.Signature,
Commit: c,
}
}
func tagToGitObject(t *git.Tag, gitRepo *git.Repository) GitObject {
commit, _ := t.Commit(gitRepo)
return GitObject{
ID: t.ID,
Committer: t.Tagger,
Signature: t.Signature,
Commit: commit,
}
}
// ParseObjectWithSignature check if signature is good against keystore.
func ParseObjectWithSignature(ctx context.Context, c *GitObject) *ObjectVerification {
var committer *user_model.User
if c.Committer != nil {
var err error
// Find Committer account
committer, err = user_model.GetUserByEmail(ctx, c.Committer.Email) // This finds the user by primary email or activated email so commit will not be valid if email is not
if err != nil { // Skipping not user for committer
committer = &user_model.User{
Name: c.Committer.Name,
Email: c.Committer.Email,
}
// We can expect this to often be an ErrUserNotExist. in the case
// it is not, however, it is important to log it.
if !user_model.IsErrUserNotExist(err) {
log.Error("GetUserByEmail: %v", err)
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.no_committer_account",
}
}
}
}
// If no signature just report the committer
if c.Signature == nil {
return &ObjectVerification{
CommittingUser: committer,
Verified: false, // Default value
Reason: "gpg.error.not_signed_commit", // Default value
}
}
// If this a SSH signature handle it differently
if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") {
return ParseObjectWithSSHSignature(ctx, c, committer)
}
// Parsing signature
sig, err := extractSignature(c.Signature.Signature)
if err != nil { // Skipping failed to extract sign
log.Error("SignatureRead err: %v", err)
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.extract_sign",
}
}
keyID := ""
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
keyID = fmt.Sprintf("%X", *sig.IssuerKeyId)
}
if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
}
defaultReason := NoKeyFound
// First check if the sig has a keyID and if so just look at that
if commitVerification := hashAndVerifyForKeyID(
ctx,
sig,
c.Signature.Payload,
committer,
keyID,
setting.AppName,
""); commitVerification != nil {
if commitVerification.Reason == BadSignature {
defaultReason = BadSignature
} else {
return commitVerification
}
}
// Now try to associate the signature with the committer, if present
if committer.ID != 0 {
keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
OwnerID: committer.ID,
})
if err != nil { // Skipping failed to get gpg keys of user
log.Error("ListGPGKeys: %v", err)
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys",
}
}
if err := GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
log.Error("LoadSubKeys: %v", err)
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys",
}
}
committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID)
activated := false
for _, e := range committerEmailAddresses {
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
activated = true
break
}
}
for _, k := range keys {
// Pre-check (& optimization) that emails attached to key can be attached to the committer email and can validate
canValidate := false
email := ""
if k.Verified && activated {
canValidate = true
email = c.Committer.Email
}
if !canValidate {
for _, e := range k.Emails {
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
canValidate = true
email = e.Email
break
}
}
}
if !canValidate {
continue // Skip this key
}
commitVerification := hashAndVerifyWithSubKeysObjectVerification(sig, c.Signature.Payload, k, committer, committer, email)
if commitVerification != nil {
return commitVerification
}
}
}
if setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
// OK we should try the default key
gpgSettings := git.GPGSettings{
Sign: true,
KeyID: setting.Repository.Signing.SigningKey,
Name: setting.Repository.Signing.SigningName,
Email: setting.Repository.Signing.SigningEmail,
}
if err := gpgSettings.LoadPublicKeyContent(); err != nil {
log.Error("Error getting default signing key: %s %v", gpgSettings.KeyID, err)
} else if commitVerification := verifyWithGPGSettings(ctx, &gpgSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
if commitVerification.Reason == BadSignature {
defaultReason = BadSignature
} else {
return commitVerification
}
}
}
defaultGPGSettings, err := c.Commit.GetRepositoryDefaultPublicGPGKey(false)
if err != nil {
log.Error("Error getting default public gpg key: %v", err)
} else if defaultGPGSettings == nil {
log.Warn("Unable to get defaultGPGSettings for unattached commit: %s", c.Commit.ID.String())
} else if defaultGPGSettings.Sign {
if commitVerification := verifyWithGPGSettings(ctx, defaultGPGSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
if commitVerification.Reason == BadSignature {
defaultReason = BadSignature
} else {
return commitVerification
}
}
}
return &ObjectVerification{ // Default at this stage
CommittingUser: committer,
Verified: false,
Warning: defaultReason != NoKeyFound,
Reason: defaultReason,
SigningKey: &GPGKey{
KeyID: keyID,
},
}
}
func verifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, sig *packet.Signature, payload string, committer *user_model.User, keyID string) *ObjectVerification {
// First try to find the key in the db
if commitVerification := hashAndVerifyForKeyID(ctx, sig, payload, committer, gpgSettings.KeyID, gpgSettings.Name, gpgSettings.Email); commitVerification != nil {
return commitVerification
}
// Otherwise we have to parse the key
ekeys, err := checkArmoredGPGKeyString(gpgSettings.PublicKeyContent)
if err != nil {
log.Error("Unable to get default signing key: %v", err)
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.generate_hash",
}
}
for _, ekey := range ekeys {
pubkey := ekey.PrimaryKey
content, err := base64EncPubKey(pubkey)
if err != nil {
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.generate_hash",
}
}
k := &GPGKey{
Content: content,
CanSign: pubkey.CanSign(),
KeyID: pubkey.KeyIdString(),
}
for _, subKey := range ekey.Subkeys {
content, err := base64EncPubKey(subKey.PublicKey)
if err != nil {
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.generate_hash",
}
}
k.SubsKey = append(k.SubsKey, &GPGKey{
Content: content,
CanSign: subKey.PublicKey.CanSign(),
KeyID: subKey.PublicKey.KeyIdString(),
})
}
if commitVerification := hashAndVerifyWithSubKeysObjectVerification(sig, payload, k, committer, &user_model.User{
Name: gpgSettings.Name,
Email: gpgSettings.Email,
}, gpgSettings.Email); commitVerification != nil {
return commitVerification
}
if keyID == k.KeyID {
// This is a bad situation ... We have a key id that matches our default key but the signature doesn't match.
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Warning: true,
Reason: BadSignature,
}
}
}
return nil
}
func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error {
// Check if key can sign
if !k.CanSign {
return fmt.Errorf("key can not sign")
}
// Decode key
pkey, err := base64DecPubKey(k.Content)
if err != nil {
return err
}
return pkey.VerifySignature(h, s)
}
func hashAndVerify(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
// Generating hash of commit
hash, err := populateHash(sig.Hash, []byte(payload))
if err != nil { // Skipping as failed to generate hash
log.Error("PopulateHash: %v", err)
return nil, err
}
// We will ignore errors in verification as they don't need to be propagated up
err = verifySign(sig, hash, k)
if err != nil {
return nil, nil
}
return k, nil
}
func hashAndVerifyWithSubKeys(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
verified, err := hashAndVerify(sig, payload, k)
if err != nil || verified != nil {
return verified, err
}
for _, sk := range k.SubsKey {
verified, err := hashAndVerify(sig, payload, sk)
if err != nil || verified != nil {
return verified, err
}
}
return nil, nil
}
func hashAndVerifyWithSubKeysObjectVerification(sig *packet.Signature, payload string, k *GPGKey, committer, signer *user_model.User, email string) *ObjectVerification {
key, err := hashAndVerifyWithSubKeys(sig, payload, k)
if err != nil { // Skipping failed to generate hash
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.generate_hash",
}
}
if key != nil {
return &ObjectVerification{ // Everything is ok
CommittingUser: committer,
Verified: true,
Reason: fmt.Sprintf("%s / %s", signer.Name, key.KeyID),
SigningUser: signer,
SigningKey: key,
SigningEmail: email,
}
}
return nil
}
func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload string, committer *user_model.User, keyID, name, email string) *ObjectVerification {
if keyID == "" {
return nil
}
keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
KeyID: keyID,
IncludeSubKeys: true,
})
if err != nil {
log.Error("GetGPGKeysByKeyID: %v", err)
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys",
}
}
if len(keys) == 0 {
return nil
}
for _, key := range keys {
var primaryKeys []*GPGKey
if key.PrimaryKeyID != "" {
primaryKeys, err = db.Find[GPGKey](ctx, FindGPGKeyOptions{
KeyID: key.PrimaryKeyID,
IncludeSubKeys: true,
})
if err != nil {
log.Error("GetGPGKeysByKeyID: %v", err)
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys",
}
}
}
activated, email := checkKeyEmails(ctx, email, append([]*GPGKey{key}, primaryKeys...)...)
if !activated {
continue
}
signer := &user_model.User{
Name: name,
Email: email,
}
if key.OwnerID != 0 {
owner, err := user_model.GetUserByID(ctx, key.OwnerID)
if err == nil {
signer = owner
} else if !user_model.IsErrUserNotExist(err) {
log.Error("Failed to user_model.GetUserByID: %d for key ID: %d (%s) %v", key.OwnerID, key.ID, key.KeyID, err)
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.no_committer_account",
}
}
}
commitVerification := hashAndVerifyWithSubKeysObjectVerification(sig, payload, key, committer, signer, email)
if commitVerification != nil {
return commitVerification
}
}
// This is a bad situation ... We have a key id that is in our database but the signature doesn't match.
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Warning: true,
Reason: BadSignature,
}
}
// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
// There are several trust models in Gitea
func CalculateTrustStatus(verification *ObjectVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) error {
if !verification.Verified {
return nil
}
// In the Committer trust model a signature is trusted if it matches the committer
// - it doesn't matter if they're a collaborator, the owner, Gitea or Github
// NB: This model is commit verification only
if repoTrustModel == repo_model.CommitterTrustModel {
// default to "unmatched"
verification.TrustStatus = "unmatched"
// We can only verify against users in our database but the default key will match
// against by email if it is not in the db.
if (verification.SigningUser.ID != 0 &&
verification.CommittingUser.ID == verification.SigningUser.ID) ||
(verification.SigningUser.ID == 0 && verification.CommittingUser.ID == 0 &&
verification.SigningUser.Email == verification.CommittingUser.Email) {
verification.TrustStatus = "trusted"
}
return nil
}
// Now we drop to the more nuanced trust models...
verification.TrustStatus = "trusted"
if verification.SigningUser.ID == 0 {
// This commit is signed by the default key - but this key is not assigned to a user in the DB.
// However in the repo_model.CollaboratorCommitterTrustModel we cannot mark this as trusted
// unless the default key matches the email of a non-user.
if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && (verification.CommittingUser.ID != 0 ||
verification.SigningUser.Email != verification.CommittingUser.Email) {
verification.TrustStatus = "untrusted"
}
return nil
}
// Check we actually have a GPG SigningKey
var err error
if verification.SigningKey != nil {
var isMember bool
if keyMap != nil {
var has bool
isMember, has = (*keyMap)[verification.SigningKey.KeyID]
if !has {
isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
(*keyMap)[verification.SigningKey.KeyID] = isMember
}
} else {
isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
}
if !isMember {
verification.TrustStatus = "untrusted"
if verification.CommittingUser.ID != verification.SigningUser.ID {
// The committing user and the signing user are not the same
// This should be marked as questionable unless the signing user is a collaborator/team member etc.
verification.TrustStatus = "unmatched"
}
} else if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && verification.CommittingUser.ID != verification.SigningUser.ID {
// The committing user and the signing user are not the same and our trustmodel states that they must match
verification.TrustStatus = "unmatched"
}
}
return err
}

View file

@ -0,0 +1,15 @@
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
// SPDX-License-Identifier: MIT
package asymkey
import (
"context"
"code.gitea.io/gitea/modules/git"
)
func ParseTagWithSignature(ctx context.Context, gitRepo *git.Repository, t *git.Tag) *ObjectVerification {
o := tagToGitObject(t, gitRepo)
return ParseObjectWithSignature(ctx, &o)
}

View file

@ -11,14 +11,13 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"github.com/42wim/sshsig"
)
// ParseCommitWithSSHSignature check if signature is good against keystore.
func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *user_model.User) *CommitVerification {
// ParseObjectWithSSHSignature check if signature is good against keystore.
func ParseObjectWithSSHSignature(ctx context.Context, c *GitObject, committer *user_model.User) *ObjectVerification {
// Now try to associate the signature with the committer, if present
if committer.ID != 0 {
keys, err := db.Find[PublicKey](ctx, FindPublicKeyOptions{
@ -27,7 +26,7 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
})
if err != nil { // Skipping failed to get ssh keys of user
log.Error("ListPublicKeys: %v", err)
return &CommitVerification{
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys",
@ -55,7 +54,7 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
for _, k := range keys {
if k.Verified && activated {
commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email)
commitVerification := verifySSHObjectVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email)
if commitVerification != nil {
return commitVerification
}
@ -63,19 +62,19 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
}
}
return &CommitVerification{
return &ObjectVerification{
CommittingUser: committer,
Verified: false,
Reason: NoKeyFound,
}
}
func verifySSHCommitVerification(sig, payload string, k *PublicKey, committer, signer *user_model.User, email string) *CommitVerification {
func verifySSHObjectVerification(sig, payload string, k *PublicKey, committer, signer *user_model.User, email string) *ObjectVerification {
if err := sshsig.Verify(bytes.NewBuffer([]byte(payload)), []byte(sig), []byte(k.Content), "git"); err != nil {
return nil
}
return &CommitVerification{ // Everything is ok
return &ObjectVerification{ // Everything is ok
CommittingUser: committer,
Verified: true,
Reason: fmt.Sprintf("%s / %s", signer.Name, k.Fingerprint),

View file

@ -22,7 +22,8 @@ func TestParseCommitWithSSHSignature(t *testing.T) {
sshKey := unittest.AssertExistsAndLoadBean(t, &PublicKey{ID: 1000, OwnerID: 2})
t.Run("No commiter", func(t *testing.T) {
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, &git.Commit{}, &user_model.User{})
o := commitToGitObject(&git.Commit{})
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, &user_model.User{})
assert.False(t, commitVerification.Verified)
assert.Equal(t, NoKeyFound, commitVerification.Reason)
})
@ -30,7 +31,8 @@ func TestParseCommitWithSSHSignature(t *testing.T) {
t.Run("Commiter without keys", func(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, &git.Commit{Committer: &git.Signature{Email: user.Email}}, user)
o := commitToGitObject(&git.Commit{Committer: &git.Signature{Email: user.Email}})
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user)
assert.False(t, commitVerification.Verified)
assert.Equal(t, NoKeyFound, commitVerification.Reason)
})
@ -57,7 +59,8 @@ AAAAQIMufOuSjZeDUujrkVK4sl7ICa0WwEftas8UAYxx0Thdkiw2qWjR1U1PKfTLm16/w8
`,
},
}
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
o := commitToGitObject(gitCommit)
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
assert.False(t, commitVerification.Verified)
assert.Equal(t, NoKeyFound, commitVerification.Reason)
})
@ -79,7 +82,8 @@ Add content
},
}
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
o := commitToGitObject(gitCommit)
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
assert.False(t, commitVerification.Verified)
assert.Equal(t, NoKeyFound, commitVerification.Reason)
})
@ -107,7 +111,8 @@ fs9cMpZVM9BfIKNUSO8QY=
},
}
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
o := commitToGitObject(gitCommit)
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
assert.True(t, commitVerification.Verified)
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
assert.Equal(t, sshKey, commitVerification.SigningSSHKey)
@ -138,7 +143,8 @@ muPLbvEduU+Ze/1Ol1pgk=
},
}
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
o := commitToGitObject(gitCommit)
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
assert.True(t, commitVerification.Verified)
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
assert.Equal(t, sshKey, commitVerification.SigningSSHKey)

View file

@ -155,8 +155,14 @@ func InitEngine(ctx context.Context) error {
Logger: log.GetLogger("xorm"),
})
}
errorLogger := log.GetLogger("xorm")
if setting.IsInTesting {
errorLogger = log.GetLogger(log.DEFAULT)
}
xormEngine.AddHook(&ErrorQueryHook{
Logger: log.GetLogger("xorm"),
Logger: errorLogger,
})
SetDefaultEngine(ctx, xormEngine)

View file

@ -54,6 +54,8 @@ var migrations = []*Migration{
NewMigration("Add the `enable_repo_unit_hints` column to the `user` table", forgejo_v1_22.AddUserRepoUnitHintsSetting),
// v7 -> v8
NewMigration("Modify the `release`.`note` content to remove SSH signatures", forgejo_v1_22.RemoveSSHSignaturesFromReleaseNotes),
// v8 -> v9
NewMigration("Add the `apply_to_admins` column to the `protected_branch` table", forgejo_v1_22.AddApplyToAdminsSetting),
}
// GetCurrentDBVersion returns the current Forgejo database version.

View file

@ -0,0 +1,15 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_22 //nolint
import "xorm.io/xorm"
func AddApplyToAdminsSetting(x *xorm.Engine) error {
type ProtectedBranch struct {
ID int64 `xorm:"pk autoincr"`
ApplyToAdmins bool `xorm:"NOT NULL DEFAULT false"`
}
return x.Sync(&ProtectedBranch{})
}

View file

@ -58,6 +58,7 @@ type ProtectedBranch struct {
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
ProtectedFilePatterns string `xorm:"TEXT"`
UnprotectedFilePatterns string `xorm:"TEXT"`
ApplyToAdmins bool `xorm:"NOT NULL DEFAULT false"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`

View file

@ -16,7 +16,6 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
@ -24,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder"
@ -234,6 +234,7 @@ type SizeDetail struct {
}
// SizeDetails forms a struct with various size details about repository
// Note: SizeDetailsString below expects it to have 2 entries
func (repo *Repository) SizeDetails() []SizeDetail {
sizeDetails := []SizeDetail{
{
@ -249,13 +250,9 @@ func (repo *Repository) SizeDetails() []SizeDetail {
}
// SizeDetailsString returns a concatenation of all repository size details as a string
func (repo *Repository) SizeDetailsString() string {
var str strings.Builder
func (repo *Repository) SizeDetailsString(locale translation.Locale) string {
sizeDetails := repo.SizeDetails()
for _, detail := range sizeDetails {
str.WriteString(fmt.Sprintf("%s: %s, ", detail.Name, base.FileSize(detail.Size)))
}
return strings.TrimSuffix(str.String(), ", ")
return locale.TrString("repo.size_format", sizeDetails[0].Name, locale.TrSize(sizeDetails[0].Size), sizeDetails[1].Name, locale.TrSize(sizeDetails[1].Size))
}
func (repo *Repository) LogString() string {

View file

@ -4,18 +4,76 @@
package git
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"os"
"strings"
"sync/atomic"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
)
var LinguistAttributes = []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language", "linguist-documentation", "linguist-detectable"}
// newCheckAttrStdoutReader parses the nul-byte separated output of git check-attr on each call of
// the returned function. The first reading error will stop the reading and be returned on all
// subsequent calls.
func newCheckAttrStdoutReader(r io.Reader, count int) func() (map[string]GitAttribute, error) {
scanner := bufio.NewScanner(r)
// adapted from bufio.ScanLines to split on nul-byte \x00
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, '\x00'); i >= 0 {
// We have a full nul-terminated line.
return i + 1, data[0:i], nil
}
// If we're at EOF, we have a final, non-terminated line. Return it.
if atEOF {
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
})
var err error
nextText := func() string {
if err != nil {
return ""
}
if !scanner.Scan() {
err = scanner.Err()
if err == nil {
err = io.ErrUnexpectedEOF
}
return ""
}
return scanner.Text()
}
nextAttribute := func() (string, GitAttribute, error) {
nextText() // discard filename
key := nextText()
value := GitAttribute(nextText())
return key, value, err
}
return func() (map[string]GitAttribute, error) {
values := make(map[string]GitAttribute, count)
for range count {
k, v, err := nextAttribute()
if err != nil {
return values, err
}
values[k] = v
}
return values, scanner.Err()
}
}
// GitAttribute exposes an attribute from the .gitattribute file
type GitAttribute string //nolint:revive
@ -54,29 +112,15 @@ func (ca GitAttribute) Bool() optional.Option[bool] {
return optional.None[bool]()
}
// GitAttributeFirst returns the first specified attribute
//
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
func (repo *Repository) GitAttributeFirst(treeish, filename string, attributes ...string) (GitAttribute, error) {
values, err := repo.GitAttributes(treeish, filename, attributes...)
if err != nil {
return "", err
}
for _, a := range attributes {
if values[a].IsSpecified() {
return values[a], nil
}
}
return "", nil
}
// gitCheckAttrCommand prepares the "git check-attr" command for later use as one-shot or streaming
// instanciation.
func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string) (*Command, *RunOpts, context.CancelFunc, error) {
if len(attributes) == 0 {
return nil, nil, nil, fmt.Errorf("no provided attributes to check-attr")
}
env := os.Environ()
var deleteTemporaryFile context.CancelFunc
var removeTempFiles context.CancelFunc = func() {}
// git < 2.40 cannot run check-attr on bare repo, but needs INDEX + WORK_TREE
hasIndex := treeish == ""
@ -85,7 +129,7 @@ func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string
if err != nil {
return nil, nil, nil, err
}
deleteTemporaryFile = cancel
removeTempFiles = cancel
env = append(env, "GIT_INDEX_FILE="+indexFilename, "GIT_WORK_TREE="+worktree)
@ -94,16 +138,8 @@ func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string
// clear treeish to read from provided index/work_tree
treeish = ""
}
ctx, cancel := context.WithCancel(repo.Ctx)
if deleteTemporaryFile != nil {
ctxCancel := cancel
cancel = func() {
ctxCancel()
deleteTemporaryFile()
}
}
cmd := NewCommand(ctx, "check-attr", "-z")
cmd := NewCommand(repo.Ctx, "check-attr", "-z")
if hasIndex {
cmd.AddArguments("--cached")
@ -126,18 +162,34 @@ func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string
return cmd, &RunOpts{
Env: env,
Dir: repo.Path,
}, cancel, nil
}, removeTempFiles, nil
}
// GitAttributes returns gitattribute.
// GitAttributeFirst returns the first specified attribute of the given filename.
//
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
func (repo *Repository) GitAttributeFirst(treeish, filename string, attributes ...string) (GitAttribute, error) {
values, err := repo.GitAttributes(treeish, filename, attributes...)
if err != nil {
return "", err
}
for _, a := range attributes {
if values[a].IsSpecified() {
return values[a], nil
}
}
return "", nil
}
// GitAttributes returns the gitattribute of the given filename.
//
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
func (repo *Repository) GitAttributes(treeish, filename string, attributes ...string) (map[string]GitAttribute, error) {
cmd, runOpts, cancel, err := repo.gitCheckAttrCommand(treeish, attributes...)
cmd, runOpts, removeTempFiles, err := repo.gitCheckAttrCommand(treeish, attributes...)
if err != nil {
return nil, err
}
defer cancel()
defer removeTempFiles()
stdOut := new(bytes.Buffer)
runOpts.Stdout = stdOut
@ -151,163 +203,84 @@ func (repo *Repository) GitAttributes(treeish, filename string, attributes ...st
return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String())
}
// FIXME: This is incorrect on versions < 1.8.5
fields := bytes.Split(stdOut.Bytes(), []byte{'\000'})
if len(fields)%3 != 1 {
return nil, fmt.Errorf("wrong number of fields in return from check-attr")
}
values := make(map[string]GitAttribute, len(attributes))
for ; len(fields) >= 3; fields = fields[3:] {
// filename := string(fields[0])
attribute := string(fields[1])
value := string(fields[2])
values[attribute] = GitAttribute(value)
}
return values, nil
return newCheckAttrStdoutReader(stdOut, len(attributes))()
}
type attributeTriple struct {
Filename string
Attribute string
Value string
}
type nulSeparatedAttributeWriter struct {
tmp []byte
attributes chan attributeTriple
closed chan struct{}
working attributeTriple
pos int
}
func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
l, read := len(p), 0
nulIdx := bytes.IndexByte(p, '\x00')
for nulIdx >= 0 {
wr.tmp = append(wr.tmp, p[:nulIdx]...)
switch wr.pos {
case 0:
wr.working = attributeTriple{
Filename: string(wr.tmp),
}
case 1:
wr.working.Attribute = string(wr.tmp)
case 2:
wr.working.Value = string(wr.tmp)
}
wr.tmp = wr.tmp[:0]
wr.pos++
if wr.pos > 2 {
wr.attributes <- wr.working
wr.pos = 0
}
read += nulIdx + 1
if l > read {
p = p[nulIdx+1:]
nulIdx = bytes.IndexByte(p, '\x00')
} else {
return l, nil
}
}
wr.tmp = append(wr.tmp, p...)
return len(p), nil
}
func (wr *nulSeparatedAttributeWriter) Close() error {
select {
case <-wr.closed:
return nil
default:
}
close(wr.attributes)
close(wr.closed)
return nil
}
// GitAttributeChecker creates an AttributeChecker for the given repository and provided commit ID.
// GitAttributeChecker creates an AttributeChecker for the given repository and provided commit ID
// to retrieve the attributes of multiple files. The AttributeChecker must be closed after use.
//
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
func (repo *Repository) GitAttributeChecker(treeish string, attributes ...string) (AttributeChecker, error) {
cmd, runOpts, cancel, err := repo.gitCheckAttrCommand(treeish, attributes...)
cmd, runOpts, removeTempFiles, err := repo.gitCheckAttrCommand(treeish, attributes...)
if err != nil {
return AttributeChecker{}, err
}
ac := AttributeChecker{
attributeNumber: len(attributes),
ctx: cmd.parentContext,
cancel: cancel, // will be cancelled on Close
}
stdinReader, stdinWriter, err := os.Pipe()
if err != nil {
ac.cancel()
return AttributeChecker{}, err
}
ac.stdinWriter = stdinWriter // will be closed on Close
lw := new(nulSeparatedAttributeWriter)
lw.attributes = make(chan attributeTriple, len(attributes))
lw.closed = make(chan struct{})
ac.attributesCh = lw.attributes
cmd.AddArguments("--stdin")
// os.Pipe is needed (and not io.Pipe), otherwise cmd.Wait will wait for the stdinReader
// to be closed before returning (which would require another goroutine)
// https://go.dev/issue/23019
stdinReader, stdinWriter, err := os.Pipe() // reader closed in goroutine / writer closed on ac.Close
if err != nil {
return AttributeChecker{}, err
}
stdoutReader, stdoutWriter := io.Pipe() // closed in goroutine
ac := AttributeChecker{
removeTempFiles: removeTempFiles, // called on ac.Close
stdinWriter: stdinWriter,
readStdout: newCheckAttrStdoutReader(stdoutReader, len(attributes)),
err: &atomic.Value{},
}
go func() {
defer stdinReader.Close()
defer lw.Close()
defer stdoutWriter.Close() // in case of a panic (no-op if already closed by CloseWithError at the end)
stdErr := new(bytes.Buffer)
runOpts.Stdin = stdinReader
runOpts.Stdout = lw
runOpts.Stdout = stdoutWriter
runOpts.Stderr = stdErr
err := cmd.Run(runOpts)
if err != nil && // If there is an error we need to return but:
cmd.parentContext.Err() != err && // 1. Ignore the context error if the context is cancelled or exceeds the deadline (RunWithContext could return c.ctx.Err() which is Canceled or DeadlineExceeded)
err.Error() != "signal: killed" { // 2. We should not pass up errors due to the program being killed
log.Error("failed to run attr-check. Error: %v\nStderr: %s", err, stdErr.String())
// if the context was cancelled, Run error is irrelevant
if e := cmd.parentContext.Err(); e != nil {
err = e
}
if err != nil { // decorate the returned error
err = fmt.Errorf("git check-attr (stderr: %q): %w", strings.TrimSpace(stdErr.String()), err)
ac.err.Store(err)
}
stdoutWriter.CloseWithError(err)
}()
return ac, nil
}
type AttributeChecker struct {
ctx context.Context
cancel context.CancelFunc
stdinWriter *os.File
attributeNumber int
attributesCh <-chan attributeTriple
removeTempFiles context.CancelFunc
stdinWriter io.WriteCloser
readStdout func() (map[string]GitAttribute, error)
err *atomic.Value
}
func (ac AttributeChecker) CheckPath(path string) (map[string]GitAttribute, error) {
if err := ac.ctx.Err(); err != nil {
return nil, err
}
if _, err := ac.stdinWriter.Write([]byte(path + "\x00")); err != nil {
return nil, err
// try to return the Run error if available, since it is likely more helpful
// than just "broken pipe"
if aerr, _ := ac.err.Load().(error); aerr != nil {
return nil, aerr
}
return nil, fmt.Errorf("git check-attr: %w", err)
}
rs := make(map[string]GitAttribute)
for i := 0; i < ac.attributeNumber; i++ {
select {
case attr, ok := <-ac.attributesCh:
if !ok {
return nil, ac.ctx.Err()
}
rs[attr.Attribute] = GitAttribute(attr.Value)
case <-ac.ctx.Done():
return nil, ac.ctx.Err()
}
}
return rs, nil
return ac.readStdout()
}
func (ac AttributeChecker) Close() error {
ac.cancel()
ac.removeTempFiles()
return ac.stdinWriter.Close()
}

View file

@ -4,7 +4,14 @@
package git
import (
"context"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
@ -14,90 +21,63 @@ import (
"github.com/stretchr/testify/require"
)
func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
wr := &nulSeparatedAttributeWriter{
attributes: make(chan attributeTriple, 5),
}
func TestNewCheckAttrStdoutReader(t *testing.T) {
t.Run("two_times", func(t *testing.T) {
read := newCheckAttrStdoutReader(strings.NewReader(
".gitignore\x00linguist-vendored\x00unspecified\x00"+
".gitignore\x00linguist-vendored\x00specified",
), 1)
testStr := ".gitignore\"\n\x00linguist-vendored\x00unspecified\x00"
// first read
attr, err := read()
assert.NoError(t, err)
assert.Equal(t, map[string]GitAttribute{
"linguist-vendored": GitAttribute("unspecified"),
}, attr)
n, err := wr.Write([]byte(testStr))
// second read
attr, err = read()
assert.NoError(t, err)
assert.Equal(t, map[string]GitAttribute{
"linguist-vendored": GitAttribute("specified"),
}, attr)
})
t.Run("incomplete", func(t *testing.T) {
read := newCheckAttrStdoutReader(strings.NewReader(
"filename\x00linguist-vendored",
), 1)
assert.Len(t, testStr, n)
assert.NoError(t, err)
select {
case attr := <-wr.attributes:
assert.Equal(t, ".gitignore\"\n", attr.Filename)
assert.Equal(t, "linguist-vendored", attr.Attribute)
assert.Equal(t, "unspecified", attr.Value)
case <-time.After(100 * time.Millisecond):
assert.FailNow(t, "took too long to read an attribute from the list")
}
// Write a second attribute again
n, err = wr.Write([]byte(testStr))
_, err := read()
assert.Equal(t, io.ErrUnexpectedEOF, err)
})
t.Run("three_times", func(t *testing.T) {
read := newCheckAttrStdoutReader(strings.NewReader(
"shouldbe.vendor\x00linguist-vendored\x00set\x00"+
"shouldbe.vendor\x00linguist-generated\x00unspecified\x00"+
"shouldbe.vendor\x00linguist-language\x00unspecified\x00",
), 1)
assert.Len(t, testStr, n)
assert.NoError(t, err)
// first read
attr, err := read()
assert.NoError(t, err)
assert.Equal(t, map[string]GitAttribute{
"linguist-vendored": GitAttribute("set"),
}, attr)
select {
case attr := <-wr.attributes:
assert.Equal(t, ".gitignore\"\n", attr.Filename)
assert.Equal(t, "linguist-vendored", attr.Attribute)
assert.Equal(t, "unspecified", attr.Value)
case <-time.After(100 * time.Millisecond):
assert.FailNow(t, "took too long to read an attribute from the list")
}
// second read
attr, err = read()
assert.NoError(t, err)
assert.Equal(t, map[string]GitAttribute{
"linguist-generated": GitAttribute("unspecified"),
}, attr)
// Write a partial attribute
_, err = wr.Write([]byte("incomplete-file"))
assert.NoError(t, err)
_, err = wr.Write([]byte("name\x00"))
assert.NoError(t, err)
select {
case <-wr.attributes:
assert.FailNow(t, "There should not be an attribute ready to read")
case <-time.After(100 * time.Millisecond):
}
_, err = wr.Write([]byte("attribute\x00"))
assert.NoError(t, err)
select {
case <-wr.attributes:
assert.FailNow(t, "There should not be an attribute ready to read")
case <-time.After(100 * time.Millisecond):
}
_, err = wr.Write([]byte("value\x00"))
assert.NoError(t, err)
attr := <-wr.attributes
assert.Equal(t, "incomplete-filename", attr.Filename)
assert.Equal(t, "attribute", attr.Attribute)
assert.Equal(t, "value", attr.Value)
_, err = wr.Write([]byte("shouldbe.vendor\x00linguist-vendored\x00set\x00shouldbe.vendor\x00linguist-generated\x00unspecified\x00shouldbe.vendor\x00linguist-language\x00unspecified\x00"))
assert.NoError(t, err)
attr = <-wr.attributes
assert.NoError(t, err)
assert.EqualValues(t, attributeTriple{
Filename: "shouldbe.vendor",
Attribute: "linguist-vendored",
Value: "set",
}, attr)
attr = <-wr.attributes
assert.NoError(t, err)
assert.EqualValues(t, attributeTriple{
Filename: "shouldbe.vendor",
Attribute: "linguist-generated",
Value: "unspecified",
}, attr)
attr = <-wr.attributes
assert.NoError(t, err)
assert.EqualValues(t, attributeTriple{
Filename: "shouldbe.vendor",
Attribute: "linguist-language",
Value: "unspecified",
}, attr)
// third read
attr, err = read()
assert.NoError(t, err)
assert.Equal(t, map[string]GitAttribute{
"linguist-language": GitAttribute("unspecified"),
}, attr)
})
}
func TestGitAttributeBareNonBare(t *testing.T) {
@ -114,33 +94,35 @@ func TestGitAttributeBareNonBare(t *testing.T) {
"8fee858da5796dfb37704761701bb8e800ad9ef3",
"341fca5b5ea3de596dc483e54c2db28633cd2f97",
} {
t.Run("GitAttributeChecker/"+commitID, func(t *testing.T) {
bareStats, err := gitRepo.GitAttributes(commitID, "i-am-a-python.p", LinguistAttributes...)
assert.NoError(t, err)
defer test.MockVariableValue(&SupportCheckAttrOnBare, false)()
cloneStats, err := gitRepo.GitAttributes(commitID, "i-am-a-python.p", LinguistAttributes...)
assert.NoError(t, err)
assert.EqualValues(t, cloneStats, bareStats)
refStats := cloneStats
t.Run("GitAttributeChecker/"+commitID+"/SupportBare", func(t *testing.T) {
bareChecker, err := gitRepo.GitAttributeChecker(commitID, LinguistAttributes...)
assert.NoError(t, err)
t.Cleanup(func() { bareChecker.Close() })
defer bareChecker.Close()
bareStats, err := bareChecker.CheckPath("i-am-a-python.p")
assert.NoError(t, err)
assert.EqualValues(t, refStats, bareStats)
})
t.Run("GitAttributeChecker/"+commitID+"/NoBareSupport", func(t *testing.T) {
defer test.MockVariableValue(&SupportCheckAttrOnBare, false)()
cloneChecker, err := gitRepo.GitAttributeChecker(commitID, LinguistAttributes...)
assert.NoError(t, err)
t.Cleanup(func() { cloneChecker.Close() })
defer cloneChecker.Close()
cloneStats, err := cloneChecker.CheckPath("i-am-a-python.p")
assert.NoError(t, err)
assert.EqualValues(t, cloneStats, bareStats)
})
t.Run("GitAttributes/"+commitID, func(t *testing.T) {
bareStats, err := gitRepo.GitAttributes(commitID, "i-am-a-python.p", LinguistAttributes...)
assert.NoError(t, err)
defer test.MockVariableValue(&SupportCheckAttrOnBare, false)()
cloneStats, err := gitRepo.GitAttributes(commitID, "i-am-a-python.p", LinguistAttributes...)
assert.NoError(t, err)
assert.EqualValues(t, cloneStats, bareStats)
assert.EqualValues(t, refStats, cloneStats)
})
}
}
@ -208,3 +190,162 @@ func TestGitAttributeStruct(t *testing.T) {
assert.Equal(t, "text?token=Error", GitAttribute("text?token=Error").String())
assert.Equal(t, "text", GitAttribute("text?token=Error").Prefix())
}
func TestGitAttributeCheckerError(t *testing.T) {
prepareRepo := func(t *testing.T) *Repository {
t.Helper()
path := t.TempDir()
// we can't use unittest.CopyDir because of an import cycle (git.Init in unittest)
require.NoError(t, CopyFS(path, os.DirFS(filepath.Join(testReposDir, "language_stats_repo"))))
gitRepo, err := openRepositoryWithDefaultContext(path)
require.NoError(t, err)
return gitRepo
}
t.Run("RemoveAll/BeforeRun", func(t *testing.T) {
gitRepo := prepareRepo(t)
defer gitRepo.Close()
assert.NoError(t, os.RemoveAll(gitRepo.Path))
ac, err := gitRepo.GitAttributeChecker("", "linguist-language")
require.NoError(t, err)
_, err = ac.CheckPath("i-am-a-python.p")
assert.Error(t, err)
assert.Contains(t, err.Error(), `git check-attr (stderr: ""):`)
})
t.Run("RemoveAll/DuringRun", func(t *testing.T) {
gitRepo := prepareRepo(t)
defer gitRepo.Close()
ac, err := gitRepo.GitAttributeChecker("", "linguist-language")
require.NoError(t, err)
// calling CheckPath before would allow git to cache part of it and succesfully return later
assert.NoError(t, os.RemoveAll(gitRepo.Path))
_, err = ac.CheckPath("i-am-a-python.p")
if err == nil {
t.Skip(
"git check-attr started too fast and CheckPath was succesful (and likely cached)",
"https://codeberg.org/forgejo/forgejo/issues/2948",
)
}
// Depending on the order of execution, the returned error can be:
// - a launch error "fork/exec /usr/bin/git: no such file or directory" (when the removal happens before the Run)
// - a git error (stderr: "fatal: Unable to read current working directory: No such file or directory"): exit status 128 (when the removal happens after the Run)
// (pipe error "write |1: broken pipe" should be replaced by one of the Run errors above)
assert.Contains(t, err.Error(), `git check-attr`)
})
t.Run("Cancelled/BeforeRun", func(t *testing.T) {
gitRepo := prepareRepo(t)
defer gitRepo.Close()
var cancel context.CancelFunc
gitRepo.Ctx, cancel = context.WithCancel(gitRepo.Ctx)
cancel()
ac, err := gitRepo.GitAttributeChecker("8fee858da5796dfb37704761701bb8e800ad9ef3", "linguist-language")
require.NoError(t, err)
_, err = ac.CheckPath("i-am-a-python.p")
assert.ErrorIs(t, err, context.Canceled)
})
t.Run("Cancelled/DuringRun", func(t *testing.T) {
gitRepo := prepareRepo(t)
defer gitRepo.Close()
var cancel context.CancelFunc
gitRepo.Ctx, cancel = context.WithCancel(gitRepo.Ctx)
ac, err := gitRepo.GitAttributeChecker("8fee858da5796dfb37704761701bb8e800ad9ef3", "linguist-language")
require.NoError(t, err)
attr, err := ac.CheckPath("i-am-a-python.p")
assert.NoError(t, err)
assert.Equal(t, "Python", attr["linguist-language"].String())
errCh := make(chan error)
go func() {
cancel()
for err == nil {
_, err = ac.CheckPath("i-am-a-python.p")
runtime.Gosched() // the cancellation must have time to propagate
}
errCh <- err
}()
select {
case <-time.After(time.Second):
t.Error("CheckPath did not complete within 1s")
case err = <-errCh:
assert.ErrorIs(t, err, context.Canceled)
}
})
t.Run("Closed/BeforeRun", func(t *testing.T) {
gitRepo := prepareRepo(t)
defer gitRepo.Close()
ac, err := gitRepo.GitAttributeChecker("8fee858da5796dfb37704761701bb8e800ad9ef3", "linguist-language")
require.NoError(t, err)
assert.NoError(t, ac.Close())
_, err = ac.CheckPath("i-am-a-python.p")
assert.ErrorIs(t, err, fs.ErrClosed)
})
t.Run("Closed/DuringRun", func(t *testing.T) {
gitRepo := prepareRepo(t)
defer gitRepo.Close()
ac, err := gitRepo.GitAttributeChecker("8fee858da5796dfb37704761701bb8e800ad9ef3", "linguist-language")
require.NoError(t, err)
attr, err := ac.CheckPath("i-am-a-python.p")
assert.NoError(t, err)
assert.Equal(t, "Python", attr["linguist-language"].String())
assert.NoError(t, ac.Close())
_, err = ac.CheckPath("i-am-a-python.p")
assert.ErrorIs(t, err, fs.ErrClosed)
})
}
// CopyFS is adapted from https://github.com/golang/go/issues/62484
// which should be available with go1.23
func CopyFS(dir string, fsys fs.FS) error {
return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, _ error) error {
targ := filepath.Join(dir, filepath.FromSlash(path))
if d.IsDir() {
return os.MkdirAll(targ, 0o777)
}
r, err := fsys.Open(path)
if err != nil {
return err
}
defer r.Close()
info, err := r.Stat()
if err != nil {
return err
}
w, err := os.OpenFile(targ, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o666|info.Mode()&0o777)
if err != nil {
return err
}
if _, err := io.Copy(w, r); err != nil {
w.Close()
return fmt.Errorf("copying %s: %v", path, err)
}
return w.Close()
})
}

View file

@ -238,7 +238,7 @@ func newRefsFromRefNames(refNames []byte) []git.Reference {
type Commit struct {
Commit *git.Commit
User *user_model.User
Verification *asymkey_model.CommitVerification
Verification *asymkey_model.ObjectVerification
Status *git_model.CommitStatus
Flow int64
Row int

View file

@ -136,7 +136,7 @@ func (g *Manager) doShutdown() {
}
g.lock.Lock()
g.shutdownCtxCancel()
atShutdownCtx := pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "post-shutdown"))
atShutdownCtx := pprof.WithLabels(g.hammerCtx, pprof.Labels("gracefulLifecycle", "post-shutdown"))
pprof.SetGoroutineLabels(atShutdownCtx)
for _, fn := range g.toRunAtShutdown {
go fn()
@ -167,7 +167,7 @@ func (g *Manager) doHammerTime(d time.Duration) {
default:
log.Warn("Setting Hammer condition")
g.hammerCtxCancel()
atHammerCtx := pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "post-hammer"))
atHammerCtx := pprof.WithLabels(g.terminateCtx, pprof.Labels("gracefulLifecycle", "post-hammer"))
pprof.SetGoroutineLabels(atHammerCtx)
}
g.lock.Unlock()
@ -183,7 +183,7 @@ func (g *Manager) doTerminate() {
default:
log.Warn("Terminating")
g.terminateCtxCancel()
atTerminateCtx := pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "post-terminate"))
atTerminateCtx := pprof.WithLabels(g.managerCtx, pprof.Labels("gracefulLifecycle", "post-terminate"))
pprof.SetGoroutineLabels(atTerminateCtx)
for _, fn := range g.toRunAtTerminate {

View file

@ -65,10 +65,10 @@ func (g *Manager) prepare(ctx context.Context) {
g.hammerCtx, g.hammerCtxCancel = context.WithCancel(ctx)
g.managerCtx, g.managerCtxCancel = context.WithCancel(ctx)
g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "with-terminate"))
g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("graceful-lifecycle", "with-shutdown"))
g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "with-hammer"))
g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "with-manager"))
g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("gracefulLifecycle", "with-terminate"))
g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("gracefulLifecycle", "with-shutdown"))
g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("gracefulLifecycle", "with-hammer"))
g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("gracefulLifecycle", "with-manager"))
if !g.setStateTransition(stateInit, stateRunning) {
panic("invalid graceful manager state: transition from init to running failed")

View file

@ -44,7 +44,7 @@ func (g *Manager) notify(msg systemdNotifyMsg) {
}
func (g *Manager) start() {
// Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
// Now label this and all goroutines created by this goroutine with the gracefulLifecycle manager
pprof.SetGoroutineLabels(g.managerCtx)
defer pprof.SetGoroutineLabels(g.ctx)

View file

@ -29,7 +29,7 @@ const (
)
func (g *Manager) start() {
// Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
// Now label this and all goroutines created by this goroutine with the gracefulLifecycle manager
pprof.SetGoroutineLabels(g.managerCtx)
defer pprof.SetGoroutineLabels(g.ctx)

View file

@ -0,0 +1,323 @@
// Copyright The Forgejo Authors.
// SPDX-License-Identifier: MIT
package markup
import (
"bufio"
"bytes"
"html/template"
"regexp"
"slices"
"strconv"
"strings"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
)
// filePreviewPattern matches "http://domain/org/repo/src/commit/COMMIT/filepath#L1-L2"
var filePreviewPattern = regexp.MustCompile(`https?://((?:\S+/){3})src/commit/([0-9a-f]{4,64})/(\S+)#(L\d+(?:-L\d+)?)`)
type FilePreview struct {
fileContent []template.HTML
subTitle template.HTML
lineOffset int
urlFull string
filePath string
start int
end int
isTruncated bool
}
func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Locale) *FilePreview {
if setting.FilePreviewMaxLines == 0 {
// Feature is disabled
return nil
}
preview := &FilePreview{}
m := filePreviewPattern.FindStringSubmatchIndex(node.Data)
if m == nil {
return nil
}
// Ensure that every group has a match
if slices.Contains(m, -1) {
return nil
}
preview.urlFull = node.Data[m[0]:m[1]]
// Ensure that we only use links to local repositories
if !strings.HasPrefix(preview.urlFull, setting.AppURL+setting.AppSubURL) {
return nil
}
projPath := strings.TrimSuffix(node.Data[m[2]:m[3]], "/")
commitSha := node.Data[m[4]:m[5]]
preview.filePath = node.Data[m[6]:m[7]]
hash := node.Data[m[8]:m[9]]
preview.start = m[0]
preview.end = m[1]
projPathSegments := strings.Split(projPath, "/")
var language string
fileBlob, err := DefaultProcessorHelper.GetRepoFileBlob(
ctx.Ctx,
projPathSegments[len(projPathSegments)-2],
projPathSegments[len(projPathSegments)-1],
commitSha, preview.filePath,
&language,
)
if err != nil {
return nil
}
lineSpecs := strings.Split(hash, "-")
commitLinkBuffer := new(bytes.Buffer)
err = html.Render(commitLinkBuffer, createLink(node.Data[m[0]:m[5]], commitSha[0:7], "text black"))
if err != nil {
log.Error("failed to render commitLink: %v", err)
}
var startLine, endLine int
if len(lineSpecs) == 1 {
startLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
endLine = startLine
preview.subTitle = locale.Tr(
"markup.filepreview.line", startLine,
template.HTML(commitLinkBuffer.String()),
)
preview.lineOffset = startLine - 1
} else {
startLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
endLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[1], "L"))
preview.subTitle = locale.Tr(
"markup.filepreview.lines", startLine, endLine,
template.HTML(commitLinkBuffer.String()),
)
preview.lineOffset = startLine - 1
}
lineCount := endLine - (startLine - 1)
if startLine < 1 || endLine < 1 || lineCount < 1 {
return nil
}
if setting.FilePreviewMaxLines > 0 && lineCount > setting.FilePreviewMaxLines {
preview.isTruncated = true
lineCount = setting.FilePreviewMaxLines
}
dataRc, err := fileBlob.DataAsync()
if err != nil {
return nil
}
defer dataRc.Close()
reader := bufio.NewReader(dataRc)
// skip all lines until we find our startLine
for i := 1; i < startLine; i++ {
_, err := reader.ReadBytes('\n')
if err != nil {
return nil
}
}
// capture the lines we're interested in
lineBuffer := new(bytes.Buffer)
for i := 0; i < lineCount; i++ {
buf, err := reader.ReadBytes('\n')
if err != nil {
break
}
lineBuffer.Write(buf)
}
// highlight the file...
fileContent, _, err := highlight.File(fileBlob.Name(), language, lineBuffer.Bytes())
if err != nil {
log.Error("highlight.File failed, fallback to plain text: %v", err)
fileContent = highlight.PlainText(lineBuffer.Bytes())
}
preview.fileContent = fileContent
return preview
}
func (p *FilePreview) CreateHTML(locale translation.Locale) *html.Node {
table := &html.Node{
Type: html.ElementNode,
Data: atom.Table.String(),
Attr: []html.Attribute{{Key: "class", Val: "file-preview"}},
}
tbody := &html.Node{
Type: html.ElementNode,
Data: atom.Tbody.String(),
}
status := &charset.EscapeStatus{}
statuses := make([]*charset.EscapeStatus, len(p.fileContent))
for i, line := range p.fileContent {
statuses[i], p.fileContent[i] = charset.EscapeControlHTML(line, locale, charset.FileviewContext)
status = status.Or(statuses[i])
}
for idx, code := range p.fileContent {
tr := &html.Node{
Type: html.ElementNode,
Data: atom.Tr.String(),
}
lineNum := strconv.Itoa(p.lineOffset + idx + 1)
tdLinesnum := &html.Node{
Type: html.ElementNode,
Data: atom.Td.String(),
Attr: []html.Attribute{
{Key: "class", Val: "lines-num"},
},
}
spanLinesNum := &html.Node{
Type: html.ElementNode,
Data: atom.Span.String(),
Attr: []html.Attribute{
{Key: "data-line-number", Val: lineNum},
},
}
tdLinesnum.AppendChild(spanLinesNum)
tr.AppendChild(tdLinesnum)
if status.Escaped {
tdLinesEscape := &html.Node{
Type: html.ElementNode,
Data: atom.Td.String(),
Attr: []html.Attribute{
{Key: "class", Val: "lines-escape"},
},
}
if statuses[idx].Escaped {
btnTitle := ""
if statuses[idx].HasInvisible {
btnTitle += locale.TrString("repo.invisible_runes_line") + " "
}
if statuses[idx].HasAmbiguous {
btnTitle += locale.TrString("repo.ambiguous_runes_line")
}
escapeBtn := &html.Node{
Type: html.ElementNode,
Data: atom.Button.String(),
Attr: []html.Attribute{
{Key: "class", Val: "toggle-escape-button btn interact-bg"},
{Key: "title", Val: btnTitle},
},
}
tdLinesEscape.AppendChild(escapeBtn)
}
tr.AppendChild(tdLinesEscape)
}
tdCode := &html.Node{
Type: html.ElementNode,
Data: atom.Td.String(),
Attr: []html.Attribute{
{Key: "class", Val: "lines-code chroma"},
},
}
codeInner := &html.Node{
Type: html.ElementNode,
Data: atom.Code.String(),
Attr: []html.Attribute{{Key: "class", Val: "code-inner"}},
}
codeText := &html.Node{
Type: html.RawNode,
Data: string(code),
}
codeInner.AppendChild(codeText)
tdCode.AppendChild(codeInner)
tr.AppendChild(tdCode)
tbody.AppendChild(tr)
}
table.AppendChild(tbody)
twrapper := &html.Node{
Type: html.ElementNode,
Data: atom.Div.String(),
Attr: []html.Attribute{{Key: "class", Val: "ui table"}},
}
twrapper.AppendChild(table)
header := &html.Node{
Type: html.ElementNode,
Data: atom.Div.String(),
Attr: []html.Attribute{{Key: "class", Val: "header"}},
}
afilepath := &html.Node{
Type: html.ElementNode,
Data: atom.A.String(),
Attr: []html.Attribute{
{Key: "href", Val: p.urlFull},
{Key: "class", Val: "muted"},
},
}
afilepath.AppendChild(&html.Node{
Type: html.TextNode,
Data: p.filePath,
})
header.AppendChild(afilepath)
psubtitle := &html.Node{
Type: html.ElementNode,
Data: atom.Span.String(),
Attr: []html.Attribute{{Key: "class", Val: "text small grey"}},
}
psubtitle.AppendChild(&html.Node{
Type: html.RawNode,
Data: string(p.subTitle),
})
header.AppendChild(psubtitle)
node := &html.Node{
Type: html.ElementNode,
Data: atom.Div.String(),
Attr: []html.Attribute{{Key: "class", Val: "file-preview-box"}},
}
node.AppendChild(header)
if p.isTruncated {
warning := &html.Node{
Type: html.ElementNode,
Data: atom.Div.String(),
Attr: []html.Attribute{{Key: "class", Val: "ui warning message tw-text-left"}},
}
warning.AppendChild(&html.Node{
Type: html.TextNode,
Data: locale.TrString("markup.filepreview.truncated"),
})
node.AppendChild(warning)
}
node.AppendChild(twrapper)
return node
}

View file

@ -171,6 +171,7 @@ type processor func(ctx *RenderContext, node *html.Node)
var defaultProcessors = []processor{
fullIssuePatternProcessor,
comparePatternProcessor,
filePreviewPatternProcessor,
fullHashPatternProcessor,
shortLinkProcessor,
linkProcessor,
@ -1054,6 +1055,47 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
}
}
func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.Metas == nil {
return
}
if DefaultProcessorHelper.GetRepoFileBlob == nil {
return
}
next := node.NextSibling
for node != nil && node != next {
locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale)
if !ok {
locale = translation.NewLocale("en-US")
}
preview := NewFilePreview(ctx, node, locale)
if preview == nil {
return
}
previewNode := preview.CreateHTML(locale)
// Specialized version of replaceContent, so the parent paragraph element is not destroyed from our div
before := node.Data[:preview.start]
after := node.Data[preview.end:]
node.Data = before
nextSibling := node.NextSibling
node.Parent.InsertBefore(&html.Node{
Type: html.RawNode,
Data: "</p>",
}, nextSibling)
node.Parent.InsertBefore(previewNode, nextSibling)
node.Parent.InsertBefore(&html.Node{
Type: html.RawNode,
Data: "<p>" + after,
}, nextSibling)
node = node.NextSibling
}
}
// emojiShortCodeProcessor for rendering text like :smile: into emoji
func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) {
start := 0

View file

@ -17,9 +17,11 @@ import (
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var localMetas = map[string]string{
@ -676,3 +678,68 @@ func TestIssue18471(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "<a href=\"http://domain/org/repo/compare/783b039...da951ce\" class=\"compare\"><code class=\"nohighlight\">783b039...da951ce</code></a>", res.String())
}
func TestRender_FilePreview(t *testing.T) {
setting.StaticRootPath = "../../"
setting.Names = []string{"english"}
setting.Langs = []string{"en-US"}
translation.InitLocales(context.Background())
setting.AppURL = markup.TestAppURL
markup.Init(&markup.ProcessorHelper{
GetRepoFileBlob: func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error) {
gitRepo, err := git.OpenRepository(git.DefaultContext, "./tests/repo/repo1_filepreview")
require.NoError(t, err)
defer gitRepo.Close()
commit, err := gitRepo.GetCommit("HEAD")
require.NoError(t, err)
blob, err := commit.GetBlobByPath("path/to/file.go")
require.NoError(t, err)
return blob, nil
},
})
sha := "190d9492934af498c3f669d6a2431dc5459e5b20"
commitFilePreview := util.URLJoin(markup.TestRepoURL, "src", "commit", sha, "path", "to", "file.go") + "#L2-L3"
test := func(input, expected string) {
buffer, err := markup.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
RelativePath: ".md",
Metas: localMetas,
}, input)
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
}
test(
commitFilePreview,
`<p></p>`+
`<div class="file-preview-box">`+
`<div class="header">`+
`<a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" class="muted" rel="nofollow">path/to/file.go</a>`+
`<span class="text small grey">`+
`Lines 2 to 3 in <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" class="text black" rel="nofollow">190d949</a>`+
`</span>`+
`</div>`+
`<div class="ui table">`+
`<table class="file-preview">`+
`<tbody>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="2"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">B</span>`+"\n"+`</code></td>`+
`</tr>`+
`<tr>`+
`<td class="lines-num"><span data-line-number="3"></span></td>`+
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">C</span>`+"\n"+`</code></td>`+
`</tr>`+
`</tbody>`+
`</table>`+
`</div>`+
`</div>`+
`<p></p>`,
)
}

View file

@ -0,0 +1,19 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package markdown
import "regexp"
var (
hexRGB = regexp.MustCompile(`^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$`)
hsl = regexp.MustCompile(`^hsl\([ ]*([012]?[0-9]{1,2}|3[0-5][0-9]|360),[ ]*([0-9]{0,2}|100)\%,[ ]*([0-9]{0,2}|100)\%\)$`)
hsla = regexp.MustCompile(`^hsla\(([ ]*[012]?[0-9]{1,2}|3[0-5][0-9]|360),[ ]*([0-9]{0,2}|100)\%,[ ]*([0-9]{0,2}|100)\%,[ ]*(1|1\.0|0|(0\.[0-9]+))\)$`)
rgb = regexp.MustCompile(`^rgb\(([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))),){2}([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))))\)$`)
rgba = regexp.MustCompile(`^rgba\(([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))),){3}[ ]*(1(\.0)?|0|(0\.[0-9]+))\)$`)
)
// matchColor return if color is in the form of hex RGB, HSL(A) or RGB(A).
func matchColor(color string) bool {
return hexRGB.MatchString(color) || rgb.MatchString(color) || rgba.MatchString(color) || hsl.MatchString(color) || hsla.MatchString(color)
}

View file

@ -0,0 +1,50 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package markdown
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMatchColor(t *testing.T) {
testCases := []struct {
input string
expected bool
}{
{"#ddeeffa0", true},
{"#ddeefe", true},
{"#abcdef", true},
{"#abcdeg", false},
{"#abcdefg0", false},
{"black", false},
{"violet", false},
{"rgb(255, 255, 255)", true},
{"rgb(0, 0, 0)", true},
{"rgb(256, 0, 0)", false},
{"rgb(0, 256, 0)", false},
{"rgb(0, 0, 256)", false},
{"rgb(0, 0, 0, 1)", false},
{"rgba(0, 0, 0)", false},
{"rgba(0, 255, 0, 1)", true},
{"rgba(32, 255, 12, 0.55)", true},
{"rgba(32, 256, 12, 0.55)", false},
{"hsl(0, 0%, 0%)", true},
{"hsl(360, 100%, 100%)", true},
{"hsl(361, 100%, 50%)", false},
{"hsl(360, 101%, 50%)", false},
{"hsl(360, 100%, 101%)", false},
{"hsl(0, 0%, 0%, 0)", false},
{"hsla(0, 0%, 0%)", false},
{"hsla(0, 0%, 0%, 0)", true},
{"hsla(0, 0%, 0%, 1)", true},
{"hsla(0, 0%, 0%, 0.5)", true},
{"hsla(0, 0%, 0%, 1.5)", false},
}
for _, testCase := range testCases {
actual := matchColor(testCase.input)
assert.Equal(t, testCase.expected, actual)
}
}

View file

@ -16,7 +16,6 @@ import (
"code.gitea.io/gitea/modules/setting"
giteautil "code.gitea.io/gitea/modules/util"
"github.com/microcosm-cc/bluemonday/css"
"github.com/yuin/goldmark/ast"
east "github.com/yuin/goldmark/extension/ast"
"github.com/yuin/goldmark/parser"
@ -199,7 +198,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
}
case *ast.CodeSpan:
colorContent := n.Text(reader.Source())
if css.ColorHandler(strings.ToLower(string(colorContent))) {
if matchColor(strings.ToLower(string(colorContent))) {
v.AppendChild(v, NewColorPreview(colorContent))
}
}

View file

@ -31,6 +31,7 @@ const (
type ProcessorHelper struct {
IsUsernameMentionable func(ctx context.Context, username string) bool
GetRepoFileBlob func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error)
ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute
}

View file

@ -113,6 +113,23 @@ func createDefaultPolicy() *bluemonday.Policy {
// Allow 'color' and 'background-color' properties for the style attribute on text elements.
policy.AllowStyles("color", "background-color").OnElements("span", "p")
// Allow classes for file preview links...
policy.AllowAttrs("class").Matching(regexp.MustCompile("^(lines-num|lines-code chroma)$")).OnElements("td")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^code-inner$")).OnElements("code")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^file-preview-box$")).OnElements("div")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui table$")).OnElements("div")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^header$")).OnElements("div")
policy.AllowAttrs("data-line-number").Matching(regexp.MustCompile("^[0-9]+$")).OnElements("span")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^text small grey$")).OnElements("span")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^file-preview*")).OnElements("table")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^lines-escape$")).OnElements("td")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^toggle-escape-button btn interact-bg$")).OnElements("button")
policy.AllowAttrs("title").OnElements("button")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ambiguous-code-point$")).OnElements("span")
policy.AllowAttrs("data-tooltip-content").OnElements("span")
policy.AllowAttrs("class").Matching(regexp.MustCompile("muted|(text black)")).OnElements("a")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui warning message tw-text-left$")).OnElements("div")
// Allow generally safe attributes
generalSafeAttrs := []string{
"abbr", "accept", "accept-charset",

View file

@ -0,0 +1 @@
ref: refs/heads/master

View file

@ -0,0 +1,6 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true
[remote "origin"]
url = /home/mai/projects/codeark/forgejo/forgejo/modules/markup/tests/repo/repo1_filepreview/../../__test_repo

View file

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View file

@ -0,0 +1,6 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~

View file

@ -0,0 +1 @@
x+)JMU06e040031QHËÌIÕKÏghQºÂ/TX'·7潊ç·såË#3ô

View file

@ -0,0 +1 @@
190d9492934af498c3f669d6a2431dc5459e5b20

View file

@ -26,7 +26,7 @@ var (
)
// DescriptionPProfLabel is a label set on goroutines that have a process attached
const DescriptionPProfLabel = "process-description"
const DescriptionPProfLabel = "processDescription"
// PIDPProfLabel is a label set on goroutines that have a process attached
const PIDPProfLabel = "pid"
@ -35,7 +35,7 @@ const PIDPProfLabel = "pid"
const PPIDPProfLabel = "ppid"
// ProcessTypePProfLabel is a label set on goroutines that have a process attached
const ProcessTypePProfLabel = "process-type"
const ProcessTypePProfLabel = "processType"
// IDType is a pid type
type IDType string

View file

@ -15,6 +15,7 @@ var (
ExternalMarkupRenderers []*MarkupRenderer
ExternalSanitizerRules []MarkupSanitizerRule
MermaidMaxSourceCharacters int
FilePreviewMaxLines int
)
const (
@ -62,6 +63,7 @@ func loadMarkupFrom(rootCfg ConfigProvider) {
mustMapSetting(rootCfg, "markdown", &Markdown)
MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000)
FilePreviewMaxLines = rootCfg.Section("markup").Key("FILEPREVIEW_MAX_LINES").MustInt(50)
ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10)
ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10)

View file

@ -47,6 +47,7 @@ type BranchProtection struct {
RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
ApplyToAdmins bool `json:"apply_to_admins"`
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
// swagger:strfmt date-time
@ -80,6 +81,7 @@ type CreateBranchProtectionOption struct {
RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
ApplyToAdmins bool `json:"apply_to_admins"`
}
// EditBranchProtectionOption options for editing a branch protection
@ -106,4 +108,5 @@ type EditBranchProtectionOption struct {
RequireSignedCommits *bool `json:"require_signed_commits"`
ProtectedFilePatterns *string `json:"protected_file_patterns"`
UnprotectedFilePatterns *string `json:"unprotected_file_patterns"`
ApplyToAdmins *bool `json:"apply_to_admins"`
}

View file

@ -63,7 +63,7 @@ func NewFuncMap() template.FuncMap {
// -----------------------------------------------------------------
// time / number / format
"FileSize": base.FileSize,
"FileSize": FileSizePanic,
"CountFmt": base.FormatNumberSI,
"TimeSince": timeutil.TimeSince,
"TimeSinceUnix": timeutil.TimeSinceUnix,
@ -249,3 +249,7 @@ func Eval(tokens ...any) (any, error) {
n, err := eval.Expr(tokens...)
return n.Value, err
}
func FileSizePanic(s int64) string {
panic("Usage of FileSize in templates is deprecated in Forgejo. Locale.TrSize should be used instead.")
}

View file

@ -43,9 +43,7 @@ func (w *testLoggerWriterCloser) pushT(t testing.TB) {
}
func (w *testLoggerWriterCloser) Log(level log.Level, msg string) {
if len(msg) > 0 && msg[len(msg)-1] == '\n' {
msg = msg[:len(msg)-1]
}
msg = strings.TrimSpace(msg)
w.printMsg(msg)
if level >= log.ERROR {
@ -56,10 +54,13 @@ func (w *testLoggerWriterCloser) Log(level log.Level, msg string) {
// list of error message which will not fail the test
// ideally this list should be empty, however ensuring that it does not grow
// is already a good first step.
var ignoredErrorMessageSuffixes = []string{
var ignoredErrorMessage = []string{
// only seen on mysql tests https://codeberg.org/forgejo/forgejo/pulls/2657#issuecomment-1693055
`table columns using inconsistent collation, they should use "utf8mb4_0900_ai_ci". Please go to admin panel Self Check page`,
// TestPullWIPConvertSidebar
`:PullRequestPushCommits() [E] comment.LoadIssue: issue does not exist [id:`,
// TestAPIDeleteReleaseByTagName
// action notification were a commit cannot be computed (because the commit got deleted)
`Notify() [E] an error occurred while executing the DeleteRelease actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/release-tag, rel_path: ]`,
@ -76,6 +77,14 @@ var ignoredErrorMessageSuffixes = []string{
// TestAPIGenerateRepo
`Notify() [E] an error occurred while executing the CreateRepository actions method: gitRepo.GetCommit: object does not exist [id: , rel_path: ]`,
// TestAPIPullUpdateByRebase
`:testPR() [E] Unable to GetPullRequestByID[`,
`:PullRequestSynchronized() [E] LoadAttributes: getRepositoryByID `,
`:PullRequestSynchronized() [E] pr.Issue.LoadRepo: getRepositoryByID [`,
`:handler() [E] Was unable to create issue notification: issue does not exist [`,
`:func1() [E] PullRequestList.LoadAttributes: issues and prs may be not in sync: cannot find issue`,
`:func1() [E] checkForInvalidation: GetRepositoryByIDCtx: repository does not exist `,
// TestAPIPullReview
`PullRequestReview() [E] Unsupported review webhook type`,
@ -111,11 +120,251 @@ var ignoredErrorMessageSuffixes = []string{
// TestRebuildCargo
`RebuildCargoIndex() [E] RebuildIndex failed: GetRepositoryByOwnerAndName: repository does not exist [id: 0, uid: 0, owner_name: user2, name: _cargo-index]`,
// TestDangerZoneConfirmation/Convert_fork/Fail
`/gitea-repositories/user20/big_test_public_fork_7.git Error: no such file or directory`,
// TestGitSmartHTTP
`:sendFile() [E] request file path contains invalid path: objects/info/..\..\..\..\custom\conf\app.ini`,
// TestGit/HTTP/BranchProtectMerge
`:SSHLog() [E] ssh: Not allowed to push to protected branch protected. HookPreReceive(last) failed: internal API error response, status=403`,
// TestGit/HTTP/BranchProtectMerge
`:SSHLog() [E] ssh: Not allowed to push to protected branch protected. HookPreReceive(last) failed: internal API error response, status=403`,
// TestGit/HTTP/BranchProtectMerge
`:SSHLog() [E] ssh: branch protected is protected from force push. HookPreReceive(last) failed: internal API error response, status=403`,
// TestGit/HTTP/MergeFork/CreatePRAndMerge
`:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1099 name: user2:master]`, // sqlite
"s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 10000 name: user2:master]", // mysql
"s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1060 name: user2:master]", // pgsql
// TestGit/HTTP/BranchProtectMerge
`:func1() [E] PushToBaseRepo: PushRejected Error: exit status 1 - remote: error: cannot lock ref`,
// TestGit/SSH/BranchProtectMerge
`:func1() [E] PushToBaseRepo: PushRejected Error: exit status 1 - remote: error: cannot lock ref`,
// TestGit/SSH/LFS/PushCommit/Little
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/LFS/PushCommit/Little
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/LFS/PushCommit/Big
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/LFS/PushCommit/Big
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/LFS/Locks
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/LFS/Locks
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/LFS/Locks
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/LFS/Locks
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/LFS/Locks
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull/PushParams/NoParams
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull/PushParams/NoParams
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull/PushParams/TitleOverride
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull/PushParams/TitleOverride
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull/PushParams/DescriptionOverride
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull/PushParams/DescriptionOverride
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull/Force_push/Fails
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull/Force_push/Fails
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull/Force_push/Succeeds
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull/Force_push/Succeeds
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull/Force_push
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull/Force_push
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull/Branch_already_contains_commit
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull/Branch_already_contains_commit
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/CreateAgitFlowPull
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Not allowed to push to protected branch protected. HookPreReceive(last) failed: internal API error response, status=403`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: branch protected is protected from force push. HookPreReceive(last) failed: internal API error response, status=403`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/BranchProtectMerge
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
// TestGit/SSH/MergeFork/CreatePRAndMerge
`:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1102 name: user2:master]`, // sqlite
"s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 10003 name: user2:master]", // mysql
"s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1063 name: user2:master]", // pgsql
// TestGit/SSH/PushCreate
`:SSHLog() [E] ssh: Push to create is not enabled for users. ServCommand failed: internal API error response, status=403`,
// TestGit/SSH/PushCreate
`:SSHLog() [E] ssh: Cannot find repository: user2/repo-tmp-push-create-ssh. ServCommand failed: internal API error response, status=404`,
// TestGit/SSH/PushCreate
`:SSHLog() [E] ssh: Invalid repo name. Invalid repo name: invalid/repo-tmp-push-create-ssh`,
// TestIssueReaction
`:ChangeIssueReaction() [E] ChangeIssueReaction: '8ball' is not an allowed reaction`,
// TestIssuePinMove
`:IssuePinMove() [E] Issue does not belong to this repository`,
// TestLinksLogin
`:GetIssuesAllCommitStatus() [E] getAllCommitStatus: cant get commit statuses of pull [6]: object does not exist [id: refs/pull/2/head, rel_path: ]`,
// TestLinksLogin
`:GetIssuesAllCommitStatus() [E] getAllCommitStatus: cant get commit statuses of pull [6]: object does not exist [id: refs/pull/2/head, rel_path: ]`,
// TestLinksLogin
`:GetIssuesAllCommitStatus() [E] getAllCommitStatus: cant get commit statuses of pull [6]: object does not exist [id: refs/pull/2/head, rel_path: ]`,
// TestLinksLogin
`:GetIssuesAllCommitStatus() [E] Cannot open git repository <Repository 23:org17/big_test_public_4> for issue #1[20]. Error: no such file or directory`,
// TestMigrate
`] for OwnerID[2] failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
// TestMigrate
`:handler() [E] Run task failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
// TestMigrate
`] for OwnerID[2] failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
// TestMigrate
`:handler() [E] Run task failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
// TestMirrorPush
`:GetInfoRefs() [E] fork/exec /usr/bin/git: no such file or directory -`,
// TestOrgMembers
`:loadOrganizationOwners() [E] Organization does not have owner team: 25`,
// TestOrgMembers
`:loadOrganizationOwners() [E] Organization does not have owner team: 25`,
// TestOrgMembers
`:loadOrganizationOwners() [E] Organization does not have owner team: 25`,
// TestRecentlyPushed/unrelated_branches_are_not_shown
`:SyncRepoBranches() [E] OpenRepository[user30/repo50]: %!w(*errors.errorString=&{no such file or directory})`,
// TestRecentlyPushed/unrelated_branches_are_not_shown
`:handlerBranchSync() [E] syncRepoBranches [50] failed: no such file or directory`,
// TestRecentlyPushed/unrelated_branches_are_not_shown
`:SyncRepoBranches() [E] OpenRepository[user30/repo51]: %!w(*errors.errorString=&{no such file or directory})`,
// TestRecentlyPushed/unrelated_branches_are_not_shown
`:handlerBranchSync() [E] syncRepoBranches [51] failed: no such file or directory`,
// TestRecentlyPushed/unrelated_branches_are_not_shown
`:SyncRepoBranches() [E] OpenRepository[user2/scoped_label]: %!w(*errors.errorString=&{no such file or directory})`,
// TestRecentlyPushed/unrelated_branches_are_not_shown
`:handlerBranchSync() [E] syncRepoBranches [55] failed: no such file or directory`,
// TestCantMergeConflict
"]user1/repo1#1[base...conflict]> Unable to merge tracking into base: Merge Conflict Error: exit status 1: \nAuto-merging README.md\nCONFLICT (content): Merge conflict in README.md\nAutomatic merge failed; fix conflicts and then commit the result.",
// TestCantMergeUnrelated
`]user1/repo1#1[base...unrelated]> Unable to merge tracking into base: Merge UnrelatedHistories Error: exit status 128: fatal: refusing to merge unrelated histories`,
// TestCantFastForwardOnlyMergeDiverging
"]user1/repo1#1[master...diverging]> Unable to merge tracking into base: Merge DivergingFastForwardOnly Error: exit status 128: hint: Diverging branches can't be fast-forwarded, you need to either:\nhint: \nhint: \tgit merge --no-ff\nhint: \nhint: or:\nhint: \nhint: \tgit rebase\nhint: \nhint: Disable this message with \"git config advice.diverging false\"\nfatal: Not possible to fast-forward, aborting.",
// TestPullrequestReopen/Base_branch_deleted
`fatal: couldn't find remote ref base-branch`,
// TestPullrequestReopen/Head_branch_deleted
`]user2/reopen-base#1[base-branch...org26/reopen-head:head-branch]>]: branch does not exist [repo_id: 0 name: head-branch]`,
// TestDatabaseMissingABranch
`:SyncRepoBranches() [E] OpenRepository[user30/repo50]: %!w(*errors.errorString=&{no such file or directory})`,
// TestDatabaseMissingABranch
`:handlerBranchSync() [E] syncRepoBranches [50] failed: no such file or directory`,
// TestDatabaseMissingABranch
`:SyncRepoBranches() [E] OpenRepository[user30/repo51]: %!w(*errors.errorString=&{no such file or directory})`,
// TestDatabaseMissingABranch
`:handlerBranchSync() [E] syncRepoBranches [51] failed: no such file or directory`,
// TestDatabaseMissingABranch
`:SyncRepoBranches() [E] OpenRepository[user2/scoped_label]: %!w(*errors.errorString=&{no such file or directory})`,
// TestDatabaseMissingABranch
`:handlerBranchSync() [E] syncRepoBranches [55] failed: no such file or directory`,
// TestDatabaseMissingABranch
`:LoadBranches() [E] loadOneBranch() on repo #1, branch 'will-be-missing' failed: CountDivergingCommits: exit status 128 - fatal: bad revision 'master...refs/heads/will-be-missing'
- fatal: bad revision 'master...refs/heads/will-be-missing'`,
// TestDatabaseMissingABranch
`:SyncRepoBranches() [E] OpenRepository[user30/repo50]: %!w(*errors.errorString=&{no such file or directory})`,
// TestDatabaseMissingABranch
`:handlerBranchSync() [E] syncRepoBranches [50] failed: no such file or directory`,
// TestDatabaseMissingABranch
`:SyncRepoBranches() [E] OpenRepository[user30/repo51]: %!w(*errors.errorString=&{no such file or directory})`,
// TestDatabaseMissingABranch
`:handlerBranchSync() [E] syncRepoBranches [51] failed: no such file or directory`,
// TestDatabaseMissingABranch
`:SyncRepoBranches() [E] OpenRepository[user2/scoped_label]: %!w(*errors.errorString=&{no such file or directory})`,
// TestDatabaseMissingABranch
`:handlerBranchSync() [E] syncRepoBranches [55] failed: no such file or directory`,
// TestDatabaseMissingABranch
"LoadBranches() [E] loadOneBranch() on repo #1, branch 'will-be-missing' failed: CountDivergingCommits: exit status 128 - fatal: bad revision 'master...refs/heads/will-be-missing'\n - fatal: bad revision 'master...refs/heads/will-be-missing'",
// TestCreateNewTagProtected/Git
`:SSHLog() [E] ssh: Tag v-2 is protected. HookPreReceive(last) failed: internal API error response, status=403`,
// TestMarkDownReadmeImage
`:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: home-md-img-check]`,
// TestMarkDownReadmeImage
`:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: home-md-img-check]`,
// TestMarkDownReadmeImageSubfolder
`:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: sub-home-md-img-check]`,
// TestMarkDownReadmeImageSubfolder
`:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: sub-home-md-img-check]`,
// TestKeyOnlyOneType
`:ssh-key-test-repo-push is not authorized to write to user2/ssh-key-test-repo. ServCommand failed: internal API error response, status=401`,
// TestPullRebase
"/gitea-repositories/user2/repo1.git' does not appear to be a git repository\nfatal: Could not read from remote repository.\n\nPlease make sure you have the correct access rights\nand the repository exists.",
// TestPullRebaseMerge
"]user2/repo1#3[master...branch2]>]: branch does not exist [repo_id: 0 name: branch2]",
// TestAuthorizeNoClientID
`TrString() [E] Missing translation "form.ResponseType"`,
// TestWebhookForms
`TrString() [E] Missing translation "form.AuthorizationHeader"`,
`TrString() [E] Missing translation "form.Channel"`,
`TrString() [E] Missing translation "form.ContentType"`,
`TrString() [E] Missing translation "form.HTTPMethod"`,
`TrString() [E] Missing translation "form.PayloadURL"`,
// TestRenameInvalidUsername
`TrString() [E] Missing translation "form.Name"`,
// TestDatabaseCollation
`[E] [Error SQL Query] INSERT INTO test_collation_tbl (txt) VALUES ('main') []`,
}
func (w *testLoggerWriterCloser) recordError(msg string) {
for _, s := range ignoredErrorMessageSuffixes {
if strings.HasSuffix(msg, s) {
for _, s := range ignoredErrorMessage {
if strings.Contains(msg, s) {
return
}
}
@ -128,6 +377,11 @@ func (w *testLoggerWriterCloser) recordError(msg string) {
err = w.errs[len(w.errs)-1]
}
if len(w.t) > 0 {
// format error message to easily add it to the ignore list
msg = fmt.Sprintf("// %s\n\t%q,", w.t[len(w.t)-1].Name(), msg)
}
err = errors.Join(err, errors.New(msg))
if len(w.errs) > 0 {
@ -231,7 +485,9 @@ func PrintCurrentTest(t testing.TB, skip ...int) func() {
}
if err := WriterCloser.popT(); err != nil {
t.Errorf("testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err)
// disable test failure for now (too flacky)
_, _ = fmt.Fprintf(os.Stdout, "testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err)
// t.Errorf("testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err)
}
}
}

View file

@ -31,6 +31,10 @@ func (l MockLocale) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
return template.HTML(key1)
}
func (l MockLocale) TrSize(s int64) ReadableSize {
return ReadableSize{fmt.Sprint(s), ""}
}
func (l MockLocale) PrettyNumber(v any) string {
return fmt.Sprint(v)
}

View file

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/translation/i18n"
"code.gitea.io/gitea/modules/util"
"github.com/dustin/go-humanize"
"golang.org/x/text/language"
"golang.org/x/text/message"
"golang.org/x/text/number"
@ -33,6 +34,8 @@ type Locale interface {
Tr(key string, args ...any) template.HTML
TrN(cnt any, key1, keyN string, args ...any) template.HTML
TrSize(size int64) ReadableSize
PrettyNumber(v any) string
}
@ -252,6 +255,35 @@ func (l *locale) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
return l.Tr(keyN, args...)
}
type ReadableSize struct {
PrettyNumber string
TranslatedUnit string
}
func (bs ReadableSize) String() string {
return bs.PrettyNumber + " " + bs.TranslatedUnit
}
// TrSize returns array containing pretty formatted size and localized output of FileSize
// output of humanize.IBytes has to be split in order to be localized
func (l *locale) TrSize(s int64) ReadableSize {
us := uint64(s)
if s < 0 {
us = uint64(-s)
}
untranslated := humanize.IBytes(us)
if s < 0 {
untranslated = "-" + untranslated
}
numberVal, unitVal, found := strings.Cut(untranslated, " ")
if !found {
log.Error("no space in go-humanized size of %d: %q", s, untranslated)
}
numberVal = l.PrettyNumber(numberVal)
unitVal = l.TrString("munits.data." + strings.ToLower(unitVal))
return ReadableSize{numberVal, unitVal}
}
func (l *locale) PrettyNumber(v any) string {
// TODO: this mechanism is not good enough, the complete solution is to switch the translation system to ICU message format
if s, ok := v.(string); ok {

View file

@ -3,6 +3,8 @@
package translation
// TODO: make this package friendly to testing
import (
"testing"
@ -11,9 +13,25 @@ import (
"github.com/stretchr/testify/assert"
)
func TestPrettyNumber(t *testing.T) {
// TODO: make this package friendly to testing
func TestTrSize(t *testing.T) {
l := NewLocale("")
size := int64(1)
assert.EqualValues(t, "1 munits.data.b", l.TrSize(size).String())
size *= 2048
assert.EqualValues(t, "2 munits.data.kib", l.TrSize(size).String())
size *= 2048
assert.EqualValues(t, "4 munits.data.mib", l.TrSize(size).String())
size *= 2048
assert.EqualValues(t, "8 munits.data.gib", l.TrSize(size).String())
size *= 2048
assert.EqualValues(t, "16 munits.data.tib", l.TrSize(size).String())
size *= 2048
assert.EqualValues(t, "32 munits.data.pib", l.TrSize(size).String())
size *= 128
assert.EqualValues(t, "4 munits.data.eib", l.TrSize(size).String())
}
func TestPrettyNumber(t *testing.T) {
i18n.ResetDefaultLocales()
allLangMap = make(map[string]*LangType)

View file

@ -680,7 +680,7 @@ issues.self_assign_at = `كلّف نفسه بها %s`
issues.label_deletion = احذف التصنيف
issues.filter_milestone_all = كل الأهداف
issues.unlock.notice_2 = - يمكنك دوما إقفال هذه المسألة من جديد في المستقبل.
issues.num_participants = %d متحاور
issues.num_participants_few = %d متحاور
release.title = عنوان الإصدار
issues.closed_at = `أغلق هذه المسألة <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.lock.title = إقفال التحاور في هذه المسألة.

View file

@ -125,10 +125,16 @@ orgs_none = Не сте участник в никакви организаци
repos_none = Не притежавате никакви хранилища.
blocked_users_none = Няма блокирани потребители.
profile_desc = Контролирайте как вашият профил се показва на другите потребители. Вашият основен адрес на ел. поща ще се използва за известия, възстановяване на паролата и уеб базирани Git операции.
permission_write = Четене и Писане
permission_write = Четене и писане
twofa_disable = Изключване на двуфакторното удостоверяване
twofa_enroll = Включване на двуфакторно удостоверяване
ssh_key_name_used = Вече съществува SSH ключ със същото име във вашия акаунт.
email_notifications.enable = Включване на известията по ел. поща
delete_prompt = Тази операция ще изтрие перманентно потребителския ви акаунт. Това <strong>НЕ МОЖЕ</strong> да бъде отменено.
email_notifications.disable = Изключване на известията по ел. поща
delete_account = Изтриване на акаунта ви
confirm_delete_account = Потвърждаване на изтриването
email_notifications.onmention = Ел. поща само при споменаване
[packages]
container.labels.value = Стойност
@ -258,7 +264,7 @@ new_fork = Ново разклонение на хранилище
unpin = Откачване
pin = Закачване
filter = Филтър
filter.clear = Изчистване на филтъра
filter.clear = Изчистване на филтрите
filter.is_archived = Архивирани
filter.not_archived = Не архивирани
filter.is_fork = Разклонени
@ -269,6 +275,7 @@ filter.not_template = Не шаблони
filter.private = Частни
filter.is_mirror = Огледални
filter.not_mirror = Не огледални
copy_hash = Копиране на контролната сума
[repo]
issues.context.edit = Редактиране
@ -366,7 +373,7 @@ issues.keyword_search_unavailable = В момента търсенето по к
repo_desc_helper = Въведете кратко описание (опционално)
mirror_address = Клониране от URL
owner_helper = Някои организации може да не се показват в падащото меню поради ограничение за максимален брой хранилища.
new_repo_helper = Хранилище съдържа всички файлове на проекта, включително хронологията на ревизиите. Вече хоствате хранилище другаде? <a href="%s">Мигрирайте хранилище.</a>
new_repo_helper = Хранилището съдържа всички файлове на проекта, включително хронологията на ревизиите. Вече хоствате хранилище другаде? <a href="%s">Мигрирайте хранилище.</a>
repo_name_helper = Добрите имена на хранилища използват кратки, запомнящи се и уникални ключови думи.
migrated_from = Мигрирано от <a href="%[1]s">%[2]s</a>
visibility_description = Само притежателят или участниците в организацията, ако имат права, ще могат да го видят.
@ -448,7 +455,7 @@ fork_from = Разклоняване от
diff.comment.placeholder = Оставете коментар
projects.edit = Редактиране на проекта
projects.modify = Редактиране на проекта
issues.new.no_label = Няма етикет
issues.new.no_label = Няма етикети
issues.new.title_empty = Заглавието не може да бъде празно
issues.new.projects = Проекти
issues.new.clear_projects = Изчистване на проектите
@ -534,7 +541,7 @@ settings.collaboration.write = Писане
settings.collaboration.read = Четене
settings.collaboration.owner = Притежател
settings.basic_settings = Основни настройки
settings.wiki_desc = Включване на уики на хранилището
settings.wiki_desc = Включване на уикито за хранилището
settings.use_internal_wiki = Използване на вграденото уики
settings.wiki_globally_editable = Позволяване на всеки да редактира уикито
settings.add_collaborator = Добавяне на сътрудник
@ -629,7 +636,7 @@ issues.filter_milestone_all = Всички етапи
issues.filter_milestone_open = Отворени етапи
issues.filter_milestone_none = Без етапи
issues.filter_project = Проект
issues.num_participants = %d участващи
issues.num_participants_few = %d участващи
issues.filter_assignee = Изпълнител
issues.filter_milestone_closed = Затворени етапи
issues.filter_assginee_no_select = Всички изпълнители
@ -667,10 +674,10 @@ milestones.close = Затваряне
issues.label_templates.use = Използване на набор от етикети
issues.add_milestone_at = `добави това към етапа <b>%s</b> %s`
issues.add_label = добави етикета %s %s
issues.add_labels = добави етикетите %s %s
issues.add_labels = добави етикети %s %s
issues.remove_label = премахна етикета %s %s
issues.remove_labels = премахна етикетите %s %s
issues.add_remove_labels = добави етикетите %s и премахна %s %s
issues.add_remove_labels = добави етикети %s и премахна %s %s
issues.add_project_at = `добави това към проекта <b>%s</b> %s`
issues.remove_project_at = `премахна това от проекта <b>%s</b> %s`
issues.remove_milestone_at = `премахна това от етапа <b>%s</b> %s`
@ -702,7 +709,7 @@ more_operations = Още операции
download_archive = Изтегляне на хранилището
branch = Клон
tree = Дърво
branches = Клони
branches = Клонове
tags = Маркери
tag = Маркер
filter_branch_and_tag = Филтр. на клон или маркер
@ -759,7 +766,7 @@ pulls.merged_by = от <a href="%[2]s">%[3]s</a> бе слята %[1]s
pulls.merged_by_fake = от %[2]s бе слята %[1]s
issues.label_deletion = Изтриване на етикета
issues.label_modify = Редактиране на етикета
issues.due_date_added = добави крайния срок %s %s
issues.due_date_added = добави краен срок %s %s
issues.due_date_remove = премахна крайния срок %s %s
release.new_release = Ново издание
release.tag_helper_existing = Съществуващ маркер.
@ -822,7 +829,7 @@ editor.fail_to_update_file = Неуспешно обновяване/създа
editor.add_subdir = Добавяне на директория…
commits.commits = Подавания
commits.find = Търсене
commits.search_all = Всички клони
commits.search_all = Всички клонове
commits.search = Потърсете подавания…
commit.operations = Операции
issues.deleted_milestone = `(изтрит)`
@ -848,7 +855,7 @@ release.edit_release = Обновяване на изданието
diff.committed_by = подадено от
release.downloads = Изтегляния
issues.sign_in_require_desc = <a href="%s">Влезте</a> за да се присъедините към това обсъждане.
activity.git_stats_push_to_all_branches = към всички клони.
activity.git_stats_push_to_all_branches = към всички клонове.
release.deletion_tag_success = Маркерът е изтрит.
release.cancel = Отказ
release.deletion = Изтриване на изданието
@ -928,9 +935,9 @@ settings.web_hook_name_discord = Discord
settings.web_hook_name_telegram = Telegram
settings.web_hook_name_matrix = Matrix
settings.web_hook_name_gogs = Gogs
settings.web_hook_name_feishu_or_larksuite = Feishu / Lark Suite
settings.web_hook_name_feishu = Feishu
settings.web_hook_name_larksuite = Lark Suite
settings.web_hook_name_feishu = Feishu / Lark Suite
settings.web_hook_name_feishu_only = Feishu
settings.web_hook_name_larksuite_only = Lark Suite
settings.web_hook_name_wechatwork = WeCom (Wechat Work)
settings.web_hook_name_packagist = Packagist
diff.file_byte_size = Размер
@ -959,6 +966,110 @@ search.results = Резултати от търсенето на "%s" в <a href
object_format = Формат на обектите
release.releases_for = Издания за %s
release.tags_for = Маркери за %s
pulls.cmd_instruction_hint = `Вижте <a class="show-instruction">инструкциите за командния ред</a>.`
pulls.showing_only_single_commit = Показани са само промените в подаване %[1]s
issues.lock_no_reason = заключи и ограничи обсъждането до сътрудници %s
pulls.expand_files = Разгъване на всички файлове
pulls.title_desc_few = иска да слее %[1]d подавания от <code>%[2]s</code> в <code id="branch_target">%[3]s</code>
issues.content_history.deleted = изтрито
activity.git_stats_exclude_merges = С изключение на сливанията,
activity.navbar.pulse = Последна дейност
activity.no_git_activity = Не е имало никаква дейност с подавания през този период.
pulls.merged_title_desc_few = сля %[1]d подавания от <code>%[2]s</code> в <code>%[3]s</code> %[4]s
diff.stats_desc_file = %d промени: %d добавяния и %d изтривания
issues.content_history.created = създадено
pulls.status_checks_success = Всички проверки бяха успешни
activity.git_stats_pushed_n = са изтласкали
pulls.select_commit_hold_shift_for_range = Изберете подаване. Задръжте shift + click, за да изберете обхвата
activity.git_stats_addition_1 = %d добавяне
activity.git_stats_on_default_branch = В %s,
activity.git_stats_files_changed_1 = е променен
activity.git_stats_files_changed_n = са променени
activity.git_stats_additions = и е имало
pulls.collapse_files = Свиване на всички файлове
pulls.show_all_commits = Показване на всички подавания
diff.whitespace_button = Празни знаци
issues.content_history.edited = редактирано
pulls.title_desc_one = иска да слее %[1]d подаване от <code>%[2]s</code> в <code id="branch_target">%[3]s</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.git_stats_pushed_1 = е изтласкал
activity.git_stats_push_to_branch = към %s и
contributors.contribution_type.commits = Подавания
stars = Звезди
n_commit_few = %s подавания
n_branch_one = %s клон
n_branch_few = %s клона
n_tag_one = %s маркер
n_tag_few = %s маркера
commit_graph = Граф с подавания
commits.renamed_from = Преименувано от %s
commits.view_path = Преглед на този момент в историята
commits.search_branch = Този клон
n_commit_one = %s подаване
release.ahead.commits = <strong>%d</strong> подавания
release.stable = Стабилно
commits.gpg_key_id = ID на GPG ключ
diff.options_button = Опции за разликите
activity.title.unresolved_conv_1 = %d нерешено обсъждане
activity.title.unresolved_conv_n = %d нерешени обсъждания
issues.comment_pull_merged_at = сля подаване %[1]s в %[2]s %[3]s
issues.comment_manually_pull_merged_at = ръчно сля подаване %[1]s в %[2]s %[3]s
issues.dependency.add = Добавяне на зависимост…
issues.dependency.cancel = Отказ
issues.dependency.add_error_dep_exists = Зависимостта вече съществува.
issues.dependency.add_error_dep_not_exist = Зависимостта не съществува.
issues.remove_ref_at = `премахна препратката <b>%s</b> %s`
issues.ref_pull_from = `<a href="%[3]s">спомена тази заявка за сливане %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.dependency.pr_no_dependencies = Няма зададени зависимости.
issues.dependency.remove_info = Премахване на тази зависимост
issues.dependency.removed_dependency = `премахна зависимост %s`
issues.dependency.added_dependency = `добави нова зависимост %s`
issues.dependency.issue_closing_blockedby = Затварянето на тази задача е блокирано от следните задачи
issues.dependency.issue_close_blocks = Тази задача блокира затварянето на следните задачи
issues.dependency.issue_close_blocked = Трябва да затворите всички задачи, блокиращи тази задача, преди да можете да я затворите.
issues.dependency.blocks_short = Блокира
issues.dependency.remove_header = Премахване на зависимост
issues.dependency.issue_remove_text = Това ще премахне зависимостта от тази задача. Продължаване?
issues.reference_link = Препратка: %s
pulls.closed = Заявката за сливане е затворена
pulls.merged_success = Заявката за сливане е успешно слята и затворена
branch.confirm_create_branch = Създаване на клон
branch.create_branch_operation = Създаване на клон
tag.create_tag_operation = Създаване на маркер
tag.confirm_create_tag = Създаване на маркер
pulls.data_broken = Тази заявка за сливане е повредена поради липсваща информация за разклонението.
issues.dependency.pr_closing_blockedby = Затварянето на тази заявка за сливане е блокирано от следните задачи
issues.dependency.pr_remove_text = Това ще премахне зависимостта от тази заявка за сливане. Продължаване?
issues.dependency.title = Зависимости
issues.dependency.issue_no_dependencies = Няма зададени зависимости.
issues.dependency.pr_close_blocked = Трябва да затворите всички задачи, блокиращи тази заявка за сливане, преди да можете да я слеете.
issues.dependency.pr_close_blocks = Тази заявка за сливане блокира затварянето на следните задачи
issues.ref_issue_from = `<a href="%[3]s">спомена тази задача %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commit_ref_at = `спомена тази задача в подаване <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.add_ref_at = `добави препратка <b>%s</b> %s`
pulls.merged_info_text = Клонът %s вече може да бъде изтрит.
pulls.commit_ref_at = `спомена тази заявка за сливане в подаване <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.change_ref_at = `промени препратката от <b><strike>%s</strike></b> на <b>%s</b> %s`
diff.review.reject = Поискване на промени
diff.bin_not_shown = Двоичният файл не е показан.
settings.units.units = Елементи на хранилището
settings.delete_notices_fork_1 = - Разклоненията на това хранилище ще станат независими след изтриване.
settings.actions_desc = Включване на действията за хранилището
settings.packages_desc = Включване на регистъра на пакетите за хранилището
settings.units.add_more = Добавяне...
settings.use_external_issue_tracker = Използване на външен тракер за задачи
settings.releases_desc = Включване на изданията за хранилището
settings.projects_desc = Включване на проектите за хранилището
settings.pulls_desc = Включване на заявките за сливане за хранилището
settings.issues_desc = Включване на задачите за хранилището
settings.use_internal_issue_tracker = Използване на вградения тракер за задачи
pulls.compare_changes_desc = Изберете клона, в който да слеете, и клона, от който да издърпате.
pulls.compare_base = сливане в
pulls.compare_compare = издърпване от
pulls.title_wip_desc = `<a href="#">Започнете заглавието с <strong>%s</strong></a> за да предотвратите случайно сливане на заявката за сливане.`
[modal]
confirm = Потвърждаване
@ -1053,6 +1164,8 @@ members.owner = Притежател
members.member_role = Роля на участника:
members.member = Участник
members.private_helper = да е видим
teams.no_desc = Този екип няма описание
settings.delete_org_desc = Тази организация ще бъде изтрита перманентно. Продължаване?
[install]
admin_password = Парола
@ -1090,6 +1203,7 @@ sqlite_helper = Път на файла за SQLite3 базата данни.<br>
err_empty_admin_email = Администраторският адрес на ел. поща не може да бъде празен.
password_algorithm = Алгоритъм за хеш. на паролите
default_keep_email_private = Скриване на адресите на ел. поща по подразбиране
invalid_password_algorithm = Невалиден алгоритъм за хеш. на паролите
[filter]
string.asc = А - Я
@ -1135,6 +1249,7 @@ change_avatar = Променете профилната си снимка…
email_visibility.limited = Вашият адрес на ел. поща е видим за всички удостоверени потребители
disabled_public_activity = Този потребител е изключил публичната видимост на дейността.
email_visibility.private = Вашият адрес на ел. поща е видим само за вас и администраторите
show_on_map = Показване на това място на картата
[home]
filter = Други филтри
@ -1263,6 +1378,8 @@ SSHTitle = Име на SSH ключ
repo_name_been_taken = Името на хранилището вече е използвано.
team_name_been_taken = Името на екипа вече е заето.
org_name_been_taken = Името на организацията вече е заето.
still_own_packages = Вашият акаунт притежава един или повече пакети, първо ги изтрийте.
still_own_repo = Вашият акаунт притежава едно или повече хранилища, първо ги изтрийте или прехвърлете.
[action]
close_issue = `затвори задача <a href="%[1]s">%[3]s#%[2]s</a>`
@ -1288,6 +1405,7 @@ publish_release = `публикува издание <a href="%[2]s"> "%[4]s" </
push_tag = изтласка маркер <a href="%[2]s">%[3]s</a> към <a href="%[1]s">%[4]s</a>
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 = Сравняване
[auth]
tab_openid = OpenID
@ -1408,6 +1526,24 @@ component_loading_failed = Неуспешно зареждане на %s
contributors.what = приноси
recent_commits.what = скорошни подавания
component_loading = Зареждане на %s...
component_loading_info = Това може да отнеме известно време…
[projects]
type-1.display_name = Индивидуален проект
[search]
no_results = Няма намерени съответстващи резултати.
team_kind = Търсене на екипи...
repo_kind = Търсене на хранилища...
org_kind = Търсене на организации...
user_kind = Търсене на потребители...
code_kind = Търсене на код...
commit_kind = Търсене на подавания...
project_kind = Търсене на проекти...
package_kind = Търсене на пакети...
search = Търсене...
[markup]
filepreview.lines = Редове от %[1]d до %[2]d в %[3]s
filepreview.line = Ред %[1]d в %[2]s

View file

@ -73,11 +73,11 @@ all=Vše
sources=Zdrojové kódy
mirrors=Zrcadla
collaborative=Spolupráce
forks=Rozštěpení
forks=Forky
activities=Aktivity
pull_requests=Požadavky na sloučení
issues=Úkoly
issues=Problémy
milestones=Milníky
ok=OK
@ -147,7 +147,7 @@ confirm_delete_artifact = Opravdu chcete odstranit artefakt „%s“?
toggle_menu = Přepnout nabídku
filter = Filtr
filter.is_fork = Forknuto
filter.not_fork = Není forkuto
filter.not_fork = Není forknuto
filter.is_mirror = Zrcadleno
filter.is_template = Šablona
filter.not_template = Není šablona
@ -156,7 +156,9 @@ filter.private = Soukromé
filter.is_archived = Archivováno
filter.not_mirror = Není zrcadleno
filter.not_archived = Není archivováno
filter.clear = Vymazat filtr
filter.clear = Vymazat filtry
more_items = Další položky
invalid_data = Neplatná data: %v
[aria]
navbar=Navigační lišta
@ -217,7 +219,7 @@ license_desc=Vše je na <a target="_blank" rel="noopener noreferrer" href="https
install=Instalace
title=Počáteční konfigurace
docker_helper=Pokud spouštíte Forgejo v Dockeru, přečtěte si <a target="_blank" rel="noopener noreferrer" href="%s">dokumentaci</a>, než budete měnit jakákoliv nastavení.
require_db_desc=Forgejo requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol).
require_db_desc=Forgejo vyžaduje MySQL, PostgreSQL, MSSQL, SQLite3 nebo TiDB (protokol MySQL).
db_title=Nastavení databáze
db_type=Typ databáze
host=Hostitel
@ -375,7 +377,7 @@ org_no_results=Nebyly nalezeny žádné odpovídající organizace.
code_no_results=Nebyl nalezen žádný zdrojový kód odpovídající hledanému výrazu.
code_search_results=Výsledky hledání pro „%s“
code_last_indexed_at=Naposledy indexováno %s
relevant_repositories_tooltip=Repozitáře, které jsou rozštěpení nebo nemají žádné téma, ikonu a žádný popis jsou skryty.
relevant_repositories_tooltip=Repozitáře, které jsou forky nebo nemají žádné téma, žádnou ikonu a žádný popis, jsou skryty.
relevant_repositories=Zobrazují se pouze relevantní repositáře, <a href="%s">zobrazit nefiltrované výsledky</a>.
forks_one = %d fork
forks_few = %d forků
@ -486,8 +488,8 @@ reset_password.text=Pokud jste to byli vy, klikněte na následující odkaz pro
register_success=Registrace byla úspěšná
issue_assigned.pull=@%[1]s vás přiřadil/a k požadavku na natažení %[2]s repozitáři %[3]s.
issue_assigned.issue=@%[1]s vás přiřadil/a k úkolu %[2]s repozitáři %[3]s.
issue_assigned.pull=@%[1]s vás přiřadil/a k žádosti o sloučení %[2]s v repozitáři %[3]s.
issue_assigned.issue=@%[1]s vás přiřadil/a k problému %[2]s v repozitáři %[3]s.
issue.x_mentioned_you=<b>@%s</b> vás zmínil/a:
issue.action.force_push=<b>%[1]s</b> vynutil/a nahrání <b>%[2]s</b> z %[3]s do %[4]s.
@ -496,11 +498,11 @@ issue.action.push_n=<b>@%[1]s</b> nahrál/a %[3]d commity do %[2]s
issue.action.close=<b>@%[1]s</b> uzavřel/a #%[2]d.
issue.action.reopen=<b>@%[1]s</b> znovu otevřel/a #%[2]d.
issue.action.merge=<b>@%[1]s</b> sloučil/a #%[2]d do %[3]s.
issue.action.approve=<b>@%[1]s</b> schválil/a tento požadavek na natažení.
issue.action.reject=<b>@%[1]s</b> požadoval/a změny v tomto požadavku na natažení.
issue.action.review=<b>@%[1]s</b> okomentoval/a tento požadavek na natažení.
issue.action.review_dismissed=<b>@%[1]s</b> odmítl/a poslední kontrolu z %[2]s pro tento požadavek na natažení.
issue.action.ready_for_review=<b>@%[1]s</b> označil/a tento požadavek na natažení jako připravený ke kontrole.
issue.action.approve=<b>@%[1]s</b> schválil/a tuto žádost o sloučení.
issue.action.reject=<b>@%[1]s</b> požaduje změny v této žádosti o sloučení.
issue.action.review=<b>@%[1]s</b> okomentoval/a tuto žádost o sloučení.
issue.action.review_dismissed=<b>@%[1]s</b> odmítl/a poslední kontrolu od %[2]s této žádosti o sloučení.
issue.action.ready_for_review=<b>@%[1]s</b> označil/a tuto žádost o sloučení jako připravenou ke kontrole.
issue.action.new=<b>@%[1]s</b> vytvořil/a #%[2]d.
issue.in_tree_path=V %s:
@ -722,7 +724,7 @@ comment_type_group_lock=Stav zámku
comment_type_group_review_request=Žádost o posouzení
comment_type_group_pull_request_push=Přidané commity
comment_type_group_project=Projekt
comment_type_group_issue_ref=Referenční číslo úkolu
comment_type_group_issue_ref=Referenční číslo problému
saved_successfully=Vaše nastavení bylo úspěšně uloženo.
privacy=Soukromí
keep_activity_private=Skrýt aktivitu z profilové stránky
@ -811,7 +813,7 @@ gpg_token=Token
gpg_token_help=Podpis můžete vygenerovat pomocí:
gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
gpg_token_signature=Zakódovaný podpis GPG
key_signature_gpg_placeholder=Začíná s „-----BEGIN PGP SIGNATURE-----“
key_signature_gpg_placeholder=Začíná textem „-----BEGIN PGP SIGNATURE-----“
verify_gpg_key_success=GPG klíč „%s“ byl ověřen.
ssh_key_verified=Ověřený klíč
ssh_key_verified_long=Klíč byl ověřen pomocí tokenu a může být použit k ověření commitů shodujících se s libovolnou vaší aktivovanou e-mailovou adresou pro tohoto uživatele.
@ -880,7 +882,7 @@ permissions_access_all=Vše (veřejné, soukromé a omezené)
select_permissions=Vyberte oprávnění
permission_no_access=Bez přístupu
permission_read=Přečtené
permission_write=čtení i zápis
permission_write=Čtení a zápis
at_least_one_permission=Musíte vybrat alespoň jedno oprávnění pro vytvoření tokenu
permissions_list=Oprávnění:
@ -931,7 +933,7 @@ scan_this_image=Naskenujte tento obrázek s vaší ověřovací aplikací:
or_enter_secret=Nebo zadejte tajný kód: %s
then_enter_passcode=A zadejte přístupový kód zobrazený ve vaší aplikaci:
passcode_invalid=Přístupový kód není platný. Zkuste to znovu.
twofa_enrolled=Ve vašem účtu bylo povoleno dvoufaktorové ověřování. Uložte si pomocný token (%s) na bezpečném místě, protože bude zobrazen pouze jednou!
twofa_enrolled=Ve vašem účtu bylo povoleno dvoufaktorové ověřování. Uložte si jednorázový obnovovací klíč (%s) na bezpečné místo, jelikož již nebude znovu zobrazen.
twofa_failed_get_secret=Nepodařilo se získat tajemství.
webauthn_desc=Bezpečnostní klíče jsou hardwarová zařízení obsahující kryptografické klíče. Mohou být použity pro dvoufaktorové ověřování. Bezpečnostní klíče musí podporovat <a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">WebAuthn Authenticator</a> standard.
@ -957,7 +959,7 @@ repos_none=Nevlastníte žádné repozitáře.
delete_account=Odstranit svůj účet
delete_prompt=Tato operace natrvalo odstraní váš uživatelský účet. <strong>NELZE</strong> ji vrátit zpět.
delete_with_all_comments=Váš účet je mladší než %s. Aby se zabránilo fantomovým komentářům, všechny komentáře k úkolům/požadavkům na natažení budou smazány.
delete_with_all_comments=Váš účet je mladší než %s. Pro zabránění fantomovým komentářům budou společně s ním odstraněny všechny komentáře u problémů a ŽS.
confirm_delete_account=Potvrdit odstranění
delete_account_title=Odstranit uživatelský účet
delete_account_desc=Jste si jisti, že chcete trvale smazat tento účet?
@ -1026,7 +1028,7 @@ repo_lang=Jazyk
repo_gitignore_helper=Vyberte šablony .gitignore.
repo_gitignore_helper_desc=Vyberte soubory, které nechcete sledovat ze seznamu šablon pro běžné jazyky. Typické artefakty generované nástroji pro sestavení každého jazyka jsou ve výchozím stavu součástí .gitignore.
issue_labels=Štítky problémů
issue_labels_helper=Vyberte sadu štítků úkolů.
issue_labels_helper=Vyberte sadu štítků problémů.
license=Licence
license_helper=Vyberte licenční soubor.
license_helper_desc=Licence řídí, co ostatní mohou a nemohou dělat s vaším kódem. Nejste si jisti, která je pro váš projekt správná? Podívejte se na <a target="_blank" rel="noopener noreferrer" href="%s">Zvolte licenci</a>
@ -1044,7 +1046,7 @@ trust_model_helper_default=Výchozí: Použít výchozí model důvěry pro tuto
create_repo=Vytvořit repozitář
default_branch=Výchozí větev
default_branch_label=výchozí
default_branch_helper=Výchozí větev je základní větev pro požadavky na natažení a commity kódu.
default_branch_helper=Výchozí větev je základní větev pro žádosti o sloučení a commity kódu.
mirror_prune=Vyčistit
mirror_prune_desc=Odstranit zastaralé reference na vzdálené sledování
mirror_interval=Interval zrcadlení (platné časové jednotky jsou „h“, „m“ a „s“). Nastavením na 0 zakážete periodickou synchronizaci. (Minimální interval: %s)
@ -1065,7 +1067,7 @@ mirror_password_help=Změňte uživatelské jméno pro vymazání uloženého he
watchers=Sledující
stargazers=Sledující
stars_remove_warning=Tímto odstraníte všechny hvězdičky z tohoto repozitáře.
forks=Rozštěpení
forks=Forky
reactions_more=a %d dalších
unit_disabled=Správce webu zakázal tuto sekci repozitáře.
language_other=Jiný
@ -1112,9 +1114,9 @@ template.one_item=Musíte vybrat alespoň jednu položku šablony
template.invalid=Musíte vybrat repositář šablony
archive.title=Tento repozitář je archivovaný. Můžete prohlížet soubory, klonovat, ale nemůžete nahrávat a vytvářet nové problémy nebo žádosti o sloučení.
archive.title_date=Tento repositář byl archivován %s. Můžete zobrazit soubory a klonovat je, ale nemůžete nahrávat ani otevírat problémy nebo požadavky na natažení.
archive.issue.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat úkoly.
archive.pull.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat požadavky na natažení.
archive.title_date=Tento repozitář byl archivován %s. Můžete si prohlížet a klonovat soubory, ale nemůžete nahrávat ani otevírat problémy nebo žádosti o sloučení.
archive.issue.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat problémy.
archive.pull.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat žádosti o sloučení.
form.reach_limit_of_creation_1=Již jste dosáhli svůj limit %d repozitář.
form.reach_limit_of_creation_n=Již jste dosáhli svůj limit %d repozitářů.
@ -1134,7 +1136,7 @@ migrate_items=Položky pro migrování
migrate_items_wiki=Wiki
migrate_items_milestones=Milníky
migrate_items_labels=Štítky
migrate_items_issues=Úkoly
migrate_items_issues=Problémy
migrate_items_pullrequests=Žádosti o sloučení
migrate_items_merge_requests=Sloučit žádosti
migrate_items_releases=Vydání
@ -1178,12 +1180,12 @@ mirror_from=zrcadlo
forked_from=rozštěpen z
generated_from=generováno z
fork_from_self=Nemůžete rozštěpit váš vlastní repozitář.
fork_guest_user=Přihlaste se pro rozštěpení tohoto repozitáře.
fork_guest_user=Přihlaste se pro vytvoření forku tohoto repozitáře.
watch_guest_user=Pro sledování tohoto repozitáře se přihlaste.
star_guest_user=Pro hodnocení tohoto repozitáře se přihlaste.
unwatch=Přestat sledovat
watch=Sledovat
unstar=Odoblíbit
unstar=Zrušit oblíbení
star=Oblíbit
fork=Rozštěpit
download_archive=Stáhnout repozitář
@ -1207,7 +1209,7 @@ filter_branch_and_tag=Filtr pro větev nebo značku
find_tag=Najít značku
branches=Větve
tags=Značky
issues=Úkoly
issues=Problémy
pulls=Žádosti o sloučení
project_board=Projekty
packages=Balíčky
@ -1284,8 +1286,8 @@ editor.name_your_file=Pojmenujte váš soubor…
editor.filename_help=Přidejte adresář zapsáním jeho jména následovaného lomítkem („/“). Adresář odeberete stiskem backspace na začátku vstupního pole.
editor.or=nebo
editor.cancel_lower=Zrušit
editor.commit_signed_changes=Odevzdat podepsané změny
editor.commit_changes=Odevzdat změny
editor.commit_signed_changes=Commitnout podepsané změny
editor.commit_changes=Commitnout změny
editor.add_tmpl=Přidat „<nazevsouboru>“
editor.add=Přidat %s
editor.update=Aktualizovat %s
@ -1297,7 +1299,7 @@ editor.new_patch=Nová záplata
editor.commit_message_desc=Přidat volitelný rozšířený popis…
editor.signoff_desc=Přidat Signed-off-by podpis přispěvatele na konec zprávy o commitu.
editor.commit_directly_to_this_branch=Odevzdat přímo do větve <strong class="branch-name">%s</strong>.
editor.create_new_branch=Vytvořit <strong>novou větev</strong> pro tento commit a spustit požadavek na natažení.
editor.create_new_branch=Vytvořit <strong>novou větev</strong> pro tento commit a vytvořit žádost o sloučení.
editor.create_new_branch_np=Vytvořte <strong>novou větev</strong> z tohoto commitu.
editor.propose_file_change=Navrhnout změnu souboru
editor.new_branch_name=Pojmenujte novou větev pro tento commit
@ -1312,7 +1314,7 @@ editor.file_is_a_symlink=`„%s“ je symbolický odkaz. Symbolické odkazy nemo
editor.filename_is_a_directory=Jméno souboru „%s“ je již použito jako jméno adresáře v tomto repozitáři.
editor.file_editing_no_longer_exists=Upravovaný soubor „%s“ již není součástí tohoto repozitáře.
editor.file_deleting_no_longer_exists=Odstraňovaný soubor „%s“ již není součástí tohoto repozitáře.
editor.file_changed_while_editing=Obsah souboru byl změněn od doby, kdy jste začaly s úpravou. <a target="_blank" rel="noopener noreferrer" href="%s">Klikněte zde</a>, abyste je zobrazili, nebo <strong>potvrďte změny ještě jednou</strong> pro jejich přepsání.
editor.file_changed_while_editing=Obsah souboru se od zahájení úprav změnil. <a target="_blank" rel="noopener noreferrer" href="%s">Klikněte sem</a> pro jejich zobrazení nebo <strong>proveďte commit změn ještě jednou</strong> pro jejich přepsání.
editor.file_already_exists=Soubor „%s“ již existuje v tomto repozitáři.
editor.commit_empty_file_header=Odevzdat prázdný soubor
editor.commit_empty_file_text=Soubor, který se chystáte odevzdat, je prázdný. Pokračovat?
@ -1367,10 +1369,10 @@ commitstatus.pending=Čekající
commitstatus.success=Úspěch
ext_issues=Přístup k externím problémům
ext_issues.desc=Odkaz na externí systém úkolů.
ext_issues.desc=Odkaz na externí systém problémů.
projects=Projekty
projects.desc=Spravovat úkoly a požadavky na natažení na projektových nástěnkách.
projects.desc=Spravovat problémy a žádosti o sloučení na projektových nástěnkách.
projects.description=Popis (volitelné)
projects.description_placeholder=Popis
projects.create=Vytvořit projekt
@ -1379,14 +1381,14 @@ projects.new=Nový projekt
projects.new_subheader=Koordinujte, sledujte a aktualizujte svou práci na jednom místě, aby projekty zůstaly transparentní a v plánu.
projects.create_success=Projekt „%s“ byl vytvořen.
projects.deletion=Odstranit projekt
projects.deletion_desc=Odstranění projektu jej odstraní ze všech souvisejících úkolů. Pokračovat?
projects.deletion_desc=Odstraněním projektu jej odstraníte ze všech souvisejících problémů. Pokračovat?
projects.deletion_success=Projekt byl odstraněn.
projects.edit=Upravit projekt
projects.edit_subheader=Projekty organizují úkoly a sledují pokrok.
projects.edit_subheader=Projekty organizují problémy a sledují pokrok.
projects.modify=Upravit projekt
projects.edit_success=Projekt „%s“ byl aktualizován.
projects.type.none=Žádný
projects.type.basic_kanban=Základní Kanban
projects.type.basic_kanban=Základní kanban
projects.type.bug_triage=Třídění chyb
projects.template.desc=Šablona
projects.template.desc_helper=Začněte vybráním šablony projektu
@ -1401,7 +1403,7 @@ projects.column.set_default_desc=Nastavit tento sloupec jako výchozí pro nekat
projects.column.unset_default=Zrušit nastavení jako výchozí
projects.column.unset_default_desc=Zrušit nastavení tohoto sloupce jako výchozí
projects.column.delete=Odstranit sloupec
projects.column.deletion_desc=Odstranění projektového sloupce přesune všechny související problémy do kategorie „Nezařazené“. Pokračovat?
projects.column.deletion_desc=Odstranění projektového sloupce přesune všechny související problémy do výchozího sloupce. Pokračovat?
projects.column.color=Barva
projects.open=Otevřít
projects.close=Zavřít
@ -1419,7 +1421,7 @@ issues.filter_reviewers=Filtrovat posuzovatele
issues.new=Nový problém
issues.new.title_empty=Název nesmí být prázdný
issues.new.labels=Štítky
issues.new.no_label=Bez štítku
issues.new.no_label=Bez štítků
issues.new.clear_labels=Zrušit štítky
issues.new.projects=Projekty
issues.new.clear_projects=Vymazat projekty
@ -1439,7 +1441,7 @@ issues.new.no_reviewers=Žádní posuzovatelé
issues.choose.get_started=Začínáme
issues.choose.open_external_link=Otevřít
issues.choose.blank=Výchozí
issues.choose.blank_about=Vytvořit úkol z výchozí šablony.
issues.choose.blank_about=Vytvořit problém z výchozí šablony.
issues.choose.ignore_invalid_templates=Neplatné šablony byly ignorovány
issues.choose.invalid_templates=%v nalezených neplatných šablon
issues.choose.invalid_config=Nastavení problému obsahuje chyby:
@ -1494,7 +1496,7 @@ issues.filter_assginee_no_assignee=Bez zpracovatele
issues.filter_poster=Autor
issues.filter_poster_no_select=Všichni autoři
issues.filter_type=Typ
issues.filter_type.all_issues=Všechny úkoly
issues.filter_type.all_issues=Všechny problémy
issues.filter_type.assigned_to_you=Přiřazené vám
issues.filter_type.created_by_you=Vytvořené vámi
issues.filter_type.mentioning_you=Zmiňující vás
@ -1511,8 +1513,8 @@ issues.filter_sort.nearduedate=Nejbližší datum dokončení
issues.filter_sort.farduedate=Nejvzdálenější datum dokončení
issues.filter_sort.moststars=Nejvíce hvězdiček
issues.filter_sort.feweststars=Nejméně hvězdiček
issues.filter_sort.mostforks=Nejvíce rozštěpení
issues.filter_sort.fewestforks=Nejméně rozštěpení
issues.filter_sort.mostforks=Nejvíce forků
issues.filter_sort.fewestforks=Nejméně forků
issues.keyword_search_unavailable=Hledání podle klíčového slova není momentálně dostupné. Obraťte se na správce webu.
issues.action_open=Otevřít
issues.action_close=Zavřít
@ -1531,8 +1533,8 @@ issues.opened_by_fake=otevřeno %[1]s uživatelem %[2]s
issues.closed_by_fake=od %[2]s byl uzavřen %[1]s
issues.previous=Předchozí
issues.next=Další
issues.open_title=otevřený
issues.closed_title=zavřený
issues.open_title=Otevřeno
issues.closed_title=Uzavřeno
issues.draft_title=Koncept
issues.num_comments_1=%d komentář
issues.num_comments=%d komentářů
@ -1551,15 +1553,15 @@ issues.close_comment_issue=Okomentovat a zavřít
issues.reopen_issue=Znovuotevřít
issues.reopen_comment_issue=Okomentovat a znovu otevřít
issues.create_comment=Okomentovat
issues.closed_at=`uzavřel/a tento úkol <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at=`znovuotevřel/a tento úkol <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commit_ref_at=`odkázal na tento úkol z commitu <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_issue_from=`<a href="%[3]s">odkazoval/a na tento úkol %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_pull_from=`<a href="%[3]s">odkazoval/a na tento požadavek na natažení %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closing_from=`<a href="%[3]s">odkazoval/a na požadavek na natažení %[4]s, který uzavře tento úkol</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopening_from=`<a href="%[3]s">odkazoval/a na požadavek na natažení %[4]s, který znovu otevře tento úkol</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closed_from=`<a href="%[3]s">uzavřel/a tento úkol %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopened_from=`<a href="%[3]s">znovu otevřel/a tento úkol %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
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>`
issues.commit_ref_at=`odkázal/a na tento problém z commitu <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_issue_from=`<a href="%[3]s">odkázal/a na tento problém %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_pull_from=`<a href="%[3]s">odkázal/a na tuto žádost o sloučení %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closing_from=`<a href="%[3]s">odkazoval/a na žádost o sloučení %[4]s, která uzavře tento problém</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopening_from=`<a href="%[3]s">odkazoval/a na žádost o sloučení %[4]s, která znovu otevře tento problém</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closed_from=`<a href="%[3]s">uzavřel/a tento problém %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopened_from=`<a href="%[3]s">znovu otevřel/a tento problém %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_from=`z %[1]s`
issues.author=Autor
issues.author_helper=Tento uživatel je autor.
@ -1573,7 +1575,7 @@ issues.role.first_time_contributor_helper=Toto je první příspěvek tohoto už
issues.role.contributor=Přispěvatel
issues.role.contributor_helper=Tento uživatel již dříve přispíval do repozitáře.
issues.re_request_review=Znovu požádat o posouzení
issues.is_stale=Od tohoto posouzení došlo ke změnám v tomto požadavku na natažení
issues.is_stale=Od tohoto posouzení došlo v této žádosti ke změnám
issues.remove_request_review=Odstranit žádost o posouzení
issues.remove_request_review_block=Nelze odstranit žádost o posouzení
issues.dismiss_review=Zamítnout posouzení
@ -1592,18 +1594,18 @@ issues.label_archive_tooltip=Archivované štítky jsou ve výchozím nastavení
issues.label_exclusive_desc=Pojmenujte štítek <code>rozsah/položka</code>, aby se stal vzájemně exkluzivním s jinými štítky <code>rozsah/</code>.
issues.label_exclusive_warning=Jakékoliv protichůdné rozsahy štítků budou odstraněny při úpravě štítků u úkolů nebo u požadavku na natažení.
issues.label_count=%d štítků
issues.label_open_issues=%d otevřených úkolů
issues.label_open_issues=%d otevřených problémů / žádostí o sloučení
issues.label_edit=Upravit
issues.label_delete=Smazat
issues.label_modify=Upravit štítek
issues.label_deletion=Odstranit štítek
issues.label_deletion_desc=Odstranění štítku jej smaže ze všech úkolů. Pokračovat?
issues.label_deletion_desc=Odstraněním štítku jej odeberete ze všech problémů. Pokračovat?
issues.label_deletion_success=Štítek byl odstraněn.
issues.label.filter_sort.alphabetically=Od začátku abecedy
issues.label.filter_sort.reverse_alphabetically=Od konce abecedy
issues.label.filter_sort.by_size=Nejmenší velikost
issues.label.filter_sort.reverse_by_size=Největší velikost
issues.num_participants=%d účastníků
issues.num_participants_few=%d účastníků
issues.attachment.open_tab=`Klikněte pro zobrazení „%s“ v nové záložce`
issues.attachment.download=`Klikněte pro stažení „%s“`
issues.subscribe=Odebírat
@ -1614,9 +1616,9 @@ issues.pin_comment=připnul/a tento %s
issues.unpin_comment=odepnul/a tento %s
issues.lock=Uzamknout konverzaci
issues.unlock=Odemknout konverzaci
issues.lock.unknown_reason=Úkol nelze z neznámého důvodu uzamknout.
issues.lock_duplicate=Úkol nemůže být uzamčený dvakrát.
issues.unlock_error=Nelze odemknout úkol, který je uzamčený.
issues.lock.unknown_reason=Problém nelze z neznámého důvodu uzamknout.
issues.lock_duplicate=Problém nemůže být uzamčený dvakrát.
issues.unlock_error=Nelze odemknout problém, který není uzamčený.
issues.lock_with_reason=uzamkl/a jako <strong>%s</strong> a omezil/a konverzaci na spolupracovníky %s
issues.lock_no_reason=uzamkl/a a omezil/a konverzaci na spolupracovníky %s
issues.unlock_comment=odemkl/a tuto konverzaci %s
@ -1624,22 +1626,22 @@ issues.lock_confirm=Uzamknout
issues.unlock_confirm=Odemknout
issues.lock.notice_1=- Další uživatelé nemohou komentovat tento problém.
issues.lock.notice_2=- Vy a ostatní spolupracovníci s přístupem k tomuto repozitáři můžete stále přidávat komentáře, které ostatní uvidí.
issues.lock.notice_3=- V budoucnu budete moci vždy znovu tento úkol odemknout.
issues.unlock.notice_1=- Všichni budou moci znovu komentovat tento úkol.
issues.unlock.notice_2=- V budoucnu budete moci vždy znovu tento úkol uzamknout.
issues.lock.notice_3=- Vždy budete moci tento problém znovu odemknout.
issues.unlock.notice_1=- Všichni budou moci znovu komentovat tento problém.
issues.unlock.notice_2=- Vždy budete moci tento problém znovu uzamknout.
issues.lock.reason=Důvod pro uzamčení
issues.lock.title=Uzamknout konverzaci u tohoto úkolu.
issues.unlock.title=Odemknout konverzaci u tohoto úkolu.
issues.comment_on_locked=Nemůžete komentovat uzamčený úkol.
issues.lock.title=Uzamknout konverzaci u tohoto problému.
issues.unlock.title=Odemknout konverzaci u tohoto problému.
issues.comment_on_locked=Nemůžete komentovat uzamčený problém.
issues.delete=Smazat
issues.delete.title=Smazat tento úkol?
issues.delete.text=Opravdu chcete tento úkol smazat? (Tím se trvale odstraní veškerý obsah. Pokud jej hodláte archivovat, zvažte raději jeho uzavření.)
issues.delete.title=Smazat tento problém?
issues.delete.text=Opravdu chcete smazat tento problém? (Tím se trvale odstraní veškerý obsah. Pokud jej hodláte archivovat, zvažte raději jeho uzavření)
issues.tracker=Sledování času
issues.start_tracking_short=Spustit časovač
issues.start_tracking=Spustit sledování času
issues.start_tracking_history=`započal/a práci %s`
issues.tracker_auto_close=Časovač se automaticky zastaví po zavření tohoto úkolu
issues.tracking_already_started=`Již jste spustili sledování času na <a href="%s">jiném úkolu</a>!`
issues.tracker_auto_close=Časovač se automaticky zastaví po zavření tohoto problému
issues.tracking_already_started=`Sledování času jste již spustili u <a href="%s">jiného problému</a>!`
issues.stop_tracking=Zastavit časovač
issues.stop_tracking_history=`ukončil/a práci %s`
issues.cancel_tracking=Zahodit
@ -1686,27 +1688,27 @@ issues.dependency.remove=Odstranit
issues.dependency.remove_info=Odstranit tuto závislost
issues.dependency.added_dependency=`přidal/a novou závislost %s`
issues.dependency.removed_dependency=`odstranil/a závislost %s`
issues.dependency.pr_closing_blockedby=Uzavření tohoto požadavku na natažení je blokováno následujícími úkoly
issues.dependency.issue_closing_blockedby=Uzavření tohoto úkolu je blokováno následujícími úkoly
issues.dependency.issue_close_blocks=Tento úkol blokuje uzavření následujících úkolů
issues.dependency.pr_close_blocks=Tento požadavek na natažení blokuje uzavření následujících úkolů
issues.dependency.issue_close_blocked=Musíte zavřít všechny úkoly, které blokují tento úkol, aby jej bylo možné zavřít.
issues.dependency.pr_closing_blockedby=Uzavření této žádosti o sloučení je blokováno následujícími problémy
issues.dependency.issue_closing_blockedby=Uzavření tohoto problému je blokováno následujícími problémy
issues.dependency.issue_close_blocks=Tento problém blokuje uzavření následujících problémů
issues.dependency.pr_close_blocks=Tato žádost o sloučení blokuje uzavření následujících problémů
issues.dependency.issue_close_blocked=Aby bylo možné uzavřít tento problém, musíte uzavřít všechny ostatní problémy, které jej blokují.
issues.dependency.issue_batch_close_blocked=Nelze uzavřít úkoly, které jste vybrali, protože úkol #%d má stále otevřené závislosti
issues.dependency.pr_close_blocked=Musíte zavřít všechny úkoly, které blokují tento požadavek na natažení, aby jej bylo možné sloučit.
issues.dependency.pr_close_blocked=Aby bylo možné sloučit tuto žádost, musíte uzavřít všechny problémy, které ji blokují.
issues.dependency.blocks_short=Blokuje
issues.dependency.blocked_by_short=Závisí na
issues.dependency.remove_header=Odstranit závislost
issues.dependency.issue_remove_text=Tímto krokem odeberete závislost z úkolu. Pokračovat?
issues.dependency.pr_remove_text=Tímto krokem odeberete závislost z požadavku na natažení. Pokračovat?
issues.dependency.issue_remove_text=Tímto krokem odeberete závislost z tohoto problému. Pokračovat?
issues.dependency.pr_remove_text=Tímto krokem odeberete závislost z této žádosti o sloučení. Pokračovat?
issues.dependency.setting=Povolit závislosti pro problémy a žádosti o sloučení
issues.dependency.add_error_same_issue=Úkol nemůže záviset sám na sobě.
issues.dependency.add_error_dep_issue_not_exist=Související úkol neexistuje.
issues.dependency.add_error_same_issue=Problém nemůže záviset sám na sobě.
issues.dependency.add_error_dep_issue_not_exist=Závislý problém neexistuje.
issues.dependency.add_error_dep_not_exist=Závislost neexistuje.
issues.dependency.add_error_dep_exists=Závislost již existuje.
issues.dependency.add_error_cannot_create_circular=Nemůžete vytvořit závislost dvou úkolů, které se vzájemně blokují.
issues.dependency.add_error_dep_not_same_repo=Oba úkoly musí být ve stejném repozitáři.
issues.review.self.approval=Nemůžete schválit svůj požadavek na natažení.
issues.review.self.rejection=Nemůžete požadovat změny ve svém vlastním požadavku na natažení.
issues.dependency.add_error_cannot_create_circular=Nelze vytvořit závislost dvou problémů, které se vzájemně blokují.
issues.dependency.add_error_dep_not_same_repo=Oba problémy musí být ve stejném repozitáři.
issues.review.self.approval=Nemůžete schválit vlastní žádost o sloučení.
issues.review.self.rejection=Nemůžete požadovat změny ve své vlastní žádosti o sloučení.
issues.review.approve=schválil/a tyto změny %s
issues.review.comment=posoudil/a %s
issues.review.dismissed=zamítl/a posouzení uživatele %s %s
@ -1746,14 +1748,14 @@ issues.reference_link=Reference: %s
compare.compare_base=základní
compare.compare_head=porovnat
pulls.desc=Povolit požadavky na natažení a posuzování kódu.
pulls.desc=Povolit žádosti o sloučení a posuzování kódu.
pulls.new=Nová žádost o sloučení
pulls.view=Zobrazit žádost o sloučení
pulls.compare_changes=Nová žádost o sloučení
pulls.allow_edits_from_maintainers=Povolit úpravy od správců
pulls.allow_edits_from_maintainers_desc=Uživatelé s přístupem k zápisu do základní větve mohou také nahrávat do této větve
pulls.allow_edits_from_maintainers_err=Aktualizace se nezdařila
pulls.compare_changes_desc=Vyberte větev pro sloučení a větev pro natažení.
pulls.compare_changes_desc=Vyberte větev pro sloučení a větev, ze které provést pull.
pulls.has_viewed_file=Zobrazeno
pulls.has_changed_since_last_review=Změněno od vašeho posledního posouzení
pulls.viewed_files_label=%[1]d / %[2]d souborů zobrazeno
@ -1772,10 +1774,10 @@ pulls.showing_specified_commit_range=Zobrazují se pouze změny mezi %[1]s..%[2]
pulls.select_commit_hold_shift_for_range=Vyberte commit. Podržte klávesu shift + klepněte pro výběr rozsahu
pulls.review_only_possible_for_full_diff=Posouzení je možné pouze při zobrazení plného rozlišení
pulls.filter_changes_by_commit=Filtrovat podle commitu
pulls.nothing_to_compare=Tyto větve jsou stejné. Není potřeba vytvářet požadavek na natažení.
pulls.nothing_to_compare=Tyto větve jsou stejné. Není třeba vytvářet žádost o sloučení.
pulls.nothing_to_compare_have_tag=Vybraná větev/značka je stejná.
pulls.nothing_to_compare_and_allow_empty_pr=Tyto větve jsou stejné. Tento požadavek na natažení bude prázdný.
pulls.has_pull_request=`Požadavek na natažení mezi těmito větvemi již existuje: <a href="%[1]s">%[2]s#%[3]d</a>`
pulls.nothing_to_compare_and_allow_empty_pr=Tyto větve jsou stejné. Tato žádost o sloučení bude prázdná.
pulls.has_pull_request=`Žádost o sloučení mezi těmito větvemi již existuje: <a href="%[1]s">%[2]s#%[3]d</a>`
pulls.create=Vytvořit žádost o sloučení
pulls.title_desc_few=chce sloučit %[1]d commity z větve <code>%[2]s</code> do <code id="branch_target">%[3]s</code>
pulls.merged_title_desc_few=sloučil %[1]d commity z větve <code>%[2]s</code> do větve <code>%[3]s</code> před %[4]s
@ -1783,35 +1785,35 @@ pulls.change_target_branch_at=`změnil/a cílovou větev z <b>%s</b> na <b>%s</b
pulls.tab_conversation=Konverzace
pulls.tab_commits=Commity
pulls.tab_files=Změněné soubory
pulls.reopen_to_merge=Prosíme, otevřete znovu tento požadavek na natažení, aby se provedlo sloučení.
pulls.cant_reopen_deleted_branch=Tento požadavek na natažení nemůže být znovu otevřen protože větev byla smazána.
pulls.reopen_to_merge=Otevřete znovu tuto žádost pro provedení sloučení.
pulls.cant_reopen_deleted_branch=Tuto žádost o sloučení nelze znovu otevřít, protože větev byla smazána.
pulls.merged=Sloučený
pulls.merged_success=Požadavek na natažení byl úspěšně sloučen a uzavřen
pulls.closed=Požadavek na natažení uzavřen
pulls.merged_success=Žádost byla úspěšně sloučena a uzavřena
pulls.closed=Žádost o sloučení uzavřena
pulls.manually_merged=Sloučeno ručně
pulls.merged_info_text=Větev %s může být nyní odstraněna.
pulls.is_closed=Požadavek na natažení byl uzavřen.
pulls.title_wip_desc=`<a href="#">Začněte název s <strong>%s</strong></a> a zamezíte tak nechtěnému sloučení požadavku na natažení.`
pulls.cannot_merge_work_in_progress=Tento požadavek na natažení je označen jako probíhající práce.
pulls.is_closed=Žádost o sloučení byla uzavřena.
pulls.title_wip_desc=`<a href="#">Začněte název textem <strong>%s</strong></a> pro zamezení nechtěnému sloučení žádosti.`
pulls.cannot_merge_work_in_progress=Tato žádost o slolučení je označena jako rozpracovaná.
pulls.still_in_progress=Stále probíhá?
pulls.add_prefix=Přidat prefix <strong>%s</strong>
pulls.remove_prefix=Odstranit prefix <strong>%s</strong>
pulls.data_broken=Tento požadavek na natažení je rozbitý kvůli chybějícím informacím o rozštěpení.
pulls.files_conflicted=Tento požadavek na natažení obsahuje změny, které kolidují s cílovou větví.
pulls.data_broken=Tato žádost o sloučení je rozbitá kvůli chybějícím informacím o forku.
pulls.files_conflicted=Tato žádost o sloučení obsahuje změny, které jsou v rozporu s cílovou větví.
pulls.is_checking=Právě probíhá kontrola konfliktů při sloučení. Zkuste to za chvíli.
pulls.is_ancestor=Tato větev je již součástí cílové větve. Není co sloučit.
pulls.is_empty=Změny na této větvi jsou již na cílové větvi. Toto bude prázdný commit.
pulls.required_status_check_failed=Některé požadované kontroly nebyly úspěšné.
pulls.required_status_check_missing=Některé požadované kontroly chybí.
pulls.required_status_check_administrator=Jako administrátor stále můžete sloučit tento požadavek na natažení.
pulls.required_status_check_administrator=Jako administrátor stále můžete sloučit tuto žádost.
pulls.blocked_by_approvals=Tato žádost o sloučení ještě nemá dostatek schválení. Uděleno %d z %d schválení.
pulls.blocked_by_rejection=Tato žádost o sloučení obsahuje změny požadované oficiálním posuzovatelem.
pulls.blocked_by_official_review_requests=Tato žádost o sloučení je zablokována, protože jí chybí schválení oficiálních posuzovatelů.
pulls.blocked_by_outdated_branch=Tato žádost o sloučení je zablokována, protože je zastaralá.
pulls.blocked_by_changed_protected_files_1=Tato žádost o sloučení je zablokována, protože mění chráněný soubor:
pulls.blocked_by_changed_protected_files_n=Tato žádost o sloučení je zablokována, protože mění chráněné soubory:
pulls.can_auto_merge_desc=Tento požadavek na natažení může být automaticky sloučen.
pulls.cannot_auto_merge_desc=Tento požadavek na natažení nemůže být automaticky sloučen, neboť se v něm nachází konflikty.
pulls.can_auto_merge_desc=Tato žádost může být automaticky sloučena.
pulls.cannot_auto_merge_desc=Tato žádost nemůže být automaticky sloučena, neboť se v ní nachází konflikty.
pulls.cannot_auto_merge_helper=Pro vyřešení konfliktů proveďte ruční sloučení.
pulls.num_conflicting_files_1=%d konfliktní soubor
pulls.num_conflicting_files_n=%d konfliktních souborů
@ -1823,11 +1825,11 @@ pulls.waiting_count_1=%d čekající posouzení
pulls.waiting_count_n=%d čekajících posouzení
pulls.wrong_commit_id=id commitu musí být id commitu v cílové větvi
pulls.no_merge_desc=Tento požadavek na natažení nemůže být sloučen, protože všechny možnosti repozitáře na sloučení jsou zakázány.
pulls.no_merge_helper=Povolte možnosti sloučení v nastavení repozitáře nebo proveďte sloučení požadavku na natažení ručně.
pulls.no_merge_wip=Požadavek na natažení nemůže být sloučen protože je označen jako nedokončený.
pulls.no_merge_not_ready=Tento požadavek na natažení není připraven na sloučení, zkontrolujte stav posouzení a kontrolu stavu.
pulls.no_merge_access=Nemáte oprávnění sloučit tento požadavek na natažení.
pulls.no_merge_desc=Tato žádost nemůže být sloučena, protože všechny možnosti repozitáře na sloučení jsou zakázány.
pulls.no_merge_helper=Povolte možnosti sloučení v nastavení repozitáře nebo proveďte sloučení žádosti ručně.
pulls.no_merge_wip=Tato žádost nemůže být sloučena, protože je označena jako rozpracovaná.
pulls.no_merge_not_ready=Tento žádost není připravena na sloučení, zkontrolujte stav posouzení a kontroly stavu.
pulls.no_merge_access=Nemáte oprávnění sloučit tuto žádost.
pulls.merge_pull_request=Vytvořit slučovací commit
pulls.rebase_merge_pull_request=Rebase pak fast-forward
pulls.rebase_merge_commit_pull_request=Rebase a poté vytvořit slučovací commit
@ -1836,7 +1838,7 @@ pulls.merge_manually=Sloučeno ručně
pulls.merge_commit_id=ID slučovacího commitu
pulls.require_signed_wont_sign=Větev vyžaduje podepsané commity, ale toto sloučení nebude podepsáno
pulls.invalid_merge_option=Nemůžete použít tuto možnost sloučení pro tento požadavek na natažení.
pulls.invalid_merge_option=Pro tuto žádost nemůžete použít tuto možnost sloučení.
pulls.merge_conflict=Sloučení selhalo: Došlo ke konfliktu při sloučení. Tip: Zkuste jinou strategii
pulls.merge_conflict_summary=Chybové hlášení
pulls.rebase_conflict=Sloučení selhalo: Došlo ke konfliktu při rebase commitu: %[1]s. Tip: Zkuste jinou strategii
@ -1844,7 +1846,7 @@ pulls.rebase_conflict_summary=Chybové hlášení
pulls.unrelated_histories=Sloučení selhalo: Hlavní a základní revize nesdílí společnou historii. Tip: Zkuste jinou strategii
pulls.merge_out_of_date=Sloučení selhalo: Základ byl aktualizován při generování sloučení. Tip: Zkuste to znovu.
pulls.head_out_of_date=Sloučení selhalo: Hlavní revize byla aktualizován při generování sloučení. Tip: Zkuste to znovu.
pulls.has_merged=Chyba: Požadavek na natažení byl sloučen, nelze znovu sloučit nebo změnit cílovou větev.
pulls.has_merged=Chyba: žádost byla sloučena, nelze ji znovu sloučit nebo změnit cílovou větev.
pulls.push_rejected=Push selhal: nahrání bylo zamítnuto. Zkontrolujte Git hooky pro tento repozitář.
pulls.push_rejected_summary=Úplná zpráva o odmítnutí
pulls.push_rejected_no_message=Push selhal: nahrání bylo odmítnuto, ale nebyla nalezena žádná vzdálená zpráva. Zkontrolujte Git hooky pro tento repozitář
@ -1897,28 +1899,28 @@ milestones.no_due_date=Bez lhůty dokončení
milestones.open=Otevřít
milestones.close=Zavřít
milestones.new_subheader=Milníky vám pomohou organizovat úkoly a sledovat jejich pokrok.
milestones.completeness=%d%% Dokončeno
milestones.completeness=Dokončeno <strong>%d%%</strong>
milestones.create=Vytvořit milník
milestones.title=Název
milestones.desc=Popis
milestones.due_date=Termín (volitelný)
milestones.clear=Zrušit
milestones.clear=Vymazat
milestones.invalid_due_date_format=Termín dokončení musí být ve formátu „rrrr-mm-dd“.
milestones.create_success=Milník „%s“ byl vytvořen.
milestones.edit=Upravit milník
milestones.edit_subheader=Milník organizuje úkoly a sledují pokrok.
milestones.edit_subheader=Milníky organizují problémy a sledují pokrok.
milestones.cancel=Zrušit
milestones.modify=Upravit milník
milestones.edit_success=Milník „%s“ byl aktualizován.
milestones.deletion=Odstranit milník
milestones.deletion_desc=Odstranění milníku jej smaže ze všech souvisejících úkolů. Pokračovat?
milestones.deletion_desc=Smazáním milníku jej odstraníte ze všech souvisejících problémů. Pokračovat?
milestones.deletion_success=Milník byl odstraněn.
milestones.filter_sort.earliest_due_data=Nejbližší termín dokončení
milestones.filter_sort.latest_due_date=Nejzazší termín dokončení
milestones.filter_sort.least_complete=Nejméně dokončené
milestones.filter_sort.most_complete=Nejvíce dokončené
milestones.filter_sort.most_issues=Nejvíce úkolů
milestones.filter_sort.least_issues=Nejméně úkolů
milestones.filter_sort.most_issues=Nejvíce problémů
milestones.filter_sort.least_issues=Nejméně problémů
signing.will_sign=Tento commit bude podepsána klíčem „%s“.
signing.wont_sign.error=Došlo k chybě při kontrole, zda může být commit podepsán.
@ -2002,7 +2004,7 @@ activity.new_issues_count_n=Nové problémy
activity.new_issue_label=Otevřený
activity.title.unresolved_conv_1=%d nevyřešená konverzace
activity.title.unresolved_conv_n=%d nevyřešených konverzací
activity.unresolved_conv_desc=Tyto nedávno změněné úkolu a požadavky na natažení ještě nebyly vyřešeny.
activity.unresolved_conv_desc=Tyto nedávno změněné problémy a žádosti o sloučení zatím nebyly vyřešeny.
activity.unresolved_conv_label=Otevřít
activity.title.releases_1=%d vydání
activity.title.releases_n=%d vydání
@ -2093,17 +2095,17 @@ settings.issues_desc=Povolit systém problémů repozitáře
settings.use_internal_issue_tracker=Použít vestavěný systém problémů
settings.use_external_issue_tracker=Použít externí systém problémů
settings.external_tracker_url=Adresa URL externího systému problémů
settings.external_tracker_url_error=URL externího systému úkolu není platné URL.
settings.external_tracker_url_desc=Když návštěvníci kliknou na záložku úkolů, jsou přesměrování na externí systém úkolů.
settings.external_tracker_url_error=Adresa URL externího systému problémů není platnou adresou URL.
settings.external_tracker_url_desc=Pokud návštěvníci kliknou na záložku problémů, budou přesměrování na externí systém problémů.
settings.tracker_url_format=Formát adresy URL externího systému problémů
settings.tracker_url_format_error=Formát URL externího systému úkolu není platné URL.
settings.tracker_url_format_error=Formát adresy URL externího systému problémů není platná adresa URL.
settings.tracker_issue_style=Formát čísel externího systému problémů
settings.tracker_issue_style.numeric=Číselný
settings.tracker_issue_style.alphanumeric=Alfanumerický
settings.tracker_issue_style.regexp=Regulární výraz
settings.tracker_issue_style.regexp_pattern=Vzor regulárního výrazu
settings.tracker_issue_style.regexp_pattern_desc=První zachycená skupina bude použita místo <code>{index}</code>.
settings.tracker_url_format_desc=Použijte zástupné symboly <code>{user}</code>, <code>{repo}</code> a <code>{index}</code> pro uživatelské jméno, jméno repozitáře a číslo úkolu.
settings.tracker_url_format_desc=Použijte proměnné <code>{user}</code>, <code>{repo}</code> a <code>{index}</code> pro uživatelské jméno, název repozitáře a číslo problému.
settings.enable_timetracker=Povolit sledování času
settings.allow_only_contributors_to_track_time=Povolit sledování času pouze přispěvatelům
settings.pulls_desc=Povolit žádosti o sloučení
@ -2124,7 +2126,7 @@ settings.admin_indexer_commit_sha=Poslední indexovaná SHA
settings.admin_indexer_unindexed=Neindexováno
settings.reindex_button=Přidat do fronty reindexace
settings.reindex_requested=Požadováno reindexování
settings.admin_enable_close_issues_via_commit_in_any_branch=Zavřít úkol pomocí commitu v jiné než výchozí větvi
settings.admin_enable_close_issues_via_commit_in_any_branch=Zavřít problém pomocí commitu v jiné než výchozí větvi
settings.danger_zone=Nebezpečná zóna
settings.new_owner_has_same_repo=Nový vlastník již repozitář se stejným názvem má. Vyberte prosím jiné jméno.
settings.convert=Převést na běžný repozitář
@ -2133,10 +2135,10 @@ settings.convert_notices_1=Tato operace převede toto zrcadlo na běžný repozi
settings.convert_confirm=Převést repozitář
settings.convert_succeed=Zrcadlo bylo převedeno na běžný repozitář.
settings.convert_fork=Převést na běžný repozitář
settings.convert_fork_desc=Můžete převést toto rozštěpení na běžný repozitář. Tuto akci nelze vrátit zpět.
settings.convert_fork_notices_1=Tato operace převede rozštěpení na běžný repozitář a nelze ji vrátit zpět.
settings.convert_fork_desc=Tento fork můžete převést na běžný repozitář. Tato akce je nevratná.
settings.convert_fork_notices_1=Tato operace převede fork na běžný repozitář a nelze ji vrátit zpět.
settings.convert_fork_confirm=Převést repozitář
settings.convert_fork_succeed=Rozštěpení bylo překonvertován na běžný repozitář.
settings.convert_fork_succeed=Fork bylo převeden na běžný repozitář.
settings.transfer=Předat vlastnictví
settings.transfer.rejected=Převod repozitáře byl zamítnut.
settings.transfer.success=Převod repozitáře byl úspěšný.
@ -2174,8 +2176,8 @@ settings.wiki_deletion_success=Wiki data repozitáře byla odstraněna.
settings.delete=Odstranit tento repozitář
settings.delete_desc=Smazání repozitáře je trvalé a nemůže být vráceno zpět.
settings.delete_notices_1=- Tuto operaci <strong>nelze</strong> zvrátit.
settings.delete_notices_2=- Tato operace trvale smaže repozitář <strong>%s</strong> včetně kódu, úkolů, komentářů, Wiki dat a nastavení spolupracovníků.
settings.delete_notices_fork_1=- Rozštěpení repozitáře bude nezávislé po smazání.
settings.delete_notices_2=- Tato operace trvale smaže repozitář <strong>%s</strong> včetně kódu, problémů, komentářů, dat wiki a nastavení spolupracovníků.
settings.delete_notices_fork_1=- Fork tohoto repozitáře bude po smazání nezávislý.
settings.deletion_success=Repozitář byl odstraněn.
settings.update_settings_success=Nastavení repozitáře bylo aktualizováno.
settings.update_settings_no_unit=Repozitář by měl povolit alespoň určitý druh interakce.
@ -2253,16 +2255,16 @@ settings.event_push_desc=Nahrání pomocí Gitu do repozitáře.
settings.event_repository=Repozitář
settings.event_repository_desc=Repozitář vytvořen nebo smazán.
settings.event_header_issue=Události problémů
settings.event_issues=Úkoly
settings.event_issues_desc=Úkol otevřen, uzavřen, znovu otevřen nebo upraven.
settings.event_issues=Problémy
settings.event_issues_desc=Problém otevřen, uzavřen, znovu otevřen nebo upraven.
settings.event_issue_assign=Problém přiřazen
settings.event_issue_assign_desc=Úkol přiřazen nebo nepřiřazen.
settings.event_issue_assign_desc=Problém přiřazen nebo nepřiřazen.
settings.event_issue_label=Problém označen
settings.event_issue_label_desc=Štítky úkolu aktualizovány nebo vymazány.
settings.event_issue_label_desc=Štítky problému upraveny nebo vymazány.
settings.event_issue_milestone=K problému přidán milník
settings.event_issue_milestone_desc=Úkolu přidán nebo odebrán milník.
settings.event_issue_milestone_desc=K problému přidán nebo odebrán milník.
settings.event_issue_comment=Komentář k problému
settings.event_issue_comment_desc=Komentář úkolu přidán, upraven nebo smazán.
settings.event_issue_comment_desc=Přidán, upraven nebo smazán komentář problému.
settings.event_header_pull_request=Události žádosti o sloučení
settings.event_pull_request=Žádost o sloučení
settings.event_pull_request_desc=Požadavek na natažení otevřen, uzavřen, znovu otevřen nebo upraven.
@ -2306,9 +2308,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Uživatelské jméno pro Packagist
@ -2426,7 +2428,7 @@ settings.archive.branchsettings_unavailable=Nastavení větví není dostupné,
settings.archive.tagsettings_unavailable=Nastavení značek není k dispozici, pokud je repozitář archivován.
settings.unarchive.button=Zrušit archivaci repozitáře
settings.unarchive.header=Obnovit tento repozitář
settings.unarchive.text=Obnovení repozitáře vrátí možnost přijímání commitů a nahrávání. Stejně tak se obnoví i možnost zadávání nových úkolů a požadavků na natažení.
settings.unarchive.text=Obnovení repozitáře vrátí možnost přijímání commitů a nahrávání. Stejně tak se obnoví i možnost vytváření nových problémů a žádostí o sloučení.
settings.unarchive.success=Repozitář byl úspěšně obnoven.
settings.unarchive.error=Nastala chyba při obnovování repozitáře. Prohlédněte si záznam pro více detailů.
settings.update_avatar_success=Avatar repozitáře byl aktualizován.
@ -2705,6 +2707,20 @@ pulls.title_desc_one = žádá o sloučení %[1]d commitu z <code>%[2]s</code> d
pulls.merged_title_desc_one = sloučil %[1]d commit z <code>%[2]s</code> do <code>%[3]s</code> %[4]s
open_with_editor = Otevřít pomocí %s
commits.search_branch = Tato větev
editor.commit_id_not_matching = ID commitu se neshoduje s ID commitu, který jste upravovali. Proveďte commit do nové větve a poté je slučte.
pulls.ready_for_review = Připraveni na posouzení?
settings.rename_branch_failed_protected = Nepodařilo se přejmenovat větev %s, jelikož se jedná o chráněnou větev.
editor.push_out_of_date = Push je nejspíše zastaralý.
stars = Oblíbení
n_commit_one = %s commit
n_commit_few = %s commitů
n_branch_one = %s větev
n_tag_one = %s značka
n_tag_few = %s značek
n_branch_few = %s větví
settings.event_pull_request_enforcement = Vynucení
settings.enforce_on_admins = Vynutit toto pravidlo pro správce repozitáře
settings.enforce_on_admins_desc = Správci repozitáře nemohou obejít toto pravidlo.
[graphs]
component_loading_info = Tohle může chvíli trvat…
@ -2770,7 +2786,7 @@ settings.delete_org_title=Odstranit organizaci
settings.delete_org_desc=Tato organizace bude trvale smazána. Pokračovat?
settings.hooks_desc=Přidat webové háčky, které budou spouštěny pro <strong>všechny repozitáře</strong> v této organizaci.
settings.labels_desc=Přidejte štítky, které mohou být použity pro úkoly <strong>všech repositářů</strong> v rámci této organizace.
settings.labels_desc=Přidejte štítky, které mohou být použity pro problémy <strong>všech repozitářů</strong> v rámci této organizace.
members.membership_visibility=Viditelnost členství:
members.public=Viditelný
@ -3028,7 +3044,7 @@ repos.private=Soukromý
repos.watches=Sledovače
repos.stars=Oblíbení
repos.forks=Rozštěpení
repos.issues=Úkoly
repos.issues=Problémy
repos.size=Velikost
repos.lfs_size=Velikost LFS
@ -3153,7 +3169,7 @@ auths.tip.google_plus=Získejte klientské pověření OAuth2 z Google API konzo
auths.tip.openid_connect=Použijte OpenID URL pro objevování spojení (<server>/.well-known/openid-configuration) k nastavení koncových bodů
auths.tip.twitter=Jděte na https://dev.twitter.com/apps, vytvořte aplikaci a ujistěte se, že volba „Allow this application to be used to Sign in with Twitter“ je povolená
auths.tip.discord=Registrujte novou aplikaci na https://discordapp.com/developers/applications/me
auths.tip.gitea=Registrovat novou Oauth2 aplikaci. Návod naleznete na https://docs.gitea.com/development/oauth2-provider
auths.tip.gitea=Registrovat novou Oauth2 aplikaci. Návod naleznete na https://forgejo.org/docs/latest/user/oauth2-provider
auths.tip.yandex=Vytvořte novou aplikaci na https://oauth.yandex.com/client/new. Vyberte následující oprávnění z „Yandex.Passport API“ sekce: „Přístup k e-mailové adrese“, „Přístup k uživatelskému avataru“ a „Přístup k uživatelskému jménu, jménu a příjmení, pohlaví“
auths.tip.mastodon=Vložte vlastní URL instance pro mastodon, kterou se chcete autentizovat (nebo použijte výchozí)
auths.edit=Upravit zdroj ověřování
@ -3188,7 +3204,7 @@ config.repo_root_path=Kořenový adresář repozitářů
config.lfs_root_path=Kořenový adresář LFS
config.log_file_root_path=Adresář protokolů
config.script_type=Typ skriptu
config.reverse_auth_user=Obrátit uživatele ověření
config.reverse_auth_user=Obrátit uživatele ověření proxy
config.ssh_config=Nastavení SSH
config.ssh_enabled=Zapnutý
@ -3237,7 +3253,7 @@ config.allow_dots_in_usernames = Povolit uživatelům používat tečky ve svýc
config.default_allow_only_contributors_to_track_time=Povolit sledování času pouze přispěvatelům
config.no_reply_address=Skrytá e-mailová doména
config.default_visibility_organization=Výchozí viditelnost nových organizací
config.default_enable_dependencies=Povolit ve výchozím nastavení závislosti úkolů
config.default_enable_dependencies=Povolit ve výchozím nastavení závislosti problémů
config.webhook_config=Nastavení webhooků
config.queue_length=Délka fronty
@ -3391,14 +3407,15 @@ auths.tips.gmail_settings = Nastavení služby Gmail:
config_summary = Souhrn
config.open_with_editor_app_help = Editory v nabídce „Otevřít pomocí“ v nabídce klonování. Ponechte prázdné pro použití výchozího editoru (zobrazíte jej rozšířením).
config_settings = Nastavení
auths.tip.gitlab_new = Zaregistrujte si novou aplikaci na https://gitlab.com/-/profile/applications
[action]
create_repo=vytvořil/a repozitář <a href="%s">%s</a>
rename_repo=přejmenoval/a repozitář z <code>%[1]s</code> na <a href="%[2]s">%[3]s</a>
commit_repo=nahrál/a do <a href="%[2]s">%[3]s</a> v <a href="%[1]s">%[4]s</a>
create_issue=`otevřel/a úkol <a href="%[1]s">%[3]s#%[2]s</a>`
close_issue=`uzavřel/a úkol <a href="%[1]s">%[3]s#%[2]s</a>`
reopen_issue=`znovuotevřel/a úkol <a href="%[1]s">%[3]s#%[2]s</a>`
create_issue=`otevřel/a problém <a href="%[1]s">%[3]s#%[2]s</a>`
close_issue=`uzavřel/a problém <a href="%[1]s">%[3]s#%[2]s</a>`
reopen_issue=`znovu otevřel/a problém <a href="%[1]s">%[3]s#%[2]s</a>`
create_pull_request=`vytvořil/a požadavek na natažení <a href="%[1]s">%[3]s#%[2]s</a>`
close_pull_request=`uzavřel/a požadavek na natažení <a href="%[1]s">%[3]s#%[2]s</a>`
reopen_pull_request=`znovuotevřel/a požadavek na natažení <a href="%[1]s">%[3]s#%[2]s</a>`
@ -3745,6 +3762,7 @@ runners = Runnery
runs.pushed_by = pushnuto uživatelem
need_approval_desc = Potřebovat schválení pro spouštění workflowů pro žádosti o sloučení forků.
runners.runner_manage_panel = Správa runnerů
runs.no_job_without_needs = Workflow musí obsahovat alespoň jednu práci bez závislostí.
[projects]
type-1.display_name=Samostatný projekt
@ -3781,4 +3799,19 @@ runner_kind = Hledat runnery...
no_results = Nenalezeny žádné odpovídající výsledky.
fuzzy_tooltip = Zahrnout také výsledky, které úzce odpovídají hledanému výrazu
search = Hledat...
keyword_search_unavailable = Hledání pomocí klíčových slov momentálně není dostupné. Kontaktujte prosím administrátora webu.
keyword_search_unavailable = Hledání pomocí klíčových slov momentálně není dostupné. Kontaktujte prosím administrátora webu.
code_search_by_git_grep = Aktuální výsledky vyhledávání kódu jsou poskytovány službou „git grep“. Lepší výsledky dostanete, když administrátor webu povolí indexování repozitářů.
[markup]
filepreview.lines = Řádky %[1]d až %[2]d v souboru %[3]s
filepreview.line = Řádek %[1]d v souboru %[2]s
filepreview.truncated = Náhled byl zkrácen
[munits.data]
b = B
kib = KiB
mib = MiB
gib = GiB
tib = TiB
pib = PiB
eib = EiB

View file

@ -155,6 +155,8 @@ filter.is_template = Vorlage
filter.not_template = Keine Vorlage
filter.public = Öffentlich
filter.private = Privat
more_items = Mehr Einträge
invalid_data = Ungültige Daten: %v
[aria]
navbar=Navigationsleiste
@ -979,10 +981,10 @@ user_unblock_success = Die Blockierung dieses Benutzers wurde erfolgreich zurüc
blocked_users = Blockierte Benutzer
blocked_since = Blockiert seit %s
change_password = Passwort ändern
hints = Tipps
hints = Hinweise
additional_repo_units_hint = Zur Aktivierung zusätzlicher Repository-Einheiten ermutigen
update_hints = Tipps aktualisieren
update_hints_success = Tipps wurden aktualisiert.
update_hints = Hinweise aktualisieren
update_hints_success = Hinweise wurden aktualisiert.
additional_repo_units_hint_description = Einen „Mehr Einheiten hinzufügen …“-Button für Repositorys, welche nicht alle verfügbaren Einheiten aktiviert haben, anzeigen.
[repo]
@ -1393,7 +1395,7 @@ projects.column.set_default_desc=Diese Spalte als Standard für nicht kategorisi
projects.column.unset_default=Standard entfernen
projects.column.unset_default_desc=Diese Spalte nicht als Standard verwenden
projects.column.delete=Spalte löschen
projects.column.deletion_desc=Beim Löschen einer Projektspalte werden alle dazugehörigen Issues nach „Nicht kategorisiert“ verschoben. Fortfahren?
projects.column.deletion_desc=Beim Löschen einer Projektspalte werden alle dazugehörigen Issues zur Standardspalte verschoben. Fortfahren?
projects.column.color=Farbe
projects.open=Öffnen
projects.close=Schließen
@ -1411,7 +1413,7 @@ issues.filter_reviewers=Reviewer filtern
issues.new=Neues Issue
issues.new.title_empty=Der Titel kann nicht leer sein
issues.new.labels=Labels
issues.new.no_label=Kein Label
issues.new.no_label=Keine Label
issues.new.clear_labels=Labels entfernen
issues.new.projects=Projekte
issues.new.clear_projects=Projekte löschen
@ -1596,7 +1598,7 @@ issues.label.filter_sort.alphabetically=Alphabetisch
issues.label.filter_sort.reverse_alphabetically=Umgekehrt alphabetisch
issues.label.filter_sort.by_size=Kleinste Größe
issues.label.filter_sort.reverse_by_size=Größte Größe
issues.num_participants=%d Beteiligte
issues.num_participants_few=%d Beteiligte
issues.attachment.open_tab=`Klicken, um „%s“ in einem neuen Tab zu öffnen`
issues.attachment.download=`Klicken, um „%s“ herunterzuladen`
issues.subscribe=Abonnieren
@ -2302,9 +2304,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Benutzername für Packagist
@ -2690,6 +2692,16 @@ pulls.title_desc_one = möchte %[1]d Commit von <code>%[2]s</code> nach <code id
open_with_editor = Öffnen mit %s
commits.search_branch = Dieser Branch
pulls.ready_for_review = Bereit zum Review?
settings.rename_branch_failed_protected = Branch %s kann nicht umbenannt werden, weil er ein geschützter Branch ist.
editor.commit_id_not_matching = Die Commit-ID passt nicht zur ID die du bearbeitet hast hast. Committe in einen neuen Branch, dann mach einen Merge.
editor.push_out_of_date = Der Push scheint veraltet zu sein.
n_commit_few = %s Commits
n_branch_one = %s Branch
n_branch_few = %s Branches
n_tag_one = %s Tag
n_tag_few = %s Tags
stars = Favorisierungen
n_commit_one = %s Commit
[graphs]
@ -3134,7 +3146,7 @@ auths.tip.google_plus=Du erhältst die OAuth2-Client-Zugangsdaten in der Google-
auths.tip.openid_connect=Benutze die OpenID-Connect-Discovery-URL (<server>/.well-known/openid-configuration), um die Endpunkte zu spezifizieren
auths.tip.twitter=Gehe auf https://dev.twitter.com/apps, erstelle eine Anwendung und stelle sicher, dass die Option „Allow this application to be used to Sign in with Twitter“ aktiviert ist
auths.tip.discord=Erstelle unter https://discordapp.com/developers/applications/me eine neue Anwendung.
auths.tip.gitea=Registriere eine neue OAuth2-Anwendung. Eine Anleitung findest du unter https://docs.gitea.com/development/oauth2-provider/
auths.tip.gitea=Registriere eine neue OAuth2-Anwendung. Eine Anleitung findest du unter https://forgejo.org/docs/latest/user/oauth2-provider
auths.tip.yandex=`Erstelle eine neue Anwendung auf https://oauth.yandex.com/client/new. Wähle folgende Berechtigungen aus dem Abschnitt „Yandex.Passport API“: „Zugriff auf E-Mail-Adresse“, „Zugriff auf Benutzeravatar“ und „Zugriff auf Benutzername, Vor- und Nachname, Geschlecht“`
auths.tip.mastodon=Gib eine benutzerdefinierte URL für die Mastodon-Instanz ein, mit der du dich authentifizieren möchtest (oder benutze die standardmäßige)
auths.edit=Authentifikationsquelle bearbeiten
@ -3169,7 +3181,7 @@ config.repo_root_path=Repository-Wurzelpfad
config.lfs_root_path=LFS-Wurzelpfad
config.log_file_root_path=Logdateipfad
config.script_type=Skript-Typ
config.reverse_auth_user=Nutzer bei Reverse-Authentifizierung
config.reverse_auth_user=Nutzer bei Reverse-Proxy-Authentifizierung
config.ssh_config=SSH-Konfiguration
config.ssh_enabled=Aktiviert
@ -3358,6 +3370,7 @@ auths.tips.gmail_settings = Gmail-Einstellungen:
config_settings = Einstellungen
config.open_with_editor_app_help = Die „Öffnen mit“-Editoren für das Klonmenü. Falls es leer gelassen wird, wird der Standardwert benutzt. Erweitern, um den Standardwert zu sehen.
config_summary = Zusammenfassung
auths.tip.gitlab_new = Registriere eine neue Anwendung auf https://gitlab.com/-/profile/applications
[action]
@ -3711,6 +3724,7 @@ runs.no_workflows.documentation = Für weitere Informationen über Forgejo Actio
runs.empty_commit_message = (leere Commit-Nachricht)
variables.id_not_exist = Variable mit ID %d existiert nicht.
runs.workflow = Workflow
runs.no_job_without_needs = Der Workflow muss mindestens einen Job ohne Abhängigkeiten enthalten.
[projects]
type-1.display_name=Individuelles Projekt
@ -3757,4 +3771,10 @@ commit_kind = Commits suchen …
runner_kind = Runners suchen …
no_results = Keine passenden Ergebnisse gefunden.
code_search_unavailable = Die Code-Suche ist momentan nicht verfügbar. Bitte kontaktiere den Webseitenadministrator.
keyword_search_unavailable = Suche nach Schlüsselwörtern ist momentan nicht unterstüzt. Bitte kontaktiere den Webseitenadministrator.
keyword_search_unavailable = Suche nach Schlüsselwörtern ist momentan nicht unterstüzt. Bitte kontaktiere den Webseitenadministrator.
code_search_by_git_grep = Die derzeitigen Codesuchergebnisse werden durch „git grep“ bereitgestellt. Es könnten bessere Ergebnisse erzielt werden, wenn der Administrator die Repository-Indizierung aktiviert.
[markup]
filepreview.line = Zeile %[1]d in %[2]s
filepreview.truncated = Vorschau wurde gekürzt
filepreview.lines = Zeilen %[1]d bis %[2]d in %[3]s

View file

@ -1580,7 +1580,7 @@ issues.label.filter_sort.alphabetically=Αλφαβητικά
issues.label.filter_sort.reverse_alphabetically=Αντίστροφα αλφαβητικά
issues.label.filter_sort.by_size=Μικρότερο μέγεθος
issues.label.filter_sort.reverse_by_size=Μεγαλύτερο μέγεθος
issues.num_participants=%d Συμμετέχοντες
issues.num_participants_few=%d Συμμετέχοντες
issues.attachment.open_tab=`Πατήστε εδώ για να ανοίξετε το «%s» σε μια νέα καρτέλα`
issues.attachment.download=`Πατήστε εδώ για να κατεβάσετε το «%s»`
issues.subscribe=Εγγραφή
@ -2293,9 +2293,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Όνομα χρήστη Packagist

View file

@ -146,15 +146,15 @@ name = Name
value = Value
filter = Filter
filter.clear = Clear Filter
filter.clear = Clear filters
filter.is_archived = Archived
filter.not_archived = Not Archived
filter.not_archived = Not archived
filter.is_fork = Forked
filter.not_fork = Not Forked
filter.not_fork = Not forked
filter.is_mirror = Mirrored
filter.not_mirror = Not Mirrored
filter.not_mirror = Not mirrored
filter.is_template = Template
filter.not_template = Not Template
filter.not_template = Not template
filter.public = Public
filter.private = Private
@ -222,7 +222,7 @@ missing_csrf = Bad Request: no CSRF token present
invalid_csrf = Bad Request: invalid CSRF token
not_found = The target couldn't be found.
network_error = Network error
server_internal = Internal Server Error
server_internal = Internal server error
[startpage]
app_desc = A painless, self-hosted Git service
@ -762,7 +762,6 @@ password_incorrect = The current password is incorrect.
change_password_success = Your password has been updated. Sign in using your new password from now on.
password_change_disabled = Non-local users cannot update their password through the Forgejo web interface.
emails = Email addresses
manage_emails = Manage email addresses
manage_themes = Select default theme
manage_openid = Manage OpenID addresses
@ -893,9 +892,9 @@ repo_and_org_access = Repository and Organization Access
permissions_public_only = Public only
permissions_access_all = All (public, private, and limited)
select_permissions = Select permissions
permission_no_access = No Access
permission_no_access = No access
permission_read = Read
permission_write = Read and Write
permission_write = Read and write
access_token_desc = Selected token permissions limit authorization only to the corresponding <a %s>API</a> routes. Read the <a %s>documentation</a> for more information.
at_least_one_permission = You must select at least one permission to create a token
permissions_list = Permissions:
@ -915,8 +914,8 @@ oauth2_confidential_client = Confidential client. Select for apps that keep the
oauth2_redirect_uris = Redirect URIs. Please use a new line for every URI.
save_application = Save
oauth2_client_id = Client ID
oauth2_client_secret = Client Secret
oauth2_regenerate_secret = Regenerate Secret
oauth2_client_secret = Client secret
oauth2_regenerate_secret = Regenerate secret
oauth2_regenerate_secret_hint = Lost your secret?
oauth2_client_secret_hint = The secret will not be shown again after you leave or refresh this page. Please ensure that you have saved it.
oauth2_application_edit = Edit
@ -927,7 +926,7 @@ oauth2_application_locked = Forgejo pre-registers some OAuth2 applications on st
authorized_oauth2_applications = Authorized OAuth2 applications
authorized_oauth2_applications_description = You have granted access to your personal Forgejo account to these third party applications. Please revoke access for applications that are no longer in use.
revoke_key = Revoke
revoke_oauth2_grant = Revoke Access
revoke_oauth2_grant = Revoke access
revoke_oauth2_grant_description = Revoking access for this third party application will prevent this application from accessing your data. Are you sure?
revoke_oauth2_grant_success = Access revoked successfully.
@ -1012,6 +1011,7 @@ owner_helper = Some organizations may not show up in the dropdown due to a maxim
repo_name = Repository name
repo_name_helper = Good repository names use short, memorable and unique keywords.
repo_size = Repository Size
size_format = %[1]s: %[2]s, %[3]s: %[4]s
template = Template
template_select = Select a template.
template_helper = Make repository a template
@ -1143,7 +1143,6 @@ form.name_pattern_not_allowed = The pattern "%s" is not allowed in a repository
need_auth = Authorization
migrate_options = Migration options
migrate_service = Migration service
migrate_options_mirror_helper = This repository will be a mirror
migrate_options_lfs = Migrate LFS files
migrate_options_lfs_endpoint.label = LFS endpoint
@ -1254,11 +1253,11 @@ n_tag_few=%s tags
released_this = released this
file.title = %s at %s
file_raw = Raw
file_follow = Follow Symlink
file_follow = Follow symlink
file_history = History
file_view_source = View Source
file_view_rendered = View Rendered
file_view_raw = View Raw
file_view_source = View source
file_view_rendered = View rendered
file_view_raw = View raw
file_permalink = Permalink
file_too_large = The file is too large to be shown.
invisible_runes_header = `This file contains invisible Unicode characters`
@ -1313,8 +1312,8 @@ editor.name_your_file = Name your file…
editor.filename_help = Add a directory by typing its name followed by a slash ("/"). Remove a directory by typing backspace at the beginning of the input field.
editor.or = or
editor.cancel_lower = Cancel
editor.commit_signed_changes = Commit Signed Changes
editor.commit_changes = Commit Changes
editor.commit_signed_changes = Commit signed changes
editor.commit_changes = Commit changes
editor.add_tmpl = Add "<filename>"
editor.add = Add %s
editor.update = Update %s
@ -1342,9 +1341,9 @@ editor.file_is_a_symlink = `"%s" is a symbolic link. Symbolic links cannot be ed
editor.filename_is_a_directory = Filename "%s" is already used as a directory name in this repository.
editor.file_editing_no_longer_exists = The file being edited, "%s", no longer exists in this repository.
editor.file_deleting_no_longer_exists = The file being deleted, "%s", no longer exists in this repository.
editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit Changes again</strong> to overwrite them.
editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit changes again</strong> to overwrite them.
editor.file_already_exists = A file named "%s" already exists in this repository.
editor.commit_id_not_matching = The Commit ID does not match the ID when you began editing. Commit into a patch branch and then merge.
editor.commit_id_not_matching = The commit ID does not match the one you was editing. Commit to a new branch and then merge.
editor.push_out_of_date = The push appears to be out of date.
editor.commit_empty_file_header = Commit an empty file
editor.commit_empty_file_text = The file you're about to commit is empty. Proceed?
@ -1419,26 +1418,26 @@ projects.edit_subheader = Projects organize issues and track progress.
projects.modify = Edit project
projects.edit_success = Project "%s" has been updated.
projects.type.none = None
projects.type.basic_kanban = Basic Kanban
projects.type.bug_triage = Bug Triage
projects.type.basic_kanban = Basic kanban
projects.type.bug_triage = Bug triage
projects.template.desc = Template
projects.template.desc_helper = Select a project template to get started
projects.column.edit = Edit Column
projects.column.edit = Edit column
projects.column.edit_title = Name
projects.column.new_title = Name
projects.column.new_submit = Create Column
projects.column.new = New Column
projects.column.set_default = Set Default
projects.column.new_submit = Create column
projects.column.new = New column
projects.column.set_default = Set default
projects.column.set_default_desc = Set this column as default for uncategorized issues and pulls
projects.column.delete = Delete Column
projects.column.delete = Delete column
projects.column.deletion_desc = Deleting a project column moves all related issues to the default column. Continue?
projects.column.color = Color
projects.open = Open
projects.close = Close
projects.column.assigned_to = Assigned to
projects.card_type.desc = Card Previews
projects.card_type.images_and_text = Images and Text
projects.card_type.text_only = Text Only
projects.card_type.desc = Card previews
projects.card_type.images_and_text = Images and text
projects.card_type.text_only = Text only
issues.desc = Organize bug reports, tasks and milestones.
issues.filter_assignees = Filter Assignee
@ -1449,7 +1448,7 @@ issues.filter_reviewers = Filter Reviewer
issues.new = New issue
issues.new.title_empty = Title cannot be empty
issues.new.labels = Labels
issues.new.no_label = No label
issues.new.no_label = No labels
issues.new.clear_labels = Clear labels
issues.new.projects = Projects
issues.new.clear_projects = Clear projects
@ -1606,7 +1605,7 @@ issues.re_request_review=Re-request review
issues.is_stale = There have been changes to this PR since this review
issues.remove_request_review=Remove review request
issues.remove_request_review_block=Can't remove review request
issues.dismiss_review = Dismiss Review
issues.dismiss_review = Dismiss review
issues.dismiss_review_warning = Are you sure you want to dismiss this review?
issues.sign_in_require_desc = <a href="%s">Sign in</a> to join this conversation.
issues.edit = Edit
@ -1633,7 +1632,8 @@ issues.label.filter_sort.alphabetically = Alphabetically
issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically
issues.label.filter_sort.by_size = Smallest size
issues.label.filter_sort.reverse_by_size = Largest size
issues.num_participants = %d participants
issues.num_participants_one = %d participant
issues.num_participants_few = %d participants
issues.attachment.open_tab = `Click to see "%s" in a new tab`
issues.attachment.download = `Click to download "%s"`
issues.subscribe = Subscribe
@ -1980,7 +1980,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 = Access to external Wiki
ext_wiki.desc = Link to an external wiki.
wiki = Wiki
@ -2345,6 +2345,7 @@ settings.event_pull_request_review_request = Pull request review requested
settings.event_pull_request_review_request_desc = Pull request review requested or review request removed.
settings.event_pull_request_approvals = Pull request approvals
settings.event_pull_request_merge = Pull request merge
settings.event_pull_request_enforcement = Enforcement
settings.event_package = Package
settings.event_package_desc = Package created or deleted in a repository.
settings.branch_filter = Branch filter
@ -2372,9 +2373,9 @@ settings.web_hook_name_dingtalk = DingTalk
settings.web_hook_name_telegram = Telegram
settings.web_hook_name_matrix = Matrix
settings.web_hook_name_msteams = Microsoft Teams
settings.web_hook_name_feishu_or_larksuite = Feishu / Lark Suite
settings.web_hook_name_feishu = Feishu
settings.web_hook_name_larksuite = Lark Suite
settings.web_hook_name_feishu = Feishu / Lark Suite
settings.web_hook_name_feishu_only = Feishu
settings.web_hook_name_larksuite_only = Lark Suite
settings.web_hook_name_wechatwork = WeCom (Wechat Work)
settings.web_hook_name_packagist = Packagist
settings.packagist_username = Packagist username
@ -2459,6 +2460,8 @@ settings.block_on_official_review_requests = Block merge on official review requ
settings.block_on_official_review_requests_desc = Merging will not be possible when it has official review requests, even if there are enough approvals.
settings.block_outdated_branch = Block merge if pull request is outdated
settings.block_outdated_branch_desc = Merging will not be possible when head branch is behind base branch.
settings.enforce_on_admins = Enforce this rule for repository admins
settings.enforce_on_admins_desc = Repository admins cannot bypass this rule.
settings.default_branch_desc = Select a default repository branch for pull requests and code commits:
settings.merge_style_desc = Merge styles
settings.default_merge_style_desc = Default merge style
@ -3180,7 +3183,7 @@ config.repo_root_path = Repository root path
config.lfs_root_path = LFS root path
config.log_file_root_path = Log path
config.script_type = Script type
config.reverse_auth_user = Reverse authentication user
config.reverse_auth_user = Reverse proxy authentication user
config.ssh_config = SSH configuration
config.ssh_enabled = Enabled
@ -3416,6 +3419,15 @@ years = %d years
raw_seconds = seconds
raw_minutes = minutes
[munits.data]
b = B
kib = KiB
mib = MiB
gib = GiB
tib = TiB
pib = PiB
eib = EiB
[dropzone]
default_message = Drop files or click here to upload.
invalid_input_type = You cannot upload files of this type.
@ -3727,3 +3739,8 @@ normal_file = Normal file
executable_file = Executable file
symbolic_link = Symbolic link
submodule = Submodule
[markup]
filepreview.line = Line %[1]d in %[2]s
filepreview.lines = Lines %[1]d to %[2]d in %[3]s
filepreview.truncated = Preview has been truncated

View file

@ -1561,7 +1561,7 @@ issues.label.filter_sort.alphabetically=Alfabéticamente
issues.label.filter_sort.reverse_alphabetically=Invertir alfabéticamente
issues.label.filter_sort.by_size=Tamaño más pequeño
issues.label.filter_sort.reverse_by_size=Tamaño más grande
issues.num_participants=%d participantes
issues.num_participants_few=%d participantes
issues.attachment.open_tab='Haga clic para ver "%s" en una pestaña nueva'
issues.attachment.download=`Haga clic para descargar "%s"`
issues.subscribe=Suscribir
@ -2267,9 +2267,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nombre de usuario Packagist

View file

@ -1189,7 +1189,7 @@ issues.label.filter_sort.alphabetically=الفبایی
issues.label.filter_sort.reverse_alphabetically=برعکس ترتیب الفبا
issues.label.filter_sort.by_size=کوچکترین اندازه
issues.label.filter_sort.reverse_by_size=بزرگترین اندازه
issues.num_participants=%d مشارکت کننده
issues.num_participants_few=%d مشارکت کننده
issues.attachment.open_tab=برای مشاهده "%s" در زبانه جدید، کلیک کنید
issues.attachment.download=`برای دریافت "%s" کلیک کنید`
issues.subscribe=مشترک شدن

View file

@ -920,7 +920,7 @@ issues.label.filter_sort.alphabetically=Aakkosjärjestyksessä
issues.label.filter_sort.reverse_alphabetically=Käänteisessä aakkosjärjestyksessä
issues.label.filter_sort.by_size=Pienin koko
issues.label.filter_sort.reverse_by_size=Suurin koko
issues.num_participants=%d osallistujaa
issues.num_participants_few=%d osallistujaa
issues.subscribe=Tilaa
issues.unsubscribe=Lopeta tilaus
issues.lock=Lukitse keskustelu
@ -1184,8 +1184,8 @@ settings.web_hook_name_discord=Discord
settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_packagist=Packagist
settings.deploy_keys=Julkaisuavaimet
settings.add_deploy_key=Lisää julkaisuavain

View file

@ -7,7 +7,7 @@ language = Wika
mirrors = Mga Mirror
forks = Mga Fork
activities = Mga Aktibidad
pull_requests = Mga Pull Request
pull_requests = Mga pull pequest
issues = Mga Isyu
milestones = Mga Milestone
ok = OK
@ -18,10 +18,10 @@ save = I-save
add = Magdagdag
remove_all = Tanggalin lahat
remove_label_str = Tanggalin ang item "%s"
edit = I-edit
edit = Baguhin
enabled = Naka-enable
copy = Kopyahin
copy_content = Kopyahin ang content
copy_content = Kopyahin ang nilalaman
copy_branch = Kopyahin ang pangalan ng branch
copy_success = Kinopya!
copy_error = Nabigo ang pagkopya
@ -35,11 +35,11 @@ copy_type_unsupported = Hindi makokopya ang itong uri ng file
error404 = Ang pahina na sinusubukan mong bisitahin ay alinman <strong>hindi umiiral</strong> o <strong>wala kang pahintulot</strong> para itignan.
version = Bersyon
powered_by = Pinapatakbo ng %s
explore = Mag-explore
explore = Tuklasin
help = Tulong
logo = Logo
sign_in = Mag-Sign In
sign_in_with_provider = Mag-sign in gamit ng %s
sign_in_with_provider = Mag-sign in gamit ang %s
sign_in_or = o
sign_out = Mag-Sign Out
sign_up = Magrehistro
@ -79,19 +79,19 @@ webauthn_error_empty = Kailangan mong maglapat ng pangalan para sa key na ito.
webauthn_reload = I-reload
repository = Repository
organization = Organisasyon
mirror = Mirror
mirror = Salamin
new_repo = Bagong repository
new_migrate = Bagong migration
new_mirror = Bagong mirror
new_mirror = Bagong salamin
new_fork = Bagong repository fork
new_org = Bagong organisasyon
new_project = Bagong proyekto
new_project_column = Bagong column
admin_panel = Pangangasiwa ng Site
account_settings = Mga Setting ng Account
admin_panel = Pangangasiwa ng site
account_settings = Mga setting ng Account
settings = Mga Setting
your_profile = Profile
your_starred = Naka-star
your_starred = Naka-bitwin
your_settings = Mga Setting
all = Lahat
go_back = Bumalik
@ -126,10 +126,10 @@ filter.public = Publiko
filter.private = Pribado
notifications = Mga Abiso
active_stopwatch = Aktibong Tagasubaybay ng Oras
locked = Naka-lock
locked = Naka-kandado
preview = I-preview
confirm_delete_artifact = Sigurado ka bang gusto mong burahin ang artifact na "%s"?
rerun_all = Patakbuhin muli ang lahat ng mga job
rerun_all = Patakbuhin muli ang lahat ng mga trabaho
add_all = Idagdag lahat
copy_hash = Kopyahin ang hash
error = Error
@ -138,6 +138,8 @@ loading = Naglo-load…
confirm_delete_selected = Kumpirmahin na burahin ang lahat ng piniling item?
home = Panimula
dashboard = Dashboard
more_items = Higit pang mga item
invalid_data = Hindi wastong data: %v
[home]
search_repos = Maghanap ng Repository…
@ -229,7 +231,7 @@ err_empty_admin_email = Hindi maaring walang laman ang administrator email.
err_admin_name_is_reserved = Hindi angkop ang Administrator Username, naka-reserve ang username
err_admin_name_is_invalid = Hindi angkop ang Administrator Username
general_title = Mga General Setting
app_name = Pangalan ng Instansya
app_name = Pamagat ng instansya
app_name_helper = Maari mong ilagay ang pangalan ng iyong kompanya dito.
repo_path_helper = Ang mga remote Git repository ay mase-save sa directory na ito.
repo_path = Root path ng Repository
@ -288,7 +290,7 @@ invalid_db_setting = Hindi angkop ang mga database setting: %v
invalid_db_table = Hindi angkop ang database table na "%s": %v
invalid_repo_path = Hindi angkop ang repository root path: %v
invalid_app_data_path = Hindi angkop ang app data path: %v
run_user_not_match = Ang "tumakbo bilang" na username ay hindi ang kasulukuyang username: %s -> %s
run_user_not_match = Ang "user na tatakbo bilang" na username ay hindi ang kasulukuyang username: %s -> %s
internal_token_failed = Nabigong maka-generate ng internal token: %v
secret_key_failed = Nabigong maka-generate ng secret key: %v
save_config_failed = Nabigong i-save ang configuration: %v
@ -352,7 +354,7 @@ app_desc = Isang hindi masakit, at naka self-host na Git service
install = Madaling i-install
platform = Cross-platform
platform_desc = Tumatakbo kahit saan ang Forgejo na ang <a target="_blank" rel="noopener noreferrer" href="https://go.dev/">Go</a> ay nakaka-compile para sa: Windows, macOS, Linux, ARM, atbp. Piliin ang isa na gusto mo!
lightweight = Hindi Mabigat
lightweight = Magaan
lightweight_desc = Mababa ang minimal requirements ng Forgejo at tatakbo sa isang murang Raspberry Pi. Tipirin ang enerhiya ng iyong machine!
license = Open Source
install_desc = <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download/#installation-from-binary">Patakbuhin ang binary</a> para sa iyong platform, i-ship gamit ang <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download/#container-image">Docker</a>, o kunin ito nang <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download">naka-package</a>.
@ -921,6 +923,11 @@ oauth2_application_create_description = Ang mga OAuth2 application ay pinapayaga
oauth2_application_locked = Ang Forgejo ay pini-pre register ang ibang mga OAuth2 application sa startup kapag naka-enable sa config. Para iwasan ang hindi inaasahang gawain, hindi ito maaring i-edit o tanggalin. Mangyaring sumangguni sa dokumentasyon ng OAuth2 para sa karagdagang impormasyon.
remove_account_link_desc = Ang pagtanggal ng naka-link na account ay babawiin ang pag-access nito sa iyong Forgejo account. Magpatuloy?
visibility.public_tooltip = Makikita ng lahat
hints = Mga Pahiwatig
additional_repo_units_hint_description = Mag-display ng "Magdagdag pa ng mga unit..." na button para sa mga repository na hindi naka-enable ang lahat ng mga available na unit.
additional_repo_units_hint = Hikayatin ang pag-enable ng karagdagang mga repository unit
update_hints = I-update ang mga pahiwatig
update_hints_success = Na-update na ang mga pahiwatig.
[repo]
template_description = Ang mga template repository ay pinapayagan ang mga gumagamit na mag-generate ng mga bagong repository na may magkatulad na istraktura ng direktoryo, mga file, at opsyonal na mga setting.
@ -949,4 +956,104 @@ fork_repo = I-fork ang repository
fork_from = I-fork mula sa
already_forked = Na-fork mo na ang %s
fork_to_different_account = Mag-fork sa ibang account
fork_visibility_helper = Ang visibility ng isang naka-fork na repository ay hindi maaring baguhin.
fork_visibility_helper = Ang visibility ng isang naka-fork na repository ay hindi maaring baguhin.
open_with_editor = Buksan gamit ang %s
download_bundle = I-download ang BUNDLE
repo_gitignore_helper_desc = Piliin kung anong mga file na hindi susubaybayin sa listahan ng mga template para sa mga karaniwang wika. Ang mga tipikal na artifact na ginagawa ng mga build tool ng wika ay kasama sa .gitignore ng default.
adopt_preexisting = Mag-adopt ng mga umiiral na file
repo_gitignore_helper = Pumili ng mga .gitignore template.
readme_helper_desc = Ito ang lugar kung saan makakasulat ka ng kumpletong deskripsyon para sa iyong proyekto.
trust_model_helper_collaborator_committer = Katulong+Committer: I-trust ang mga signature batay sa mga katulong na tumutugma sa committer
mirror_interval = Interval ng mirror (ang mga wastong unit ng oras ay "h", "m", "s"). 0 para i-disable ang periodic sync. (Pinakamababang interval: %s)
transfer.reject_desc = Kanselahin ang pag-transfer mula sa "%s"
mirror_lfs_endpoint_desc = Ang sync ay susubukang gamitin ang clone url upang <a target="_blank" rel="noopener noreferrer" href="%s">matukoy ang LFS server</a>. Maari ka rin tumukoy ng isang custom na endpoint kapag ang repository LFS data ay nilalagay sa ibang lugar.
adopt_search = Ilagay ang username para maghanap ng mga unadopted repository... (iwanang walang laman para hanapin lahat)
object_format = Format ng object
readme_helper = Pumili ng README file template.
default_branch_helper = Ang default branch ay ang base branch para sa mga pull request at mga commit ng code.
mirror_interval_invalid = Hindi wasto ang mirror interval.
mirror_sync = na-sync
mirror_sync_on_commit = I-sync kapag na-push ang mga commit
mirror_address = Mag-clone mula sa URL
mirror_address_desc = Maglagay ng anumang mga kinakailangang kredensyal sa Awtorisasyon na seksyon.
desc.archived = Naka-archive
desc.sha256 = SHA256
template.items = Mga template item
template.git_content = Nilalaman ng Git (Default na branch)
reactions_more = at %d pa
unit_disabled = Na-disable ng tagapangasiwa ng site ang itong seksyon ng repository.
create_repo = Gumawa ng Repository
generate_from = I-generate mula sa
repo_desc = Deskripsyon
fork_branch = Branch na mako-clone sa fork
all_branches = Lahat ng mga branch
fork_no_valid_owners = Hindi mapo-fork ang repository dahil walang mga wastong may-ari.
use_template = Gamitin ang template na ito
download_zip = I-download ang ZIP
download_tar = I-download ang TAR.GZ
issue_labels = Mga label ng isyu
generate_repo = I-generate ang repository
repo_desc_helper = Maglagay ng maikling deskripsyon (opsyonal)
repo_lang = Wika
issue_labels_helper = Pumili ng label set ng isyu.
license = Lisensya
license_helper = Pumili ng file ng lisensya.
license_helper_desc = Ang lisensya ay namamahala kung ano ang pwede at hindi pwedeng gawin ng mga ibang tao sa iyong code. Hindi sigurado kung alin ang wasto para sa iyong proyekto? Tignan ang <a target="_blank" rel="noopener noreferrer" href="%s">Pumili ng lisensya.</a>
object_format_helper = Object format ng repository. Hindi mababago mamaya. Ang SHA1 ang pinaka-compatible.
readme = README
auto_init = I-initialize ang repository (Nagdadagdag ng .gitignore, Lisensya, at README)
trust_model_helper = Pumili ng trust model para sa signature verification. Ang mga posibleng opsyon ay:
trust_model_helper_collaborator = Katulong: I-trust ang mga signature batay sa mga katulong
trust_model_helper_committer = Commiter: I-trust ang mga signature na tumutugma sa mga commiter
trust_model_helper_default = Default: Gamitin ang default trust model para sa installation na ito
default_branch = Default na branch
default_branch_label = default
mirror_prune = Pungusan
mirror_prune_desc = Tanggalin ang mga antikuwado na sangguni ng remote-tracking
mirror_address_url_invalid = Ang ibinigay na url ay hindi wasto. Kailangan mong i-escape ang lahat ng mga components ng URL ng tama.
mirror_address_protocol_invalid = Ang ibinigay na URL ay hindi wasto. Ang http(s):// o git:// na lokasyon lamang ay magagamit para sa pag-mirror.
mirror_lfs = Large File Storage (LFS)
mirror_lfs_desc = I-activate ang pag-mirror ng LFS data.
mirror_lfs_endpoint = Endpoint ng LFS
mirror_last_synced = Huling na-synchronize
mirror_password_placeholder = (Hindi nabago)
mirror_password_blank_placeholder = (Hindi tinakda)
mirror_password_help = Palitan ang username para burahin ang na-store na password.
watchers = Mga nanonood
stargazers = Mga Stargazer
stars_remove_warning = Tatanggalin nito ang lahat ng mga star sa repository na ito.
forks = Mga fork
language_other = Iba
adopt_preexisting_label = Mag-adopt ng mga file
adopt_preexisting_content = Gumawa ng repository mula sa %s
transfer.accept = Tanggapin ang paglipat
transfer.accept_desc = Ilipat sa "%s"
transfer.reject = Tanggihan ang paglipat
transfer.no_permission_to_accept = Wala kang pahintulot para tanggapin ang palilipat na ito.
transfer.no_permission_to_reject = Wala kang pahintulot para tanggihan ang palilipat na ito.
desc.private = Pribado
desc.public = Publiko
desc.template = Template
desc.internal = Internal
template.git_hooks = Mga hook ng Git
[search]
commit_kind = Maghanap ng mga commit...
keyword_search_unavailable = Kasalukuyang hindi available ang paghahanap sa pamamagitan ng keyword. Mangyaring makipag-ugnayan sa tagapangasiwa ng site.
search = Maghanap...
type_tooltip = Uri ng paghahanap
fuzzy = Fuzzy
fuzzy_tooltip = Samahan ang mga resulta na tumutugma rin sa search term nang malapit
match = Tugma
match_tooltip = Samahan lang ang mga resulta na tumutugma sa eksaktong search term
repo_kind = Maghanap ng mga repo...
user_kind = Maghanap ng mga user...
org_kind = Maghanap ng mga org...
team_kind = Maghanap ng mga koponan...
code_kind = Maghanap ng code...
code_search_unavailable = Kasalukuyang hindi available ang paghahanap ng code. Mangyaring makipag-ugnayan sa tagapangasiwa ng site.
package_kind = Maghanap ng mga pakete...
project_kind = Maghanap ng mga proyekto...
branch_kind = Maghanap ng mga branch...
runner_kind = Maghanap ng mga runner...
no_results = Walang mga tumutugma na resulta na nahanap.

View file

@ -156,6 +156,8 @@ filter.private = Privé
filter = Filtre
filter.is_mirror = Répliqué
toggle_menu = Menu va-et-vient
more_items = Plus d'éléments
invalid_data = Données invalides: %v
[aria]
navbar=Barre de navigation
@ -982,6 +984,11 @@ blocked_since = Bloqué depuis %s
user_unblock_success = Cet utilisateur a été débloqué avec succès.
user_block_success = Cet utilisateur a été bloqué avec succès.
change_password = Modifier le mot de passe
hints = Suggestions
additional_repo_units_hint_description = Afficher un bouton "Ajouter plus d'unités..." pour les dépôts qui n'ont pas toutes les unités disponibles activées.
additional_repo_units_hint = Encourager l'ajout de nouvelles unités pour le dépôt
update_hints = Mettre à jour les suggestions
update_hints_success = Les suggestions ont été mises à jour.
[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>
@ -1224,7 +1231,7 @@ file_raw=Brut
file_history=Historique
file_view_source=Voir le code source
file_view_rendered=Voir le rendu
file_view_raw=Voir le Raw
file_view_raw=Voir le contenu brut
file_permalink=Lien permanent
file_too_large=Le fichier est trop gros pour être affiché.
invisible_runes_header=`Ce fichier contient des caractères Unicode invisibles.`
@ -1396,7 +1403,7 @@ projects.column.set_default_desc=Les tickets et demandes dajout non-catégori
projects.column.unset_default=Défaire par défaut
projects.column.unset_default_desc=Les tickets et demandes d'ajouts non-catégorisés seront placés dans une colonne idoine.
projects.column.delete=Supprimer la colonne
projects.column.deletion_desc=La suppression d'une colonne de projet déplace tous les tickets liés à "Non catégorisé" Continuer ?
projects.column.deletion_desc=La suppression d'une colonne de projet déplace tous les tickets liés à la colonne par défaut. Continuer ?
projects.column.color=Couleur
projects.open=Ouvrir
projects.close=Fermer
@ -1599,7 +1606,7 @@ issues.label.filter_sort.alphabetically=Par ordre alphabétique
issues.label.filter_sort.reverse_alphabetically=Par ordre alphabétique inversé
issues.label.filter_sort.by_size=Plus petite taille
issues.label.filter_sort.reverse_by_size=Plus grande taille
issues.num_participants=%d participants
issues.num_participants_few=%d participants
issues.attachment.open_tab=`Cliquez ici pour voir « %s » dans un nouvel onglet.`
issues.attachment.download=`Cliquez pour télécharger « %s ».`
issues.subscribe=Sabonner
@ -2231,7 +2238,7 @@ settings.githook_edit_desc=Si un Hook est inactif, un exemple de contenu vous se
settings.githook_name=Nom du hook
settings.githook_content=Contenu du Hook
settings.update_githook=Mettre le Hook à jour
settings.add_webhook_desc=Forgejo enverra à l'URL cible des requêtes <code>POST</code> avec un type de contenu spécifié. Lire la suite dans le <a target="_blank" rel="noopener noreferrer" href="%s">guide des webhooks</a>.
settings.add_webhook_desc=Forgejo enverra à l'URL cible des requêtes <code>POST</code> avec le Content-Type spécifié. Lire la suite dans le <a target="_blank" rel="noopener noreferrer" href="%s">guide des webhooks</a>.
settings.payload_url=URL cible
settings.http_method=Méthode HTTP
settings.content_type=Type de contenu POST
@ -2317,9 +2324,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nom d'utilisateur Packagist
@ -2697,6 +2704,18 @@ settings.confirmation_string = Chaine de confirmation
pulls.agit_explanation = Créé par le workflow AGit. AGit permet aux contributeurs de proposer des modifications en utilisant "git push" sans créer une bifurcation ou une nouvelle branche.
pulls.merged_title_desc_one = fusionné %[1]d commit depuis <code>%[2]s</code> vers <code>%[3]s</code> %[4]s
pulls.title_desc_one = veut fusionner %[1]d commit depuis <code>%[2]s</code> vers <code id="branch_target">%[3]s</code>
stars = Étoiles
n_tag_few = %s étiquettes
editor.commit_id_not_matching = L'ID de la révision ne correspond pas à celui que vous éditiez. Appliquez les modifications à une nouvelle branche puis procédez à la fusion.
commits.search_branch = Cette branche
open_with_editor = Ouvrir avec %s
pulls.ready_for_review = Prêt à être évalué?
n_commit_one = %s commit
n_commit_few = %s commits
n_branch_one = %s branch
n_branch_few = %s branches
n_tag_one = %s étiquettes
editor.push_out_of_date = Le push semble obsolète.
[graphs]
component_loading=Chargement de %s…
@ -3754,6 +3773,7 @@ component_failed_to_load = Une erreur inattendue s'est produite.
contributors.what = contributions
component_loading = Chargement %s...
component_loading_failed = Échec de chargement de %s
code_frequency.what = fŕequence de code
recent_commits.what = commits récents
@ -3761,4 +3781,21 @@ recent_commits.what = commits récents
[search]
search = Rechercher...
type_tooltip = Type de recherche
fuzzy = Approximatif
fuzzy = Approximatif
code_search_by_git_grep = Les résultats de recherche dans le code sont fournis par "git grep". Les résultats pourraient être plus pertinents si l'administrateur du site active les indexeurs de dépôt.
runner_kind = Chercher les runners...
no_results = Aucun résultat n'a été trouvé.
keyword_search_unavailable = La recherche par mot-clé n'est pas disponible actuellement. Veuillez contacter l'administrateur du site.
fuzzy_tooltip = Inclure les résultats proches des termes recherchés
match = Correspondance
match_tooltip = Uniquement inclure les résultats correspondant exactement aux termes recherchés
repo_kind = Chercher dans le dépôt...
user_kind = Chercher les utilisateurs...
org_kind = Chercher les organisations...
team_kind = Chercher les équipes...
code_kind = Chercher le code...
code_search_unavailable = La recherche dans le code n'est pas disponible. Veuillez contacter l'administrateur du site.
package_kind = Chercher les paquets...
project_kind = Chercher les projets...
branch_kind = Chercher les branches...
commit_kind = Chercher les commits...

View file

@ -857,7 +857,7 @@ issues.label.filter_sort.alphabetically=Betűrendben
issues.label.filter_sort.reverse_alphabetically=Fordított betűrendben
issues.label.filter_sort.by_size=Legkisebb méret
issues.label.filter_sort.reverse_by_size=Legnagyobb méret
issues.num_participants=%d Résztvevő
issues.num_participants_few=%d Résztvevő
issues.attachment.open_tab=`A(z) "%s" megnyitása új fülön`
issues.attachment.download=`Kattintson a(z) "%s" letöltéséhez`
issues.subscribe=Feliratkozás

View file

@ -729,7 +729,7 @@ issues.label_edit=Sunting
issues.label_delete=Hapus
issues.label.filter_sort.alphabetically=Urutan abjad
issues.label.filter_sort.reverse_alphabetically=Membalikkan menurut abjad
issues.num_participants=%d peserta
issues.num_participants_few=%d peserta
issues.attachment.open_tab=`Klik untuk melihat "%s" di tab baru`
issues.attachment.download=`Klik untuk mengunduh "%s"`
issues.subscribe=Berlangganan

View file

@ -1065,7 +1065,7 @@ settings.web_hook_name_discord=Discord
settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_feishu_only =Feishu
settings.title=Heiti
settings.deploy_key_content=Innihald
settings.branches=Greinar

View file

@ -1478,7 +1478,7 @@ issues.label.filter_sort.alphabetically=In ordine alfabetico
issues.label.filter_sort.reverse_alphabetically=In ordine alfabetico inverso
issues.label.filter_sort.by_size=Dimensione più piccola
issues.label.filter_sort.reverse_by_size=Dimensione più grande
issues.num_participants=%d partecipanti
issues.num_participants_few=%d partecipanti
issues.attachment.open_tab=`Clicca per vedere "%s" in una nuova scheda`
issues.attachment.download=`Clicca qui per scaricare "%s"`
issues.subscribe=Iscriviti
@ -2085,9 +2085,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nome utente Packagist

View file

@ -55,7 +55,7 @@ repository=リポジトリ
organization=組織
mirror=ミラー
new_repo=新しいリポジトリ
new_migrate=新しい移行
new_migrate=新しいマイグレーション
new_mirror=新しいミラー
new_fork=新しいフォーク
new_org=新しい組織
@ -155,6 +155,8 @@ filter.public = 公開
filter.private = 非公開
toggle_menu = トグルメニュー
filter.not_template = テンプレートではない
invalid_data = 無効なデータ: %v
more_items = さらに表示
[aria]
navbar=ナビゲーションバー
@ -1587,7 +1589,7 @@ issues.label.filter_sort.alphabetically=アルファベット順
issues.label.filter_sort.reverse_alphabetically=逆アルファベット順
issues.label.filter_sort.by_size=サイズの小さい順
issues.label.filter_sort.reverse_by_size=サイズの大きい順
issues.num_participants=%d 人の参加者
issues.num_participants_few=%d 人の参加者
issues.attachment.open_tab=`クリックして新しいタブで "%s" を見る`
issues.attachment.download=`クリックして "%s" をダウンロード`
issues.subscribe=購読する
@ -2301,9 +2303,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist ユーザー名
@ -3679,3 +3681,8 @@ executable_file=実行可能ファイル
symbolic_link=シンボリックリンク
submodule=サブモジュール
[search]
search = 検索...
type_tooltip = 検索タイプ

View file

@ -782,7 +782,7 @@ issues.label_deletion_desc=라벨을 삭제하면 모든 이슈로부터도 삭
issues.label_deletion_success=라벨이 삭제되었습니다.
issues.label.filter_sort.alphabetically=알파벳순
issues.label.filter_sort.reverse_alphabetically=이름 역순으로 정렬
issues.num_participants=참여자 %d명
issues.num_participants_few=참여자 %d명
issues.attachment.open_tab=`클릭하여 "%s" 새탭으로 보기`
issues.attachment.download=' "%s"를 다운로드 하려면 클릭 하십시오 '
issues.subscribe=구독하기

View file

@ -1540,7 +1540,7 @@ issues.label.filter_sort.alphabetically=Alfabētiski
issues.label.filter_sort.reverse_alphabetically=Pretēji alfabētiski
issues.label.filter_sort.by_size=Mazākais izmērs
issues.label.filter_sort.reverse_by_size=Lielākais izmērs
issues.num_participants=%d dalībnieki
issues.num_participants_few=%d dalībnieki
issues.attachment.open_tab=`Noklikšķiniet, lai apskatītos "%s" jaunā logā`
issues.attachment.download=`Noklikšķiniet, lai lejupielādētu "%s"`
issues.subscribe=Abonēt
@ -2253,9 +2253,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist lietotājvārds

View file

@ -155,6 +155,8 @@ filter.public = Publiek
filter.private = Privé
filter = Filter
filter.not_archived = Niet gearchiveerd
more_items = Meer items
invalid_data = Ongeldige data: %v
[aria]
navbar = Navigatiebalk
@ -226,7 +228,7 @@ db_schema=Schema
db_schema_helper=Laat leeg voor de standaard database ("openbaar").
ssl_mode=SSL
path=Pad
sqlite_helper=Bestandspad voor de SQLite3-database.<br>Vul een volledig pad in als je GItea als een service uitvoert.
sqlite_helper=Bestandspad voor de SQLite3-database.<br>Vul een volledig pad in als je Forgejo als een service uitvoert.
reinstall_error=U probeert te installeren in een bestaande Forgejo database
reinstall_confirm_message=Herinstalleren met een bestaande Forgejo-database kan meerdere problemen veroorzaken. In de meeste gevallen kun je het bestaande "app.ini" gebruiken om Forgejo te laten draaien. Als je weet wat je aan het doen bent, bevestig dan het volgende:
reinstall_confirm_check_1=De gegevens versleuteld door de SECRET_KEY in de app.ini kan verloren gaan: gebruikers kunnen mogelijk niet meer inloggen met 2FA/OTP & spiegels werken mogelijk niet meer. Door dit vakje aan te vinken bevestigt u dat het huidige app.ini bestand de juiste SECRET_KEY bevat.
@ -934,7 +936,7 @@ retype_new_password = Nieuw wachtwoord bevestigen
email_desc = Je primaire e-mailadres zal gebruikt worden voor notificaties, wachtwoord herstel en web-gebaseerde Git-operaties, mits het e-mailadres niet verborgen is.
can_not_add_email_activations_pending = Er is een activering in gang, probeer het over een paar minuten nogmaals als u een nieuwe e-mail wilt toevoegen.
select_permissions = Selecteer machtigingen
permission_no_access = Geen Toegang
permission_no_access = Geen toegang
permissions_list = Machtigingen:
update_oauth2_application_success = U heeft met succes een OAuth2 applicatie bijgewerkt.
twofa_recovery_tip = Als u uw apparaat verliest, kunt u gebruik maken van de eenmalige herstelcode om weer toegang te krijgen tot uw account.
@ -952,7 +954,7 @@ unbind_success = De sociale account is succesvol verwijderd.
permissions_public_only = Alleen publiek
repo_and_org_access = Repository en Organisatie Toegang
at_least_one_permission = Je moet minstens één machtiging kiezen om een token te kunnen creëren
permission_write = Lees en Schrijf
permission_write = Lees en schrijf
oauth2_client_secret_hint = Dit geheim zal niet meer worden getoond nadat u deze pagina heeft verlaten of vernieuwd. Zorg ervoor dat u het heeft opgeslagen.
revoke_oauth2_grant_success = Toegang succesvol ingetrokken.
keep_email_private_popup = Dit zal uw e-mailadres verbergen van uw profielpagina en ook wanneer u een web-gebaseerde Git-operatie uitvoert. Gepushte commits zullen niet aangepast worden. Gebruik %s in commits om deze met uw account te associëren.
@ -1237,7 +1239,7 @@ editor.name_your_file=Bestandsnaam…
editor.filename_help=Voeg een map toe door zijn naam te typen, gevolgd door een slash ("/"). Verwijder een map door op backspace te drukken aan het begin van het tekstveld.
editor.or=of
editor.cancel_lower=Annuleer
editor.commit_signed_changes=Commit Ondertekende Wijzigingen
editor.commit_signed_changes=Commit ondertekende wijzigingen
editor.commit_changes=Wijzigingen doorvoeren
editor.add_tmpl="<bestandsnaam>" toevoegen
editor.patch=Patch toepassen
@ -1312,8 +1314,8 @@ projects.edit=Projecten bewerken
projects.edit_subheader=Projecten organiseren issues en houden voortgang bij.
projects.modify=Project bewerken
projects.type.none=Geen
projects.type.basic_kanban=Basis Kanban
projects.type.bug_triage=Bug Triage
projects.type.basic_kanban=Basis kanban
projects.type.bug_triage=Bug triage
projects.template.desc=Project sjabloon
projects.template.desc_helper=Selecteer een projectsjabloon om aan de slag te gaan
projects.type.uncategorized=Ongecategoriseerd
@ -1482,7 +1484,7 @@ issues.label.filter_sort.alphabetically=Alfabetisch
issues.label.filter_sort.reverse_alphabetically=Omgekeerd alfabetisch
issues.label.filter_sort.by_size=Kleinste grootte
issues.label.filter_sort.reverse_by_size=Grootste grootte
issues.num_participants=%d deelnemers
issues.num_participants_few=%d deelnemers
issues.attachment.open_tab=`Klik om "%s" in een nieuw tabblad te bekijken`
issues.attachment.download=`Klik om "%s" te downloaden`
issues.subscribe=Abonneren
@ -1749,7 +1751,7 @@ milestones.filter_sort.most_issues=Meeste problemen
milestones.filter_sort.least_issues=Minste problemen
ext_wiki=Toegang tot Externe Wiki
ext_wiki=Toegang tot externe wiki
ext_wiki.desc=Koppelen aan een externe wiki.
wiki=Wiki
@ -2030,9 +2032,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist gebruikersnaam
@ -2354,15 +2356,15 @@ commitstatus.failure = Mislukking
commitstatus.success = Succes
projects.create_success = Het project "%s" is gecreëerd.
projects.edit_success = Project "%s" is bijgewerkt.
projects.column.edit = Kolom Bewerken
projects.column.new_submit = Kolom Maken
projects.column.new = Nieuwe Kolom
projects.column.set_default = Standaard Instellen
projects.column.edit = Kolom bewerken
projects.column.new_submit = Kolom maken
projects.column.new = Nieuwe kolom
projects.column.set_default = Standaard instellen
projects.column.unset_default = Standaardinstelling ongedaan maken
projects.column.delete = Kolom verwijderen
projects.column.assigned_to = Toegewezen aan
projects.card_type.images_and_text = Afbeeldingen en Tekst
projects.card_type.text_only = Alleen Tekst
projects.card_type.images_and_text = Afbeeldingen en tekst
projects.card_type.text_only = Alleen tekst
issues.choose.ignore_invalid_templates = Ongeldige sjablonen zijn genegeerd
issues.choose.invalid_templates = %v ongeldige sjablon(en) gevonden
issues.choose.invalid_config = Deze issue configuratie bevat fouten:
@ -2428,7 +2430,7 @@ editor.invalid_commit_mail = Ongeldige mail voor het aanmaken van een commit.
editor.branch_does_not_exist = Branch "%s" bestaat niet in deze repository.
editor.directory_is_a_file = Mapnaam "%s" wordt al gebruikt als bestandsnaam in deze repository.
commits.renamed_from = Hernoemd van %s
projects.card_type.desc = Kaart Voorbeeld
projects.card_type.desc = Kaart voorbeeld
pulls.filter_changes_by_commit = Filter op commit
pulls.nothing_to_compare_have_tag = De geselecteerde branch/tag zijn gelijk.
pulls.merged_success = Pull request succesvol samengevoegd en gesloten
@ -2514,7 +2516,7 @@ settings.admin_stats_indexer = Code statistieken indexer
settings.new_owner_blocked_doer = De nieuwe eigenaar heeft u geblokkeerd.
settings.transfer_notices_2 = - Je behoudt toegang tot de repository als je het overdraagt aan een organisatie waarvan je (mede-)eigenaar bent.
commits.search.tooltip = U kunt zoektermen voorvoegen met "author:", "committer:", "after:", of "before:", bijvoorbeeld: "revert author:Alice before:2019-01-13".
projects.column.deletion_desc = Het verwijderen van een projectkolom verplaatst alle issues naar "Ongecategoriseerd". Wilt u doorgaan?
projects.column.deletion_desc = Het verwijderen van een projectkolom verplaatst alle gerelateerde problemen naar de standaard kolom. Doorgaan?
projects.column.set_default_desc = Stel deze kolom in als standaard voor ongecategoriseerde issues and pulls
issues.action_check = Aanvinken/uitvinken
issues.dependency.issue_batch_close_blocked = Het is niet mogelijk om de issues die u gekozen heeft in bulk te sluiten, omdat issue #%d nog open afhankelijkheden heeft
@ -2679,13 +2681,23 @@ pulls.agit_explanation = Gemaakt met behulp van de AGit workflow. AGit laat bijd
settings.confirmation_string = Confirmatie string
activity.navbar.code_frequency = Code frequentie
activity.navbar.recent_commits = Recente commits
file_follow = Volg Symlink
file_follow = Volg symlink
error.broken_git_hook = it hooks van deze repository lijken kapot te zijn. Volg alsjeblieft <a target="_blank" rel="noreferrer" href="%s">de documentatie</a> om ze te repareren, push daarna wat commits om de status te vernieuwen.
pulls.title_desc_one = wilt %[1]d commit van <code>%[2]s</code> samenvoegen in <code id="branch_target">%[3]s</code>
open_with_editor = Open met %s
commits.search_branch = Deze branch
pulls.merged_title_desc_one = heeft %[1]d commit van <code>%[2]s</code> samengevoegd in <code>%[3]s</code> %[4]s
pulls.ready_for_review = Klaar voor een beoordeling?
editor.push_out_of_date = De push lijkt verouderd.
editor.commit_id_not_matching = De commit ID komt niet overeen met degene die je aan het bewerken was. Committeer naar een nieuwe branch en voeg dan samen.
settings.rename_branch_failed_protected = Kan branch %s niet hernoemen omdat het een beschermde branch is.
stars = Sterren
n_commit_few = %s commits
n_branch_one = %s branch
n_branch_few = %s branches
n_tag_one = %s tag
n_tag_few = %s tags
n_commit_one = %d commit
@ -3072,7 +3084,7 @@ config.repo_root_path=Repository basis pad
config.lfs_root_path=LFS rootpad
config.log_file_root_path=Log-pad
config.script_type=Script soort
config.reverse_auth_user=Omgekeerde verificatie gebruiker
config.reverse_auth_user=Reverse proxy verificatie gebruiker
config.ssh_config=SSH-configuratie
config.ssh_enabled=Ingeschakeld
@ -3318,7 +3330,7 @@ auths.skip_local_two_fa_helper = Niet ingesteld betekent dat lokale gebruikers m
auths.skip_local_two_fa = Lokale 2FA overslaan
auths.oauth2_icon_url = Pictogram URL
auths.pam_email_domain = PAM e-maildomein (optioneel)
auths.tip.gitea = Registreer een nieuwe OAuth2-toepassing. De handleiding is te vinden op https://docs.gitea.com/development/oauth2-provider
auths.tip.gitea = Registreer een nieuwe OAuth2-toepassing. De handleiding is te vinden op https://forgejo.org/docs/latest/user/oauth2-provider
auths.tip.discord = Registreer een nieuwe toepassing op https://discordapp.com/developers/applications/me
auths.tip.bitbucket = Registreer een nieuwe OAuth consumer op https://bitbucket.org/account/user/<uw gebruikersnaam>/oauth-consumers/new en voeg de rechten "Account" - "Read"
auths.tips.oauth2.general.tip = Bij het registreren van een nieuwe OAuth2-authenticatie moet de callback/redirect URL zijn:
@ -3356,6 +3368,7 @@ config_settings = Instellingen
auths.tips.gmail_settings = Gmail instellingen:
config_summary = Samenvatting
config.open_with_editor_app_help = De "Openen met" editors voor het kloonmenu. Als deze leeg blijft, wordt de standaardwaarde gebruikt. Uitvouwen om de standaard te zien.
auths.tip.gitlab_new = Registreer een nieuwe applicatie op https://gitlab.com/-/profile/applications
[action]
@ -3704,6 +3717,7 @@ variables.description = Variabelen worden doorgegeven aan bepaalde acties en kun
runners.delete_runner_success = Runner succesvol verwijderd
runs.no_matching_online_runner_helper = Geen overeenkomende online runner met label: %s
runs.workflow = Workflow
runs.no_job_without_needs = De workflow moet ten minste één taak zonder afhankelijkheden bevatten.
@ -3753,4 +3767,5 @@ no_results = Geen overeenkomende resultaten gevonden.
type_tooltip = Zoektype
fuzzy_tooltip = Neem resultaten op die ook sterk overeenkomen met de zoekterm
code_search_unavailable = Code zoeken is momenteel niet beschikbaar. Neem contact op met de sitebeheerder.
keyword_search_unavailable = Zoeken op trefwoord is momenteel niet beschikbaar. Neem contact op met de beheerder van de site.
keyword_search_unavailable = Zoeken op trefwoord is momenteel niet beschikbaar. Neem contact op met de beheerder van de site.
code_search_by_git_grep = Huidige code zoekresultaten worden geleverd door "git grep". Er kunnen betere resultaten zijn als de sitebeheerder Repository Indexer inschakelt.

View file

@ -1179,7 +1179,7 @@ issues.label.filter_sort.alphabetically=Alfabetycznie
issues.label.filter_sort.reverse_alphabetically=Alfabetycznie odwrotnie
issues.label.filter_sort.by_size=Najmniejszy rozmiar
issues.label.filter_sort.reverse_by_size=Największy rozmiar
issues.num_participants=Uczestnicy %d
issues.num_participants_few=Uczestnicy %d
issues.attachment.open_tab=`Kliknij, aby zobaczyć "%s" w nowej karcie`
issues.attachment.download=`Kliknij, aby pobrać "%s"`
issues.subscribe=Subskrybuj

View file

@ -1552,7 +1552,7 @@ issues.label.filter_sort.alphabetically=Alfabeticamente
issues.label.filter_sort.reverse_alphabetically=Alfabeticamente inverso
issues.label.filter_sort.by_size=Menor tamanho
issues.label.filter_sort.reverse_by_size=Maior tamanho
issues.num_participants=%d participante(s)
issues.num_participants_few=%d participante(s)
issues.attachment.open_tab=`Clique para ver "%s" em uma nova aba`
issues.attachment.download=`Clique para baixar "%s"`
issues.subscribe=Inscrever-se
@ -2236,9 +2236,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nome de usuário no Packagist

View file

@ -1548,7 +1548,7 @@ issues.label.filter_sort.alphabetically=por ordem alfabética
issues.label.filter_sort.reverse_alphabetically=por ordem alfabética inversa
issues.label.filter_sort.by_size=Menor tamanho
issues.label.filter_sort.reverse_by_size=Maior tamanho
issues.num_participants=%d Participantes
issues.num_participants_few=%d Participantes
issues.attachment.open_tab=`Clique para ver "%s" num separador novo`
issues.attachment.download=`Clique para descarregar "%s"`
issues.subscribe=Subscrever
@ -2269,9 +2269,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nome de utilizador no Packagist

View file

@ -155,6 +155,8 @@ filter.public = Публичные
filter.private = Приватные
filter.is_archived = Архивированные
filter.not_mirror = Не зеркала
more_items = Больше элементов
invalid_data = Неверные данные: %v
[aria]
navbar=Панель навигации
@ -251,16 +253,16 @@ run_user=Запуск от имени пользователя
run_user_helper=Имя пользователя операционной системы, под которым работает Forgejo. Обратите внимание, что этот пользователь должен иметь доступ к корневому пути репозиториев.
domain=Домен сервера
domain_helper=Домен или адрес хоста для сервера.
ssh_port=Порт SSH сервера
ssh_port_helper=Номер порта, который использует SSH сервер. Оставьте пустым, чтобы отключить SSH.
http_port=Forgejo HTTP порт
http_port_helper=Номер порта, который будет прослушиваться Forgejo веб-сервером.
ssh_port=Порт SSH-сервера
ssh_port_helper=Номер порта, используемый SSH-сервером. Оставьте пустым для отключения доступа по SSH.
http_port=Порт HTTP-сервера
http_port_helper=Номер порта, используемый веб-сервером Forgejo.
app_url=Базовый URL Forgejo
app_url_helper=Этот параметр влияет на URL для клонирования по HTTP/HTTPS и на некоторые уведомления по эл. почте.
log_root_path=Путь журналов
log_root_path_helper=Файлы журнала будут записываться в этот каталог.
optional_title=Расширенные настройки
optional_title=Дополнительные настройки
email_title=Настройки эл. почты
smtp_addr=Адрес SMTP
smtp_port=Порт SMTP
@ -991,6 +993,7 @@ owner_helper=Некоторые организации могут не отоб
repo_name=Название репозитория
repo_name_helper=Лучшие названия репозиториев состоят из коротких, легко запоминаемых и уникальных ключевых слов.
repo_size=Размер репозитория
size_format = `%[1]s: %[2]s; %[3]s: %[4]s`
template=Шаблон
template_select=Выбрать шаблон.
template_helper=Сделать репозиторий шаблоном
@ -1142,7 +1145,7 @@ migrate.migrating_failed_no_addr=Перенос не удался.
migrate.github.description=Перенесите данные с github.com или сервера GitHub Enterprise.
migrate.git.description=Перенести только репозиторий из любого Git сервиса.
migrate.gitlab.description=Перенести данные с gitlab.com или других серверов GitLab.
migrate.gitea.description=Перенести данные с gitea.com или других серверов Gitea/Forgejo.
migrate.gitea.description=Перенести данные с gitea.com или других серверов Gitea.
migrate.gogs.description=Перенести данные с notabug.org или других серверов Gogs.
migrate.onedev.description=Перенести данные с code.onedev.io или других серверов OneDev.
migrate.codebase.description=Перенос данных с codebasehq.com.
@ -1232,7 +1235,7 @@ symbolic_link=Символическая ссылка
executable_file=Исполняемый файл
commit_graph=Граф коммитов
commit_graph.select=Выбрать ветку
commit_graph.hide_pr_refs=Скрыть запросы на слияние
commit_graph.hide_pr_refs=Скрыть запросы слияний
commit_graph.monochrome=Моно
commit_graph.color=Цвет
commit.contained_in=Этот коммит содержится в:
@ -1344,7 +1347,7 @@ commitstatus.failure=Неудача
commitstatus.pending=Ожидание
commitstatus.success=Успешно
ext_issues=Доступ к внешним задачам
ext_issues=Доступ ко внешним задачам
ext_issues.desc=Ссылка на внешнюю систему отслеживания задач.
projects=Проекты
@ -1359,7 +1362,7 @@ projects.create_success=Проект «%s» создан.
projects.deletion=Удалить проект
projects.deletion_desc=Удаление проекта приведёт к его удалению из всех связанных задач. Продолжить?
projects.deletion_success=Проект удалён.
projects.edit=Редактировать проекты
projects.edit=Изменить проект
projects.edit_subheader=Создавайте и организуйте задачи и отслеживайте прогресс.
projects.modify=Обновить проект
projects.edit_success=Проект «%s» обновлён.
@ -1372,14 +1375,14 @@ projects.type.uncategorized=Без категории
projects.column.edit=Изменить столбец
projects.column.edit_title=Название
projects.column.new_title=Название
projects.column.new_submit=Создать столбец
projects.column.new=Новый столбец
projects.column.new_submit=Добавить столбец
projects.column.new=Добавить столбец
projects.column.set_default=Установить по умолчанию
projects.column.set_default_desc=Назначить этот столбец по умолчанию для задач и запросов на слияние без категории
projects.column.unset_default=Снять установку по умолчанию
projects.column.unset_default_desc=Снять установку этого столбца по умолчанию
projects.column.delete=Удалить столбец
projects.column.deletion_desc=При удалении столбца проекта все связанные задачи перемещаются в «Без категории». Продолжить?
projects.column.deletion_desc=При удалении столбца все задачи в нём будут перемещены в столбец по умолчанию. Продолжить?
projects.column.color=Цвет
projects.open=Открыть
projects.close=Закрыть
@ -1581,7 +1584,8 @@ issues.label.filter_sort.alphabetically=По алфавиту
issues.label.filter_sort.reverse_alphabetically=С конца алфавита
issues.label.filter_sort.by_size=Меньший размер
issues.label.filter_sort.reverse_by_size=Больший размер
issues.num_participants=%d участвующих
issues.num_participants_one=%d участник
issues.num_participants_few=%d участников
issues.attachment.open_tab=`Нажмите, чтобы увидеть «%s» в новой вкладке`
issues.attachment.download=`Нажмите, чтобы скачать «%s»`
issues.subscribe=Подписаться
@ -1901,7 +1905,7 @@ signing.wont_sign.commitssigned=Слияние не будет подписан
signing.wont_sign.approved=Слияние не будет подписано, так как запрос на слияние не одобрен.
signing.wont_sign.not_signed_in=Вы не вошли в систему.
ext_wiki=Доступ к внешней вики
ext_wiki=Доступ ко внешней вики
ext_wiki.desc=Ссылка на внешнюю вики.
wiki=Вики
@ -2276,9 +2280,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu или Lark Suite
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=Feishu или Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Имя пользователя Packagist
@ -2653,7 +2657,7 @@ signing.wont_sign.nokey = Нет ключей для подписи этого
settings.wiki_globally_editable = Разрешить редактирование Вики всем пользователям
settings.webhook.test_delivery_desc_disabled = Активируйте этот веб-хук для проверки тестовым событием.
commits.browse_further = Смотреть далее
vendored = Предоставленный
vendored = Сторонний
settings.units.add_more = Доб. больше...
pulls.fast_forward_only_merge_pull_request = Только fast-forward
settings.units.overview = Обзор
@ -2685,6 +2689,16 @@ settings.mirror_settings.docs.doc_link_pull_section = раздел докуме
wiki.original_git_entry_tooltip = Перейти по настоящему пути вместо читабельной ссылки.
open_with_editor = Открыть в %s
commits.search_branch = В этой ветке
stars = Добавившие в избранное
n_tag_one = %s тег
n_branch_few = %s веток
n_commit_few = %s коммитов
n_commit_one = %s коммит
n_tag_few = %s тегов
n_branch_one = %s ветка
pulls.ready_for_review = Готово к рецензии?
editor.commit_id_not_matching = ID коммита не совпадает с тем, который вы редактировали. Сохраните изменения в новую ветку и выполните слияние.
editor.push_out_of_date = Похоже, отправка устарела.
[graphs]
@ -2940,10 +2954,10 @@ users.prohibit_login=Запретить вход в учётную запись
users.is_admin=У этой учётной записи есть права администратора
users.is_restricted=Ограничен
users.allow_git_hook=Может создавать Git-хуки
users.allow_git_hook_tooltip=Git Hooks выполняется как пользователь ОС с Forgejo и будет иметь одинаковый уровень доступа к хосту. В результате пользователи с привилегией Git Hook могут получить доступ и модифицировать все репозитории Forgejo, а также базу данных, используемую Forgejo. Следовательно, они также могут получить привилегии администратора Forgejo.
users.allow_import_local=Пользователь имеет право импортировать локальные репозитории
users.allow_create_organization=Эта учётная запись имеет разрешения на создание организаций
users.update_profile=Обновить профиль пользователя
users.allow_git_hook_tooltip=Git hooks выполняются от пользователя ОС, под которым работает Forgejo. Они будут иметь такой же доступ к хосту. Из-за этого пользователи с правами на Git hook будут иметь возможность получать доступ и модифицировать все репозитории в Forgejo, а также базу данных Forgejo. Следовательно, они также могут получить права администратора Forgejo.
users.allow_import_local=Может импортировать локальные репозитории
users.allow_create_organization=Может создавать организации
users.update_profile=Обновить учётную запись
users.delete_account=Удалить эту учётную запись
users.cannot_delete_self=Вы не можете удалить свою учётную запись
users.still_own_repo=Этот пользователь всё ещё является владельцем одного или более репозиториев. Сначала удалите или передайте эти репозитории.
@ -2955,16 +2969,16 @@ users.deletion_success=Учётная запись успешно удалена
users.reset_2fa=Сброс 2FA
users.list_status_filter.menu_text=Фильтр
users.list_status_filter.reset=Сбросить
users.list_status_filter.is_active=Активный
users.list_status_filter.not_active=Неактивный
users.list_status_filter.is_admin=Администратор
users.list_status_filter.not_admin=Не администратор
users.list_status_filter.is_restricted=Ограничено
users.list_status_filter.not_restricted=Не ограничено
users.list_status_filter.is_prohibit_login=Запретить вход
users.list_status_filter.not_prohibit_login=Разрешить вход
users.list_status_filter.is_2fa_enabled=2FA включено
users.list_status_filter.not_2fa_enabled=2FA отключено
users.list_status_filter.is_active=Активные
users.list_status_filter.not_active=Неактивные
users.list_status_filter.is_admin=Администраторы
users.list_status_filter.not_admin=Не администраторы
users.list_status_filter.is_restricted=Ограниченные
users.list_status_filter.not_restricted=Не ограниченные
users.list_status_filter.is_prohibit_login=Вход запрещён
users.list_status_filter.not_prohibit_login=Вход разрешён
users.list_status_filter.is_2fa_enabled=2FA включена
users.list_status_filter.not_2fa_enabled=2FA выключена
users.details=О пользователе
emails.email_manage_panel=Управление адресами эл. почты пользователей
@ -3155,7 +3169,7 @@ config.repo_root_path=Путь до каталога репозиториев
config.lfs_root_path=Корневой путь LFS
config.log_file_root_path=Путь журналов
config.script_type=Тип сценария
config.reverse_auth_user=Имя пользователя для авторизации на reverse proxy
config.reverse_auth_user=Пользователь для авторизации на обратном прокси
config.ssh_config=Конфигурация SSH
config.ssh_enabled=SSH включён
@ -3329,7 +3343,7 @@ notices.desc=Описание
notices.op=Oп.
notices.delete_success=Уведомления системы были удалены.
self_check.no_problem_found = Пока проблем не обнаружено.
auths.tip.gitea = Зарегистрируйте новое приложение OAuth2. Доступна инструкция: https://docs.gitea.com/development/oauth2-provider
auths.tip.gitea = Зарегистрируйте новое приложение OAuth2. Доступна инструкция: https://forgejo.org/docs/latest/user/oauth2-provider
auths.tips.oauth2.general.tip = При регистрации нового приложения OAuth2 ссылка обратного перенаправления должна быть:
self_check.database_fix_mssql = В настоящий момент пользователи MSSQL могут исправить проблемы с сопоставлением только ручным прописыванием "ALTER ... COLLATE ..." в SQL.
self_check.database_fix_mysql = Пользователи MySQL и MariaDB могут исправить проблемы с сопоставлением командой "gitea doctor convert". Также можно вручную вписать "ALTER ... COLLATE ..." в SQL.
@ -3352,6 +3366,7 @@ config_summary = Сводка
config.open_with_editor_app_help = Приложения для "Открыть в" в меню. Оставьте пустым для приложений по умолчанию. Разверните для просмотра.
config_settings = Настройки
auths.tips.gmail_settings = Настройки Gmail:
auths.tip.gitlab_new = Создайте новое приложение в https://gitlab.com/-/profile/applications
[action]
@ -3407,6 +3422,15 @@ years=%d лет
raw_seconds=секунд
raw_minutes=минут
[munits.data]
b = Б
kib = КиБ
mib = МиБ
gib = ГиБ
tib = ТиБ
pib = ПиБ
eib = ЕиБ
[dropzone]
default_message=Перетащите файл или кликните сюда для загрузки.
invalid_input_type=Вы не можете загружать файлы этого типа.
@ -3596,6 +3620,7 @@ rpm.repository = О репозитории
rpm.repository.architectures = Архитектуры
rpm.repository.multiple_groups = Этот пакет доступен в нескольких группах.
owner.settings.chef.keypair.description = Для аутентификации реестра Chef необходима пара ключей. Если до этого вы уже сгенерировали пару ключей, генерация новой приведёт к прекращению действия предыдущей.
owner.settings.cargo.rebuild.no_index = Невозможно выполнить пересборку. Нет инициализированного индекса.
[secrets]
secrets=Секреты
@ -3747,4 +3772,6 @@ commit_kind = Поиск коммитов...
no_results = По запросу ничего не найдено.
keyword_search_unavailable = Поиск по ключевым словам недоступен. Уточните подробности у администратора.
match_tooltip = Включать только результаты, точно соответствующие запросу
code_search_unavailable = Поиск по коду сейчас недоступен. Уточните подробности у администратора.
code_search_unavailable = Поиск по коду сейчас недоступен. Уточните подробности у администратора.
runner_kind = Поиск раннеров...
code_search_by_git_grep = Эти результаты получены через «git grep». Результатов может быть больше, если администратор сервера включит индексатор кода.

View file

@ -1132,7 +1132,7 @@ issues.label.filter_sort.alphabetically=අකාරාදී
issues.label.filter_sort.reverse_alphabetically=අකාරාදී ප්රතිවිකුණුම්
issues.label.filter_sort.by_size=කුඩාම ප්‍රමාණය
issues.label.filter_sort.reverse_by_size=විශාලම ප්‍රමාණය
issues.num_participants=සහභාගිවන්නන් %d
issues.num_participants_few=සහභාගිවන්නන් %d
issues.attachment.open_tab=`නව වගුවක "%s" බැලීමට ක්ලික් කරන්න`
issues.attachment.download=`"%s" බාගැනීමට ඔබන්න`
issues.subscribe=දායක වන්න

View file

@ -972,7 +972,7 @@ issues.label.filter_sort.alphabetically=Alfabetiskt A-Ö
issues.label.filter_sort.reverse_alphabetically=Alfabetiskt Ö-A
issues.label.filter_sort.by_size=Minsta storlek
issues.label.filter_sort.reverse_by_size=Största storlek
issues.num_participants=%d Deltagare
issues.num_participants_few=%d Deltagare
issues.attachment.open_tab=`Klicka för att se "%s" i en ny flik`
issues.attachment.download=`Klicka för att hämta "%s"`
issues.subscribe=Prenumerera

View file

@ -1539,7 +1539,7 @@ issues.label.filter_sort.alphabetically=Alfabetik
issues.label.filter_sort.reverse_alphabetically=Ters alfabetik
issues.label.filter_sort.by_size=En küçük boyut
issues.label.filter_sort.reverse_by_size=En büyük boyut
issues.num_participants=%d Katılımcı
issues.num_participants_few=%d Katılımcı
issues.attachment.open_tab=`Yeni bir sekmede "%s" görmek için tıkla`
issues.attachment.download=`"%s" indirmek için tıkla`
issues.subscribe=Abone Ol
@ -2252,9 +2252,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist kullanıcı adı

View file

@ -1248,7 +1248,7 @@ issues.label.filter_sort.alphabetically=За алфавітом
issues.label.filter_sort.reverse_alphabetically=З кінця алфавіту
issues.label.filter_sort.by_size=Найменший розмір
issues.label.filter_sort.reverse_by_size=Найбільший розмір
issues.num_participants=%d учасників
issues.num_participants_few=%d учасників
issues.attachment.open_tab=`Натисніть щоб побачити "%s" у новій вкладці`
issues.attachment.download=`Натисніть щоб завантажити "%s"`
issues.subscribe=Підписатися

View file

@ -155,6 +155,8 @@ filter.not_template = 非模板
filter.public = 公开
filter.private = 私有
toggle_menu = 菜单
invalid_data = 无效数据: %v
more_items = 显示更多
[aria]
navbar=导航栏
@ -1597,7 +1599,7 @@ issues.label.filter_sort.alphabetically=按字母顺序排序
issues.label.filter_sort.reverse_alphabetically=按字母逆序排序
issues.label.filter_sort.by_size=最小尺寸
issues.label.filter_sort.reverse_by_size=最大尺寸
issues.num_participants=%d 名参与者
issues.num_participants_few=%d 名参与者
issues.attachment.open_tab=`在新的标签页中查看 '%s'`
issues.attachment.download=`点击下载 '%s'`
issues.subscribe=订阅
@ -2316,9 +2318,9 @@ settings.web_hook_name_dingtalk=钉钉
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=飞书 / Lark Suite
settings.web_hook_name_feishu=飞书
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=飞书 / Lark Suite
settings.web_hook_name_feishu_only =飞书
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=企业微信
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist 用户名
@ -2688,6 +2690,20 @@ activity.navbar.code_frequency = 代码频率
activity.navbar.recent_commits = 近期提交
pulls.agit_explanation = 该合并请求是用 AGit 创建的。AGit 是一种可以让贡献者直接通过 “git push” 提出更改代码而不需要派生或建立新分支。
error.broken_git_hook = 该仓库的 Git 钩子似乎已经损坏,请按照 <a target="_blank" rel="noreferrer" href="%s">此文档</a>来修复这些问题,然后推送一些提交来刷新状态。
pulls.merged_title_desc_one = 已将来自 <code>%[2]s</code> 的 %[1]d 提交合并入 <code>%[3]s</code> %[4]s
commits.search_branch = 此分支
open_with_editor = 使用 %s 打开
pulls.title_desc_one = 想要将来自 <code>%[2]s</code> 的 %[1]d 提交合并到 <code id="branch_target">%[3]s</code>
settings.rename_branch_failed_protected = 无法重命名受保护的分支 %s。
stars = 点赞
settings.confirmation_string = 确认输入
n_commit_one = %s 提交
n_commit_few = %s 提交
n_branch_one = %s 分支
n_branch_few = %s 分支
n_tag_one = %s 标签
n_tag_few = %s 标签
editor.commit_id_not_matching = 此提交ID与您当前编辑的不匹配将提交至新分支后合并。
[graphs]
component_loading=正在加载 %s...
@ -2958,7 +2974,7 @@ users.max_repo_creation_desc=(设置为 -1 表示使用全局默认值)
users.is_activated=该用户已被激活
users.prohibit_login=禁用登录
users.is_admin=是管理员
users.is_restricted=受限制的
users.is_restricted=受限
users.allow_git_hook=允许创建 Git 钩子
users.allow_git_hook_tooltip=Git 钩子将会被以操作系统用户运行将会拥有同样的主机访问权限。因此拥有此特殊的Git 钩子权限将能够访问合修改所有的 Forgejo 仓库或者Forgejo的数据库。同时也能获得Forgejo的管理员权限。
users.allow_import_local=允许导入本地仓库
@ -3370,6 +3386,8 @@ self_check.database_inconsistent_collation_columns=数据库正在使用%s的排
self_check.database_fix_mysql=对于MySQL/MariaDB用户您可以使用“gitea doctor convert”命令来解决校验问题。 或者您也可以通过 "ALTER ... COLLATE ..." 这样的SQL 来手动解决这个问题。
self_check.database_fix_mssql=对于MSSQL用户您现在只能通过"ALTER ... COLLATE ..."SQLs手动解决这个问题。
auths.tips.gmail_settings = Gmail 设置:
auths.tip.gitlab_new = 在 https://gitlab.com/-/profile/applications 上注册新应用
config_settings = 设置
[action]
create_repo=创建了仓库 <a href="%s">%s</a>
@ -3738,3 +3756,25 @@ executable_file=可执行文件
symbolic_link=符号链接
submodule=子模块
[search]
keyword_search_unavailable = 关键词搜索目前不可用,请联系站点管理员。
search = 搜索...
repo_kind = 搜索仓库...
user_kind = 搜索用户...
org_kind = 搜索组织...
team_kind = 搜索团队...
code_kind = 搜索代码...
code_search_unavailable = 代码搜索目前不可用,请联系站点管理员。
package_kind = 搜索软件包...
project_kind = 搜索项目...
branch_kind = 搜索分支...
commit_kind = 搜索提交...
runner_kind = 搜索Runners...
no_results = 未找到匹配的结果。
type_tooltip = 搜索类型
fuzzy = 模糊
code_search_by_git_grep = 当前搜索结果由 git grep 提供,如果站点管理员启用了仓库索引可能会有更好的结果。
match = 匹配
match_tooltip = 仅包含与搜索词完全匹配的结果

View file

@ -467,7 +467,7 @@ issues.label_edit=編輯
issues.label_delete=刪除
issues.label.filter_sort.alphabetically=按字母顺序排序
issues.label.filter_sort.reverse_alphabetically=按字母反向排序
issues.num_participants=%d 參與者
issues.num_participants_few=%d 參與者
issues.attachment.open_tab=`在新的標籤頁中查看 '%s'`
issues.attachment.download=`點擊下載 '%s'`
issues.subscribe=訂閱

View file

@ -1433,7 +1433,7 @@ issues.label.filter_sort.alphabetically=按字母順序排序
issues.label.filter_sort.reverse_alphabetically=按字母反向排序
issues.label.filter_sort.by_size=檔案由小到大
issues.label.filter_sort.reverse_by_size=檔案由大到小
issues.num_participants=%d 參與者
issues.num_participants_few=%d 參與者
issues.attachment.open_tab=`在新分頁中查看「%s」`
issues.attachment.download=`點擊下載「%s」`
issues.subscribe=訂閱
@ -2071,9 +2071,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu
settings.web_hook_name_larksuite=Lark Suite
settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist 帳號

179
package-lock.json generated
View file

@ -70,6 +70,7 @@
"@stylistic/eslint-plugin-js": "1.7.0",
"@stylistic/stylelint-plugin": "2.1.0",
"@vitejs/plugin-vue": "5.0.4",
"@vue/test-utils": "2.4.5",
"eslint": "8.57.0",
"eslint-plugin-array-func": "4.0.0",
"eslint-plugin-github": "4.10.2",
@ -85,7 +86,7 @@
"eslint-plugin-vue": "9.24.0",
"eslint-plugin-vue-scoped-css": "2.8.0",
"eslint-plugin-wc": "2.0.4",
"happy-dom": "14.3.7",
"happy-dom": "14.3.10",
"markdownlint-cli": "0.39.0",
"postcss-html": "1.6.0",
"stylelint": "16.3.0",
@ -1329,6 +1330,12 @@
"node": ">= 8"
}
},
"node_modules/@one-ini/wasm": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
"dev": true
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@ -2680,6 +2687,16 @@
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
"integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
},
"node_modules/@vue/test-utils": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.5.tgz",
"integrity": "sha512-oo2u7vktOyKUked36R93NB7mg2B+N7Plr8lxp2JBGwr18ch6EggFjixSCdIVVLkT6Qr0z359Xvnafc9dcKyDUg==",
"dev": true,
"dependencies": {
"js-beautify": "^1.14.9",
"vue-component-type-helpers": "^2.0.0"
}
},
"node_modules/@webassemblyjs/ast": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
@ -2862,6 +2879,15 @@
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
},
"node_modules/abbrev": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
"integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
"dev": true,
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@ -3799,6 +3825,22 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/config-chain": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
"integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
"dev": true,
"dependencies": {
"ini": "^1.3.4",
"proto-list": "~1.2.1"
}
},
"node_modules/config-chain/node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"dev": true
},
"node_modules/core-js-compat": {
"version": "3.36.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz",
@ -4775,6 +4817,48 @@
"marked": "^4.1.0"
}
},
"node_modules/editorconfig": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz",
"integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
"dev": true,
"dependencies": {
"@one-ini/wasm": "0.1.1",
"commander": "^10.0.0",
"minimatch": "9.0.1",
"semver": "^7.5.3"
},
"bin": {
"editorconfig": "bin/editorconfig"
},
"engines": {
"node": ">=14"
}
},
"node_modules/editorconfig/node_modules/commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"dev": true,
"engines": {
"node": ">=14"
}
},
"node_modules/editorconfig/node_modules/minimatch": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
"integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.716",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.716.tgz",
@ -6513,9 +6597,9 @@
}
},
"node_modules/happy-dom": {
"version": "14.3.7",
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.3.7.tgz",
"integrity": "sha512-lUfDRGzjrVJF2pnvh13OL+qEJ9eDpcedVLm77a3aMg8gPGKXfG+xFMNk3cOWetjucU8FveJ4qcSC/EX55nJ4fQ==",
"version": "14.3.10",
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.3.10.tgz",
"integrity": "sha512-Rh5li9vA9MF9Gkg85CbFABKTa3uoSAByILRNGb92u/vswDd561gBg2p1UW1ZauvDWWwRxPcbACK5zv3BR+gHnQ==",
"dev": true,
"dependencies": {
"entities": "^4.5.0",
@ -7398,6 +7482,58 @@
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
},
"node_modules/js-beautify": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz",
"integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==",
"dev": true,
"dependencies": {
"config-chain": "^1.1.13",
"editorconfig": "^1.0.4",
"glob": "^10.3.3",
"js-cookie": "^3.0.5",
"nopt": "^7.2.0"
},
"bin": {
"css-beautify": "js/bin/css-beautify.js",
"html-beautify": "js/bin/html-beautify.js",
"js-beautify": "js/bin/js-beautify.js"
},
"engines": {
"node": ">=14"
}
},
"node_modules/js-beautify/node_modules/glob": {
"version": "10.3.12",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
"integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^2.3.6",
"minimatch": "^9.0.1",
"minipass": "^7.0.4",
"path-scurry": "^1.10.2"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"dev": true,
"engines": {
"node": ">=14"
}
},
"node_modules/js-levenshtein-esm": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz",
@ -8801,6 +8937,21 @@
"resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",
"integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="
},
"node_modules/nopt": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz",
"integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==",
"dev": true,
"dependencies": {
"abbrev": "^2.0.0"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
@ -9140,11 +9291,11 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/path-scurry": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
"integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
"dependencies": {
"lru-cache": "^9.1.1 || ^10.0.0",
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
@ -9727,6 +9878,12 @@
"integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==",
"dev": true
},
"node_modules/proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
"integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
"dev": true
},
"node_modules/proto-props": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/proto-props/-/proto-props-2.0.0.tgz",
@ -12089,6 +12246,12 @@
"vue": "^3.0.0-0 || ^2.7.0"
}
},
"node_modules/vue-component-type-helpers": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.7.tgz",
"integrity": "sha512-7e12Evdll7JcTIocojgnCgwocX4WzIYStGClBQ+QuWPinZo/vQolv2EMq4a3lg16TKfwWafLimG77bxb56UauA==",
"dev": true
},
"node_modules/vue-eslint-parser": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz",

View file

@ -69,6 +69,7 @@
"@stylistic/eslint-plugin-js": "1.7.0",
"@stylistic/stylelint-plugin": "2.1.0",
"@vitejs/plugin-vue": "5.0.4",
"@vue/test-utils": "2.4.5",
"eslint": "8.57.0",
"eslint-plugin-array-func": "4.0.0",
"eslint-plugin-github": "4.10.2",
@ -84,7 +85,7 @@
"eslint-plugin-vue": "9.24.0",
"eslint-plugin-vue-scoped-css": "2.8.0",
"eslint-plugin-wc": "2.0.4",
"happy-dom": "14.3.7",
"happy-dom": "14.3.10",
"markdownlint-cli": "0.39.0",
"postcss-html": "1.6.0",
"stylelint": "16.3.0",

View file

@ -621,6 +621,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
ProtectedFilePatterns: form.ProtectedFilePatterns,
UnprotectedFilePatterns: form.UnprotectedFilePatterns,
BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
ApplyToAdmins: form.ApplyToAdmins,
}
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
@ -808,6 +809,10 @@ func EditBranchProtection(ctx *context.APIContext) {
protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
}
if form.ApplyToAdmins != nil {
protectBranch.ApplyToAdmins = *form.ApplyToAdmins
}
var whitelistUsers []int64
if form.PushWhitelistUsernames != nil {
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)

View file

@ -337,13 +337,9 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
return
}
// If we're an admin for the repository we can ignore status checks, reviews and override protected files
if ctx.userPerm.IsAdmin() {
return
}
// Now if we're not an admin - we can't overwrite protected files so fail now
if changedProtectedfiles {
// It's not allowed t overwrite protected files. Unless if the user is an
// admin and the protected branch rule doesn't apply to admins.
if changedProtectedfiles && (!ctx.user.IsAdmin || protectBranch.ApplyToAdmins) {
log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath)
ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath),
@ -352,8 +348,12 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
}
// Check all status checks and reviews are ok
if err := pull_service.CheckPullBranchProtections(ctx, pr, true); err != nil {
if pb, err := pull_service.CheckPullBranchProtections(ctx, pr, true); err != nil {
if models.IsErrDisallowedToMerge(err) {
// Allow this if the rule doesn't apply to admins and the user is an admin.
if ctx.user.IsAdmin && !pb.ApplyToAdmins {
return
}
log.Warn("Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s", ctx.opts.UserID, branchName, repo, pr.Index, err.Error())
ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, ctx.opts.PullRequestID, err.Error()),

View file

@ -26,6 +26,7 @@ import (
org_service "code.gitea.io/gitea/services/org"
repo_service "code.gitea.io/gitea/services/repository"
user_service "code.gitea.io/gitea/services/user"
webhook_service "code.gitea.io/gitea/services/webhook"
)
const (
@ -210,6 +211,7 @@ func Webhooks(ctx *context.Context) {
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["BaseLink"] = ctx.Org.OrgLink + "/settings/hooks"
ctx.Data["BaseLinkNew"] = ctx.Org.OrgLink + "/settings/hooks"
ctx.Data["WebhookList"] = webhook_service.List()
ctx.Data["Description"] = ctx.Tr("org.settings.hooks_desc")
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{OwnerID: ctx.Org.Organization.ID})

View file

@ -950,7 +950,7 @@ func getGitIdentity(ctx *context.Context, commitMailID int64, tpl base.TplName,
if email == nil || !email.IsActivated {
ctx.Data["Err_CommitMailID"] = true
ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_mail"), tplEditFile, form)
ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_mail"), tpl, form)
return nil
}

View file

@ -11,6 +11,7 @@ import (
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
@ -18,6 +19,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
@ -192,6 +194,7 @@ func Releases(ctx *context.Context) {
}
ctx.Data["Releases"] = releases
addVerifyTagToContext(ctx)
numReleases := ctx.Data["NumReleases"].(int64)
pager := context.NewPagination(int(numReleases), listOptions.PageSize, listOptions.Page, 5)
@ -201,6 +204,44 @@ func Releases(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplReleasesList)
}
func verifyTagSignature(ctx *context.Context, r *repo_model.Release) (*asymkey.ObjectVerification, error) {
if err := r.LoadAttributes(ctx); err != nil {
return nil, err
}
gitRepo, err := gitrepo.OpenRepository(ctx, r.Repo)
if err != nil {
return nil, err
}
defer gitRepo.Close()
tag, err := gitRepo.GetTag(r.TagName)
if err != nil {
return nil, err
}
if tag.Signature == nil {
return nil, nil
}
verification := asymkey.ParseTagWithSignature(ctx, gitRepo, tag)
return verification, nil
}
func addVerifyTagToContext(ctx *context.Context) {
ctx.Data["VerifyTag"] = func(r *repo_model.Release) *asymkey.ObjectVerification {
v, err := verifyTagSignature(ctx, r)
if err != nil {
return nil
}
return v
}
ctx.Data["HasSignature"] = func(verification *asymkey.ObjectVerification) bool {
if verification == nil {
return false
}
return verification.Reason != "gpg.error.not_signed_commit"
}
}
// TagsList render tags list page
func TagsList(ctx *context.Context) {
ctx.Data["PageIsTagList"] = true
@ -240,6 +281,7 @@ func TagsList(ctx *context.Context) {
}
ctx.Data["Releases"] = releases
addVerifyTagToContext(ctx)
numTags := ctx.Data["NumTags"].(int64)
pager := context.NewPagination(int(numTags), opts.PageSize, opts.Page, 5)
@ -304,6 +346,7 @@ func SingleRelease(ctx *context.Context) {
if release.IsTag && release.Title == "" {
release.Title = release.TagName
}
addVerifyTagToContext(ctx)
ctx.Data["PageIsSingleTag"] = release.IsTag
if release.IsTag {

View file

@ -237,6 +237,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns
protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns
protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch
protectBranch.ApplyToAdmins = f.ApplyToAdmins
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
UserIDs: whitelistUsers,

View file

@ -45,6 +45,7 @@ func WebhookList(ctx *context.Context) {
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["BaseLink"] = ctx.Repo.RepoLink + "/settings/hooks"
ctx.Data["BaseLinkNew"] = ctx.Repo.RepoLink + "/settings/hooks"
ctx.Data["WebhookList"] = webhook_service.List()
ctx.Data["Description"] = ctx.Tr("repo.settings.hooks_desc", "https://forgejo.org/docs/latest/user/webhooks/")
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{RepoID: ctx.Repo.Repository.ID})
@ -132,13 +133,16 @@ func WebhookNew(ctx *context.Context) {
}
hookType := ctx.Params(":type")
if webhook_service.GetWebhookHandler(hookType) == nil {
handler := webhook_service.GetWebhookHandler(hookType)
if handler == nil {
ctx.NotFound("GetWebhookHandler", nil)
return
}
ctx.Data["HookType"] = hookType
ctx.Data["WebhookHandler"] = handler
ctx.Data["BaseLink"] = orCtx.LinkNew
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
ctx.Data["WebhookList"] = webhook_service.List()
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
}
@ -194,6 +198,7 @@ func WebhookCreate(ctx *context.Context) {
ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}}
ctx.Data["HookType"] = hookType
ctx.Data["WebhookHandler"] = handler
orCtx, err := getOwnerRepoCtx(ctx)
if err != nil {
@ -202,6 +207,7 @@ func WebhookCreate(ctx *context.Context) {
}
ctx.Data["BaseLink"] = orCtx.LinkNew
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
ctx.Data["WebhookList"] = webhook_service.List()
if ctx.HasError() {
// pre-fill the form with the submitted data
@ -336,6 +342,7 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
}
ctx.Data["BaseLink"] = orCtx.Link
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
ctx.Data["WebhookList"] = webhook_service.List()
var w *webhook.Webhook
if orCtx.RepoID > 0 {
@ -358,6 +365,7 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
if handler := webhook_service.GetWebhookHandler(w.Type); handler != nil {
ctx.Data["HookMetadata"] = handler.Metadata(w)
ctx.Data["WebhookHandler"] = handler
}
ctx.Data["History"], err = w.History(ctx, 1)

View file

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context"
webhook_service "code.gitea.io/gitea/services/webhook"
)
const (
@ -23,6 +24,7 @@ func Webhooks(ctx *context.Context) {
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["BaseLink"] = setting.AppSubURL + "/user/settings/hooks"
ctx.Data["BaseLinkNew"] = setting.AppSubURL + "/user/settings/hooks"
ctx.Data["WebhookList"] = webhook_service.List()
ctx.Data["Description"] = ctx.Tr("settings.hooks.desc")
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{OwnerID: ctx.Doer.ID})

View file

@ -118,4 +118,5 @@ func WebfingerQuery(ctx *context.Context) {
Aliases: aliases,
Links: links,
})
ctx.Resp.Header().Set("Content-Type", "application/jrd+json")
}

View file

@ -162,6 +162,7 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch) *api
RequireSignedCommits: bp.RequireSignedCommits,
ProtectedFilePatterns: bp.ProtectedFilePatterns,
UnprotectedFilePatterns: bp.UnprotectedFilePatterns,
ApplyToAdmins: bp.ApplyToAdmins,
Created: bp.CreatedUnix.AsTime(),
Updated: bp.UpdatedUnix.AsTime(),
}

View file

@ -219,6 +219,7 @@ type ProtectBranchForm struct {
RequireSignedCommits bool
ProtectedFilePatterns string
UnprotectedFilePatterns string
ApplyToAdmins bool
}
// Validate validates the fields

View file

@ -5,10 +5,18 @@ package markup
import (
"context"
"fmt"
"code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
gitea_context "code.gitea.io/gitea/services/context"
file_service "code.gitea.io/gitea/services/repository/files"
)
func ProcessorHelper() *markup.ProcessorHelper {
@ -29,5 +37,51 @@ func ProcessorHelper() *markup.ProcessorHelper {
// when using gitea context (web context), use user's visibility and user's permission to check
return user.IsUserVisibleToViewer(giteaCtx, mentionedUser, giteaCtx.Doer)
},
GetRepoFileBlob: func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error) {
repo, err := repo.GetRepositoryByOwnerAndName(ctx, ownerName, repoName)
if err != nil {
return nil, err
}
var user *user.User
giteaCtx, ok := ctx.(*gitea_context.Context)
if ok {
user = giteaCtx.Doer
}
perms, err := access.GetUserRepoPermission(ctx, repo, user)
if err != nil {
return nil, err
}
if !perms.CanRead(unit.TypeCode) {
return nil, fmt.Errorf("cannot access repository code")
}
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
if err != nil {
return nil, err
}
defer gitRepo.Close()
commit, err := gitRepo.GetCommit(commitSha)
if err != nil {
return nil, err
}
if language != nil {
*language, err = file_service.TryGetContentLanguage(gitRepo, commitSha, filePath)
if err != nil {
log.Error("Unable to get file language for %-v:%s. Error: %v", repo, filePath, err)
}
}
blob, err := commit.GetBlobByPath(filePath)
if err != nil {
return nil, err
}
return blob, nil
},
}
}

View file

@ -104,7 +104,7 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
return ErrIsChecking
}
if err := CheckPullBranchProtections(ctx, pr, false); err != nil {
if pb, err := CheckPullBranchProtections(ctx, pr, false); err != nil {
if !models.IsErrDisallowedToMerge(err) {
log.Error("Error whilst checking pull branch protection for %-v: %v", pr, err)
return err
@ -117,8 +117,9 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
err = nil
}
// * if the doer is admin, they could skip the branch protection check
if adminSkipProtectionCheck {
// * if the doer is admin, they could skip the branch protection check,
// if that's allowed by the protected branch rule.
if adminSkipProtectionCheck && !pb.ApplyToAdmins {
if isRepoAdmin, errCheckAdmin := access_model.IsUserRepoAdmin(ctx, pr.BaseRepo, doer); errCheckAdmin != nil {
log.Error("Unable to check if %-v is a repo admin in %-v: %v", doer, pr.BaseRepo, errCheckAdmin)
return errCheckAdmin

View file

@ -424,63 +424,64 @@ func IsUserAllowedToMerge(ctx context.Context, pr *issues_model.PullRequest, p a
return false, nil
}
// CheckPullBranchProtections checks whether the PR is ready to be merged (reviews and status checks)
func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullRequest, skipProtectedFilesCheck bool) (err error) {
// CheckPullBranchProtections checks whether the PR is ready to be merged (reviews and status checks).
// Returns the protected branch rule when `ErrDisallowedToMerge` is returned as error.
func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullRequest, skipProtectedFilesCheck bool) (protectedBranchRule *git_model.ProtectedBranch, err error) {
if err = pr.LoadBaseRepo(ctx); err != nil {
return fmt.Errorf("LoadBaseRepo: %w", err)
return nil, fmt.Errorf("LoadBaseRepo: %w", err)
}
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
if err != nil {
return fmt.Errorf("LoadProtectedBranch: %v", err)
return nil, fmt.Errorf("LoadProtectedBranch: %v", err)
}
if pb == nil {
return nil
return nil, nil
}
isPass, err := IsPullCommitStatusPass(ctx, pr)
if err != nil {
return err
return nil, err
}
if !isPass {
return models.ErrDisallowedToMerge{
return pb, models.ErrDisallowedToMerge{
Reason: "Not all required status checks successful",
}
}
if !issues_model.HasEnoughApprovals(ctx, pb, pr) {
return models.ErrDisallowedToMerge{
return pb, models.ErrDisallowedToMerge{
Reason: "Does not have enough approvals",
}
}
if issues_model.MergeBlockedByRejectedReview(ctx, pb, pr) {
return models.ErrDisallowedToMerge{
return pb, models.ErrDisallowedToMerge{
Reason: "There are requested changes",
}
}
if issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pr) {
return models.ErrDisallowedToMerge{
return pb, models.ErrDisallowedToMerge{
Reason: "There are official review requests",
}
}
if issues_model.MergeBlockedByOutdatedBranch(pb, pr) {
return models.ErrDisallowedToMerge{
return pb, models.ErrDisallowedToMerge{
Reason: "The head branch is behind the base branch",
}
}
if skipProtectedFilesCheck {
return nil
return nil, nil
}
if pb.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) {
return models.ErrDisallowedToMerge{
return pb, models.ErrDisallowedToMerge{
Reason: "Changed protected files",
}
}
return nil
return nil, nil
}
// MergedManually mark pr as merged manually

View file

@ -10,6 +10,7 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
"html/template"
"io"
"net/http"
"net/url"
@ -17,6 +18,7 @@ import (
webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/svg"
webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/services/forms"
)
@ -34,6 +36,14 @@ func (dh defaultHandler) Type() webhook_module.HookType {
return webhook_module.GITEA
}
func (dh defaultHandler) Icon(size int) template.HTML {
if dh.forgejo {
// forgejo.svg is not in web_src/svg/, so svg.RenderHTML does not work
return imgIcon("forgejo.svg", size)
}
return svg.RenderHTML("gitea-gitea", size, "img")
}
func (defaultHandler) Metadata(*webhook_model.Webhook) any { return nil }
func (defaultHandler) FormFields(bind func(any)) FormFields {

Some files were not shown because too many files have changed in this diff Show more