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).TrString
func (MockLocale).Tr func (MockLocale).Tr
func (MockLocale).TrN func (MockLocale).TrN
func (MockLocale).TrSize
func (MockLocale).PrettyNumber func (MockLocale).PrettyNumber
package "code.gitea.io/gitea/modules/util/filebuffer" 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" package "code.gitea.io/gitea/services/webhook"
func NewNotifier func NewNotifier
func List

View file

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

View file

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

View file

@ -88,8 +88,13 @@ STORED_VERSION=$(shell cat $(STORED_VERSION_FILE) 2>/dev/null)
ifneq ($(STORED_VERSION),) ifneq ($(STORED_VERSION),)
FORGEJO_VERSION ?= $(STORED_VERSION) FORGEJO_VERSION ?= $(STORED_VERSION)
else else
# drop the "g" prefix prepended by git describe to the commit hash ifneq ($(GITEA_VERSION),)
FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY} 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 endif
FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//') 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/') 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} RELEASE_VERSION ?= ${FORGEJO_VERSION}
VERSION ?= ${RELEASE_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 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 out io.Writer
var dWriter *delayWriter
out = &nilWriter{} out = &nilWriter{}
if setting.Git.VerbosePush { if setting.Git.VerbosePush {
if setting.Git.VerbosePushDelay > 0 { if setting.Git.VerbosePushDelay > 0 {
dWriter = newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay) dWriter := newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
defer dWriter.Close() defer dWriter.Close()
out = dWriter out = dWriter
} else { } else {
@ -414,7 +413,6 @@ Forgejo or set your environment appropriately.`, "")
hookOptions.RefFullNames = refFullNames hookOptions.RefFullNames = refFullNames
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions) resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
if extra.HasError() { if extra.HasError() {
_ = dWriter.Close()
hookPrintResults(results) hookPrintResults(results)
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error) 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) fmt.Fprintf(out, "Processed %d references in total\n", total)
_ = dWriter.Close()
hookPrintResults(results) hookPrintResults(results)
return nil return nil
} }
@ -447,7 +444,6 @@ Forgejo or set your environment appropriately.`, "")
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions) resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
if resp == nil { if resp == nil {
_ = dWriter.Close()
hookPrintResults(results) hookPrintResults(results)
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error) 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) return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error)
} }
} }
_ = dWriter.Close()
hookPrintResults(results)
hookPrintResults(results)
return nil return nil
} }

View file

@ -7,10 +7,20 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"io"
"net/http"
"net/http/httptest"
"os"
"strings" "strings"
"testing" "testing"
"time"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
) )
func TestPktLine(t *testing.T) { func TestPktLine(t *testing.T) {
@ -83,3 +93,72 @@ func TestPktLine(t *testing.T) {
assert.Empty(t, w.Bytes()) 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) ;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
;MERMAID_MAX_SOURCE_CHARACTERS = 5000 ;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/editorconfig/editorconfig-core-go/v2 v2.6.1
github.com/emersion/go-imap v1.2.1 github.com/emersion/go-imap v1.2.1
github.com/emirpasic/gods v1.18.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/fsnotify/fsnotify v1.7.0
github.com/gliderlabs/ssh v0.3.7 github.com/gliderlabs/ssh v0.3.7
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 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-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.11.0 github.com/go-git/go-git/v5 v5.11.0
github.com/go-ldap/ldap/v3 v3.4.6 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-swagger/go-swagger v0.30.5
github.com/go-testfixtures/testfixtures/v3 v3.10.0 github.com/go-testfixtures/testfixtures/v3 v3.10.0
github.com/go-webauthn/webauthn v0.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/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.2.0 github.com/golang-jwt/jwt/v5 v5.2.0
github.com/google/go-github/v57 v57.0.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/google/uuid v1.6.0
github.com/gorilla/feeds v1.1.2 github.com/gorilla/feeds v1.1.2
github.com/gorilla/sessions v1.2.2 github.com/gorilla/sessions v1.2.2
@ -74,7 +74,7 @@ require (
github.com/meilisearch/meilisearch-go v0.26.1 github.com/meilisearch/meilisearch-go v0.26.1
github.com/mholt/archiver/v3 v3.5.1 github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.26 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/msteinert/pam v1.2.0
github.com/nektos/act v0.2.52 github.com/nektos/act v0.2.52
github.com/niklasfasching/go-org v1.7.0 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/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 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ=
github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0= 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.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 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 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/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.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 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/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 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 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.4 h1:ocDNwMFlnA0NU0zSB3I52xkO4sFXk80VK9lXjLClu88=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= 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 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= 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-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-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.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.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= 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 h1:SQ2+xSonWjjoEMOV5tcOnZJVlfyUfCBhGQGArS1b9+U=
github.com/go-swagger/go-swagger v0.30.5/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewetCdvEXqgPvbc/Q= 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= 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/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 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 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.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.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 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-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-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-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-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 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.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.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 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 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-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.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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/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/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= 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/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 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw= github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs= 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 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 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/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 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 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 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= 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-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-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-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-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-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-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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 ( import (
"context" "context"
"fmt"
"hash"
"strings"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "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 // 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. // SignCommit represents a commit with validation of signature.
type SignCommit struct { type SignCommit struct {
Verification *CommitVerification Verification *ObjectVerification
*user_model.UserCommit *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. // 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 { 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)) newCommits := make([]*SignCommit, 0, len(oldCommits))
keyMap := map[string]bool{} keyMap := map[string]bool{}
for _, c := range oldCommits { for _, c := range oldCommits {
o := commitToGitObject(c.Commit)
signCommit := &SignCommit{ signCommit := &SignCommit{
UserCommit: c, UserCommit: c,
Verification: ParseCommitWithSignature(ctx, c.Commit), Verification: ParseObjectWithSignature(ctx, &o),
} }
_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap) _ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap)
@ -88,456 +57,7 @@ func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.Use
return newCommits return newCommits
} }
// ParseCommitWithSignature check if signature is good against keystore. func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *ObjectVerification {
func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerification { o := commitToGitObject(c)
var committer *user_model.User return ParseObjectWithSignature(ctx, &o)
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
} }

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" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" 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/log"
"github.com/42wim/sshsig" "github.com/42wim/sshsig"
) )
// ParseCommitWithSSHSignature check if signature is good against keystore. // ParseObjectWithSSHSignature check if signature is good against keystore.
func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *user_model.User) *CommitVerification { func ParseObjectWithSSHSignature(ctx context.Context, c *GitObject, committer *user_model.User) *ObjectVerification {
// Now try to associate the signature with the committer, if present // Now try to associate the signature with the committer, if present
if committer.ID != 0 { if committer.ID != 0 {
keys, err := db.Find[PublicKey](ctx, FindPublicKeyOptions{ 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 if err != nil { // Skipping failed to get ssh keys of user
log.Error("ListPublicKeys: %v", err) log.Error("ListPublicKeys: %v", err)
return &CommitVerification{ return &ObjectVerification{
CommittingUser: committer, CommittingUser: committer,
Verified: false, Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys", Reason: "gpg.error.failed_retrieval_gpg_keys",
@ -55,7 +54,7 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
for _, k := range keys { for _, k := range keys {
if k.Verified && activated { 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 { if commitVerification != nil {
return commitVerification return commitVerification
} }
@ -63,19 +62,19 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
} }
} }
return &CommitVerification{ return &ObjectVerification{
CommittingUser: committer, CommittingUser: committer,
Verified: false, Verified: false,
Reason: NoKeyFound, 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 { if err := sshsig.Verify(bytes.NewBuffer([]byte(payload)), []byte(sig), []byte(k.Content), "git"); err != nil {
return nil return nil
} }
return &CommitVerification{ // Everything is ok return &ObjectVerification{ // Everything is ok
CommittingUser: committer, CommittingUser: committer,
Verified: true, Verified: true,
Reason: fmt.Sprintf("%s / %s", signer.Name, k.Fingerprint), 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}) sshKey := unittest.AssertExistsAndLoadBean(t, &PublicKey{ID: 1000, OwnerID: 2})
t.Run("No commiter", func(t *testing.T) { 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.False(t, commitVerification.Verified)
assert.Equal(t, NoKeyFound, commitVerification.Reason) assert.Equal(t, NoKeyFound, commitVerification.Reason)
}) })
@ -30,7 +31,8 @@ func TestParseCommitWithSSHSignature(t *testing.T) {
t.Run("Commiter without keys", func(t *testing.T) { t.Run("Commiter without keys", func(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) 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.False(t, commitVerification.Verified)
assert.Equal(t, NoKeyFound, commitVerification.Reason) 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.False(t, commitVerification.Verified)
assert.Equal(t, NoKeyFound, commitVerification.Reason) 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.False(t, commitVerification.Verified)
assert.Equal(t, NoKeyFound, commitVerification.Reason) 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.True(t, commitVerification.Verified)
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason) assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
assert.Equal(t, sshKey, commitVerification.SigningSSHKey) 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.True(t, commitVerification.Verified)
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason) assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
assert.Equal(t, sshKey, commitVerification.SigningSSHKey) assert.Equal(t, sshKey, commitVerification.SigningSSHKey)

View file

@ -155,8 +155,14 @@ func InitEngine(ctx context.Context) error {
Logger: log.GetLogger("xorm"), Logger: log.GetLogger("xorm"),
}) })
} }
errorLogger := log.GetLogger("xorm")
if setting.IsInTesting {
errorLogger = log.GetLogger(log.DEFAULT)
}
xormEngine.AddHook(&ErrorQueryHook{ xormEngine.AddHook(&ErrorQueryHook{
Logger: log.GetLogger("xorm"), Logger: errorLogger,
}) })
SetDefaultEngine(ctx, xormEngine) 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), NewMigration("Add the `enable_repo_unit_hints` column to the `user` table", forgejo_v1_22.AddUserRepoUnitHintsSetting),
// v7 -> v8 // v7 -> v8
NewMigration("Modify the `release`.`note` content to remove SSH signatures", forgejo_v1_22.RemoveSSHSignaturesFromReleaseNotes), 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. // 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"` RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
ProtectedFilePatterns string `xorm:"TEXT"` ProtectedFilePatterns string `xorm:"TEXT"`
UnprotectedFilePatterns string `xorm:"TEXT"` UnprotectedFilePatterns string `xorm:"TEXT"`
ApplyToAdmins bool `xorm:"NOT NULL DEFAULT false"`
CreatedUnix timeutil.TimeStamp `xorm:"created"` CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"` UpdatedUnix timeutil.TimeStamp `xorm:"updated"`

View file

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

View file

@ -4,18 +4,76 @@
package git package git
import ( import (
"bufio"
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"io"
"os" "os"
"strings" "strings"
"sync/atomic"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
) )
var LinguistAttributes = []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language", "linguist-documentation", "linguist-detectable"} 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 // GitAttribute exposes an attribute from the .gitattribute file
type GitAttribute string //nolint:revive type GitAttribute string //nolint:revive
@ -54,29 +112,15 @@ func (ca GitAttribute) Bool() optional.Option[bool] {
return optional.None[bool]() return optional.None[bool]()
} }
// GitAttributeFirst returns the first specified attribute // gitCheckAttrCommand prepares the "git check-attr" command for later use as one-shot or streaming
// // instanciation.
// 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
}
func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string) (*Command, *RunOpts, context.CancelFunc, error) { func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string) (*Command, *RunOpts, context.CancelFunc, error) {
if len(attributes) == 0 { if len(attributes) == 0 {
return nil, nil, nil, fmt.Errorf("no provided attributes to check-attr") return nil, nil, nil, fmt.Errorf("no provided attributes to check-attr")
} }
env := os.Environ() 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 // git < 2.40 cannot run check-attr on bare repo, but needs INDEX + WORK_TREE
hasIndex := treeish == "" hasIndex := treeish == ""
@ -85,7 +129,7 @@ func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
deleteTemporaryFile = cancel removeTempFiles = cancel
env = append(env, "GIT_INDEX_FILE="+indexFilename, "GIT_WORK_TREE="+worktree) 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 // clear treeish to read from provided index/work_tree
treeish = "" 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 { if hasIndex {
cmd.AddArguments("--cached") cmd.AddArguments("--cached")
@ -126,18 +162,34 @@ func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string
return cmd, &RunOpts{ return cmd, &RunOpts{
Env: env, Env: env,
Dir: repo.Path, 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). // 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) { 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 { if err != nil {
return nil, err return nil, err
} }
defer cancel() defer removeTempFiles()
stdOut := new(bytes.Buffer) stdOut := new(bytes.Buffer)
runOpts.Stdout = stdOut 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()) 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 return newCheckAttrStdoutReader(stdOut, len(attributes))()
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
} }
type attributeTriple struct { // GitAttributeChecker creates an AttributeChecker for the given repository and provided commit ID
Filename string // to retrieve the attributes of multiple files. The AttributeChecker must be closed after use.
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.
// //
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare). // 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) { 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 { if err != nil {
return AttributeChecker{}, err 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") 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() { go func() {
defer stdinReader.Close() 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) stdErr := new(bytes.Buffer)
runOpts.Stdin = stdinReader runOpts.Stdin = stdinReader
runOpts.Stdout = lw runOpts.Stdout = stdoutWriter
runOpts.Stderr = stdErr runOpts.Stderr = stdErr
err := cmd.Run(runOpts) err := cmd.Run(runOpts)
if err != nil && // If there is an error we need to return but: // if the context was cancelled, Run error is irrelevant
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) if e := cmd.parentContext.Err(); e != nil {
err.Error() != "signal: killed" { // 2. We should not pass up errors due to the program being killed err = e
log.Error("failed to run attr-check. Error: %v\nStderr: %s", err, stdErr.String())
} }
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 return ac, nil
} }
type AttributeChecker struct { type AttributeChecker struct {
ctx context.Context removeTempFiles context.CancelFunc
cancel context.CancelFunc stdinWriter io.WriteCloser
stdinWriter *os.File readStdout func() (map[string]GitAttribute, error)
attributeNumber int err *atomic.Value
attributesCh <-chan attributeTriple
} }
func (ac AttributeChecker) CheckPath(path string) (map[string]GitAttribute, error) { 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 { 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) return ac.readStdout()
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
} }
func (ac AttributeChecker) Close() error { func (ac AttributeChecker) Close() error {
ac.cancel() ac.removeTempFiles()
return ac.stdinWriter.Close() return ac.stdinWriter.Close()
} }

View file

@ -4,7 +4,14 @@
package git package git
import ( import (
"context"
"fmt"
"io"
"io/fs"
"os"
"path/filepath" "path/filepath"
"runtime"
"strings"
"testing" "testing"
"time" "time"
@ -14,90 +21,63 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) { func TestNewCheckAttrStdoutReader(t *testing.T) {
wr := &nulSeparatedAttributeWriter{ t.Run("two_times", func(t *testing.T) {
attributes: make(chan attributeTriple, 5), 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) _, err := read()
assert.NoError(t, err) assert.Equal(t, io.ErrUnexpectedEOF, err)
select { })
case attr := <-wr.attributes: t.Run("three_times", func(t *testing.T) {
assert.Equal(t, ".gitignore\"\n", attr.Filename) read := newCheckAttrStdoutReader(strings.NewReader(
assert.Equal(t, "linguist-vendored", attr.Attribute) "shouldbe.vendor\x00linguist-vendored\x00set\x00"+
assert.Equal(t, "unspecified", attr.Value) "shouldbe.vendor\x00linguist-generated\x00unspecified\x00"+
case <-time.After(100 * time.Millisecond): "shouldbe.vendor\x00linguist-language\x00unspecified\x00",
assert.FailNow(t, "took too long to read an attribute from the list") ), 1)
}
// Write a second attribute again
n, err = wr.Write([]byte(testStr))
assert.Len(t, testStr, n) // first read
assert.NoError(t, err) attr, err := read()
assert.NoError(t, err)
assert.Equal(t, map[string]GitAttribute{
"linguist-vendored": GitAttribute("set"),
}, attr)
select { // second read
case attr := <-wr.attributes: attr, err = read()
assert.Equal(t, ".gitignore\"\n", attr.Filename) assert.NoError(t, err)
assert.Equal(t, "linguist-vendored", attr.Attribute) assert.Equal(t, map[string]GitAttribute{
assert.Equal(t, "unspecified", attr.Value) "linguist-generated": GitAttribute("unspecified"),
case <-time.After(100 * time.Millisecond): }, attr)
assert.FailNow(t, "took too long to read an attribute from the list")
}
// Write a partial attribute // third read
_, err = wr.Write([]byte("incomplete-file")) attr, err = read()
assert.NoError(t, err) assert.NoError(t, err)
_, err = wr.Write([]byte("name\x00")) assert.Equal(t, map[string]GitAttribute{
assert.NoError(t, err) "linguist-language": GitAttribute("unspecified"),
}, attr)
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)
} }
func TestGitAttributeBareNonBare(t *testing.T) { func TestGitAttributeBareNonBare(t *testing.T) {
@ -114,33 +94,35 @@ func TestGitAttributeBareNonBare(t *testing.T) {
"8fee858da5796dfb37704761701bb8e800ad9ef3", "8fee858da5796dfb37704761701bb8e800ad9ef3",
"341fca5b5ea3de596dc483e54c2db28633cd2f97", "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...) bareChecker, err := gitRepo.GitAttributeChecker(commitID, LinguistAttributes...)
assert.NoError(t, err) assert.NoError(t, err)
t.Cleanup(func() { bareChecker.Close() }) defer bareChecker.Close()
bareStats, err := bareChecker.CheckPath("i-am-a-python.p") bareStats, err := bareChecker.CheckPath("i-am-a-python.p")
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, refStats, bareStats)
})
t.Run("GitAttributeChecker/"+commitID+"/NoBareSupport", func(t *testing.T) {
defer test.MockVariableValue(&SupportCheckAttrOnBare, false)() defer test.MockVariableValue(&SupportCheckAttrOnBare, false)()
cloneChecker, err := gitRepo.GitAttributeChecker(commitID, LinguistAttributes...) cloneChecker, err := gitRepo.GitAttributeChecker(commitID, LinguistAttributes...)
assert.NoError(t, err) assert.NoError(t, err)
t.Cleanup(func() { cloneChecker.Close() }) defer cloneChecker.Close()
cloneStats, err := cloneChecker.CheckPath("i-am-a-python.p") cloneStats, err := cloneChecker.CheckPath("i-am-a-python.p")
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, cloneStats, bareStats) assert.EqualValues(t, refStats, cloneStats)
})
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)
}) })
} }
} }
@ -208,3 +190,162 @@ func TestGitAttributeStruct(t *testing.T) {
assert.Equal(t, "text?token=Error", GitAttribute("text?token=Error").String()) assert.Equal(t, "text?token=Error", GitAttribute("text?token=Error").String())
assert.Equal(t, "text", GitAttribute("text?token=Error").Prefix()) 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 { type Commit struct {
Commit *git.Commit Commit *git.Commit
User *user_model.User User *user_model.User
Verification *asymkey_model.CommitVerification Verification *asymkey_model.ObjectVerification
Status *git_model.CommitStatus Status *git_model.CommitStatus
Flow int64 Flow int64
Row int Row int

View file

@ -136,7 +136,7 @@ func (g *Manager) doShutdown() {
} }
g.lock.Lock() g.lock.Lock()
g.shutdownCtxCancel() 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) pprof.SetGoroutineLabels(atShutdownCtx)
for _, fn := range g.toRunAtShutdown { for _, fn := range g.toRunAtShutdown {
go fn() go fn()
@ -167,7 +167,7 @@ func (g *Manager) doHammerTime(d time.Duration) {
default: default:
log.Warn("Setting Hammer condition") log.Warn("Setting Hammer condition")
g.hammerCtxCancel() 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) pprof.SetGoroutineLabels(atHammerCtx)
} }
g.lock.Unlock() g.lock.Unlock()
@ -183,7 +183,7 @@ func (g *Manager) doTerminate() {
default: default:
log.Warn("Terminating") log.Warn("Terminating")
g.terminateCtxCancel() 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) pprof.SetGoroutineLabels(atTerminateCtx)
for _, fn := range g.toRunAtTerminate { 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.hammerCtx, g.hammerCtxCancel = context.WithCancel(ctx)
g.managerCtx, g.managerCtxCancel = context.WithCancel(ctx) g.managerCtx, g.managerCtxCancel = context.WithCancel(ctx)
g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "with-terminate")) g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("gracefulLifecycle", "with-terminate"))
g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("graceful-lifecycle", "with-shutdown")) g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("gracefulLifecycle", "with-shutdown"))
g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "with-hammer")) g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("gracefulLifecycle", "with-hammer"))
g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "with-manager")) g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("gracefulLifecycle", "with-manager"))
if !g.setStateTransition(stateInit, stateRunning) { if !g.setStateTransition(stateInit, stateRunning) {
panic("invalid graceful manager state: transition from init to running failed") 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() { 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) pprof.SetGoroutineLabels(g.managerCtx)
defer pprof.SetGoroutineLabels(g.ctx) defer pprof.SetGoroutineLabels(g.ctx)

View file

@ -29,7 +29,7 @@ const (
) )
func (g *Manager) start() { 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) pprof.SetGoroutineLabels(g.managerCtx)
defer pprof.SetGoroutineLabels(g.ctx) 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{ var defaultProcessors = []processor{
fullIssuePatternProcessor, fullIssuePatternProcessor,
comparePatternProcessor, comparePatternProcessor,
filePreviewPatternProcessor,
fullHashPatternProcessor, fullHashPatternProcessor,
shortLinkProcessor, shortLinkProcessor,
linkProcessor, 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 // emojiShortCodeProcessor for rendering text like :smile: into emoji
func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) { func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) {
start := 0 start := 0

View file

@ -17,9 +17,11 @@ import (
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
var localMetas = map[string]string{ var localMetas = map[string]string{
@ -676,3 +678,68 @@ func TestIssue18471(t *testing.T) {
assert.NoError(t, err) 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()) 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" "code.gitea.io/gitea/modules/setting"
giteautil "code.gitea.io/gitea/modules/util" giteautil "code.gitea.io/gitea/modules/util"
"github.com/microcosm-cc/bluemonday/css"
"github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/ast"
east "github.com/yuin/goldmark/extension/ast" east "github.com/yuin/goldmark/extension/ast"
"github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/parser"
@ -199,7 +198,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
} }
case *ast.CodeSpan: case *ast.CodeSpan:
colorContent := n.Text(reader.Source()) colorContent := n.Text(reader.Source())
if css.ColorHandler(strings.ToLower(string(colorContent))) { if matchColor(strings.ToLower(string(colorContent))) {
v.AppendChild(v, NewColorPreview(colorContent)) v.AppendChild(v, NewColorPreview(colorContent))
} }
} }

View file

@ -31,6 +31,7 @@ const (
type ProcessorHelper struct { type ProcessorHelper struct {
IsUsernameMentionable func(ctx context.Context, username string) bool 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 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. // Allow 'color' and 'background-color' properties for the style attribute on text elements.
policy.AllowStyles("color", "background-color").OnElements("span", "p") 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 // Allow generally safe attributes
generalSafeAttrs := []string{ generalSafeAttrs := []string{
"abbr", "accept", "accept-charset", "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 // 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 // PIDPProfLabel is a label set on goroutines that have a process attached
const PIDPProfLabel = "pid" const PIDPProfLabel = "pid"
@ -35,7 +35,7 @@ const PIDPProfLabel = "pid"
const PPIDPProfLabel = "ppid" const PPIDPProfLabel = "ppid"
// ProcessTypePProfLabel is a label set on goroutines that have a process attached // ProcessTypePProfLabel is a label set on goroutines that have a process attached
const ProcessTypePProfLabel = "process-type" const ProcessTypePProfLabel = "processType"
// IDType is a pid type // IDType is a pid type
type IDType string type IDType string

View file

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

View file

@ -47,6 +47,7 @@ type BranchProtection struct {
RequireSignedCommits bool `json:"require_signed_commits"` RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"` ProtectedFilePatterns string `json:"protected_file_patterns"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns"` UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
ApplyToAdmins bool `json:"apply_to_admins"`
// swagger:strfmt date-time // swagger:strfmt date-time
Created time.Time `json:"created_at"` Created time.Time `json:"created_at"`
// swagger:strfmt date-time // swagger:strfmt date-time
@ -80,6 +81,7 @@ type CreateBranchProtectionOption struct {
RequireSignedCommits bool `json:"require_signed_commits"` RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"` ProtectedFilePatterns string `json:"protected_file_patterns"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns"` UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
ApplyToAdmins bool `json:"apply_to_admins"`
} }
// EditBranchProtectionOption options for editing a branch protection // EditBranchProtectionOption options for editing a branch protection
@ -106,4 +108,5 @@ type EditBranchProtectionOption struct {
RequireSignedCommits *bool `json:"require_signed_commits"` RequireSignedCommits *bool `json:"require_signed_commits"`
ProtectedFilePatterns *string `json:"protected_file_patterns"` ProtectedFilePatterns *string `json:"protected_file_patterns"`
UnprotectedFilePatterns *string `json:"unprotected_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 // time / number / format
"FileSize": base.FileSize, "FileSize": FileSizePanic,
"CountFmt": base.FormatNumberSI, "CountFmt": base.FormatNumberSI,
"TimeSince": timeutil.TimeSince, "TimeSince": timeutil.TimeSince,
"TimeSinceUnix": timeutil.TimeSinceUnix, "TimeSinceUnix": timeutil.TimeSinceUnix,
@ -249,3 +249,7 @@ func Eval(tokens ...any) (any, error) {
n, err := eval.Expr(tokens...) n, err := eval.Expr(tokens...)
return n.Value, err 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) { func (w *testLoggerWriterCloser) Log(level log.Level, msg string) {
if len(msg) > 0 && msg[len(msg)-1] == '\n' { msg = strings.TrimSpace(msg)
msg = msg[:len(msg)-1]
}
w.printMsg(msg) w.printMsg(msg)
if level >= log.ERROR { 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 // list of error message which will not fail the test
// ideally this list should be empty, however ensuring that it does not grow // ideally this list should be empty, however ensuring that it does not grow
// is already a good first step. // 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 // 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`, `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 // TestAPIDeleteReleaseByTagName
// action notification were a commit cannot be computed (because the commit got deleted) // 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: ]`, `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 // TestAPIGenerateRepo
`Notify() [E] an error occurred while executing the CreateRepository actions method: gitRepo.GetCommit: object does not exist [id: , rel_path: ]`, `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 // TestAPIPullReview
`PullRequestReview() [E] Unsupported review webhook type`, `PullRequestReview() [E] Unsupported review webhook type`,
@ -111,11 +120,251 @@ var ignoredErrorMessageSuffixes = []string{
// TestRebuildCargo // TestRebuildCargo
`RebuildCargoIndex() [E] RebuildIndex failed: GetRepositoryByOwnerAndName: repository does not exist [id: 0, uid: 0, owner_name: user2, name: _cargo-index]`, `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) { func (w *testLoggerWriterCloser) recordError(msg string) {
for _, s := range ignoredErrorMessageSuffixes { for _, s := range ignoredErrorMessage {
if strings.HasSuffix(msg, s) { if strings.Contains(msg, s) {
return return
} }
} }
@ -128,6 +377,11 @@ func (w *testLoggerWriterCloser) recordError(msg string) {
err = w.errs[len(w.errs)-1] 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)) err = errors.Join(err, errors.New(msg))
if len(w.errs) > 0 { if len(w.errs) > 0 {
@ -231,7 +485,9 @@ func PrintCurrentTest(t testing.TB, skip ...int) func() {
} }
if err := WriterCloser.popT(); err != nil { 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) return template.HTML(key1)
} }
func (l MockLocale) TrSize(s int64) ReadableSize {
return ReadableSize{fmt.Sprint(s), ""}
}
func (l MockLocale) PrettyNumber(v any) string { func (l MockLocale) PrettyNumber(v any) string {
return fmt.Sprint(v) return fmt.Sprint(v)
} }

View file

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation/i18n"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/dustin/go-humanize"
"golang.org/x/text/language" "golang.org/x/text/language"
"golang.org/x/text/message" "golang.org/x/text/message"
"golang.org/x/text/number" "golang.org/x/text/number"
@ -33,6 +34,8 @@ type Locale interface {
Tr(key string, args ...any) template.HTML Tr(key string, args ...any) template.HTML
TrN(cnt any, key1, keyN string, args ...any) template.HTML TrN(cnt any, key1, keyN string, args ...any) template.HTML
TrSize(size int64) ReadableSize
PrettyNumber(v any) string 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...) 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 { 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 // 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 { if s, ok := v.(string); ok {

View file

@ -3,6 +3,8 @@
package translation package translation
// TODO: make this package friendly to testing
import ( import (
"testing" "testing"
@ -11,9 +13,25 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestPrettyNumber(t *testing.T) { func TestTrSize(t *testing.T) {
// TODO: make this package friendly to testing 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() i18n.ResetDefaultLocales()
allLangMap = make(map[string]*LangType) allLangMap = make(map[string]*LangType)

View file

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

View file

@ -125,10 +125,16 @@ orgs_none = Не сте участник в никакви организаци
repos_none = Не притежавате никакви хранилища. repos_none = Не притежавате никакви хранилища.
blocked_users_none = Няма блокирани потребители. blocked_users_none = Няма блокирани потребители.
profile_desc = Контролирайте как вашият профил се показва на другите потребители. Вашият основен адрес на ел. поща ще се използва за известия, възстановяване на паролата и уеб базирани Git операции. profile_desc = Контролирайте как вашият профил се показва на другите потребители. Вашият основен адрес на ел. поща ще се използва за известия, възстановяване на паролата и уеб базирани Git операции.
permission_write = Четене и Писане permission_write = Четене и писане
twofa_disable = Изключване на двуфакторното удостоверяване twofa_disable = Изключване на двуфакторното удостоверяване
twofa_enroll = Включване на двуфакторно удостоверяване twofa_enroll = Включване на двуфакторно удостоверяване
ssh_key_name_used = Вече съществува SSH ключ със същото име във вашия акаунт. ssh_key_name_used = Вече съществува SSH ключ със същото име във вашия акаунт.
email_notifications.enable = Включване на известията по ел. поща
delete_prompt = Тази операция ще изтрие перманентно потребителския ви акаунт. Това <strong>НЕ МОЖЕ</strong> да бъде отменено.
email_notifications.disable = Изключване на известията по ел. поща
delete_account = Изтриване на акаунта ви
confirm_delete_account = Потвърждаване на изтриването
email_notifications.onmention = Ел. поща само при споменаване
[packages] [packages]
container.labels.value = Стойност container.labels.value = Стойност
@ -258,7 +264,7 @@ new_fork = Ново разклонение на хранилище
unpin = Откачване unpin = Откачване
pin = Закачване pin = Закачване
filter = Филтър filter = Филтър
filter.clear = Изчистване на филтъра filter.clear = Изчистване на филтрите
filter.is_archived = Архивирани filter.is_archived = Архивирани
filter.not_archived = Не архивирани filter.not_archived = Не архивирани
filter.is_fork = Разклонени filter.is_fork = Разклонени
@ -269,6 +275,7 @@ filter.not_template = Не шаблони
filter.private = Частни filter.private = Частни
filter.is_mirror = Огледални filter.is_mirror = Огледални
filter.not_mirror = Не огледални filter.not_mirror = Не огледални
copy_hash = Копиране на контролната сума
[repo] [repo]
issues.context.edit = Редактиране issues.context.edit = Редактиране
@ -366,7 +373,7 @@ issues.keyword_search_unavailable = В момента търсенето по к
repo_desc_helper = Въведете кратко описание (опционално) repo_desc_helper = Въведете кратко описание (опционално)
mirror_address = Клониране от URL mirror_address = Клониране от URL
owner_helper = Някои организации може да не се показват в падащото меню поради ограничение за максимален брой хранилища. owner_helper = Някои организации може да не се показват в падащото меню поради ограничение за максимален брой хранилища.
new_repo_helper = Хранилище съдържа всички файлове на проекта, включително хронологията на ревизиите. Вече хоствате хранилище другаде? <a href="%s">Мигрирайте хранилище.</a> new_repo_helper = Хранилището съдържа всички файлове на проекта, включително хронологията на ревизиите. Вече хоствате хранилище другаде? <a href="%s">Мигрирайте хранилище.</a>
repo_name_helper = Добрите имена на хранилища използват кратки, запомнящи се и уникални ключови думи. repo_name_helper = Добрите имена на хранилища използват кратки, запомнящи се и уникални ключови думи.
migrated_from = Мигрирано от <a href="%[1]s">%[2]s</a> migrated_from = Мигрирано от <a href="%[1]s">%[2]s</a>
visibility_description = Само притежателят или участниците в организацията, ако имат права, ще могат да го видят. visibility_description = Само притежателят или участниците в организацията, ако имат права, ще могат да го видят.
@ -448,7 +455,7 @@ fork_from = Разклоняване от
diff.comment.placeholder = Оставете коментар diff.comment.placeholder = Оставете коментар
projects.edit = Редактиране на проекта projects.edit = Редактиране на проекта
projects.modify = Редактиране на проекта projects.modify = Редактиране на проекта
issues.new.no_label = Няма етикет issues.new.no_label = Няма етикети
issues.new.title_empty = Заглавието не може да бъде празно issues.new.title_empty = Заглавието не може да бъде празно
issues.new.projects = Проекти issues.new.projects = Проекти
issues.new.clear_projects = Изчистване на проектите issues.new.clear_projects = Изчистване на проектите
@ -534,7 +541,7 @@ settings.collaboration.write = Писане
settings.collaboration.read = Четене settings.collaboration.read = Четене
settings.collaboration.owner = Притежател settings.collaboration.owner = Притежател
settings.basic_settings = Основни настройки settings.basic_settings = Основни настройки
settings.wiki_desc = Включване на уики на хранилището settings.wiki_desc = Включване на уикито за хранилището
settings.use_internal_wiki = Използване на вграденото уики settings.use_internal_wiki = Използване на вграденото уики
settings.wiki_globally_editable = Позволяване на всеки да редактира уикито settings.wiki_globally_editable = Позволяване на всеки да редактира уикито
settings.add_collaborator = Добавяне на сътрудник settings.add_collaborator = Добавяне на сътрудник
@ -629,7 +636,7 @@ issues.filter_milestone_all = Всички етапи
issues.filter_milestone_open = Отворени етапи issues.filter_milestone_open = Отворени етапи
issues.filter_milestone_none = Без етапи issues.filter_milestone_none = Без етапи
issues.filter_project = Проект issues.filter_project = Проект
issues.num_participants = %d участващи issues.num_participants_few = %d участващи
issues.filter_assignee = Изпълнител issues.filter_assignee = Изпълнител
issues.filter_milestone_closed = Затворени етапи issues.filter_milestone_closed = Затворени етапи
issues.filter_assginee_no_select = Всички изпълнители issues.filter_assginee_no_select = Всички изпълнители
@ -667,10 +674,10 @@ milestones.close = Затваряне
issues.label_templates.use = Използване на набор от етикети issues.label_templates.use = Използване на набор от етикети
issues.add_milestone_at = `добави това към етапа <b>%s</b> %s` issues.add_milestone_at = `добави това към етапа <b>%s</b> %s`
issues.add_label = добави етикета %s %s issues.add_label = добави етикета %s %s
issues.add_labels = добави етикетите %s %s issues.add_labels = добави етикети %s %s
issues.remove_label = премахна етикета %s %s issues.remove_label = премахна етикета %s %s
issues.remove_labels = премахна етикетите %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.add_project_at = `добави това към проекта <b>%s</b> %s`
issues.remove_project_at = `премахна това от проекта <b>%s</b> %s` issues.remove_project_at = `премахна това от проекта <b>%s</b> %s`
issues.remove_milestone_at = `премахна това от етапа <b>%s</b> %s` issues.remove_milestone_at = `премахна това от етапа <b>%s</b> %s`
@ -702,7 +709,7 @@ more_operations = Още операции
download_archive = Изтегляне на хранилището download_archive = Изтегляне на хранилището
branch = Клон branch = Клон
tree = Дърво tree = Дърво
branches = Клони branches = Клонове
tags = Маркери tags = Маркери
tag = Маркер tag = Маркер
filter_branch_and_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 pulls.merged_by_fake = от %[2]s бе слята %[1]s
issues.label_deletion = Изтриване на етикета issues.label_deletion = Изтриване на етикета
issues.label_modify = Редактиране на етикета issues.label_modify = Редактиране на етикета
issues.due_date_added = добави крайния срок %s %s issues.due_date_added = добави краен срок %s %s
issues.due_date_remove = премахна крайния срок %s %s issues.due_date_remove = премахна крайния срок %s %s
release.new_release = Ново издание release.new_release = Ново издание
release.tag_helper_existing = Съществуващ маркер. release.tag_helper_existing = Съществуващ маркер.
@ -822,7 +829,7 @@ editor.fail_to_update_file = Неуспешно обновяване/създа
editor.add_subdir = Добавяне на директория… editor.add_subdir = Добавяне на директория…
commits.commits = Подавания commits.commits = Подавания
commits.find = Търсене commits.find = Търсене
commits.search_all = Всички клони commits.search_all = Всички клонове
commits.search = Потърсете подавания… commits.search = Потърсете подавания…
commit.operations = Операции commit.operations = Операции
issues.deleted_milestone = `(изтрит)` issues.deleted_milestone = `(изтрит)`
@ -848,7 +855,7 @@ release.edit_release = Обновяване на изданието
diff.committed_by = подадено от diff.committed_by = подадено от
release.downloads = Изтегляния release.downloads = Изтегляния
issues.sign_in_require_desc = <a href="%s">Влезте</a> за да се присъедините към това обсъждане. 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.deletion_tag_success = Маркерът е изтрит.
release.cancel = Отказ release.cancel = Отказ
release.deletion = Изтриване на изданието release.deletion = Изтриване на изданието
@ -928,9 +935,9 @@ settings.web_hook_name_discord = Discord
settings.web_hook_name_telegram = Telegram settings.web_hook_name_telegram = Telegram
settings.web_hook_name_matrix = Matrix settings.web_hook_name_matrix = Matrix
settings.web_hook_name_gogs = Gogs settings.web_hook_name_gogs = Gogs
settings.web_hook_name_feishu_or_larksuite = Feishu / Lark Suite settings.web_hook_name_feishu = Feishu / Lark Suite
settings.web_hook_name_feishu = Feishu settings.web_hook_name_feishu_only = Feishu
settings.web_hook_name_larksuite = Lark Suite settings.web_hook_name_larksuite_only = Lark Suite
settings.web_hook_name_wechatwork = WeCom (Wechat Work) settings.web_hook_name_wechatwork = WeCom (Wechat Work)
settings.web_hook_name_packagist = Packagist settings.web_hook_name_packagist = Packagist
diff.file_byte_size = Размер diff.file_byte_size = Размер
@ -959,6 +966,110 @@ search.results = Резултати от търсенето на "%s" в <a href
object_format = Формат на обектите object_format = Формат на обектите
release.releases_for = Издания за %s release.releases_for = Издания за %s
release.tags_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] [modal]
confirm = Потвърждаване confirm = Потвърждаване
@ -1053,6 +1164,8 @@ members.owner = Притежател
members.member_role = Роля на участника: members.member_role = Роля на участника:
members.member = Участник members.member = Участник
members.private_helper = да е видим members.private_helper = да е видим
teams.no_desc = Този екип няма описание
settings.delete_org_desc = Тази организация ще бъде изтрита перманентно. Продължаване?
[install] [install]
admin_password = Парола admin_password = Парола
@ -1090,6 +1203,7 @@ sqlite_helper = Път на файла за SQLite3 базата данни.<br>
err_empty_admin_email = Администраторският адрес на ел. поща не може да бъде празен. err_empty_admin_email = Администраторският адрес на ел. поща не може да бъде празен.
password_algorithm = Алгоритъм за хеш. на паролите password_algorithm = Алгоритъм за хеш. на паролите
default_keep_email_private = Скриване на адресите на ел. поща по подразбиране default_keep_email_private = Скриване на адресите на ел. поща по подразбиране
invalid_password_algorithm = Невалиден алгоритъм за хеш. на паролите
[filter] [filter]
string.asc = А - Я string.asc = А - Я
@ -1135,6 +1249,7 @@ change_avatar = Променете профилната си снимка…
email_visibility.limited = Вашият адрес на ел. поща е видим за всички удостоверени потребители email_visibility.limited = Вашият адрес на ел. поща е видим за всички удостоверени потребители
disabled_public_activity = Този потребител е изключил публичната видимост на дейността. disabled_public_activity = Този потребител е изключил публичната видимост на дейността.
email_visibility.private = Вашият адрес на ел. поща е видим само за вас и администраторите email_visibility.private = Вашият адрес на ел. поща е видим само за вас и администраторите
show_on_map = Показване на това място на картата
[home] [home]
filter = Други филтри filter = Други филтри
@ -1263,6 +1378,8 @@ SSHTitle = Име на SSH ключ
repo_name_been_taken = Името на хранилището вече е използвано. repo_name_been_taken = Името на хранилището вече е използвано.
team_name_been_taken = Името на екипа вече е заето. team_name_been_taken = Името на екипа вече е заето.
org_name_been_taken = Името на организацията вече е заето. org_name_been_taken = Името на организацията вече е заето.
still_own_packages = Вашият акаунт притежава един или повече пакети, първо ги изтрийте.
still_own_repo = Вашият акаунт притежава едно или повече хранилища, първо ги изтрийте или прехвърлете.
[action] [action]
close_issue = `затвори задача <a href="%[1]s">%[3]s#%[2]s</a>` 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> 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>` approve_pull_request = `одобри <a href="%[1]s">%[3]s#%[2]s</a>`
reject_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] [auth]
tab_openid = OpenID tab_openid = OpenID
@ -1408,6 +1526,24 @@ component_loading_failed = Неуспешно зареждане на %s
contributors.what = приноси contributors.what = приноси
recent_commits.what = скорошни подавания recent_commits.what = скорошни подавания
component_loading = Зареждане на %s... component_loading = Зареждане на %s...
component_loading_info = Това може да отнеме известно време…
[projects] [projects]
type-1.display_name = Индивидуален проект 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 sources=Zdrojové kódy
mirrors=Zrcadla mirrors=Zrcadla
collaborative=Spolupráce collaborative=Spolupráce
forks=Rozštěpení forks=Forky
activities=Aktivity activities=Aktivity
pull_requests=Požadavky na sloučení pull_requests=Požadavky na sloučení
issues=Úkoly issues=Problémy
milestones=Milníky milestones=Milníky
ok=OK ok=OK
@ -147,7 +147,7 @@ confirm_delete_artifact = Opravdu chcete odstranit artefakt „%s“?
toggle_menu = Přepnout nabídku toggle_menu = Přepnout nabídku
filter = Filtr filter = Filtr
filter.is_fork = Forknuto filter.is_fork = Forknuto
filter.not_fork = Není forkuto filter.not_fork = Není forknuto
filter.is_mirror = Zrcadleno filter.is_mirror = Zrcadleno
filter.is_template = Šablona filter.is_template = Šablona
filter.not_template = Není šablona filter.not_template = Není šablona
@ -156,7 +156,9 @@ filter.private = Soukromé
filter.is_archived = Archivováno filter.is_archived = Archivováno
filter.not_mirror = Není zrcadleno filter.not_mirror = Není zrcadleno
filter.not_archived = Není archivováno 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] [aria]
navbar=Navigační lišta 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 install=Instalace
title=Počáteční konfigurace 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í. 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_title=Nastavení databáze
db_type=Typ databáze db_type=Typ databáze
host=Hostitel 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_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_search_results=Výsledky hledání pro „%s“
code_last_indexed_at=Naposledy indexováno %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>. relevant_repositories=Zobrazují se pouze relevantní repositáře, <a href="%s">zobrazit nefiltrované výsledky</a>.
forks_one = %d fork forks_one = %d fork
forks_few = %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á 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.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 úkolu %[2]s 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.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. 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.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.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.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.approve=<b>@%[1]s</b> schválil/a tuto žádost o sloučení.
issue.action.reject=<b>@%[1]s</b> požadoval/a změny v tomto požadavku na nataž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 tento požadavek na nataž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 z %[2]s pro tento požadavek na nataž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 tento požadavek na natažení jako připravený ke kontrole. 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.action.new=<b>@%[1]s</b> vytvořil/a #%[2]d.
issue.in_tree_path=V %s: 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_review_request=Žádost o posouzení
comment_type_group_pull_request_push=Přidané commity comment_type_group_pull_request_push=Přidané commity
comment_type_group_project=Projekt 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. saved_successfully=Vaše nastavení bylo úspěšně uloženo.
privacy=Soukromí privacy=Soukromí
keep_activity_private=Skrýt aktivitu z profilové stránky 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_help=Podpis můžete vygenerovat pomocí:
gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
gpg_token_signature=Zakódovaný podpis GPG 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. verify_gpg_key_success=GPG klíč „%s“ byl ověřen.
ssh_key_verified=Ověřený klíč 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. 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í select_permissions=Vyberte oprávnění
permission_no_access=Bez přístupu permission_no_access=Bez přístupu
permission_read=Přečtené 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 at_least_one_permission=Musíte vybrat alespoň jedno oprávnění pro vytvoření tokenu
permissions_list=Oprávnění: 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 or_enter_secret=Nebo zadejte tajný kód: %s
then_enter_passcode=A zadejte přístupový kód zobrazený ve vaší aplikaci: then_enter_passcode=A zadejte přístupový kód zobrazený ve vaší aplikaci:
passcode_invalid=Přístupový kód není platný. Zkuste to znovu. 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í. 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. 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_account=Odstranit svůj účet
delete_prompt=Tato operace natrvalo odstraní váš uživatelský účet. <strong>NELZE</strong> ji vrátit zpět. 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í confirm_delete_account=Potvrdit odstranění
delete_account_title=Odstranit uživatelský účet delete_account_title=Odstranit uživatelský účet
delete_account_desc=Jste si jisti, že chcete trvale smazat tento úč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=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. 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=Štítky problémů
issue_labels_helper=Vyberte sadu štítků úkolů. issue_labels_helper=Vyberte sadu štítků problémů.
license=Licence license=Licence
license_helper=Vyberte licenční soubor. 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> 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ář create_repo=Vytvořit repozitář
default_branch=Výchozí větev default_branch=Výchozí větev
default_branch_label=výchozí 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=Vyčistit
mirror_prune_desc=Odstranit zastaralé reference na vzdálené sledování 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) 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í watchers=Sledující
stargazers=Sledující stargazers=Sledující
stars_remove_warning=Tímto odstraníte všechny hvězdičky z tohoto repozitáře. 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 reactions_more=a %d dalších
unit_disabled=Správce webu zakázal tuto sekci repozitáře. unit_disabled=Správce webu zakázal tuto sekci repozitáře.
language_other=Jiný 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 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=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.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 úkoly. archive.issue.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat problémy.
archive.pull.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat požadavky na natažení. 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_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ářů. 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_wiki=Wiki
migrate_items_milestones=Milníky migrate_items_milestones=Milníky
migrate_items_labels=Štítky migrate_items_labels=Štítky
migrate_items_issues=Úkoly migrate_items_issues=Problémy
migrate_items_pullrequests=Žádosti o sloučení migrate_items_pullrequests=Žádosti o sloučení
migrate_items_merge_requests=Sloučit žádosti migrate_items_merge_requests=Sloučit žádosti
migrate_items_releases=Vydání migrate_items_releases=Vydání
@ -1178,12 +1180,12 @@ mirror_from=zrcadlo
forked_from=rozštěpen z forked_from=rozštěpen z
generated_from=generováno z generated_from=generováno z
fork_from_self=Nemůžete rozštěpit váš vlastní repozitář. 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. watch_guest_user=Pro sledování tohoto repozitáře se přihlaste.
star_guest_user=Pro hodnocení tohoto repozitáře se přihlaste. star_guest_user=Pro hodnocení tohoto repozitáře se přihlaste.
unwatch=Přestat sledovat unwatch=Přestat sledovat
watch=Sledovat watch=Sledovat
unstar=Odoblíbit unstar=Zrušit oblíbení
star=Oblíbit star=Oblíbit
fork=Rozštěpit fork=Rozštěpit
download_archive=Stáhnout repozitář 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 find_tag=Najít značku
branches=Větve branches=Větve
tags=Značky tags=Značky
issues=Úkoly issues=Problémy
pulls=Žádosti o sloučení pulls=Žádosti o sloučení
project_board=Projekty project_board=Projekty
packages=Balíčky 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.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.or=nebo
editor.cancel_lower=Zrušit editor.cancel_lower=Zrušit
editor.commit_signed_changes=Odevzdat podepsané změny editor.commit_signed_changes=Commitnout podepsané změny
editor.commit_changes=Odevzdat změny editor.commit_changes=Commitnout změny
editor.add_tmpl=Přidat „<nazevsouboru>“ editor.add_tmpl=Přidat „<nazevsouboru>“
editor.add=Přidat %s editor.add=Přidat %s
editor.update=Aktualizovat %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.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.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.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.create_new_branch_np=Vytvořte <strong>novou větev</strong> z tohoto commitu.
editor.propose_file_change=Navrhnout změnu souboru editor.propose_file_change=Navrhnout změnu souboru
editor.new_branch_name=Pojmenujte novou větev pro tento commit 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.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_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_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.file_already_exists=Soubor „%s“ již existuje v tomto repozitáři.
editor.commit_empty_file_header=Odevzdat prázdný soubor 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? 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 commitstatus.success=Úspěch
ext_issues=Přístup k externím problémům 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=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=Popis (volitelné)
projects.description_placeholder=Popis projects.description_placeholder=Popis
projects.create=Vytvořit projekt 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.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.create_success=Projekt „%s“ byl vytvořen.
projects.deletion=Odstranit projekt 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.deletion_success=Projekt byl odstraněn.
projects.edit=Upravit projekt 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.modify=Upravit projekt
projects.edit_success=Projekt „%s“ byl aktualizován. projects.edit_success=Projekt „%s“ byl aktualizován.
projects.type.none=Žádný 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.type.bug_triage=Třídění chyb
projects.template.desc=Šablona projects.template.desc=Šablona
projects.template.desc_helper=Začněte vybráním šablony projektu 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=Zrušit nastavení jako výchozí
projects.column.unset_default_desc=Zrušit nastavení tohoto sloupce jako výchozí projects.column.unset_default_desc=Zrušit nastavení tohoto sloupce jako výchozí
projects.column.delete=Odstranit sloupec 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.column.color=Barva
projects.open=Otevřít projects.open=Otevřít
projects.close=Zavřít projects.close=Zavřít
@ -1419,7 +1421,7 @@ issues.filter_reviewers=Filtrovat posuzovatele
issues.new=Nový problém issues.new=Nový problém
issues.new.title_empty=Název nesmí být prázdný issues.new.title_empty=Název nesmí být prázdný
issues.new.labels=Štítky 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.clear_labels=Zrušit štítky
issues.new.projects=Projekty issues.new.projects=Projekty
issues.new.clear_projects=Vymazat 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.get_started=Začínáme
issues.choose.open_external_link=Otevřít issues.choose.open_external_link=Otevřít
issues.choose.blank=Výchozí 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.ignore_invalid_templates=Neplatné šablony byly ignorovány
issues.choose.invalid_templates=%v nalezených neplatných šablon issues.choose.invalid_templates=%v nalezených neplatných šablon
issues.choose.invalid_config=Nastavení problému obsahuje chyby: 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=Autor
issues.filter_poster_no_select=Všichni autoři issues.filter_poster_no_select=Všichni autoři
issues.filter_type=Typ 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.assigned_to_you=Přiřazené vám
issues.filter_type.created_by_you=Vytvořené vámi issues.filter_type.created_by_you=Vytvořené vámi
issues.filter_type.mentioning_you=Zmiňující vás 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.farduedate=Nejvzdálenější datum dokončení
issues.filter_sort.moststars=Nejvíce hvězdiček issues.filter_sort.moststars=Nejvíce hvězdiček
issues.filter_sort.feweststars=Nejméně hvězdiček issues.filter_sort.feweststars=Nejméně hvězdiček
issues.filter_sort.mostforks=Nejvíce rozštěpení issues.filter_sort.mostforks=Nejvíce forků
issues.filter_sort.fewestforks=Nejméně rozštěpení 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.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_open=Otevřít
issues.action_close=Zavří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.closed_by_fake=od %[2]s byl uzavřen %[1]s
issues.previous=Předchozí issues.previous=Předchozí
issues.next=Další issues.next=Další
issues.open_title=otevřený issues.open_title=Otevřeno
issues.closed_title=zavřený issues.closed_title=Uzavřeno
issues.draft_title=Koncept issues.draft_title=Koncept
issues.num_comments_1=%d komentář issues.num_comments_1=%d komentář
issues.num_comments=%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_issue=Znovuotevřít
issues.reopen_comment_issue=Okomentovat a znovu otevřít issues.reopen_comment_issue=Okomentovat a znovu otevřít
issues.create_comment=Okomentovat issues.create_comment=Okomentovat
issues.closed_at=`uzavřel/a tento úkol <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=`znovuotevřel/a tento úkol <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 na tento úkol z commitu <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">odkazoval/a na tento úkol %[4]s</a> <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">odkazoval/a na tento požadavek na natažení %[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 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_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 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_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 úkol %[4]s</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 ú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 problém %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_from=`z %[1]s` issues.ref_from=`z %[1]s`
issues.author=Autor issues.author=Autor
issues.author_helper=Tento uživatel je 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=Přispěvatel
issues.role.contributor_helper=Tento uživatel již dříve přispíval do repozitáře. 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.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=Odstranit žádost o posouzení
issues.remove_request_review_block=Nelze odstranit žádost o posouzení issues.remove_request_review_block=Nelze odstranit žádost o posouzení
issues.dismiss_review=Zamítnout 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_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_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_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_edit=Upravit
issues.label_delete=Smazat issues.label_delete=Smazat
issues.label_modify=Upravit štítek issues.label_modify=Upravit štítek
issues.label_deletion=Odstranit š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_deletion_success=Štítek byl odstraněn.
issues.label.filter_sort.alphabetically=Od začátku abecedy issues.label.filter_sort.alphabetically=Od začátku abecedy
issues.label.filter_sort.reverse_alphabetically=Od konce abecedy issues.label.filter_sort.reverse_alphabetically=Od konce abecedy
issues.label.filter_sort.by_size=Nejmenší velikost issues.label.filter_sort.by_size=Nejmenší velikost
issues.label.filter_sort.reverse_by_size=Největší 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.open_tab=`Klikněte pro zobrazení „%s“ v nové záložce`
issues.attachment.download=`Klikněte pro stažení „%s“` issues.attachment.download=`Klikněte pro stažení „%s“`
issues.subscribe=Odebírat issues.subscribe=Odebírat
@ -1614,9 +1616,9 @@ issues.pin_comment=připnul/a tento %s
issues.unpin_comment=odepnul/a tento %s issues.unpin_comment=odepnul/a tento %s
issues.lock=Uzamknout konverzaci issues.lock=Uzamknout konverzaci
issues.unlock=Odemknout konverzaci issues.unlock=Odemknout konverzaci
issues.lock.unknown_reason=Úkol nelze z neznámého důvodu uzamknout. issues.lock.unknown_reason=Problém nelze z neznámého důvodu uzamknout.
issues.lock_duplicate=Úkol nemůže být uzamčený dvakrát. issues.lock_duplicate=Problém nemůže být uzamčený dvakrát.
issues.unlock_error=Nelze odemknout úkol, který je uzamčený. 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_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.lock_no_reason=uzamkl/a a omezil/a konverzaci na spolupracovníky %s
issues.unlock_comment=odemkl/a tuto konverzaci %s issues.unlock_comment=odemkl/a tuto konverzaci %s
@ -1624,22 +1626,22 @@ issues.lock_confirm=Uzamknout
issues.unlock_confirm=Odemknout issues.unlock_confirm=Odemknout
issues.lock.notice_1=- Další uživatelé nemohou komentovat tento problém. 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_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.lock.notice_3=- Vždy budete moci tento problém znovu odemknout.
issues.unlock.notice_1=- Všichni budou moci znovu komentovat tento úkol. issues.unlock.notice_1=- Všichni budou moci znovu komentovat tento problém.
issues.unlock.notice_2=- V budoucnu budete moci vždy znovu tento úkol uzamknout. issues.unlock.notice_2=- Vždy budete moci tento problém znovu uzamknout.
issues.lock.reason=Důvod pro uzamčení issues.lock.reason=Důvod pro uzamčení
issues.lock.title=Uzamknout konverzaci u tohoto úkolu. issues.lock.title=Uzamknout konverzaci u tohoto problému.
issues.unlock.title=Odemknout konverzaci u tohoto úkolu. issues.unlock.title=Odemknout konverzaci u tohoto problému.
issues.comment_on_locked=Nemůžete komentovat uzamčený úkol. issues.comment_on_locked=Nemůžete komentovat uzamčený problém.
issues.delete=Smazat issues.delete=Smazat
issues.delete.title=Smazat tento úkol? issues.delete.title=Smazat tento problém?
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.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.tracker=Sledování času
issues.start_tracking_short=Spustit časovač issues.start_tracking_short=Spustit časovač
issues.start_tracking=Spustit sledování času issues.start_tracking=Spustit sledování času
issues.start_tracking_history=`započal/a práci %s` issues.start_tracking_history=`započal/a práci %s`
issues.tracker_auto_close=Časovač se automaticky zastaví po zavření tohoto úkolu issues.tracker_auto_close=Časovač se automaticky zastaví po zavření tohoto problému
issues.tracking_already_started=`Již jste spustili sledování času na <a href="%s">jiném úkolu</a>!` 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=Zastavit časovač
issues.stop_tracking_history=`ukončil/a práci %s` issues.stop_tracking_history=`ukončil/a práci %s`
issues.cancel_tracking=Zahodit issues.cancel_tracking=Zahodit
@ -1686,27 +1688,27 @@ issues.dependency.remove=Odstranit
issues.dependency.remove_info=Odstranit tuto závislost issues.dependency.remove_info=Odstranit tuto závislost
issues.dependency.added_dependency=`přidal/a novou závislost %s` issues.dependency.added_dependency=`přidal/a novou závislost %s`
issues.dependency.removed_dependency=`odstranil/a 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.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 úkolu je blokováno následujícími úkoly 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 úkol blokuje uzavření následujících úkolů issues.dependency.issue_close_blocks=Tento problém blokuje uzavření následujících problémů
issues.dependency.pr_close_blocks=Tento požadavek na natažení blokuje uzavření následujících úkolů issues.dependency.pr_close_blocks=Tato žádost o sloučení blokuje uzavření následujících problémů
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.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.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.blocks_short=Blokuje
issues.dependency.blocked_by_short=Závisí na issues.dependency.blocked_by_short=Závisí na
issues.dependency.remove_header=Odstranit závislost issues.dependency.remove_header=Odstranit závislost
issues.dependency.issue_remove_text=Tímto krokem odeberete závislost z úkolu. 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 požadavku na natažení. 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.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_same_issue=Problém nemůže záviset sám na sobě.
issues.dependency.add_error_dep_issue_not_exist=Související úkol neexistuje. 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_not_exist=Závislost neexistuje.
issues.dependency.add_error_dep_exists=Závislost již existuje. 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_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 úkoly musí být ve stejném repozitáři. 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 svůj požadavek na natažení. 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ém vlastním požadavku na nataž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.approve=schválil/a tyto změny %s
issues.review.comment=posoudil/a %s issues.review.comment=posoudil/a %s
issues.review.dismissed=zamítl/a posouzení uživatele %s %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_base=základní
compare.compare_head=porovnat 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.new=Nová žádost o sloučení
pulls.view=Zobrazit žádost o sloučení pulls.view=Zobrazit žádost o sloučení
pulls.compare_changes=Nová žá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=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_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.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_viewed_file=Zobrazeno
pulls.has_changed_since_last_review=Změněno od vašeho posledního posouzení 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 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.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.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.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_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.nothing_to_compare_and_allow_empty_pr=Tyto větve jsou stejné. Tato žádost o slouč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.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.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.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 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_conversation=Konverzace
pulls.tab_commits=Commity pulls.tab_commits=Commity
pulls.tab_files=Změněné soubory 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.reopen_to_merge=Otevřete znovu tuto žádost pro provedení 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.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=Sloučený
pulls.merged_success=Požadavek na natažení byl úspěšně sloučen a uzavřen pulls.merged_success=Žádost byla úspěšně sloučena a uzavřena
pulls.closed=Požadavek na natažení uzavřen pulls.closed=Žádost o sloučení uzavřena
pulls.manually_merged=Sloučeno ručně pulls.manually_merged=Sloučeno ručně
pulls.merged_info_text=Větev %s může být nyní odstraněna. 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.is_closed=Žádost o sloučení byla uzavřena.
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.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=Tento požadavek na natažení je označen jako probíhající práce. 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.still_in_progress=Stále probíhá?
pulls.add_prefix=Přidat prefix <strong>%s</strong> pulls.add_prefix=Přidat prefix <strong>%s</strong>
pulls.remove_prefix=Odstranit 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.data_broken=Tato žádost o sloučení je rozbitá kvůli chybějícím informacím o forku.
pulls.files_conflicted=Tento požadavek na natažení obsahuje změny, které kolidují s cílovou větví. 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_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_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.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_failed=Některé požadované kontroly nebyly úspěšné.
pulls.required_status_check_missing=Některé požadované kontroly chybí. 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_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_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_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_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_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.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.can_auto_merge_desc=Tato žádost může být automaticky sloučena.
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.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.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_1=%d konfliktní soubor
pulls.num_conflicting_files_n=%d konfliktních 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.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.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_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í požadavku na natažení ručně. 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=Požadavek na natažení nemůže být sloučen protože je označen jako nedokončený. 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 požadavek na natažení není připraven na sloučení, zkontrolujte stav posouzení a kontrolu stavu. 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 tento požadavek na natažení. pulls.no_merge_access=Nemáte oprávnění sloučit tuto žádost.
pulls.merge_pull_request=Vytvořit slučovací commit pulls.merge_pull_request=Vytvořit slučovací commit
pulls.rebase_merge_pull_request=Rebase pak fast-forward pulls.rebase_merge_pull_request=Rebase pak fast-forward
pulls.rebase_merge_commit_pull_request=Rebase a poté vytvořit slučovací commit 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.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.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=Sloučení selhalo: Došlo ke konfliktu při sloučení. Tip: Zkuste jinou strategii
pulls.merge_conflict_summary=Chybové hlášení 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 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.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.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.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=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_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ář 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.open=Otevřít
milestones.close=Zavřít milestones.close=Zavřít
milestones.new_subheader=Milníky vám pomohou organizovat úkoly a sledovat jejich pokrok. 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.create=Vytvořit milník
milestones.title=Název milestones.title=Název
milestones.desc=Popis milestones.desc=Popis
milestones.due_date=Termín (volitelný) 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.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.create_success=Milník „%s“ byl vytvořen.
milestones.edit=Upravit milník 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.cancel=Zrušit
milestones.modify=Upravit milník milestones.modify=Upravit milník
milestones.edit_success=Milník „%s“ byl aktualizován. milestones.edit_success=Milník „%s“ byl aktualizován.
milestones.deletion=Odstranit milník 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.deletion_success=Milník byl odstraněn.
milestones.filter_sort.earliest_due_data=Nejbližší termín dokončení 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.latest_due_date=Nejzazší termín dokončení
milestones.filter_sort.least_complete=Nejmé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_complete=Nejvíce dokončené
milestones.filter_sort.most_issues=Nejvíce úkolů milestones.filter_sort.most_issues=Nejvíce problémů
milestones.filter_sort.least_issues=Nejméně úkolů milestones.filter_sort.least_issues=Nejméně problémů
signing.will_sign=Tento commit bude podepsána klíčem „%s“. 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. 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.new_issue_label=Otevřený
activity.title.unresolved_conv_1=%d nevyřešená konverzace activity.title.unresolved_conv_1=%d nevyřešená konverzace
activity.title.unresolved_conv_n=%d nevyřešených konverzací 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.unresolved_conv_label=Otevřít
activity.title.releases_1=%d vydání activity.title.releases_1=%d vydání
activity.title.releases_n=%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_internal_issue_tracker=Použít vestavěný systém problémů
settings.use_external_issue_tracker=Použít externí 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=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_error=Adresa URL externího systému problémů není platnou adresou 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_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=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=Formát čísel externího systému problémů
settings.tracker_issue_style.numeric=Číselný settings.tracker_issue_style.numeric=Číselný
settings.tracker_issue_style.alphanumeric=Alfanumerický settings.tracker_issue_style.alphanumeric=Alfanumerický
settings.tracker_issue_style.regexp=Regulární výraz 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=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_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.enable_timetracker=Povolit sledování času
settings.allow_only_contributors_to_track_time=Povolit sledování času pouze přispěvatelům settings.allow_only_contributors_to_track_time=Povolit sledování času pouze přispěvatelům
settings.pulls_desc=Povolit žádosti o sloučení 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.admin_indexer_unindexed=Neindexováno
settings.reindex_button=Přidat do fronty reindexace settings.reindex_button=Přidat do fronty reindexace
settings.reindex_requested=Požadováno reindexování 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.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.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ář 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_confirm=Převést repozitář
settings.convert_succeed=Zrcadlo bylo převedeno na běžný 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=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_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 rozštěpení na běžný repozitář a nelze ji vrátit zpět. 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_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=Předat vlastnictví
settings.transfer.rejected=Převod repozitáře byl zamítnut. settings.transfer.rejected=Převod repozitáře byl zamítnut.
settings.transfer.success=Převod repozitáře byl úspěšný. 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=Odstranit tento repozitář
settings.delete_desc=Smazání repozitáře je trvalé a nemůže být vráceno zpět. 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_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_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=- Rozštěpení repozitáře bude nezávislé po smazání. settings.delete_notices_fork_1=- Fork tohoto repozitáře bude po smazání nezávislý.
settings.deletion_success=Repozitář byl odstraněn. settings.deletion_success=Repozitář byl odstraněn.
settings.update_settings_success=Nastavení repozitáře bylo aktualizováno. 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. 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=Repozitář
settings.event_repository_desc=Repozitář vytvořen nebo smazán. settings.event_repository_desc=Repozitář vytvořen nebo smazán.
settings.event_header_issue=Události problémů settings.event_header_issue=Události problémů
settings.event_issues=Úkoly settings.event_issues=Problémy
settings.event_issues_desc=Úkol otevřen, uzavřen, znovu otevřen nebo upraven. 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=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=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=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=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_header_pull_request=Události žádosti o sloučení
settings.event_pull_request=Žádost 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. 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_telegram=Telegram
settings.web_hook_name_matrix=Matrix settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work) settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist settings.web_hook_name_packagist=Packagist
settings.packagist_username=Uživatelské jméno pro 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.archive.tagsettings_unavailable=Nastavení značek není k dispozici, pokud je repozitář archivován.
settings.unarchive.button=Zrušit archivaci repozitáře settings.unarchive.button=Zrušit archivaci repozitáře
settings.unarchive.header=Obnovit tento repozitář 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.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.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. 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 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 open_with_editor = Otevřít pomocí %s
commits.search_branch = Tato větev 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] [graphs]
component_loading_info = Tohle může chvíli trvat… 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.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.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.membership_visibility=Viditelnost členství:
members.public=Viditelný members.public=Viditelný
@ -3028,7 +3044,7 @@ repos.private=Soukromý
repos.watches=Sledovače repos.watches=Sledovače
repos.stars=Oblíbení repos.stars=Oblíbení
repos.forks=Rozštěpení repos.forks=Rozštěpení
repos.issues=Úkoly repos.issues=Problémy
repos.size=Velikost repos.size=Velikost
repos.lfs_size=Velikost LFS 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.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.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.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.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.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í 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.lfs_root_path=Kořenový adresář LFS
config.log_file_root_path=Adresář protokolů config.log_file_root_path=Adresář protokolů
config.script_type=Typ skriptu 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_config=Nastavení SSH
config.ssh_enabled=Zapnutý 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.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.no_reply_address=Skrytá e-mailová doména
config.default_visibility_organization=Výchozí viditelnost nových organizací 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.webhook_config=Nastavení webhooků
config.queue_length=Délka fronty config.queue_length=Délka fronty
@ -3391,14 +3407,15 @@ auths.tips.gmail_settings = Nastavení služby Gmail:
config_summary = Souhrn 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.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í config_settings = Nastavení
auths.tip.gitlab_new = Zaregistrujte si novou aplikaci na https://gitlab.com/-/profile/applications
[action] [action]
create_repo=vytvořil/a repozitář <a href="%s">%s</a> 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> 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> 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>` create_issue=`otevřel/a problém <a href="%[1]s">%[3]s#%[2]s</a>`
close_issue=`uzavřel/a úkol <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=`znovuotevřel/a úkol <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>` 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>` 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>` 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 runs.pushed_by = pushnuto uživatelem
need_approval_desc = Potřebovat schválení pro spouštění workflowů pro žádosti o sloučení forků. 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ů runners.runner_manage_panel = Správa runnerů
runs.no_job_without_needs = Workflow musí obsahovat alespoň jednu práci bez závislostí.
[projects] [projects]
type-1.display_name=Samostatný projekt type-1.display_name=Samostatný projekt
@ -3781,4 +3799,19 @@ runner_kind = Hledat runnery...
no_results = Nenalezeny žádné odpovídající výsledky. no_results = Nenalezeny žádné odpovídající výsledky.
fuzzy_tooltip = Zahrnout také výsledky, které úzce odpovídají hledanému výrazu fuzzy_tooltip = Zahrnout také výsledky, které úzce odpovídají hledanému výrazu
search = Hledat... 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.not_template = Keine Vorlage
filter.public = Öffentlich filter.public = Öffentlich
filter.private = Privat filter.private = Privat
more_items = Mehr Einträge
invalid_data = Ungültige Daten: %v
[aria] [aria]
navbar=Navigationsleiste navbar=Navigationsleiste
@ -979,10 +981,10 @@ user_unblock_success = Die Blockierung dieses Benutzers wurde erfolgreich zurüc
blocked_users = Blockierte Benutzer blocked_users = Blockierte Benutzer
blocked_since = Blockiert seit %s blocked_since = Blockiert seit %s
change_password = Passwort ändern change_password = Passwort ändern
hints = Tipps hints = Hinweise
additional_repo_units_hint = Zur Aktivierung zusätzlicher Repository-Einheiten ermutigen additional_repo_units_hint = Zur Aktivierung zusätzlicher Repository-Einheiten ermutigen
update_hints = Tipps aktualisieren update_hints = Hinweise aktualisieren
update_hints_success = Tipps wurden aktualisiert. 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. additional_repo_units_hint_description = Einen „Mehr Einheiten hinzufügen …“-Button für Repositorys, welche nicht alle verfügbaren Einheiten aktiviert haben, anzeigen.
[repo] [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=Standard entfernen
projects.column.unset_default_desc=Diese Spalte nicht als Standard verwenden projects.column.unset_default_desc=Diese Spalte nicht als Standard verwenden
projects.column.delete=Spalte löschen 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.column.color=Farbe
projects.open=Öffnen projects.open=Öffnen
projects.close=Schließen projects.close=Schließen
@ -1411,7 +1413,7 @@ issues.filter_reviewers=Reviewer filtern
issues.new=Neues Issue issues.new=Neues Issue
issues.new.title_empty=Der Titel kann nicht leer sein issues.new.title_empty=Der Titel kann nicht leer sein
issues.new.labels=Labels issues.new.labels=Labels
issues.new.no_label=Kein Label issues.new.no_label=Keine Label
issues.new.clear_labels=Labels entfernen issues.new.clear_labels=Labels entfernen
issues.new.projects=Projekte issues.new.projects=Projekte
issues.new.clear_projects=Projekte löschen 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.reverse_alphabetically=Umgekehrt alphabetisch
issues.label.filter_sort.by_size=Kleinste Größe issues.label.filter_sort.by_size=Kleinste Größe
issues.label.filter_sort.reverse_by_size=Größte 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.open_tab=`Klicken, um „%s“ in einem neuen Tab zu öffnen`
issues.attachment.download=`Klicken, um „%s“ herunterzuladen` issues.attachment.download=`Klicken, um „%s“ herunterzuladen`
issues.subscribe=Abonnieren issues.subscribe=Abonnieren
@ -2302,9 +2304,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work) settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist settings.web_hook_name_packagist=Packagist
settings.packagist_username=Benutzername für 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 open_with_editor = Öffnen mit %s
commits.search_branch = Dieser Branch commits.search_branch = Dieser Branch
pulls.ready_for_review = Bereit zum Review? 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] [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.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.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.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.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.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 auths.edit=Authentifikationsquelle bearbeiten
@ -3169,7 +3181,7 @@ config.repo_root_path=Repository-Wurzelpfad
config.lfs_root_path=LFS-Wurzelpfad config.lfs_root_path=LFS-Wurzelpfad
config.log_file_root_path=Logdateipfad config.log_file_root_path=Logdateipfad
config.script_type=Skript-Typ 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_config=SSH-Konfiguration
config.ssh_enabled=Aktiviert config.ssh_enabled=Aktiviert
@ -3358,6 +3370,7 @@ auths.tips.gmail_settings = Gmail-Einstellungen:
config_settings = 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.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 config_summary = Zusammenfassung
auths.tip.gitlab_new = Registriere eine neue Anwendung auf https://gitlab.com/-/profile/applications
[action] [action]
@ -3711,6 +3724,7 @@ runs.no_workflows.documentation = Für weitere Informationen über Forgejo Actio
runs.empty_commit_message = (leere Commit-Nachricht) runs.empty_commit_message = (leere Commit-Nachricht)
variables.id_not_exist = Variable mit ID %d existiert nicht. variables.id_not_exist = Variable mit ID %d existiert nicht.
runs.workflow = Workflow runs.workflow = Workflow
runs.no_job_without_needs = Der Workflow muss mindestens einen Job ohne Abhängigkeiten enthalten.
[projects] [projects]
type-1.display_name=Individuelles Projekt type-1.display_name=Individuelles Projekt
@ -3757,4 +3771,10 @@ commit_kind = Commits suchen …
runner_kind = Runners suchen … runner_kind = Runners suchen …
no_results = Keine passenden Ergebnisse gefunden. no_results = Keine passenden Ergebnisse gefunden.
code_search_unavailable = Die Code-Suche ist momentan nicht verfügbar. Bitte kontaktiere den Webseitenadministrator. 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.reverse_alphabetically=Αντίστροφα αλφαβητικά
issues.label.filter_sort.by_size=Μικρότερο μέγεθος issues.label.filter_sort.by_size=Μικρότερο μέγεθος
issues.label.filter_sort.reverse_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.open_tab=`Πατήστε εδώ για να ανοίξετε το «%s» σε μια νέα καρτέλα`
issues.attachment.download=`Πατήστε εδώ για να κατεβάσετε το «%s»` issues.attachment.download=`Πατήστε εδώ για να κατεβάσετε το «%s»`
issues.subscribe=Εγγραφή issues.subscribe=Εγγραφή
@ -2293,9 +2293,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work) settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist settings.web_hook_name_packagist=Packagist
settings.packagist_username=Όνομα χρήστη Packagist settings.packagist_username=Όνομα χρήστη Packagist

View file

@ -146,15 +146,15 @@ name = Name
value = Value value = Value
filter = Filter filter = Filter
filter.clear = Clear Filter filter.clear = Clear filters
filter.is_archived = Archived filter.is_archived = Archived
filter.not_archived = Not Archived filter.not_archived = Not archived
filter.is_fork = Forked filter.is_fork = Forked
filter.not_fork = Not Forked filter.not_fork = Not forked
filter.is_mirror = Mirrored filter.is_mirror = Mirrored
filter.not_mirror = Not Mirrored filter.not_mirror = Not mirrored
filter.is_template = Template filter.is_template = Template
filter.not_template = Not Template filter.not_template = Not template
filter.public = Public filter.public = Public
filter.private = Private filter.private = Private
@ -222,7 +222,7 @@ missing_csrf = Bad Request: no CSRF token present
invalid_csrf = Bad Request: invalid CSRF token invalid_csrf = Bad Request: invalid CSRF token
not_found = The target couldn't be found. not_found = The target couldn't be found.
network_error = Network error network_error = Network error
server_internal = Internal Server Error server_internal = Internal server error
[startpage] [startpage]
app_desc = A painless, self-hosted Git service 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. 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. password_change_disabled = Non-local users cannot update their password through the Forgejo web interface.
emails = Email addresses
manage_emails = Manage email addresses manage_emails = Manage email addresses
manage_themes = Select default theme manage_themes = Select default theme
manage_openid = Manage OpenID addresses manage_openid = Manage OpenID addresses
@ -893,9 +892,9 @@ repo_and_org_access = Repository and Organization Access
permissions_public_only = Public only permissions_public_only = Public only
permissions_access_all = All (public, private, and limited) permissions_access_all = All (public, private, and limited)
select_permissions = Select permissions select_permissions = Select permissions
permission_no_access = No Access permission_no_access = No access
permission_read = Read 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. 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 at_least_one_permission = You must select at least one permission to create a token
permissions_list = Permissions: 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. oauth2_redirect_uris = Redirect URIs. Please use a new line for every URI.
save_application = Save save_application = Save
oauth2_client_id = Client ID oauth2_client_id = Client ID
oauth2_client_secret = Client Secret oauth2_client_secret = Client secret
oauth2_regenerate_secret = Regenerate Secret oauth2_regenerate_secret = Regenerate secret
oauth2_regenerate_secret_hint = Lost your 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_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 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 = 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. 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_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_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. 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 = Repository name
repo_name_helper = Good repository names use short, memorable and unique keywords. repo_name_helper = Good repository names use short, memorable and unique keywords.
repo_size = Repository Size repo_size = Repository Size
size_format = %[1]s: %[2]s, %[3]s: %[4]s
template = Template template = Template
template_select = Select a template. template_select = Select a template.
template_helper = Make repository 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 need_auth = Authorization
migrate_options = Migration options migrate_options = Migration options
migrate_service = Migration service
migrate_options_mirror_helper = This repository will be a mirror migrate_options_mirror_helper = This repository will be a mirror
migrate_options_lfs = Migrate LFS files migrate_options_lfs = Migrate LFS files
migrate_options_lfs_endpoint.label = LFS endpoint migrate_options_lfs_endpoint.label = LFS endpoint
@ -1254,11 +1253,11 @@ n_tag_few=%s tags
released_this = released this released_this = released this
file.title = %s at %s file.title = %s at %s
file_raw = Raw file_raw = Raw
file_follow = Follow Symlink file_follow = Follow symlink
file_history = History file_history = History
file_view_source = View Source file_view_source = View source
file_view_rendered = View Rendered file_view_rendered = View rendered
file_view_raw = View Raw file_view_raw = View raw
file_permalink = Permalink file_permalink = Permalink
file_too_large = The file is too large to be shown. file_too_large = The file is too large to be shown.
invisible_runes_header = `This file contains invisible Unicode characters` 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.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.or = or
editor.cancel_lower = Cancel editor.cancel_lower = Cancel
editor.commit_signed_changes = Commit Signed Changes editor.commit_signed_changes = Commit signed changes
editor.commit_changes = Commit Changes editor.commit_changes = Commit changes
editor.add_tmpl = Add "<filename>" editor.add_tmpl = Add "<filename>"
editor.add = Add %s editor.add = Add %s
editor.update = Update %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.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_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_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.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.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_header = Commit an empty file
editor.commit_empty_file_text = The file you're about to commit is empty. Proceed? 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.modify = Edit project
projects.edit_success = Project "%s" has been updated. projects.edit_success = Project "%s" has been updated.
projects.type.none = None projects.type.none = None
projects.type.basic_kanban = Basic Kanban projects.type.basic_kanban = Basic kanban
projects.type.bug_triage = Bug Triage projects.type.bug_triage = Bug triage
projects.template.desc = Template projects.template.desc = Template
projects.template.desc_helper = Select a project template to get started 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.edit_title = Name
projects.column.new_title = Name projects.column.new_title = Name
projects.column.new_submit = Create Column projects.column.new_submit = Create column
projects.column.new = New Column projects.column.new = New column
projects.column.set_default = Set Default projects.column.set_default = Set default
projects.column.set_default_desc = Set this column as default for uncategorized issues and pulls 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.deletion_desc = Deleting a project column moves all related issues to the default column. Continue?
projects.column.color = Color projects.column.color = Color
projects.open = Open projects.open = Open
projects.close = Close projects.close = Close
projects.column.assigned_to = Assigned to projects.column.assigned_to = Assigned to
projects.card_type.desc = Card Previews projects.card_type.desc = Card previews
projects.card_type.images_and_text = Images and Text projects.card_type.images_and_text = Images and text
projects.card_type.text_only = Text Only projects.card_type.text_only = Text only
issues.desc = Organize bug reports, tasks and milestones. issues.desc = Organize bug reports, tasks and milestones.
issues.filter_assignees = Filter Assignee issues.filter_assignees = Filter Assignee
@ -1449,7 +1448,7 @@ issues.filter_reviewers = Filter Reviewer
issues.new = New issue issues.new = New issue
issues.new.title_empty = Title cannot be empty issues.new.title_empty = Title cannot be empty
issues.new.labels = Labels issues.new.labels = Labels
issues.new.no_label = No label issues.new.no_label = No labels
issues.new.clear_labels = Clear labels issues.new.clear_labels = Clear labels
issues.new.projects = Projects issues.new.projects = Projects
issues.new.clear_projects = Clear 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.is_stale = There have been changes to this PR since this review
issues.remove_request_review=Remove review request issues.remove_request_review=Remove review request
issues.remove_request_review_block=Can't 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.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.sign_in_require_desc = <a href="%s">Sign in</a> to join this conversation.
issues.edit = Edit 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.reverse_alphabetically = Reverse alphabetically
issues.label.filter_sort.by_size = Smallest size issues.label.filter_sort.by_size = Smallest size
issues.label.filter_sort.reverse_by_size = Largest 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.open_tab = `Click to see "%s" in a new tab`
issues.attachment.download = `Click to download "%s"` issues.attachment.download = `Click to download "%s"`
issues.subscribe = Subscribe 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.approved = The merge will not be signed as the PR is not approved.
signing.wont_sign.not_signed_in = You are not signed in. 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. ext_wiki.desc = Link to an external wiki.
wiki = 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_review_request_desc = Pull request review requested or review request removed.
settings.event_pull_request_approvals = Pull request approvals settings.event_pull_request_approvals = Pull request approvals
settings.event_pull_request_merge = Pull request merge settings.event_pull_request_merge = Pull request merge
settings.event_pull_request_enforcement = Enforcement
settings.event_package = Package settings.event_package = Package
settings.event_package_desc = Package created or deleted in a repository. settings.event_package_desc = Package created or deleted in a repository.
settings.branch_filter = Branch filter 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_telegram = Telegram
settings.web_hook_name_matrix = Matrix settings.web_hook_name_matrix = Matrix
settings.web_hook_name_msteams = Microsoft Teams settings.web_hook_name_msteams = Microsoft Teams
settings.web_hook_name_feishu_or_larksuite = Feishu / Lark Suite settings.web_hook_name_feishu = Feishu / Lark Suite
settings.web_hook_name_feishu = Feishu settings.web_hook_name_feishu_only = Feishu
settings.web_hook_name_larksuite = Lark Suite settings.web_hook_name_larksuite_only = Lark Suite
settings.web_hook_name_wechatwork = WeCom (Wechat Work) settings.web_hook_name_wechatwork = WeCom (Wechat Work)
settings.web_hook_name_packagist = Packagist settings.web_hook_name_packagist = Packagist
settings.packagist_username = Packagist username 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_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 = 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.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.default_branch_desc = Select a default repository branch for pull requests and code commits:
settings.merge_style_desc = Merge styles settings.merge_style_desc = Merge styles
settings.default_merge_style_desc = Default merge style 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.lfs_root_path = LFS root path
config.log_file_root_path = Log path config.log_file_root_path = Log path
config.script_type = Script type 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_config = SSH configuration
config.ssh_enabled = Enabled config.ssh_enabled = Enabled
@ -3416,6 +3419,15 @@ years = %d years
raw_seconds = seconds raw_seconds = seconds
raw_minutes = minutes raw_minutes = minutes
[munits.data]
b = B
kib = KiB
mib = MiB
gib = GiB
tib = TiB
pib = PiB
eib = EiB
[dropzone] [dropzone]
default_message = Drop files or click here to upload. default_message = Drop files or click here to upload.
invalid_input_type = You cannot upload files of this type. invalid_input_type = You cannot upload files of this type.
@ -3727,3 +3739,8 @@ normal_file = Normal file
executable_file = Executable file executable_file = Executable file
symbolic_link = Symbolic link symbolic_link = Symbolic link
submodule = Submodule 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.reverse_alphabetically=Invertir alfabéticamente
issues.label.filter_sort.by_size=Tamaño más pequeño 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.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.open_tab='Haga clic para ver "%s" en una pestaña nueva'
issues.attachment.download=`Haga clic para descargar "%s"` issues.attachment.download=`Haga clic para descargar "%s"`
issues.subscribe=Suscribir issues.subscribe=Suscribir
@ -2267,9 +2267,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work) settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nombre de usuario 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.reverse_alphabetically=برعکس ترتیب الفبا
issues.label.filter_sort.by_size=کوچکترین اندازه issues.label.filter_sort.by_size=کوچکترین اندازه
issues.label.filter_sort.reverse_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.open_tab=برای مشاهده "%s" در زبانه جدید، کلیک کنید
issues.attachment.download=`برای دریافت "%s" کلیک کنید` issues.attachment.download=`برای دریافت "%s" کلیک کنید`
issues.subscribe=مشترک شدن 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.reverse_alphabetically=Käänteisessä aakkosjärjestyksessä
issues.label.filter_sort.by_size=Pienin koko issues.label.filter_sort.by_size=Pienin koko
issues.label.filter_sort.reverse_by_size=Suurin 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.subscribe=Tilaa
issues.unsubscribe=Lopeta tilaus issues.unsubscribe=Lopeta tilaus
issues.lock=Lukitse keskustelu issues.lock=Lukitse keskustelu
@ -1184,8 +1184,8 @@ settings.web_hook_name_discord=Discord
settings.web_hook_name_dingtalk=DingTalk settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix settings.web_hook_name_matrix=Matrix
settings.web_hook_name_feishu=Feishu settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_packagist=Packagist settings.web_hook_name_packagist=Packagist
settings.deploy_keys=Julkaisuavaimet settings.deploy_keys=Julkaisuavaimet
settings.add_deploy_key=Lisää julkaisuavain settings.add_deploy_key=Lisää julkaisuavain

View file

@ -7,7 +7,7 @@ language = Wika
mirrors = Mga Mirror mirrors = Mga Mirror
forks = Mga Fork forks = Mga Fork
activities = Mga Aktibidad activities = Mga Aktibidad
pull_requests = Mga Pull Request pull_requests = Mga pull pequest
issues = Mga Isyu issues = Mga Isyu
milestones = Mga Milestone milestones = Mga Milestone
ok = OK ok = OK
@ -18,10 +18,10 @@ save = I-save
add = Magdagdag add = Magdagdag
remove_all = Tanggalin lahat remove_all = Tanggalin lahat
remove_label_str = Tanggalin ang item "%s" remove_label_str = Tanggalin ang item "%s"
edit = I-edit edit = Baguhin
enabled = Naka-enable enabled = Naka-enable
copy = Kopyahin copy = Kopyahin
copy_content = Kopyahin ang content copy_content = Kopyahin ang nilalaman
copy_branch = Kopyahin ang pangalan ng branch copy_branch = Kopyahin ang pangalan ng branch
copy_success = Kinopya! copy_success = Kinopya!
copy_error = Nabigo ang pagkopya 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. error404 = Ang pahina na sinusubukan mong bisitahin ay alinman <strong>hindi umiiral</strong> o <strong>wala kang pahintulot</strong> para itignan.
version = Bersyon version = Bersyon
powered_by = Pinapatakbo ng %s powered_by = Pinapatakbo ng %s
explore = Mag-explore explore = Tuklasin
help = Tulong help = Tulong
logo = Logo logo = Logo
sign_in = Mag-Sign In 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_in_or = o
sign_out = Mag-Sign Out sign_out = Mag-Sign Out
sign_up = Magrehistro sign_up = Magrehistro
@ -79,19 +79,19 @@ webauthn_error_empty = Kailangan mong maglapat ng pangalan para sa key na ito.
webauthn_reload = I-reload webauthn_reload = I-reload
repository = Repository repository = Repository
organization = Organisasyon organization = Organisasyon
mirror = Mirror mirror = Salamin
new_repo = Bagong repository new_repo = Bagong repository
new_migrate = Bagong migration new_migrate = Bagong migration
new_mirror = Bagong mirror new_mirror = Bagong salamin
new_fork = Bagong repository fork new_fork = Bagong repository fork
new_org = Bagong organisasyon new_org = Bagong organisasyon
new_project = Bagong proyekto new_project = Bagong proyekto
new_project_column = Bagong column new_project_column = Bagong column
admin_panel = Pangangasiwa ng Site admin_panel = Pangangasiwa ng site
account_settings = Mga Setting ng Account account_settings = Mga setting ng Account
settings = Mga Setting settings = Mga Setting
your_profile = Profile your_profile = Profile
your_starred = Naka-star your_starred = Naka-bitwin
your_settings = Mga Setting your_settings = Mga Setting
all = Lahat all = Lahat
go_back = Bumalik go_back = Bumalik
@ -126,10 +126,10 @@ filter.public = Publiko
filter.private = Pribado filter.private = Pribado
notifications = Mga Abiso notifications = Mga Abiso
active_stopwatch = Aktibong Tagasubaybay ng Oras active_stopwatch = Aktibong Tagasubaybay ng Oras
locked = Naka-lock locked = Naka-kandado
preview = I-preview preview = I-preview
confirm_delete_artifact = Sigurado ka bang gusto mong burahin ang artifact na "%s"? 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 add_all = Idagdag lahat
copy_hash = Kopyahin ang hash copy_hash = Kopyahin ang hash
error = Error error = Error
@ -138,6 +138,8 @@ loading = Naglo-load…
confirm_delete_selected = Kumpirmahin na burahin ang lahat ng piniling item? confirm_delete_selected = Kumpirmahin na burahin ang lahat ng piniling item?
home = Panimula home = Panimula
dashboard = Dashboard dashboard = Dashboard
more_items = Higit pang mga item
invalid_data = Hindi wastong data: %v
[home] [home]
search_repos = Maghanap ng Repository… 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_reserved = Hindi angkop ang Administrator Username, naka-reserve ang username
err_admin_name_is_invalid = Hindi angkop ang Administrator Username err_admin_name_is_invalid = Hindi angkop ang Administrator Username
general_title = Mga General Setting 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. 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_helper = Ang mga remote Git repository ay mase-save sa directory na ito.
repo_path = Root path ng Repository 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_db_table = Hindi angkop ang database table na "%s": %v
invalid_repo_path = Hindi angkop ang repository root path: %v invalid_repo_path = Hindi angkop ang repository root path: %v
invalid_app_data_path = Hindi angkop ang app data 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 internal_token_failed = Nabigong maka-generate ng internal token: %v
secret_key_failed = Nabigong maka-generate ng secret key: %v secret_key_failed = Nabigong maka-generate ng secret key: %v
save_config_failed = Nabigong i-save ang configuration: %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 install = Madaling i-install
platform = Cross-platform 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! 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! lightweight_desc = Mababa ang minimal requirements ng Forgejo at tatakbo sa isang murang Raspberry Pi. Tipirin ang enerhiya ng iyong machine!
license = Open Source 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>. 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. 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? 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 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] [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. 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 fork_from = I-fork mula sa
already_forked = Na-fork mo na ang %s already_forked = Na-fork mo na ang %s
fork_to_different_account = Mag-fork sa ibang account 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 = Filtre
filter.is_mirror = Répliqué filter.is_mirror = Répliqué
toggle_menu = Menu va-et-vient toggle_menu = Menu va-et-vient
more_items = Plus d'éléments
invalid_data = Données invalides: %v
[aria] [aria]
navbar=Barre de navigation 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_unblock_success = Cet utilisateur a été débloqué avec succès.
user_block_success = Cet utilisateur a été bloqué avec succès. user_block_success = Cet utilisateur a été bloqué avec succès.
change_password = Modifier le mot de passe 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] [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> 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_history=Historique
file_view_source=Voir le code source file_view_source=Voir le code source
file_view_rendered=Voir le rendu file_view_rendered=Voir le rendu
file_view_raw=Voir le Raw file_view_raw=Voir le contenu brut
file_permalink=Lien permanent file_permalink=Lien permanent
file_too_large=Le fichier est trop gros pour être affiché. file_too_large=Le fichier est trop gros pour être affiché.
invisible_runes_header=`Ce fichier contient des caractères Unicode invisibles.` 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=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.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.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.column.color=Couleur
projects.open=Ouvrir projects.open=Ouvrir
projects.close=Fermer 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.reverse_alphabetically=Par ordre alphabétique inversé
issues.label.filter_sort.by_size=Plus petite taille issues.label.filter_sort.by_size=Plus petite taille
issues.label.filter_sort.reverse_by_size=Plus grande 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.open_tab=`Cliquez ici pour voir « %s » dans un nouvel onglet.`
issues.attachment.download=`Cliquez pour télécharger « %s ».` issues.attachment.download=`Cliquez pour télécharger « %s ».`
issues.subscribe=Sabonner 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_name=Nom du hook
settings.githook_content=Contenu du Hook settings.githook_content=Contenu du Hook
settings.update_githook=Mettre le Hook à jour 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.payload_url=URL cible
settings.http_method=Méthode HTTP settings.http_method=Méthode HTTP
settings.content_type=Type de contenu POST 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_telegram=Telegram
settings.web_hook_name_matrix=Matrix settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work) settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nom d'utilisateur 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.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.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> 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] [graphs]
component_loading=Chargement de %s… component_loading=Chargement de %s…
@ -3754,6 +3773,7 @@ component_failed_to_load = Une erreur inattendue s'est produite.
contributors.what = contributions contributors.what = contributions
component_loading = Chargement %s... component_loading = Chargement %s...
component_loading_failed = Échec de chargement de %s component_loading_failed = Échec de chargement de %s
code_frequency.what = fŕequence de code code_frequency.what = fŕequence de code
recent_commits.what = commits récents recent_commits.what = commits récents
@ -3761,4 +3781,21 @@ recent_commits.what = commits récents
[search] [search]
search = Rechercher... search = Rechercher...
type_tooltip = Type de recherche 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.reverse_alphabetically=Fordított betűrendben
issues.label.filter_sort.by_size=Legkisebb méret issues.label.filter_sort.by_size=Legkisebb méret
issues.label.filter_sort.reverse_by_size=Legnagyobb 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.open_tab=`A(z) "%s" megnyitása új fülön`
issues.attachment.download=`Kattintson a(z) "%s" letöltéséhez` issues.attachment.download=`Kattintson a(z) "%s" letöltéséhez`
issues.subscribe=Feliratkozás issues.subscribe=Feliratkozás

View file

@ -729,7 +729,7 @@ issues.label_edit=Sunting
issues.label_delete=Hapus issues.label_delete=Hapus
issues.label.filter_sort.alphabetically=Urutan abjad issues.label.filter_sort.alphabetically=Urutan abjad
issues.label.filter_sort.reverse_alphabetically=Membalikkan menurut 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.open_tab=`Klik untuk melihat "%s" di tab baru`
issues.attachment.download=`Klik untuk mengunduh "%s"` issues.attachment.download=`Klik untuk mengunduh "%s"`
issues.subscribe=Berlangganan 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_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram settings.web_hook_name_telegram=Telegram
settings.web_hook_name_msteams=Microsoft Teams settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu=Feishu settings.web_hook_name_feishu_only =Feishu
settings.title=Heiti settings.title=Heiti
settings.deploy_key_content=Innihald settings.deploy_key_content=Innihald
settings.branches=Greinar 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.reverse_alphabetically=In ordine alfabetico inverso
issues.label.filter_sort.by_size=Dimensione più piccola issues.label.filter_sort.by_size=Dimensione più piccola
issues.label.filter_sort.reverse_by_size=Dimensione più grande 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.open_tab=`Clicca per vedere "%s" in una nuova scheda`
issues.attachment.download=`Clicca qui per scaricare "%s"` issues.attachment.download=`Clicca qui per scaricare "%s"`
issues.subscribe=Iscriviti issues.subscribe=Iscriviti
@ -2085,9 +2085,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work) settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nome utente Packagist settings.packagist_username=Nome utente Packagist

View file

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

View file

@ -782,7 +782,7 @@ issues.label_deletion_desc=라벨을 삭제하면 모든 이슈로부터도 삭
issues.label_deletion_success=라벨이 삭제되었습니다. issues.label_deletion_success=라벨이 삭제되었습니다.
issues.label.filter_sort.alphabetically=알파벳순 issues.label.filter_sort.alphabetically=알파벳순
issues.label.filter_sort.reverse_alphabetically=이름 역순으로 정렬 issues.label.filter_sort.reverse_alphabetically=이름 역순으로 정렬
issues.num_participants=참여자 %d명 issues.num_participants_few=참여자 %d명
issues.attachment.open_tab=`클릭하여 "%s" 새탭으로 보기` issues.attachment.open_tab=`클릭하여 "%s" 새탭으로 보기`
issues.attachment.download=' "%s"를 다운로드 하려면 클릭 하십시오 ' issues.attachment.download=' "%s"를 다운로드 하려면 클릭 하십시오 '
issues.subscribe=구독하기 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.reverse_alphabetically=Pretēji alfabētiski
issues.label.filter_sort.by_size=Mazākais izmērs issues.label.filter_sort.by_size=Mazākais izmērs
issues.label.filter_sort.reverse_by_size=Lielā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.open_tab=`Noklikšķiniet, lai apskatītos "%s" jaunā logā`
issues.attachment.download=`Noklikšķiniet, lai lejupielādētu "%s"` issues.attachment.download=`Noklikšķiniet, lai lejupielādētu "%s"`
issues.subscribe=Abonēt issues.subscribe=Abonēt
@ -2253,9 +2253,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work) settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist lietotājvārds settings.packagist_username=Packagist lietotājvārds

View file

@ -155,6 +155,8 @@ filter.public = Publiek
filter.private = Privé filter.private = Privé
filter = Filter filter = Filter
filter.not_archived = Niet gearchiveerd filter.not_archived = Niet gearchiveerd
more_items = Meer items
invalid_data = Ongeldige data: %v
[aria] [aria]
navbar = Navigatiebalk navbar = Navigatiebalk
@ -226,7 +228,7 @@ db_schema=Schema
db_schema_helper=Laat leeg voor de standaard database ("openbaar"). db_schema_helper=Laat leeg voor de standaard database ("openbaar").
ssl_mode=SSL ssl_mode=SSL
path=Pad 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_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_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. 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. 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. 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 select_permissions = Selecteer machtigingen
permission_no_access = Geen Toegang permission_no_access = Geen toegang
permissions_list = Machtigingen: permissions_list = Machtigingen:
update_oauth2_application_success = U heeft met succes een OAuth2 applicatie bijgewerkt. 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. 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 permissions_public_only = Alleen publiek
repo_and_org_access = Repository en Organisatie Toegang 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 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. 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. 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. 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.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.or=of
editor.cancel_lower=Annuleer editor.cancel_lower=Annuleer
editor.commit_signed_changes=Commit Ondertekende Wijzigingen editor.commit_signed_changes=Commit ondertekende wijzigingen
editor.commit_changes=Wijzigingen doorvoeren editor.commit_changes=Wijzigingen doorvoeren
editor.add_tmpl="<bestandsnaam>" toevoegen editor.add_tmpl="<bestandsnaam>" toevoegen
editor.patch=Patch toepassen editor.patch=Patch toepassen
@ -1312,8 +1314,8 @@ projects.edit=Projecten bewerken
projects.edit_subheader=Projecten organiseren issues en houden voortgang bij. projects.edit_subheader=Projecten organiseren issues en houden voortgang bij.
projects.modify=Project bewerken projects.modify=Project bewerken
projects.type.none=Geen projects.type.none=Geen
projects.type.basic_kanban=Basis Kanban projects.type.basic_kanban=Basis kanban
projects.type.bug_triage=Bug Triage projects.type.bug_triage=Bug triage
projects.template.desc=Project sjabloon projects.template.desc=Project sjabloon
projects.template.desc_helper=Selecteer een projectsjabloon om aan de slag te gaan projects.template.desc_helper=Selecteer een projectsjabloon om aan de slag te gaan
projects.type.uncategorized=Ongecategoriseerd 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.reverse_alphabetically=Omgekeerd alfabetisch
issues.label.filter_sort.by_size=Kleinste grootte issues.label.filter_sort.by_size=Kleinste grootte
issues.label.filter_sort.reverse_by_size=Grootste 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.open_tab=`Klik om "%s" in een nieuw tabblad te bekijken`
issues.attachment.download=`Klik om "%s" te downloaden` issues.attachment.download=`Klik om "%s" te downloaden`
issues.subscribe=Abonneren issues.subscribe=Abonneren
@ -1749,7 +1751,7 @@ milestones.filter_sort.most_issues=Meeste problemen
milestones.filter_sort.least_issues=Minste 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. ext_wiki.desc=Koppelen aan een externe wiki.
wiki=Wiki wiki=Wiki
@ -2030,9 +2032,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work) settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist gebruikersnaam settings.packagist_username=Packagist gebruikersnaam
@ -2354,15 +2356,15 @@ commitstatus.failure = Mislukking
commitstatus.success = Succes commitstatus.success = Succes
projects.create_success = Het project "%s" is gecreëerd. projects.create_success = Het project "%s" is gecreëerd.
projects.edit_success = Project "%s" is bijgewerkt. projects.edit_success = Project "%s" is bijgewerkt.
projects.column.edit = Kolom Bewerken projects.column.edit = Kolom bewerken
projects.column.new_submit = Kolom Maken projects.column.new_submit = Kolom maken
projects.column.new = Nieuwe Kolom projects.column.new = Nieuwe kolom
projects.column.set_default = Standaard Instellen projects.column.set_default = Standaard instellen
projects.column.unset_default = Standaardinstelling ongedaan maken projects.column.unset_default = Standaardinstelling ongedaan maken
projects.column.delete = Kolom verwijderen projects.column.delete = Kolom verwijderen
projects.column.assigned_to = Toegewezen aan projects.column.assigned_to = Toegewezen aan
projects.card_type.images_and_text = Afbeeldingen en Tekst projects.card_type.images_and_text = Afbeeldingen en tekst
projects.card_type.text_only = Alleen Tekst projects.card_type.text_only = Alleen tekst
issues.choose.ignore_invalid_templates = Ongeldige sjablonen zijn genegeerd issues.choose.ignore_invalid_templates = Ongeldige sjablonen zijn genegeerd
issues.choose.invalid_templates = %v ongeldige sjablon(en) gevonden issues.choose.invalid_templates = %v ongeldige sjablon(en) gevonden
issues.choose.invalid_config = Deze issue configuratie bevat fouten: 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.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. editor.directory_is_a_file = Mapnaam "%s" wordt al gebruikt als bestandsnaam in deze repository.
commits.renamed_from = Hernoemd van %s 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.filter_changes_by_commit = Filter op commit
pulls.nothing_to_compare_have_tag = De geselecteerde branch/tag zijn gelijk. pulls.nothing_to_compare_have_tag = De geselecteerde branch/tag zijn gelijk.
pulls.merged_success = Pull request succesvol samengevoegd en gesloten 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.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. 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". 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 projects.column.set_default_desc = Stel deze kolom in als standaard voor ongecategoriseerde issues and pulls
issues.action_check = Aanvinken/uitvinken 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 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 settings.confirmation_string = Confirmatie string
activity.navbar.code_frequency = Code frequentie activity.navbar.code_frequency = Code frequentie
activity.navbar.recent_commits = Recente commits 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. 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> 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 open_with_editor = Open met %s
commits.search_branch = Deze branch 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.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? 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.lfs_root_path=LFS rootpad
config.log_file_root_path=Log-pad config.log_file_root_path=Log-pad
config.script_type=Script soort 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_config=SSH-configuratie
config.ssh_enabled=Ingeschakeld 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.skip_local_two_fa = Lokale 2FA overslaan
auths.oauth2_icon_url = Pictogram URL auths.oauth2_icon_url = Pictogram URL
auths.pam_email_domain = PAM e-maildomein (optioneel) 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.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.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: 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: auths.tips.gmail_settings = Gmail instellingen:
config_summary = Samenvatting 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. 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] [action]
@ -3704,6 +3717,7 @@ variables.description = Variabelen worden doorgegeven aan bepaalde acties en kun
runners.delete_runner_success = Runner succesvol verwijderd runners.delete_runner_success = Runner succesvol verwijderd
runs.no_matching_online_runner_helper = Geen overeenkomende online runner met label: %s runs.no_matching_online_runner_helper = Geen overeenkomende online runner met label: %s
runs.workflow = Workflow 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 type_tooltip = Zoektype
fuzzy_tooltip = Neem resultaten op die ook sterk overeenkomen met de zoekterm 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. 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.reverse_alphabetically=Alfabetycznie odwrotnie
issues.label.filter_sort.by_size=Najmniejszy rozmiar issues.label.filter_sort.by_size=Najmniejszy rozmiar
issues.label.filter_sort.reverse_by_size=Największy 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.open_tab=`Kliknij, aby zobaczyć "%s" w nowej karcie`
issues.attachment.download=`Kliknij, aby pobrać "%s"` issues.attachment.download=`Kliknij, aby pobrać "%s"`
issues.subscribe=Subskrybuj 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.reverse_alphabetically=Alfabeticamente inverso
issues.label.filter_sort.by_size=Menor tamanho issues.label.filter_sort.by_size=Menor tamanho
issues.label.filter_sort.reverse_by_size=Maior 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.open_tab=`Clique para ver "%s" em uma nova aba`
issues.attachment.download=`Clique para baixar "%s"` issues.attachment.download=`Clique para baixar "%s"`
issues.subscribe=Inscrever-se issues.subscribe=Inscrever-se
@ -2236,9 +2236,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work) settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nome de usuário no 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.reverse_alphabetically=por ordem alfabética inversa
issues.label.filter_sort.by_size=Menor tamanho issues.label.filter_sort.by_size=Menor tamanho
issues.label.filter_sort.reverse_by_size=Maior 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.open_tab=`Clique para ver "%s" num separador novo`
issues.attachment.download=`Clique para descarregar "%s"` issues.attachment.download=`Clique para descarregar "%s"`
issues.subscribe=Subscrever issues.subscribe=Subscrever
@ -2269,9 +2269,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work) settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist settings.web_hook_name_packagist=Packagist
settings.packagist_username=Nome de utilizador no Packagist settings.packagist_username=Nome de utilizador no Packagist

View file

@ -155,6 +155,8 @@ filter.public = Публичные
filter.private = Приватные filter.private = Приватные
filter.is_archived = Архивированные filter.is_archived = Архивированные
filter.not_mirror = Не зеркала filter.not_mirror = Не зеркала
more_items = Больше элементов
invalid_data = Неверные данные: %v
[aria] [aria]
navbar=Панель навигации navbar=Панель навигации
@ -251,16 +253,16 @@ run_user=Запуск от имени пользователя
run_user_helper=Имя пользователя операционной системы, под которым работает Forgejo. Обратите внимание, что этот пользователь должен иметь доступ к корневому пути репозиториев. run_user_helper=Имя пользователя операционной системы, под которым работает Forgejo. Обратите внимание, что этот пользователь должен иметь доступ к корневому пути репозиториев.
domain=Домен сервера domain=Домен сервера
domain_helper=Домен или адрес хоста для сервера. domain_helper=Домен или адрес хоста для сервера.
ssh_port=Порт SSH сервера ssh_port=Порт SSH-сервера
ssh_port_helper=Номер порта, который использует SSH сервер. Оставьте пустым, чтобы отключить SSH. ssh_port_helper=Номер порта, используемый SSH-сервером. Оставьте пустым для отключения доступа по SSH.
http_port=Forgejo HTTP порт http_port=Порт HTTP-сервера
http_port_helper=Номер порта, который будет прослушиваться Forgejo веб-сервером. http_port_helper=Номер порта, используемый веб-сервером Forgejo.
app_url=Базовый URL Forgejo app_url=Базовый URL Forgejo
app_url_helper=Этот параметр влияет на URL для клонирования по HTTP/HTTPS и на некоторые уведомления по эл. почте. app_url_helper=Этот параметр влияет на URL для клонирования по HTTP/HTTPS и на некоторые уведомления по эл. почте.
log_root_path=Путь журналов log_root_path=Путь журналов
log_root_path_helper=Файлы журнала будут записываться в этот каталог. log_root_path_helper=Файлы журнала будут записываться в этот каталог.
optional_title=Расширенные настройки optional_title=Дополнительные настройки
email_title=Настройки эл. почты email_title=Настройки эл. почты
smtp_addr=Адрес SMTP smtp_addr=Адрес SMTP
smtp_port=Порт SMTP smtp_port=Порт SMTP
@ -991,6 +993,7 @@ owner_helper=Некоторые организации могут не отоб
repo_name=Название репозитория repo_name=Название репозитория
repo_name_helper=Лучшие названия репозиториев состоят из коротких, легко запоминаемых и уникальных ключевых слов. repo_name_helper=Лучшие названия репозиториев состоят из коротких, легко запоминаемых и уникальных ключевых слов.
repo_size=Размер репозитория repo_size=Размер репозитория
size_format = `%[1]s: %[2]s; %[3]s: %[4]s`
template=Шаблон template=Шаблон
template_select=Выбрать шаблон. template_select=Выбрать шаблон.
template_helper=Сделать репозиторий шаблоном template_helper=Сделать репозиторий шаблоном
@ -1142,7 +1145,7 @@ migrate.migrating_failed_no_addr=Перенос не удался.
migrate.github.description=Перенесите данные с github.com или сервера GitHub Enterprise. migrate.github.description=Перенесите данные с github.com или сервера GitHub Enterprise.
migrate.git.description=Перенести только репозиторий из любого Git сервиса. migrate.git.description=Перенести только репозиторий из любого Git сервиса.
migrate.gitlab.description=Перенести данные с gitlab.com или других серверов GitLab. 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.gogs.description=Перенести данные с notabug.org или других серверов Gogs.
migrate.onedev.description=Перенести данные с code.onedev.io или других серверов OneDev. migrate.onedev.description=Перенести данные с code.onedev.io или других серверов OneDev.
migrate.codebase.description=Перенос данных с codebasehq.com. migrate.codebase.description=Перенос данных с codebasehq.com.
@ -1232,7 +1235,7 @@ symbolic_link=Символическая ссылка
executable_file=Исполняемый файл executable_file=Исполняемый файл
commit_graph=Граф коммитов commit_graph=Граф коммитов
commit_graph.select=Выбрать ветку commit_graph.select=Выбрать ветку
commit_graph.hide_pr_refs=Скрыть запросы на слияние commit_graph.hide_pr_refs=Скрыть запросы слияний
commit_graph.monochrome=Моно commit_graph.monochrome=Моно
commit_graph.color=Цвет commit_graph.color=Цвет
commit.contained_in=Этот коммит содержится в: commit.contained_in=Этот коммит содержится в:
@ -1344,7 +1347,7 @@ commitstatus.failure=Неудача
commitstatus.pending=Ожидание commitstatus.pending=Ожидание
commitstatus.success=Успешно commitstatus.success=Успешно
ext_issues=Доступ к внешним задачам ext_issues=Доступ ко внешним задачам
ext_issues.desc=Ссылка на внешнюю систему отслеживания задач. ext_issues.desc=Ссылка на внешнюю систему отслеживания задач.
projects=Проекты projects=Проекты
@ -1359,7 +1362,7 @@ projects.create_success=Проект «%s» создан.
projects.deletion=Удалить проект projects.deletion=Удалить проект
projects.deletion_desc=Удаление проекта приведёт к его удалению из всех связанных задач. Продолжить? projects.deletion_desc=Удаление проекта приведёт к его удалению из всех связанных задач. Продолжить?
projects.deletion_success=Проект удалён. projects.deletion_success=Проект удалён.
projects.edit=Редактировать проекты projects.edit=Изменить проект
projects.edit_subheader=Создавайте и организуйте задачи и отслеживайте прогресс. projects.edit_subheader=Создавайте и организуйте задачи и отслеживайте прогресс.
projects.modify=Обновить проект projects.modify=Обновить проект
projects.edit_success=Проект «%s» обновлён. projects.edit_success=Проект «%s» обновлён.
@ -1372,14 +1375,14 @@ projects.type.uncategorized=Без категории
projects.column.edit=Изменить столбец projects.column.edit=Изменить столбец
projects.column.edit_title=Название projects.column.edit_title=Название
projects.column.new_title=Название projects.column.new_title=Название
projects.column.new_submit=Создать столбец projects.column.new_submit=Добавить столбец
projects.column.new=Новый столбец projects.column.new=Добавить столбец
projects.column.set_default=Установить по умолчанию projects.column.set_default=Установить по умолчанию
projects.column.set_default_desc=Назначить этот столбец по умолчанию для задач и запросов на слияние без категории projects.column.set_default_desc=Назначить этот столбец по умолчанию для задач и запросов на слияние без категории
projects.column.unset_default=Снять установку по умолчанию projects.column.unset_default=Снять установку по умолчанию
projects.column.unset_default_desc=Снять установку этого столбца по умолчанию projects.column.unset_default_desc=Снять установку этого столбца по умолчанию
projects.column.delete=Удалить столбец projects.column.delete=Удалить столбец
projects.column.deletion_desc=При удалении столбца проекта все связанные задачи перемещаются в «Без категории». Продолжить? projects.column.deletion_desc=При удалении столбца все задачи в нём будут перемещены в столбец по умолчанию. Продолжить?
projects.column.color=Цвет projects.column.color=Цвет
projects.open=Открыть projects.open=Открыть
projects.close=Закрыть projects.close=Закрыть
@ -1581,7 +1584,8 @@ issues.label.filter_sort.alphabetically=По алфавиту
issues.label.filter_sort.reverse_alphabetically=С конца алфавита issues.label.filter_sort.reverse_alphabetically=С конца алфавита
issues.label.filter_sort.by_size=Меньший размер issues.label.filter_sort.by_size=Меньший размер
issues.label.filter_sort.reverse_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.open_tab=`Нажмите, чтобы увидеть «%s» в новой вкладке`
issues.attachment.download=`Нажмите, чтобы скачать «%s»` issues.attachment.download=`Нажмите, чтобы скачать «%s»`
issues.subscribe=Подписаться issues.subscribe=Подписаться
@ -1901,7 +1905,7 @@ signing.wont_sign.commitssigned=Слияние не будет подписан
signing.wont_sign.approved=Слияние не будет подписано, так как запрос на слияние не одобрен. signing.wont_sign.approved=Слияние не будет подписано, так как запрос на слияние не одобрен.
signing.wont_sign.not_signed_in=Вы не вошли в систему. signing.wont_sign.not_signed_in=Вы не вошли в систему.
ext_wiki=Доступ к внешней вики ext_wiki=Доступ ко внешней вики
ext_wiki.desc=Ссылка на внешнюю вики. ext_wiki.desc=Ссылка на внешнюю вики.
wiki=Вики wiki=Вики
@ -2276,9 +2280,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu или Lark Suite settings.web_hook_name_feishu=Feishu или Lark Suite
settings.web_hook_name_feishu=Feishu settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work) settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist settings.web_hook_name_packagist=Packagist
settings.packagist_username=Имя пользователя Packagist settings.packagist_username=Имя пользователя Packagist
@ -2653,7 +2657,7 @@ signing.wont_sign.nokey = Нет ключей для подписи этого
settings.wiki_globally_editable = Разрешить редактирование Вики всем пользователям settings.wiki_globally_editable = Разрешить редактирование Вики всем пользователям
settings.webhook.test_delivery_desc_disabled = Активируйте этот веб-хук для проверки тестовым событием. settings.webhook.test_delivery_desc_disabled = Активируйте этот веб-хук для проверки тестовым событием.
commits.browse_further = Смотреть далее commits.browse_further = Смотреть далее
vendored = Предоставленный vendored = Сторонний
settings.units.add_more = Доб. больше... settings.units.add_more = Доб. больше...
pulls.fast_forward_only_merge_pull_request = Только fast-forward pulls.fast_forward_only_merge_pull_request = Только fast-forward
settings.units.overview = Обзор settings.units.overview = Обзор
@ -2685,6 +2689,16 @@ settings.mirror_settings.docs.doc_link_pull_section = раздел докуме
wiki.original_git_entry_tooltip = Перейти по настоящему пути вместо читабельной ссылки. wiki.original_git_entry_tooltip = Перейти по настоящему пути вместо читабельной ссылки.
open_with_editor = Открыть в %s open_with_editor = Открыть в %s
commits.search_branch = В этой ветке 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] [graphs]
@ -2940,10 +2954,10 @@ users.prohibit_login=Запретить вход в учётную запись
users.is_admin=У этой учётной записи есть права администратора users.is_admin=У этой учётной записи есть права администратора
users.is_restricted=Ограничен users.is_restricted=Ограничен
users.allow_git_hook=Может создавать Git-хуки users.allow_git_hook=Может создавать Git-хуки
users.allow_git_hook_tooltip=Git Hooks выполняется как пользователь ОС с Forgejo и будет иметь одинаковый уровень доступа к хосту. В результате пользователи с привилегией Git Hook могут получить доступ и модифицировать все репозитории Forgejo, а также базу данных, используемую Forgejo. Следовательно, они также могут получить привилегии администратора Forgejo. users.allow_git_hook_tooltip=Git hooks выполняются от пользователя ОС, под которым работает Forgejo. Они будут иметь такой же доступ к хосту. Из-за этого пользователи с правами на Git hook будут иметь возможность получать доступ и модифицировать все репозитории в Forgejo, а также базу данных Forgejo. Следовательно, они также могут получить права администратора Forgejo.
users.allow_import_local=Пользователь имеет право импортировать локальные репозитории users.allow_import_local=Может импортировать локальные репозитории
users.allow_create_organization=Эта учётная запись имеет разрешения на создание организаций users.allow_create_organization=Может создавать организации
users.update_profile=Обновить профиль пользователя users.update_profile=Обновить учётную запись
users.delete_account=Удалить эту учётную запись users.delete_account=Удалить эту учётную запись
users.cannot_delete_self=Вы не можете удалить свою учётную запись users.cannot_delete_self=Вы не можете удалить свою учётную запись
users.still_own_repo=Этот пользователь всё ещё является владельцем одного или более репозиториев. Сначала удалите или передайте эти репозитории. users.still_own_repo=Этот пользователь всё ещё является владельцем одного или более репозиториев. Сначала удалите или передайте эти репозитории.
@ -2955,16 +2969,16 @@ users.deletion_success=Учётная запись успешно удалена
users.reset_2fa=Сброс 2FA users.reset_2fa=Сброс 2FA
users.list_status_filter.menu_text=Фильтр users.list_status_filter.menu_text=Фильтр
users.list_status_filter.reset=Сбросить users.list_status_filter.reset=Сбросить
users.list_status_filter.is_active=Активный users.list_status_filter.is_active=Активные
users.list_status_filter.not_active=Неактивный users.list_status_filter.not_active=Неактивные
users.list_status_filter.is_admin=Администратор users.list_status_filter.is_admin=Администраторы
users.list_status_filter.not_admin=Не администратор users.list_status_filter.not_admin=Не администраторы
users.list_status_filter.is_restricted=Ограничено users.list_status_filter.is_restricted=Ограниченные
users.list_status_filter.not_restricted=Не ограничено users.list_status_filter.not_restricted=Не ограниченные
users.list_status_filter.is_prohibit_login=Запретить вход users.list_status_filter.is_prohibit_login=Вход запрещён
users.list_status_filter.not_prohibit_login=Разрешить вход users.list_status_filter.not_prohibit_login=Вход разрешён
users.list_status_filter.is_2fa_enabled=2FA включено users.list_status_filter.is_2fa_enabled=2FA включена
users.list_status_filter.not_2fa_enabled=2FA отключено users.list_status_filter.not_2fa_enabled=2FA выключена
users.details=О пользователе users.details=О пользователе
emails.email_manage_panel=Управление адресами эл. почты пользователей emails.email_manage_panel=Управление адресами эл. почты пользователей
@ -3155,7 +3169,7 @@ config.repo_root_path=Путь до каталога репозиториев
config.lfs_root_path=Корневой путь LFS config.lfs_root_path=Корневой путь LFS
config.log_file_root_path=Путь журналов config.log_file_root_path=Путь журналов
config.script_type=Тип сценария config.script_type=Тип сценария
config.reverse_auth_user=Имя пользователя для авторизации на reverse proxy config.reverse_auth_user=Пользователь для авторизации на обратном прокси
config.ssh_config=Конфигурация SSH config.ssh_config=Конфигурация SSH
config.ssh_enabled=SSH включён config.ssh_enabled=SSH включён
@ -3329,7 +3343,7 @@ notices.desc=Описание
notices.op=Oп. notices.op=Oп.
notices.delete_success=Уведомления системы были удалены. notices.delete_success=Уведомления системы были удалены.
self_check.no_problem_found = Пока проблем не обнаружено. 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 ссылка обратного перенаправления должна быть: auths.tips.oauth2.general.tip = При регистрации нового приложения OAuth2 ссылка обратного перенаправления должна быть:
self_check.database_fix_mssql = В настоящий момент пользователи MSSQL могут исправить проблемы с сопоставлением только ручным прописыванием "ALTER ... COLLATE ..." в SQL. self_check.database_fix_mssql = В настоящий момент пользователи MSSQL могут исправить проблемы с сопоставлением только ручным прописыванием "ALTER ... COLLATE ..." в SQL.
self_check.database_fix_mysql = Пользователи MySQL и MariaDB могут исправить проблемы с сопоставлением командой "gitea doctor convert". Также можно вручную вписать "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.open_with_editor_app_help = Приложения для "Открыть в" в меню. Оставьте пустым для приложений по умолчанию. Разверните для просмотра.
config_settings = Настройки config_settings = Настройки
auths.tips.gmail_settings = Настройки Gmail: auths.tips.gmail_settings = Настройки Gmail:
auths.tip.gitlab_new = Создайте новое приложение в https://gitlab.com/-/profile/applications
[action] [action]
@ -3407,6 +3422,15 @@ years=%d лет
raw_seconds=секунд raw_seconds=секунд
raw_minutes=минут raw_minutes=минут
[munits.data]
b = Б
kib = КиБ
mib = МиБ
gib = ГиБ
tib = ТиБ
pib = ПиБ
eib = ЕиБ
[dropzone] [dropzone]
default_message=Перетащите файл или кликните сюда для загрузки. default_message=Перетащите файл или кликните сюда для загрузки.
invalid_input_type=Вы не можете загружать файлы этого типа. invalid_input_type=Вы не можете загружать файлы этого типа.
@ -3596,6 +3620,7 @@ rpm.repository = О репозитории
rpm.repository.architectures = Архитектуры rpm.repository.architectures = Архитектуры
rpm.repository.multiple_groups = Этот пакет доступен в нескольких группах. rpm.repository.multiple_groups = Этот пакет доступен в нескольких группах.
owner.settings.chef.keypair.description = Для аутентификации реестра Chef необходима пара ключей. Если до этого вы уже сгенерировали пару ключей, генерация новой приведёт к прекращению действия предыдущей. owner.settings.chef.keypair.description = Для аутентификации реестра Chef необходима пара ключей. Если до этого вы уже сгенерировали пару ключей, генерация новой приведёт к прекращению действия предыдущей.
owner.settings.cargo.rebuild.no_index = Невозможно выполнить пересборку. Нет инициализированного индекса.
[secrets] [secrets]
secrets=Секреты secrets=Секреты
@ -3747,4 +3772,6 @@ commit_kind = Поиск коммитов...
no_results = По запросу ничего не найдено. no_results = По запросу ничего не найдено.
keyword_search_unavailable = Поиск по ключевым словам недоступен. Уточните подробности у администратора. keyword_search_unavailable = Поиск по ключевым словам недоступен. Уточните подробности у администратора.
match_tooltip = Включать только результаты, точно соответствующие запросу 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.reverse_alphabetically=අකාරාදී ප්රතිවිකුණුම්
issues.label.filter_sort.by_size=කුඩාම ප්‍රමාණය issues.label.filter_sort.by_size=කුඩාම ප්‍රමාණය
issues.label.filter_sort.reverse_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.open_tab=`නව වගුවක "%s" බැලීමට ක්ලික් කරන්න`
issues.attachment.download=`"%s" බාගැනීමට ඔබන්න` issues.attachment.download=`"%s" බාගැනීමට ඔබන්න`
issues.subscribe=දායක වන්න 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.reverse_alphabetically=Alfabetiskt Ö-A
issues.label.filter_sort.by_size=Minsta storlek issues.label.filter_sort.by_size=Minsta storlek
issues.label.filter_sort.reverse_by_size=Största 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.open_tab=`Klicka för att se "%s" i en ny flik`
issues.attachment.download=`Klicka för att hämta "%s"` issues.attachment.download=`Klicka för att hämta "%s"`
issues.subscribe=Prenumerera 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.reverse_alphabetically=Ters alfabetik
issues.label.filter_sort.by_size=En küçük boyut issues.label.filter_sort.by_size=En küçük boyut
issues.label.filter_sort.reverse_by_size=En büyü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.open_tab=`Yeni bir sekmede "%s" görmek için tıkla`
issues.attachment.download=`"%s" indirmek için tıkla` issues.attachment.download=`"%s" indirmek için tıkla`
issues.subscribe=Abone Ol issues.subscribe=Abone Ol
@ -2252,9 +2252,9 @@ settings.web_hook_name_dingtalk=DingTalk
settings.web_hook_name_telegram=Telegram settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite settings.web_hook_name_feishu=Feishu / Lark Suite
settings.web_hook_name_feishu=Feishu settings.web_hook_name_feishu_only =Feishu
settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=WeCom (Wechat Work) settings.web_hook_name_wechatwork=WeCom (Wechat Work)
settings.web_hook_name_packagist=Packagist settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist kullanıcı adı 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.reverse_alphabetically=З кінця алфавіту
issues.label.filter_sort.by_size=Найменший розмір issues.label.filter_sort.by_size=Найменший розмір
issues.label.filter_sort.reverse_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.open_tab=`Натисніть щоб побачити "%s" у новій вкладці`
issues.attachment.download=`Натисніть щоб завантажити "%s"` issues.attachment.download=`Натисніть щоб завантажити "%s"`
issues.subscribe=Підписатися issues.subscribe=Підписатися

View file

@ -155,6 +155,8 @@ filter.not_template = 非模板
filter.public = 公开 filter.public = 公开
filter.private = 私有 filter.private = 私有
toggle_menu = 菜单 toggle_menu = 菜单
invalid_data = 无效数据: %v
more_items = 显示更多
[aria] [aria]
navbar=导航栏 navbar=导航栏
@ -1597,7 +1599,7 @@ issues.label.filter_sort.alphabetically=按字母顺序排序
issues.label.filter_sort.reverse_alphabetically=按字母逆序排序 issues.label.filter_sort.reverse_alphabetically=按字母逆序排序
issues.label.filter_sort.by_size=最小尺寸 issues.label.filter_sort.by_size=最小尺寸
issues.label.filter_sort.reverse_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.open_tab=`在新的标签页中查看 '%s'`
issues.attachment.download=`点击下载 '%s'` issues.attachment.download=`点击下载 '%s'`
issues.subscribe=订阅 issues.subscribe=订阅
@ -2316,9 +2318,9 @@ settings.web_hook_name_dingtalk=钉钉
settings.web_hook_name_telegram=Telegram settings.web_hook_name_telegram=Telegram
settings.web_hook_name_matrix=Matrix settings.web_hook_name_matrix=Matrix
settings.web_hook_name_msteams=Microsoft Teams settings.web_hook_name_msteams=Microsoft Teams
settings.web_hook_name_feishu_or_larksuite=飞书 / Lark Suite settings.web_hook_name_feishu=飞书 / Lark Suite
settings.web_hook_name_feishu=飞书 settings.web_hook_name_feishu_only =飞书
settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_larksuite_only =Lark Suite
settings.web_hook_name_wechatwork=企业微信 settings.web_hook_name_wechatwork=企业微信
settings.web_hook_name_packagist=Packagist settings.web_hook_name_packagist=Packagist
settings.packagist_username=Packagist 用户名 settings.packagist_username=Packagist 用户名
@ -2688,6 +2690,20 @@ activity.navbar.code_frequency = 代码频率
activity.navbar.recent_commits = 近期提交 activity.navbar.recent_commits = 近期提交
pulls.agit_explanation = 该合并请求是用 AGit 创建的。AGit 是一种可以让贡献者直接通过 “git push” 提出更改代码而不需要派生或建立新分支。 pulls.agit_explanation = 该合并请求是用 AGit 创建的。AGit 是一种可以让贡献者直接通过 “git push” 提出更改代码而不需要派生或建立新分支。
error.broken_git_hook = 该仓库的 Git 钩子似乎已经损坏,请按照 <a target="_blank" rel="noreferrer" href="%s">此文档</a>来修复这些问题,然后推送一些提交来刷新状态。 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] [graphs]
component_loading=正在加载 %s... component_loading=正在加载 %s...
@ -2958,7 +2974,7 @@ users.max_repo_creation_desc=(设置为 -1 表示使用全局默认值)
users.is_activated=该用户已被激活 users.is_activated=该用户已被激活
users.prohibit_login=禁用登录 users.prohibit_login=禁用登录
users.is_admin=是管理员 users.is_admin=是管理员
users.is_restricted=受限制的 users.is_restricted=受限
users.allow_git_hook=允许创建 Git 钩子 users.allow_git_hook=允许创建 Git 钩子
users.allow_git_hook_tooltip=Git 钩子将会被以操作系统用户运行将会拥有同样的主机访问权限。因此拥有此特殊的Git 钩子权限将能够访问合修改所有的 Forgejo 仓库或者Forgejo的数据库。同时也能获得Forgejo的管理员权限。 users.allow_git_hook_tooltip=Git 钩子将会被以操作系统用户运行将会拥有同样的主机访问权限。因此拥有此特殊的Git 钩子权限将能够访问合修改所有的 Forgejo 仓库或者Forgejo的数据库。同时也能获得Forgejo的管理员权限。
users.allow_import_local=允许导入本地仓库 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_mysql=对于MySQL/MariaDB用户您可以使用“gitea doctor convert”命令来解决校验问题。 或者您也可以通过 "ALTER ... COLLATE ..." 这样的SQL 来手动解决这个问题。
self_check.database_fix_mssql=对于MSSQL用户您现在只能通过"ALTER ... COLLATE ..."SQLs手动解决这个问题。 self_check.database_fix_mssql=对于MSSQL用户您现在只能通过"ALTER ... COLLATE ..."SQLs手动解决这个问题。
auths.tips.gmail_settings = Gmail 设置: auths.tips.gmail_settings = Gmail 设置:
auths.tip.gitlab_new = 在 https://gitlab.com/-/profile/applications 上注册新应用
config_settings = 设置
[action] [action]
create_repo=创建了仓库 <a href="%s">%s</a> create_repo=创建了仓库 <a href="%s">%s</a>
@ -3738,3 +3756,25 @@ executable_file=可执行文件
symbolic_link=符号链接 symbolic_link=符号链接
submodule=子模块 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_delete=刪除
issues.label.filter_sort.alphabetically=按字母顺序排序 issues.label.filter_sort.alphabetically=按字母顺序排序
issues.label.filter_sort.reverse_alphabetically=按字母反向排序 issues.label.filter_sort.reverse_alphabetically=按字母反向排序
issues.num_participants=%d 參與者 issues.num_participants_few=%d 參與者
issues.attachment.open_tab=`在新的標籤頁中查看 '%s'` issues.attachment.open_tab=`在新的標籤頁中查看 '%s'`
issues.attachment.download=`點擊下載 '%s'` issues.attachment.download=`點擊下載 '%s'`
issues.subscribe=訂閱 issues.subscribe=訂閱

View file

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

179
package-lock.json generated
View file

@ -70,6 +70,7 @@
"@stylistic/eslint-plugin-js": "1.7.0", "@stylistic/eslint-plugin-js": "1.7.0",
"@stylistic/stylelint-plugin": "2.1.0", "@stylistic/stylelint-plugin": "2.1.0",
"@vitejs/plugin-vue": "5.0.4", "@vitejs/plugin-vue": "5.0.4",
"@vue/test-utils": "2.4.5",
"eslint": "8.57.0", "eslint": "8.57.0",
"eslint-plugin-array-func": "4.0.0", "eslint-plugin-array-func": "4.0.0",
"eslint-plugin-github": "4.10.2", "eslint-plugin-github": "4.10.2",
@ -85,7 +86,7 @@
"eslint-plugin-vue": "9.24.0", "eslint-plugin-vue": "9.24.0",
"eslint-plugin-vue-scoped-css": "2.8.0", "eslint-plugin-vue-scoped-css": "2.8.0",
"eslint-plugin-wc": "2.0.4", "eslint-plugin-wc": "2.0.4",
"happy-dom": "14.3.7", "happy-dom": "14.3.10",
"markdownlint-cli": "0.39.0", "markdownlint-cli": "0.39.0",
"postcss-html": "1.6.0", "postcss-html": "1.6.0",
"stylelint": "16.3.0", "stylelint": "16.3.0",
@ -1329,6 +1330,12 @@
"node": ">= 8" "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": { "node_modules/@pkgjs/parseargs": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "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", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
"integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==" "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": { "node_modules/@webassemblyjs/ast": {
"version": "1.12.1", "version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", "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", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" "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": { "node_modules/abort-controller": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" "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": { "node_modules/core-js-compat": {
"version": "3.36.1", "version": "3.36.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz",
@ -4775,6 +4817,48 @@
"marked": "^4.1.0" "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": { "node_modules/electron-to-chromium": {
"version": "1.4.716", "version": "1.4.716",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.716.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.716.tgz",
@ -6513,9 +6597,9 @@
} }
}, },
"node_modules/happy-dom": { "node_modules/happy-dom": {
"version": "14.3.7", "version": "14.3.10",
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.3.7.tgz", "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.3.10.tgz",
"integrity": "sha512-lUfDRGzjrVJF2pnvh13OL+qEJ9eDpcedVLm77a3aMg8gPGKXfG+xFMNk3cOWetjucU8FveJ4qcSC/EX55nJ4fQ==", "integrity": "sha512-Rh5li9vA9MF9Gkg85CbFABKTa3uoSAByILRNGb92u/vswDd561gBg2p1UW1ZauvDWWwRxPcbACK5zv3BR+gHnQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"entities": "^4.5.0", "entities": "^4.5.0",
@ -7398,6 +7482,58 @@
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" "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": { "node_modules/js-levenshtein-esm": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz", "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", "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",
"integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==" "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": { "node_modules/normalize-package-data": {
"version": "2.5.0", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "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==" "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
}, },
"node_modules/path-scurry": { "node_modules/path-scurry": {
"version": "1.10.1", "version": "1.10.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
"dependencies": { "dependencies": {
"lru-cache": "^9.1.1 || ^10.0.0", "lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
}, },
"engines": { "engines": {
@ -9727,6 +9878,12 @@
"integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==",
"dev": true "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": { "node_modules/proto-props": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/proto-props/-/proto-props-2.0.0.tgz", "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" "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": { "node_modules/vue-eslint-parser": {
"version": "9.4.2", "version": "9.4.2",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz", "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/eslint-plugin-js": "1.7.0",
"@stylistic/stylelint-plugin": "2.1.0", "@stylistic/stylelint-plugin": "2.1.0",
"@vitejs/plugin-vue": "5.0.4", "@vitejs/plugin-vue": "5.0.4",
"@vue/test-utils": "2.4.5",
"eslint": "8.57.0", "eslint": "8.57.0",
"eslint-plugin-array-func": "4.0.0", "eslint-plugin-array-func": "4.0.0",
"eslint-plugin-github": "4.10.2", "eslint-plugin-github": "4.10.2",
@ -84,7 +85,7 @@
"eslint-plugin-vue": "9.24.0", "eslint-plugin-vue": "9.24.0",
"eslint-plugin-vue-scoped-css": "2.8.0", "eslint-plugin-vue-scoped-css": "2.8.0",
"eslint-plugin-wc": "2.0.4", "eslint-plugin-wc": "2.0.4",
"happy-dom": "14.3.7", "happy-dom": "14.3.10",
"markdownlint-cli": "0.39.0", "markdownlint-cli": "0.39.0",
"postcss-html": "1.6.0", "postcss-html": "1.6.0",
"stylelint": "16.3.0", "stylelint": "16.3.0",

View file

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

View file

@ -337,13 +337,9 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
return return
} }
// If we're an admin for the repository we can ignore status checks, reviews and override protected files // It's not allowed t overwrite protected files. Unless if the user is an
if ctx.userPerm.IsAdmin() { // admin and the protected branch rule doesn't apply to admins.
return if changedProtectedfiles && (!ctx.user.IsAdmin || protectBranch.ApplyToAdmins) {
}
// Now if we're not an admin - we can't overwrite protected files so fail now
if changedProtectedfiles {
log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath) log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath)
ctx.JSON(http.StatusForbidden, private.Response{ ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath), 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 // 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) { 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()) 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{ 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()), 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" org_service "code.gitea.io/gitea/services/org"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
user_service "code.gitea.io/gitea/services/user" user_service "code.gitea.io/gitea/services/user"
webhook_service "code.gitea.io/gitea/services/webhook"
) )
const ( const (
@ -210,6 +211,7 @@ func Webhooks(ctx *context.Context) {
ctx.Data["PageIsSettingsHooks"] = true ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["BaseLink"] = ctx.Org.OrgLink + "/settings/hooks" ctx.Data["BaseLink"] = ctx.Org.OrgLink + "/settings/hooks"
ctx.Data["BaseLinkNew"] = 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") ctx.Data["Description"] = ctx.Tr("org.settings.hooks_desc")
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{OwnerID: ctx.Org.Organization.ID}) 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 { if email == nil || !email.IsActivated {
ctx.Data["Err_CommitMailID"] = true 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 return nil
} }

View file

@ -11,6 +11,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
@ -18,6 +19,7 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/markup/markdown"
@ -192,6 +194,7 @@ func Releases(ctx *context.Context) {
} }
ctx.Data["Releases"] = releases ctx.Data["Releases"] = releases
addVerifyTagToContext(ctx)
numReleases := ctx.Data["NumReleases"].(int64) numReleases := ctx.Data["NumReleases"].(int64)
pager := context.NewPagination(int(numReleases), listOptions.PageSize, listOptions.Page, 5) pager := context.NewPagination(int(numReleases), listOptions.PageSize, listOptions.Page, 5)
@ -201,6 +204,44 @@ func Releases(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplReleasesList) 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 // TagsList render tags list page
func TagsList(ctx *context.Context) { func TagsList(ctx *context.Context) {
ctx.Data["PageIsTagList"] = true ctx.Data["PageIsTagList"] = true
@ -240,6 +281,7 @@ func TagsList(ctx *context.Context) {
} }
ctx.Data["Releases"] = releases ctx.Data["Releases"] = releases
addVerifyTagToContext(ctx)
numTags := ctx.Data["NumTags"].(int64) numTags := ctx.Data["NumTags"].(int64)
pager := context.NewPagination(int(numTags), opts.PageSize, opts.Page, 5) pager := context.NewPagination(int(numTags), opts.PageSize, opts.Page, 5)
@ -304,6 +346,7 @@ func SingleRelease(ctx *context.Context) {
if release.IsTag && release.Title == "" { if release.IsTag && release.Title == "" {
release.Title = release.TagName release.Title = release.TagName
} }
addVerifyTagToContext(ctx)
ctx.Data["PageIsSingleTag"] = release.IsTag ctx.Data["PageIsSingleTag"] = release.IsTag
if release.IsTag { if release.IsTag {

View file

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

View file

@ -45,6 +45,7 @@ func WebhookList(ctx *context.Context) {
ctx.Data["PageIsSettingsHooks"] = true ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["BaseLink"] = ctx.Repo.RepoLink + "/settings/hooks" ctx.Data["BaseLink"] = ctx.Repo.RepoLink + "/settings/hooks"
ctx.Data["BaseLinkNew"] = 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/") 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}) 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") hookType := ctx.Params(":type")
if webhook_service.GetWebhookHandler(hookType) == nil { handler := webhook_service.GetWebhookHandler(hookType)
if handler == nil {
ctx.NotFound("GetWebhookHandler", nil) ctx.NotFound("GetWebhookHandler", nil)
return return
} }
ctx.Data["HookType"] = hookType ctx.Data["HookType"] = hookType
ctx.Data["WebhookHandler"] = handler
ctx.Data["BaseLink"] = orCtx.LinkNew ctx.Data["BaseLink"] = orCtx.LinkNew
ctx.Data["BaseLinkNew"] = orCtx.LinkNew ctx.Data["BaseLinkNew"] = orCtx.LinkNew
ctx.Data["WebhookList"] = webhook_service.List()
ctx.HTML(http.StatusOK, orCtx.NewTemplate) ctx.HTML(http.StatusOK, orCtx.NewTemplate)
} }
@ -194,6 +198,7 @@ func WebhookCreate(ctx *context.Context) {
ctx.Data["PageIsSettingsHooksNew"] = true ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}} ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}}
ctx.Data["HookType"] = hookType ctx.Data["HookType"] = hookType
ctx.Data["WebhookHandler"] = handler
orCtx, err := getOwnerRepoCtx(ctx) orCtx, err := getOwnerRepoCtx(ctx)
if err != nil { if err != nil {
@ -202,6 +207,7 @@ func WebhookCreate(ctx *context.Context) {
} }
ctx.Data["BaseLink"] = orCtx.LinkNew ctx.Data["BaseLink"] = orCtx.LinkNew
ctx.Data["BaseLinkNew"] = orCtx.LinkNew ctx.Data["BaseLinkNew"] = orCtx.LinkNew
ctx.Data["WebhookList"] = webhook_service.List()
if ctx.HasError() { if ctx.HasError() {
// pre-fill the form with the submitted data // 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["BaseLink"] = orCtx.Link
ctx.Data["BaseLinkNew"] = orCtx.LinkNew ctx.Data["BaseLinkNew"] = orCtx.LinkNew
ctx.Data["WebhookList"] = webhook_service.List()
var w *webhook.Webhook var w *webhook.Webhook
if orCtx.RepoID > 0 { 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 { if handler := webhook_service.GetWebhookHandler(w.Type); handler != nil {
ctx.Data["HookMetadata"] = handler.Metadata(w) ctx.Data["HookMetadata"] = handler.Metadata(w)
ctx.Data["WebhookHandler"] = handler
} }
ctx.Data["History"], err = w.History(ctx, 1) 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/base"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
webhook_service "code.gitea.io/gitea/services/webhook"
) )
const ( const (
@ -23,6 +24,7 @@ func Webhooks(ctx *context.Context) {
ctx.Data["PageIsSettingsHooks"] = true ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["BaseLink"] = setting.AppSubURL + "/user/settings/hooks" ctx.Data["BaseLink"] = setting.AppSubURL + "/user/settings/hooks"
ctx.Data["BaseLinkNew"] = 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") ctx.Data["Description"] = ctx.Tr("settings.hooks.desc")
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{OwnerID: ctx.Doer.ID}) 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, Aliases: aliases,
Links: links, 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, RequireSignedCommits: bp.RequireSignedCommits,
ProtectedFilePatterns: bp.ProtectedFilePatterns, ProtectedFilePatterns: bp.ProtectedFilePatterns,
UnprotectedFilePatterns: bp.UnprotectedFilePatterns, UnprotectedFilePatterns: bp.UnprotectedFilePatterns,
ApplyToAdmins: bp.ApplyToAdmins,
Created: bp.CreatedUnix.AsTime(), Created: bp.CreatedUnix.AsTime(),
Updated: bp.UpdatedUnix.AsTime(), Updated: bp.UpdatedUnix.AsTime(),
} }

View file

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

View file

@ -5,10 +5,18 @@ package markup
import ( import (
"context" "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/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" "code.gitea.io/gitea/modules/markup"
gitea_context "code.gitea.io/gitea/services/context" gitea_context "code.gitea.io/gitea/services/context"
file_service "code.gitea.io/gitea/services/repository/files"
) )
func ProcessorHelper() *markup.ProcessorHelper { 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 // when using gitea context (web context), use user's visibility and user's permission to check
return user.IsUserVisibleToViewer(giteaCtx, mentionedUser, giteaCtx.Doer) 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 return ErrIsChecking
} }
if err := CheckPullBranchProtections(ctx, pr, false); err != nil { if pb, err := CheckPullBranchProtections(ctx, pr, false); err != nil {
if !models.IsErrDisallowedToMerge(err) { if !models.IsErrDisallowedToMerge(err) {
log.Error("Error whilst checking pull branch protection for %-v: %v", pr, err) log.Error("Error whilst checking pull branch protection for %-v: %v", pr, err)
return err return err
@ -117,8 +117,9 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
err = nil err = nil
} }
// * if the doer is admin, they could skip the branch protection check // * if the doer is admin, they could skip the branch protection check,
if adminSkipProtectionCheck { // 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 { 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) log.Error("Unable to check if %-v is a repo admin in %-v: %v", doer, pr.BaseRepo, errCheckAdmin)
return errCheckAdmin return errCheckAdmin

View file

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

View file

@ -10,6 +10,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"html/template"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@ -17,6 +18,7 @@ import (
webhook_model "code.gitea.io/gitea/models/webhook" webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/svg"
webhook_module "code.gitea.io/gitea/modules/webhook" webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
) )
@ -34,6 +36,14 @@ func (dh defaultHandler) Type() webhook_module.HookType {
return webhook_module.GITEA 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) Metadata(*webhook_model.Webhook) any { return nil }
func (defaultHandler) FormFields(bind func(any)) FormFields { func (defaultHandler) FormFields(bind func(any)) FormFields {

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